From cb18f86c8b6481a338057b1b8ea0bfbd5f5acb6e Mon Sep 17 00:00:00 2001 From: malufett Date: Sun, 13 Jan 2013 01:20:04 +0800 Subject: Merge latest rathena commits to Hercules Merge r17093, r17094, r17095 of rAthena to Hercules. Welcome Hercules!! my first commit <3 --- db/pre-re/skill_db.txt | 2410 ++-- db/re/skill_db.txt | 2412 ++-- src/map/clif.c | 34256 ++++++++++++++++++++++---------------------- src/map/script.c | 35563 +++++++++++++++++++++++----------------------- src/map/skill.c | 35981 ++++++++++++++++++++++++----------------------- src/map/status.c | 22584 +++++++++++++++-------------- 6 files changed, 66611 insertions(+), 66595 deletions(-) diff --git a/db/pre-re/skill_db.txt b/db/pre-re/skill_db.txt index 580268701..8d2b9375f 100644 --- a/db/pre-re/skill_db.txt +++ b/db/pre-re/skill_db.txt @@ -1,1205 +1,1205 @@ -//id,range,hit,inf,element,nk,splash,max,list_num,castcancel,cast_defence_rate,inf2,maxcount,skill_type,blow_count,name,description -// 01 ID -// 02 range (combo skills do not check for range when used, -// if range is < 5, the skill is considered melee-range) -// 03 hit (8- repeated hitting, 6- single-hit) -// 04 inf (0- passive, 1- enemy, 2- place, 4- self, 16- friend, 32- trap) -// 05 element (0 - neutral, 1 - water, 2 - earth, 3 - fire, 4 - wind, 5 - poison, -// 6 - holy, 7 - dark, 8 - ghost, 9 - undead, -1 - use weapon element -// -2 - use endowed element, -3 - use random element.) -// 06 nk (skill damage properties): -// 0x01 - No damage skill -// 0x02 - Has splash area -// 0x04 - Damage should be split among targets -// 0x08 - Skill ignores caster's % damage cards (misc type always ignores) -// 0x10 - Skill ignores elemental adjustments -// 0x20 - Skill ignores target's defense (misc type always ignores) -// 0x40 - Skill ignores target's flee (magic type always ignores) -// 0x80 - Skill ignores target's def cards -// 07 splash/effect range (-1 for screen-wide) -// 08 MaxLv -// 09 Number of hits (when positive, damage is increased by hits, -// negative values just show number of hits without increasing total damage) -// 10 Cast interrupted when hit? -// 11 defense-reduction rate during cast. -// 12 inf2 (skill information 2): -// 0x0001- quest skill -// 0x0002- npc skill -// 0x0004- wedding skill -// 0x0008- spirit skill -// 0x0010- guild skill -// 0x0020- song/dance -// 0x0040- ensemble skill -// 0x0080- trap -// 0x0100- skill that damages/targets yourself -// 0x0200- cannot be casted on self (if inf = 4, auto-select target skill) -// 0x0400- usable only on party-members (and enemies if skill is offensive) -// 0x0800- usable only on guild-mates (and enemies if skill is offensive) -// 0x1000- disable usage on enemies (for non-offensive skills). -// 0x2000- skill ignores land protector (e.g. arrow shower) -// 0x4000- chorus skill -// 13 maxcount: max amount of skill instances to place on the ground when -// player_land_skill_limit/monster_land_skill_limit is enabled. For skills -// that attack using a path, this is the path length to be used. -// 14 attack type (none, weapon, magic, misc) -// 15 Blowcount (amount of tiles skill knockbacks) -// 16 Name -// 17 Description -1,0,0,0,0,0,0,9,0,no,0,0,0,none,0, NV_BASIC,Basic Skill -2,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, SM_SWORD,Sword Mastery -3,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, SM_TWOHAND,Two-Handed Sword Mastery -4,0,0,0,0,0,0,10,0,no,0,0,0,none,0, SM_RECOVERY,Increase HP Recovery -5,-1,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, SM_BASH,Bash -6,9,6,1,0,1,0,10,1,no,0,0,0,none,0, SM_PROVOKE,Provoke -7,0,6,4,3,0x2,2,10,1,no,0,0,0,weapon,2, SM_MAGNUM,Magnum Break -8,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, SM_ENDURE,Endure -9,0,0,0,0,0,0,10,0,no,0,0,0,none,0, MG_SRECOVERY,Increase SP Recovery -10,0,6,4,3,0x3,3,1,1,yes,0,0,0,magic,0, MG_SIGHT,Sight -11,9,6,1,8,0x6,1,10,1,yes,0,0,0,magic,0, MG_NAPALMBEAT,Napalm Beat -12,9,8,2,8,0x1,0,10,1,yes,0,0,0,magic,0, MG_SAFETYWALL,Safety Wall -13,9,8,1,8,0,0,10,1:1:2:2:3:3:4:4:5:5,yes,0,0,0,magic,0, MG_SOULSTRIKE,Soul Strike -14,9,8,1,1,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, MG_COLDBOLT,Cold Bolt -15,9,6,1,1,0,0,10,1,yes,0,0,0,magic,0, MG_FROSTDIVER,Frost Diver -16,2,6,1,2,0x1,0,10,1,yes,0,0,0,magic,0, MG_STONECURSE,Stone Curse -17,9,6,1,3,0x2,2,10,1,yes,0,0,0,magic,0, MG_FIREBALL,Fire Ball -18,9,6,2,3,0,0,10,1,yes,0,0,3,magic,2, MG_FIREWALL,Fire Wall -19,9,8,1,3,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, MG_FIREBOLT,Fire Bolt -20,9,8,1,4,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, MG_LIGHTNINGBOLT,Lightning Bolt -21,9,8,2,4,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, MG_THUNDERSTORM,Thunderstorm -22,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, AL_DP,Divine Protection -23,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, AL_DEMONBANE,Demon Bane -24,0,6,4,6,0x3,2,1,1,yes,0,0,0,magic,0, AL_RUWACH,Ruwach -25,9,6,2,0,0x1,0,1,1,yes,0,0,0,magic,0, AL_PNEUMA,Pneuma -26,0,6,4,0,0x1,0,2,1,yes,0,0,0,magic,0, AL_TELEPORT,Teleport -27,9,6,2,0,0x1,0,4,1,yes,0,0,3,magic,0, AL_WARP,Warp Portal -28,9,6,16,6,0x21,0,10,1,yes,0,0,0,magic,0, AL_HEAL,Heal -29,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, AL_INCAGI,Increase AGI -30,9,6,1,0,0x1,0,10,1,yes,0,0,0,magic,0, AL_DECAGI,Decrease AGI -31,0,6,4,0,0x1,0,1,1,yes,0,0,0,magic,0, AL_HOLYWATER,Aqua Benedicta -32,0,6,4,0,0x3,15,10,1,yes,0,0,0,magic,0, AL_CRUCIS,Signum Crucis -33,0,6,4,0,0x3,-1,10,1,yes,0,0,0,magic,0, AL_ANGELUS,Angelus -34,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, AL_BLESSING,Blessing -35,9,6,16,0,0x1,0,1,1,yes,0,0,0,magic,0, AL_CURE,Cure -36,0,0,0,0,0,0,10,0,no,0,0,0,none,0, MC_INCCARRY,Enlarge Weight Limit -37,0,0,0,0,0,0,10,0,no,0,0,0,none,0, MC_DISCOUNT,Discount -38,0,0,0,0,0,0,10,0,no,0,0,0,none,0, MC_OVERCHARGE,Overcharge -39,1,0,0,0,0,0,10,0,no,0,0,0,none,0, MC_PUSHCART,Pushcart -40,1,6,4,0,0x1,0,1,1,no,0,0,0,none,0, MC_IDENTIFY,Item Appraisal -41,1,6,4,0,0x1,0,10,1,no,0,0,0,none,0, MC_VENDING,Vending -42,-1,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, MC_MAMMONITE,Mammonite -43,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AC_OWL,Owl's Eye -44,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AC_VULTURE,Vulture's Eye -45,0,6,4,0,0x3,3,10,1,no,0,0,0,weapon,0, AC_CONCENTRATION,Improve Concentration -46,-9,8,1,-1,0,0,10,2,no,0,0,0,weapon,0, AC_DOUBLE,Double Strafe -47,-9,6,2,-1,0x2,2,10,1,no,0,0x2000,0,weapon,2, AC_SHOWER,Arrow Shower -48,-1,8,0,-1,0,0,10,2,no,0,0,0,weapon,0, TF_DOUBLE,Double Attack -49,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, TF_MISS,Improve Dodge -50,1,6,1,0,1,0,10,1,no,0,0,0,weapon,0, TF_STEAL,Steal -51,1,6,4,0,1,0,10,1,no,0,0,0,none,0, TF_HIDING,Hiding -52,-2,6,1,5,0,0,10,1,no,0,0,0,weapon,0, TF_POISON,Envenom -53,9,6,16,5,0x1,0,1,1,no,0,0,0,weapon,0, TF_DETOXIFY,Detoxify -54,9,6,16,6,0x1,0,4,1,yes,0,0,0,magic,0, ALL_RESURRECTION,Resurrection -55,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, KN_SPEARMASTERY,Spear Mastery -56,-2,8,1,-1,0,0,10,3,no,0,0,0,weapon,0, KN_PIERCE,Pierce -57,-2,6,1,-1,0x1,0,10,1,no,33,0,0,weapon,3, KN_BRANDISHSPEAR,Brandish Spear -58,-4,6,1,-1,0x2,0,10,1,no,0,0,0,weapon,6, KN_SPEARSTAB,Spear Stab -59,3:5:7:9:11,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, KN_SPEARBOOMERANG,Spear Boomerang -60,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, KN_TWOHANDQUICKEN,Twohand Quicken -61,0,6,4,-1,0x20,0,5,1,no,0,0,0,weapon,0, KN_AUTOCOUNTER,Counter Attack -62,-2,6,1,-1,0x2,1,10,1,no,33,0,0,weapon,1, KN_BOWLINGBASH,Bowling Bash -63,0,0,0,0,0,0,1,0,no,0,0,0,weapon,0, KN_RIDING,Peco Peco Riding -64,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, KN_CAVALIERMASTERY,Cavalier Mastery -65,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, PR_MACEMASTERY,Mace Mastery -66,9,6,16,0,0x1,0,5,1,yes,0,0,0,magic,0, PR_IMPOSITIO,Impositio Manus -67,9,6,16,0,0x1,0,3,1,yes,0,0x200,0,magic,0, PR_SUFFRAGIUM,Suffragium -68,9,6,16,6,0x31,0,5,1,yes,0,0,0,magic,0, PR_ASPERSIO,Aspersio -69,9,6,2,0,0x23,1,5,1,yes,0,0x40,0,magic,0, PR_BENEDICTIO,B.S. Sacramenti -70,9,6,2,6,0x21,0,10,1,yes,0,0,0,magic,1, PR_SANCTUARY,Sanctuary -71,9,6,16,0,0x1,0,4,1,yes,0,0,0,magic,0, PR_SLOWPOISON,Slow Poison -72,9,6,16,0,0x1,0,1,1,yes,0,0,0,magic,0, PR_STRECOVERY,Status Recovery -73,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, PR_KYRIE,Kyrie Eleison -74,0,6,4,0,0x3,-1,5,1,yes,0,0,0,magic,0, PR_MAGNIFICAT,Magnificat -75,0,6,4,0,0x3,-1,5,1,yes,0,0,0,magic,0, PR_GLORIA,Gloria -76,5,6,1,0,0x1,0,10,0,yes,0,0,0,magic,0, PR_LEXDIVINA,Lex Divina -77,5,6,1,6,0x28,0,10,1,yes,0,0,0,magic,0, PR_TURNUNDEAD,Turn Undead -78,9,6,1,0,0x1,0,1,0,yes,0,0,0,magic,0, PR_LEXAETERNA,Lex Aeterna -79,9,8,2,6,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, PR_MAGNUS,Magnus Exorcismus -80,9,8,2,3,0x20,1:1:1:1:1:2:2:2:2:2:2,10,3:4:5:6:7:8:9:10:11:12:12,yes,0,0x80,5,magic,0, WZ_FIREPILLAR,Fire Pillar -81,0,6,4,3,0,3,10,1,yes,0,0,0,magic,5, WZ_SIGHTRASHER,Sightrasher -83,9,8,2,3,0,3:3:3:3:3:3:3:3:3:3:14,10,1:1:2:2:3:3:4:4:5:5:15,yes,0,0,0,magic,0, WZ_METEOR,Meteor Storm -84,9,8,1,4,0,0,10,3:4:5:6:7:8:9:10:11:12,yes,0,0,0,magic,2:3:3:4:4:5:5:6:6:7, WZ_JUPITEL,Jupitel Thunder -85,9,8,2,4,0,0,10,-10,yes,0,0,0,magic,0, WZ_VERMILION,Lord of Vermilion -86,9,8,1,1,0,0,5,1,yes,0,0,0,magic,0, WZ_WATERBALL,Water Ball -87,9,6,2,1,0x1,0,10,1,yes,0,0,0,magic,0, WZ_ICEWALL,Ice Wall -88,0,6,4,1,0x2,2,10,1,yes,0,0,0,magic,0, WZ_FROSTNOVA,Frost Nova -89,9,6,2,1,0,0,10,1,yes,0,0,0,magic,2, WZ_STORMGUST,Storm Gust -90,9,8,1,2,0,0,5,1:2:3:4:5,yes,0,0,0,magic,0, WZ_EARTHSPIKE,Earth Spike -91,9,8,2,2,0,0,5,1:2:3:4:5,yes,0,0,0,magic,0, WZ_HEAVENDRIVE,Heaven's Drive -92,9,6,2,2,0x1,0,5,1,yes,0,0,3,magic,0, WZ_QUAGMIRE,Quagmire -93,9,6,1,0,0x1,0,1,1,yes,0,0,0,magic,0, WZ_ESTIMATION,Sense -94,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, BS_IRON,Iron Tempering -95,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, BS_STEEL,Steel Tempering -96,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, BS_ENCHANTEDSTONE,Enchanted Stone Craft -97,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, BS_ORIDEOCON,Oridecon Research -98,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_DAGGER,Smith Dagger -99,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_SWORD,Smith Sword -100,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_TWOHANDSWORD,Smith Two-handed Sword -101,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_AXE,Smith Axe -102,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_MACE,Smith Mace -103,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_KNUCKLE,Smith Knucklebrace -104,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_SPEAR,Smith Spear -105,0,0,0,0,0,0,1,0,no,0,0,0,weapon,0, BS_HILTBINDING,Hilt Binding -106,0,0,0,0,0,0,1,0,no,0,0,0,weapon,0, BS_FINDINGORE,Ore Discovery -107,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, BS_WEAPONRESEARCH,Weaponry Research -108,2,6,16,0,0x1,0,1,1,yes,0,0,0,weapon,0, BS_REPAIRWEAPON,Weapon Repair -109,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, BS_SKINTEMPER,Skin Tempering -110,1,6,2,0,0x3,2:2:2:2:2:14,5,1,no,0,0,0,weapon,0, BS_HAMMERFALL,Hammer Fall -111,0,6,4,0,0x3,-1,5,1,no,0,0,0,weapon,0, BS_ADRENALINE,Adrenaline Rush -112,0,6,4,0,0x3,-1,5,1,no,0,0,0,weapon,0, BS_WEAPONPERFECT,Weapon Perfection -113,0,6,4,0,0x3,-1,5,1,no,0,0,0,weapon,0, BS_OVERTHRUST,Power-Thrust -114,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, BS_MAXIMIZE,Maximize Power -115,3,6,2,0,0x1,0,5,1,no,0,0x80,0,misc,6:7:8:9:10, HT_SKIDTRAP,Skid Trap -116,3,6,2,2,0x42,1,5,1,no,0,0x80,0,misc,0, HT_LANDMINE,Land Mine -117,3,6,2,0,0x1,0,5,1,no,0,0x80,0,misc,0, HT_ANKLESNARE,Ankle Snare -118,3,6,2,0,0x2,1,5,1,no,0,0x80,0,misc,0, HT_SHOCKWAVE,Shockwave Trap -119,3,6,2,0,0x3,2,5,1,no,0,0x80,0,misc,0, HT_SANDMAN,Sandman -120,3,6,2,0,0x3,1,5,1,no,0,0x80,0,misc,0, HT_FLASHER,Flasher -121,3,6,2,1,0x42,1,5,1,no,0,0x80,0,weapon,0, HT_FREEZINGTRAP,Freezing Trap -122,3,6,2,4,0x42,1,5,1,no,0,0x80,0,misc,0, HT_BLASTMINE,Blast Mine -123,3,6,2,3,0x42,2,5,1,no,0,0x80,0,misc,0, HT_CLAYMORETRAP,Claymore Trap -124,2,6,32,0,0x1,0,1,1,no,0,0,0,misc,0, HT_REMOVETRAP,Remove Trap -125,3,6,2,0,0x1,0,1,1,no,0,0x80,0,misc,0, HT_TALKIEBOX,Talkie Box -126,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, HT_BEASTBANE,Beast Bane -127,0,0,0,0,0,0,1,0,no,0,0,0,misc,0, HT_FALCON,Falconry Mastery -128,0,0,0,0,0,0,10,0,no,0,0,0,misc,0, HT_STEELCROW,Steel Crow -129,5,8,1,0,0x42,1,5,1:2:3:4:5,yes,0,0,0,misc,0, HT_BLITZBEAT,Blitz Beat -130,3:5:7:9,6,2,0,0x3,3,4,1,no,0,0,0,misc,0, HT_DETECTING,Detect -131,4:5:6:7:8,6,32,0,0x1,0,5,1,no,0,0,0,misc,0, HT_SPRINGTRAP,Spring Trap -132,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, AS_RIGHT,Righthand Mastery -133,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, AS_LEFT,Lefthand Mastery -134,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, AS_KATAR,Katar Mastery -135,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, AS_CLOAKING,Cloaking -136,-1,8,1,-1,0,0,10,8,no,0,0,0,weapon,0, AS_SONICBLOW,Sonic Blow -137,3:4:5:6:7,6,1,-1,0x2,1,5,1,no,0,0,0,weapon,0,AS_GRIMTOOTH,Grimtooth -138,1,6,16,5,0x1,0,10,1,no,0,0x400,0,weapon,0, AS_ENCHANTPOISON,Enchant Poison -139,0,6,4,0,0,0,10,1,no,0,0,0,weapon,0, AS_POISONREACT,Poison React -140,2,6,2,5,0x1,0,10,1,no,0,0,0,weapon,0, AS_VENOMDUST,Venom Dust -141,1,6,1,-1,0x51,2,10,1,yes,0,0,0,weapon,0, AS_SPLASHER,Venom Splasher -142,0,6,4,0,0x1,0,1,1,no,0,0x1,0,none,0, NV_FIRSTAID,First Aid -143,0,6,4,0,0x1,0,1,1,no,0,0x1,0,none,0, NV_TRICKDEAD,Play Dead -144,0,0,0,0,0,0,1,0,no,0,0x1,0,none,0, SM_MOVINGRECOVERY,Moving HP-Recovery -145,0,0,0,0,0,0,1,0,no,0,0x1,0,weapon,0, SM_FATALBLOW,Fatal Blow -146,0,6,4,0,0x1,0,1,1,no,0,0x1,0,weapon,0, SM_AUTOBERSERK,Auto Berserk -147,0,0,4,0,0x1,0,1,0,no,0,0x1,0,weapon,0, AC_MAKINGARROW,Arrow Crafting -148,-9,6,1,-1,0x2,0,1,1,no,0,0x1,0,weapon,6, AC_CHARGEARROW,Arrow Repel -149,1,6,1,2,0,0,1,1,no,0,0x1,0,weapon,0, TF_SPRINKLESAND,Sand Attack -150,0,6,4,0,0x1,0,1,1,no,0,0x1,0,weapon,5, TF_BACKSLIDING,Back Slide -151,0,6,4,0,0x1,0,1,1,no,0,0x1,0,none,0, TF_PICKSTONE,Find Stone -152,7,6,1,0,0x40,0,1,1,no,0,0x1,0,misc,0, TF_THROWSTONE,Stone Fling -153,1,6,1,-1,0x2,1,1,1,no,0,0x1,0,weapon,2, MC_CARTREVOLUTION,Cart Revolution -154,0,6,4,0,0x1,0,1,1,no,0,0x1,0,none,0, MC_CHANGECART,Change Cart -155,0,6,4,0,0x1,0,1,1,no,0,0x1,0,weapon,0, MC_LOUD,Crazy Uproar -156,9,6,1,6,0,0,1,1,yes,0,0x1,0,magic,0, AL_HOLYLIGHT,Holy Light -157,0,6,4,0,0x1,0,1,1,yes,0,0x1,0,magic,0, MG_ENERGYCOAT,Energy Coat -158,3,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_PIERCINGATT,Piercing Attack -159,-1,6,1,-1,0x40,0,5,1,no,0,0x2,0,weapon,0, NPC_MENTALBREAKER,Spirit Destruction -160,9,6,1,0,0,0,10,1,no,0,0x2,0,weapon,0, NPC_RANGEATTACK,Stand off attack -161,0,0,4,0,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_ATTRICHANGE,Attribute Change -162,0,0,4,1,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEWATER,Water Attribute Change -163,0,0,4,2,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEGROUND,Earth Attribute Change -164,0,0,4,3,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEFIRE,Fire Attribute Change -165,0,0,4,4,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEWIND,Wind Attribute Change -166,0,0,4,5,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEPOISON,Poison Attribute Change -167,0,0,4,6,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEHOLY,Holy Attribute Change -168,0,0,4,7,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEDARKNESS,Shadow Attribute Change -169,0,0,4,8,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGETELEKINESIS,Ghost Attribute Change -170,-9,6,1,-1,0x20,0,10,1,no,0,0x2,0,weapon,0, NPC_CRITICALSLASH,Defense disregard attack -171,-9,8,1,-1,0,0,10,-2:-3:-4:-5:-6:-7:-8:-9:-10:-11,no,0,0x2,0,weapon,0, NPC_COMBOATTACK,Multi-stage Attack -172,-9,6,1,-1,0x40,0,10,1,no,0,0x2,0,weapon,0, NPC_GUIDEDATTACK,Guided Attack -173,5,6,4,3,0xE2,5,10,1,no,0,0x2,0,misc,3, NPC_SELFDESTRUCTION,Suicide bombing -174,-9,6,1,-1,0x2,3,1,1,no,0,0x2,0,weapon,0, NPC_SPLASHATTACK,Splash attack -175,0,0,4,0,0x41,0,10,1,no,0,0x2,0,misc,0, NPC_SUICIDE,Suicide -176,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_POISON,Poison Attack -177,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_BLINDATTACK,Blind Attack -178,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_SILENCEATTACK,Silence Attack -179,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_STUNATTACK,Stun Attack -180,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_PETRIFYATTACK,Petrify Attack -181,-9,6,1,7,0,0,5,1,no,0,0x2,0,weapon,0, NPC_CURSEATTACK,Curse Attack -182,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_SLEEPATTACK,Sleep attack -183,-9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_RANDOMATTACK,Random Attack -184,-9,6,1,1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_WATERATTACK,Water Attribute Attack -185,-9,6,1,2,0,0,10,1,no,0,0x2,0,weapon,0, NPC_GROUNDATTACK,Earth Attribute Attack -186,-9,6,1,3,0,0,10,1,no,0,0x2,0,weapon,0, NPC_FIREATTACK,Fire Attribute Attack -187,-9,6,1,4,0,0,10,1,no,0,0x2,0,weapon,0, NPC_WINDATTACK,Wind Attribute Attack -188,-9,6,1,5,0,0,10,1,no,0,0x2,0,weapon,0, NPC_POISONATTACK,Poison Attribute Attack -189,-9,6,1,6,0,0,10,1,no,0,0x2,0,weapon,0, NPC_HOLYATTACK,Holy Attribute Attack -190,-9,6,1,7,0,0,10,1,no,0,0x2,0,weapon,0, NPC_DARKNESSATTACK,Shadow Attribute Attack -191,-9,6,1,8,0,0,10,1,no,0,0x2,0,weapon,0, NPC_TELEKINESISATTACK,Ghost Attribute Attack -192,-9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_MAGICALATTACK,Demon Shock Attack -193,0,0,4,0,0x1,0,10,1,no,0,0x2,0,none,0, NPC_METAMORPHOSIS,Metamorphosis -194,0,0,4,0,0x1,0,10,1,no,0,0x2,0,none,0, NPC_PROVOCATION,Provocation -195,0,6,4,0,0x50,0,10,1,no,0,0x2,0,misc,0, NPC_SMOKING,Smoking -196,0,0,4,0,0x1,0,10,1,no,0,0x2,0,magic,0, NPC_SUMMONSLAVE,Follower Summons -197,0,0,4,0,0x1,0,10,1,no,0,0x2,0,none,0, NPC_EMOTION,Emotion -198,0,0,4,0,0x1,0,10,1,no,0,0x2,0,magic,0, NPC_TRANSFORMATION,Transformation -199,9,6,1,7,0x40,0,1,1,no,0,0x2,0,weapon,0, NPC_BLOODDRAIN,Sucking Blood -200,9,6,1,7,0,0,1,1,no,0,0x2,0,magic,0, NPC_ENERGYDRAIN,Energy Drain -201,0,0,4,0,0x1,0,1,1,no,0,0x2,0,weapon,0, NPC_KEEPING,Keeping -202,9,6,1,7,0,0,5,1,no,0,0x2,0,misc,0, NPC_DARKBREATH,Dark Breath -203,9,6,1,7,0x1,0,10,1,no,0,0x2,0,magic,0, NPC_DARKBLESSING,Dark Blessing -204,0,0,4,0,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_BARRIER,Barrier -205,0,0,4,0,0x1,0,1,1,no,0,0x2,0,weapon,0, NPC_DEFENDER,Defender -206,1,6,1,-1,0x1,0,5,1,no,0,0x2,0,weapon,0, NPC_LICK,Lick -207,9,0,1,0,0x1,0,10,1,no,0,0x2,0,magic,0, NPC_HALLUCINATION,Hallucination -208,0,0,4,0,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_REBIRTH,Rebirth -209,0,0,4,0,0x1,0,10,1,no,0,0x2,0,magic,0, NPC_SUMMONMONSTER,Monster Summons -210,0,0,0,-1,0,0,10,0,no,0,0,0,weapon,0, RG_SNATCHER,Gank -211,1,6,1,0,0x1,0,10,1,no,0,0,0,weapon,0, RG_STEALCOIN,Mug -212,-1,6,1,-1,0x40,0,10,1,no,0,0,0,weapon,0, RG_BACKSTAP,Back Stab -213,0,0,0,0,0,0,5,0,no,0,0,0,none,0, RG_TUNNELDRIVE,Stalk -214,0,6,4,-1,0x2,1,5,1,no,0,0,0,weapon,0, RG_RAID,Sightless Mind -215,1,6,1,0,0x1,0,5,1,no,0,0,0,weapon,0, RG_STRIPWEAPON,Divest Weapon -216,1,6,1,0,0x1,0,5,1,no,0,0,0,weapon,0, RG_STRIPSHIELD,Divest Shield -217,1,6,1,0,0x1,0,5,1,no,0,0,0,weapon,0, RG_STRIPARMOR,Divest Armor -218,1,6,1,0,0x1,0,5,1,no,0,0,0,weapon,0, RG_STRIPHELM,Divest Helm -219,1,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, RG_INTIMIDATE,Snatch -220,1,6,2,0,0x1,0,1,1,no,0,0,0,none,0, RG_GRAFFITI,Scribble -221,0,6,2,0,0x1,0,5,1,no,0,0,0,none,0, RG_FLAGGRAFFITI,Piece -222,1,6,2,0,0x3,5,1,1,no,0,0,0,none,0, RG_CLEANER,Remover -223,0,0,0,0,0,1,1,0,no,0,0,0,none,0, RG_GANGSTER,Slyness -224,0,0,0,0,0,0,5,0,no,0,0,0,none,0, RG_COMPULSION,Haggle -225,0,0,0,0,0,0,10,0,no,0,0,0,none,0, RG_PLAGIARISM,Intimidate -226,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, AM_AXEMASTERY,Axe Mastery -227,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AM_LEARNINGPOTION,Potion Research -228,0,6,4,0,0x1,0,10,0,no,0,0,0,none,0, AM_PHARMACY,Prepare Potion -229,9,6,2,3,0x9,0,5,1,yes,0,0,0,weapon,0, AM_DEMONSTRATION,Bomb -230,9,6,1,0,0x48,0,5,1,yes,0,0,0,weapon,0, AM_ACIDTERROR,Acid Terror -231,9,6,16,0,0x1,0,5,1,yes,0,0xC00,0,none,0, AM_POTIONPITCHER,Aid Potion -232,4,6,2,0,0x1,0,5,1,no,0,0,5,none,0, AM_CANNIBALIZE,Summon Flora -233,1,6,2,0,0x1,0,5,1,no,0,0,3,none,0, AM_SPHEREMINE,Summon Marine Sphere -234,1,6,16,0,0x1,0,5,1,yes,0,0,0,weapon,0, AM_CP_WEAPON,Alchemical Weapon -235,1,6,16,0,0x1,0,5,1,yes,0,0,0,weapon,0, AM_CP_SHIELD,Synthesized Shield -236,1,6,16,0,0x1,0,5,1,yes,0,0,0,weapon,0, AM_CP_ARMOR,Synthetic Armor -237,1,6,16,0,0x1,0,5,1,yes,0,0,0,weapon,0, AM_CP_HELM,Biochemical Helm -238,0,0,0,0,0,0,1,0,no,0,0x1,0,none,0, AM_BIOETHICS,Bioethics -//239,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AM_BIOTECHNOLOGY,Biotechnology -//240,0,0,0,0,0,0,5,0,no,0,0,0,none,0, AM_CREATECREATURE,Life Creation -//241,0,0,0,0,0,0,5,0,no,0,0,0,none,0, AM_CULTIVATION,Cultivation -//242,0,0,0,0,0,0,5,0,no,0,0,0,none,0, AM_FLAMECONTROL,Flame Control -243,0,0,4,0,0x1,1,1,0,no,0,0,0,none,0, AM_CALLHOMUN,Call Homunculus -244,0,0,4,0,0x1,0,1,0,no,0,0,0,none,0, AM_REST,Vaporize -//245,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AM_DRILLMASTER,Drillmaster -//246,9,0,0,0,0,0,10,0,no,0,0,0,none,0, AM_HEALHOMUN,Heal Homunculus -247,9,6,4,0,0x1,1,5,0,no,0,0,0,none,0, AM_RESURRECTHOMUN,Homunculus Resurrection -248,0,0,0,0,0,0,10,0,no,0,0,0,none,0, CR_TRUST,Faith -249,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, CR_AUTOGUARD,Guard -250,3,6,1,0,0,0,5,1,no,0,0,0,weapon,5:6:7:8:9, CR_SHIELDCHARGE,Smite -251,3:5:7:9:11,6,1,0,0,0,5,1,no,0,0,0,weapon,0, CR_SHIELDBOOMERANG,Shield Boomerang -252,0,6,4,0,0,0,10,1,no,0,0,0,weapon,0, CR_REFLECTSHIELD,Shield Reflect -253,-2,8,1,6,0,0,10,-2,no,0,0,0,weapon,0, CR_HOLYCROSS,Holy Cross -254,5,6,4,6,0x48,0,10,1,no,33,0x100,0,magic,0, CR_GRANDCROSS,Grand Cross -255,7:8:9:10:11,6,16,0,0x1,0,5,1,yes,0,0x600,0,none,0, CR_DEVOTION,Sacrifice -256,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,none,0, CR_PROVIDENCE,Resistant Souls -257,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, CR_DEFENDER,Defending Aura -258,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, CR_SPEARQUICKEN,Spear Quicken -259,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, MO_IRONHAND,Iron Fists -260,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, MO_SPIRITSRECOVERY,Spiritual Cadence -261,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MO_CALLSPIRITS,Summon Spirit Sphere -262,9,6,16,0,0x1,0,1,1,yes,0,0,0,weapon,0, MO_ABSORBSPIRITS,Absorb Spirit Sphere -263,-1,8,0,-1,0,0,10,-3,no,0,0,0,weapon,0, MO_TRIPLEATTACK,Raging Trifecta Blow -264,18,6,2,0,0x1,0,1,1,no,0,0,0,none,0, MO_BODYRELOCATION,Snap -265,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, MO_DODGE,Dodge -266,2,6,1,0,0x40,0,5,1,no,0,0,0,weapon,0, MO_INVESTIGATE,Occult Impaction -267,9,8,1,-1,0,0,5,1:2:3:4:5,no,0,0,0,weapon,0, MO_FINGEROFFENSIVE,Throw Spirit Sphere -268,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, MO_STEELBODY,Mental Strength -269,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, MO_BLADESTOP,Root -270,0,6,4,0,0x1,0,5,0,no,0,0,0,weapon,0, MO_EXPLOSIONSPIRITS,Fury -271,-2,6,1,0,0x60,0,5,1,yes,0,0,0,weapon,0, MO_EXTREMITYFIST,Asura Strike -272,-2,8,4,-1,0,0,5,-4,no,0,0x200,0,weapon,0, MO_CHAINCOMBO,Raging Quadruple Blow -273,-2,6,4,-1,0x2,2,5,1,no,0,0x200,0,weapon,0, MO_COMBOFINISH,Raging Thrust -274,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, SA_ADVANCEDBOOK,Study -275,0,6,4,0,0x1,0,5,1,no,0,0,0,magic,0, SA_CASTCANCEL,Cast Cancel -276,0,6,4,0,0x1,0,5,1,yes,0,0,0,magic,0, SA_MAGICROD,Magic Rod -277,9,6,1,0,0x1,0,5,1,yes,0,0,0,magic,0, SA_SPELLBREAKER,Spell Breaker -278,0,0,0,0,0,0,10,0,no,0,0,0,magic,0, SA_FREECAST,Free Cast -279,0,6,4,0,0x1,0,10,1,yes,0,0,0,magic,0, SA_AUTOSPELL,Hindsight -280,9,6,16,3,0x1,0,5,1,yes,0,0xC00,0,magic,0, SA_FLAMELAUNCHER,Endow Blaze -281,9,6,16,1,0x1,0,5,1,yes,0,0xC00,0,magic,0, SA_FROSTWEAPON,Endow Tsunami -282,9,6,16,4,0x1,0,5,1,yes,0,0xC00,0,magic,0, SA_LIGHTNINGLOADER,Endow Tornado -283,9,6,16,2,0x1,0,5,1,yes,0,0xC00,0,magic,0, SA_SEISMICWEAPON,Endow Quake -284,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, SA_DRAGONOLOGY,Dragonology -285,2,6,2,3,0x1,0,5,1,yes,0,0,0,magic,0, SA_VOLCANO,Volcano -286,2,6,2,1,0x1,0,5,1,yes,0,0,0,magic,0, SA_DELUGE,Deluge -287,2,6,2,4,0x1,0,5,1,yes,0,0,0,magic,0, SA_VIOLENTGALE,Whirlwind -288,2,6,2,0,0x1,0,5,1,yes,0,0,0,magic,0, SA_LANDPROTECTOR,Magnetic Earth -289,9,6,1,0,0x1,0:0:0:0:0:-1,5,1,yes,0,0xE00,0,magic,0, SA_DISPELL,Dispell -290,0,6,4,0,0x1,0,10,1,yes,0,0,0,magic,0, SA_ABRACADABRA,Hocus-pocus -291,9,6,1,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_MONOCELL,Monocell -292,9,6,1,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_CLASSCHANGE,Class Change -293,0,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_SUMMONMONSTER,Monster Chant -294,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_REVERSEORCISH,Grampus Morph -295,9,6,1,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_DEATH,Grim Reaper -296,9,6,1,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_FORTUNE,Gold Digger -297,9,6,1,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_TAMINGMONSTER,Beastly Hypnosis -298,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_QUESTION,Questioning -299,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_GRAVITY,Gravity -300,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_LEVELUP,Leveling -301,9,6,4,0,0,0,1,1,yes,0,0x2,0,magic,0, SA_INSTANTDEATH,Suicide -302,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_FULLRECOVERY,Rejuvenation -303,9,6,4,0,0,0,1,1,yes,0,0x2,0,magic,0, SA_COMA,Coma -304,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, BD_ADAPTATION,Amp -305,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, BD_ENCORE,Encore -306,0,6,4,0,0x1,0,1,1,no,0,0x40,0,misc,0, BD_LULLABY,Lullaby -307,0,6,4,0,0x1,0,5,1,no,0,0x40,0,misc,0, BD_RICHMANKIM,Mental Sensing -308,0,6,4,0,0x1,0,1,1,no,0,0x40,0,misc,0, BD_ETERNALCHAOS,Down Tempo -309,0,6,4,0,0x1,0,5,1,no,0,0x40,0,misc,0, BD_DRUMBATTLEFIELD,Battle Theme -310,0,6,4,0,0x1,0,5,1,no,0,0x40,0,misc,0, BD_RINGNIBELUNGEN,Harmonic Lick -311,0,6,4,0,0x1,0,1,1,no,0,0x40,0,misc,0, BD_ROKISWEIL,Classical Pluck -312,0,6,4,0,0x1,0,1,1,no,0,0x40,0,misc,0, BD_INTOABYSS,Power Chord -313,0,6,4,0,0x1,0,5,1,no,0,0x40,0,misc,0, BD_SIEGFRIED,Acoustic Rhythm -//314,0,0,0,0,0,0,1,1,no,0,0x40,0,misc,0, BD_RAGNAROK,Ragnarok -315,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, BA_MUSICALLESSON,Music Lessons -316,9,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, BA_MUSICALSTRIKE,Melody Strike -317,0,8,4,0,0x41,0,5,1,no,0,0x20,0,misc,0, BA_DISSONANCE,Unchained Serenade -318,0,6,4,0,0x3,-1,5,1,no,0,0,0,misc,0, BA_FROSTJOKER,Unbarring Octave -319,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, BA_WHISTLE,Perfect Tablature -320,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, BA_ASSASSINCROSS,Impressive Riff -321,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, BA_POEMBRAGI,Magic Strings -322,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, BA_APPLEIDUN,Song of Lutie -323,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, DC_DANCINGLESSON,Dance Lessons -324,9,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, DC_THROWARROW,Slinging Arrow -325,0,8,4,0,0x1,0,5,1,no,0,0x20,0,misc,0, DC_UGLYDANCE,Hip Shaker -326,0,6,4,0,0x3,-1,5,1,no,0,0,0,misc,0, DC_SCREAM,Dazzler -327,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, DC_HUMMING,Focus Ballet -328,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, DC_DONTFORGETME,Slow Grace -329,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, DC_FORTUNEKISS,Lady Luck -330,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, DC_SERVICEFORYOU,Gypsy's Kiss -331,0,6,4,0,0x1,0,10,0,no,0,0x2,0,none,0, NPC_RANDOMMOVE,Random Move -332,0,6,4,0,0x1,0,10,0,no,0,0x2,0,none,0, NPC_SPEEDUP,Speed UP -333,0,6,4,0,0x1,0,1,0,no,0,0x2,0,none,0, NPC_REVENGE,Revenge -334,9,6,4,0,0x1,0,1,1,yes,0,0x4,0,none,0, WE_MALE,I Will Protect You -335,9,6,4,0,0x1,0,1,1,yes,0,0x4,0,none,0, WE_FEMALE,I Look up to You -336,9,6,4,0,0x1,3,1,1,yes,0,0x4,1,none,0, WE_CALLPARTNER,I miss You -337,9,6,1,-1,0,0,1,1,no,0,0x2,0,weapon,0, ITM_TOMAHAWK,Throw Tomahawk -338,-1,8,1,7,0,0,10,-2,no,0,0x2,0,weapon,0, NPC_DARKCROSS,Cross of Darkness -339,5,6,4,7,0x48,0,10,1,no,33,0x102,0,magic,0, NPC_GRANDDARKNESS,Grand cross of Darkness -340,9,8,1,7,0,0,10,1:1:2:2:3:3:4:4:5:5,yes,0,0x2,0,magic,0, NPC_DARKSTRIKE,Soul Strike of Darkness -341,9,8,1,7,0,0,10,3:4:5:6:7:8:9:10:11:12,yes,0,0x2,0,magic,2:3:3:4:4:5:5:6:6:7, NPC_DARKTHUNDER,Darkness Jupitel -342,9,6,1,0,0x1,0,1,0,no,0,0x2,0,none,0, NPC_STOP,Stop -343,9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_WEAPONBRAKER,Break weapon -344,9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_ARMORBRAKE,Break armor -345,9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_HELMBRAKE,Break helm -346,9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_SHIELDBRAKE,Break shield -347,-9,6,1,9,0,0,10,1,no,0,0x2,0,weapon,0, NPC_UNDEADATTACK,Undead Element Attack -348,9,0,1,9,0x1,0,5,1,no,0,0x2,0,magic,0, NPC_CHANGEUNDEAD,Undead Attribute Change -349,0,6,4,0,0x1,0,10,0,no,0,0x2,0,weapon,0, NPC_POWERUP,Power Up -350,0,6,4,0,0x1,0,10,0,no,0,0x2,0,none,0, NPC_AGIUP,Agility UP -351,0,0,0,0,0x1,0,1,0,no,0,0x2,0,none,0, NPC_SIEGEMODE,Siege Mode -352,2,0,4,0,0x1,0,1,0,no,0,0x2,0,none,0, NPC_CALLSLAVE,Recall Slaves -353,0,0,0,0,0x1,0,1,0,no,0,0x2,0,none,0, NPC_INVISIBLE,Invisible -354,2,6,4,0,0x1,0,20,0,no,0,0x2,0,misc,0, NPC_RUN,Run -355,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, LK_AURABLADE,Aura Blade -356,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, LK_PARRYING,Parrying -357,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, LK_CONCENTRATION,Concentration -358,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, LK_TENSIONRELAX,Relax -359,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, LK_BERSERK,Frenzy -//360,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, LK_FURY,Fury -361,9,6,16,0,0x1,1,5,1,yes,0,0,0,magic,0, HP_ASSUMPTIO,Assumptio -362,4,6,4,0,0x1,0,5,1,yes,0,0,0,magic,2, HP_BASILICA,Basilica -363,0,0,0,0,0,0,10,0,no,0,0,0,magic,0, HP_MEDITATIO,Meditatio -364,0,0,0,0,0,0,10,1,no,0,0,0,magic,0, HW_SOULDRAIN,Soul Drain -365,9,8,1,-1,0,0,1,1,yes,0,0,0,weapon,0, HW_MAGICCRASHER,Stave Crasher -366,0,6,4,0,0x1,0,10,1,no,0,0,0,magic,0, HW_MAGICPOWER,Mystical Amplification -367,9,8,1,0,0xD0,0,5,1,no,0,0,0,misc,0, PA_PRESSURE,Gloria Domini -368,0,6,4,0,0x61,0,5,1,yes,0,0,0,weapon,0, PA_SACRIFICE, Martyr's Reckoning -369,0,6,4,0,0x41,0,10,1,yes,0,0,0,misc,0, PA_GOSPEL,Battle Chant -370,-2,6,1,-1,0,0,5,1,yes,0,0,0,weapon,3, CH_PALMSTRIKE,Raging Palm Strike -371,-2,8,4,-1,0,0,5,1,no,0,0x200,0,weapon,0, CH_TIGERFIST,Glacier Fist -372,-2,8,4,-1,0,0,10,-1:-1:-2:-2:-3:-3:-4:-4:-5:-5,no,0,0x200,0,weapon,0, CH_CHAINCRUSH,Chain Crush Combo -373,0,6,4,0,0x1,0,5,1,no,0,0,0,magic,0, PF_HPCONVERSION,Indulge -374,9,6,1,0,0x1,0,1,1,yes,0,0xE00,0,none,0, PF_SOULCHANGE,Soul Exhale -375,9,6,1,0,0x98,0,5,1,yes,0,0,0,magic,0, PF_SOULBURN,Soul Siphon -376,0,0,0,0,0x1,0,5,1,no,0,0,0,weapon,0, ASC_KATAR,Advanced Katar Mastery -//377,0,0,4,0,0x1,0,10,1,no,0,0,0,misc,0, ASC_HALLUCINATION,Hallucination Walk -378,0,6,4,5,0x1,0,5,1,no,0,0,0,weapon,0, ASC_EDP,Enchant Deadly Poison -379,7,6,1,-1,0x8,0,10,1,yes,0,0,0,weapon,0, ASC_BREAKER,Soul Destroyer -380,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, SN_SIGHT,Falcon Eyes -381,5,8,1,0,0x40,0,5,1,yes,0,0,0,misc,0, SN_FALCONASSAULT,Falcon Assault -382,9,8,1,-1,0,2,5,1,yes,0,0,13,weapon,0, SN_SHARPSHOOTING,Focused Arrow Strike -383,0,6,4,0,0x3,-1,10,1,yes,0,0,0,weapon,0, SN_WINDWALK,Wind Walker -384,0,0,4,0,0x1,0,10,1,yes,0,0,0,weapon,0, WS_MELTDOWN,Shattering Strike -//385,0,0,4,0,0x1,0,1,1,yes,0,0,0,none,0, WS_CREATECOIN,Create Coins -//386,0,0,4,0,0x1,0,1,1,yes,0,0,0,none,0, WS_CREATENUGGET,Create Nuggets -387,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, WS_CARTBOOST,Cart Boost -//388,9,6,2,0,0x1,0,5,1,no,0,0,0,none,0, WS_SYSTEMCREATE,Auto Attack System -389,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, ST_CHASEWALK,Stealth -390,0,0,4,0,0,0,5,1,yes,0,0,0,weapon,0, ST_REJECTSWORD,Counter Instinct -//391,0,0,4,0,1,0,1,1,yes,0,0,0,magic,0, ST_STEALBACKPACK,Steal Backpack -392,0,0,4,0,0x1,0,1,1,yes,0,0,0,none,0, CR_ALCHEMY,Alchemy -393,0,0,4,0,0x1,0,1,1,yes,0,0,0,none,0, CR_SYNTHESISPOTION,Potion Synthesis -394,9,8,1,-1,0,0,10,-9,yes,0,0,0,weapon,0, CG_ARROWVULCAN,Vulcan Arrow -395,0,0,4,0,0x1,3,1,1,yes,0,0x40,0,misc,2, CG_MOONLIT,Sheltering Bliss -396,1,6,16,0,0x1,0,1,1,yes,0,0x600,0,none,0, CG_MARIONETTE,Marionette Control -397,5,8,1,-1,0x20,0,5,5,no,0,0,0,weapon,0, LK_SPIRALPIERCE,Spiral Pierce -398,4,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, LK_HEADCRUSH,Traumatic Blow -399,4,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, LK_JOINTBEAT,Vital Strike -400,9,8,1,8,0x6,1,5,1:2:3:4:5,yes,0,0,0,magic,0, HW_NAPALMVULCAN,Napalm Vulcan -401,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, CH_SOULCOLLECT,Zen -402,9,6,1,0,0x1,0,5,1,no,0,0,0,none,0, PF_MINDBREAKER,Mind Breaker -403,0,0,4,0,0x1,0,1,1,yes,0,0,0,magic,0, PF_MEMORIZE,Foresight -404,9,6,2,2,0x1,0,5,1,yes,0,0x100,2,magic,0, PF_FOGWALL,Blinding Mist -405,7,6,1,0,0x1,0,1,1,no,0,0,3,magic,0, PF_SPIDERWEB,Fiber Lock -406,0,6,4,-1,0xA,2,10,1,no,33,0,0,weapon,0, ASC_METEORASSAULT,Meteor Assault -407,0,6,4,0,0x1,0,1,0,no,0,0,0,none,0, ASC_CDP,Create Deadly Poison -408,9,6,4,0,0x1,0,1,1,yes,0,0x4,0,none,0, WE_BABY,Baby -409,9,6,4,0,0x1,3,1,1,yes,0,0x4,1,none,0, WE_CALLPARENT,Call Parent -410,9,6,4,0,0x1,3,1,1,yes,0,0x4,1,none,0, WE_CALLBABY,Call Baby -411,0,6,4,0,0x1,0,10,1,yes,0,0,0,misc,4, TK_RUN,Running -412,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, TK_READYSTORM,Tornado Stance -413,-2,8,4,-1,0x2,2,7,-3,no,0,0x200,0,weapon,0, TK_STORMKICK,Tornado Kick -414,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, TK_READYDOWN,Heel Drop Stance -415,-2,8,4,-1,0,0,7,-3,no,0,0x200,0,weapon,0, TK_DOWNKICK,Heel Drop -416,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, TK_READYTURN,Roundhouse Stance -417,-2,8,4,-1,0x2,1,7,-3,no,0,0x200,0,weapon,2, TK_TURNKICK,Roundhouse Kick -418,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, TK_READYCOUNTER,Counter Kick Stance -419,-2,8,4,-1,0x40,0,7,-3,no,0,0x200,0,weapon,0, TK_COUNTER,Counter Kick -420,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, TK_DODGE,Tumbling -421,9,8,16,-1,0x1,0,7,-3,no,0,0,0,weapon,0, TK_JUMPKICK,Flying Kick -422,0,0,0,0,0,1,10,0,no,0,0,0,none,0, TK_HPTIME,Peaceful Break -423,0,0,0,0,0,1,10,0,no,0,0,0,none,0, TK_SPTIME,Happy Break -424,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, TK_POWER,Kihop -425,0,6,4,2:4:1:3:8:7:6,0x1,0,7,1,no,0,0,0,weapon,0, TK_SEVENWIND,Mild Wind -426,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, TK_HIGHJUMP,Taekwon Jump -427,0,6,4,0,0x1,0,3,1,yes,0,0,0,magic,0, SG_FEEL,Feeling the Sun Moon and Stars -428,1,6,4,-1,0x2,1,3,1,yes,0,0,0,weapon,2, SG_SUN_WARM,Warmth of the Sun -429,1,6,4,-1,0x2,1,3,1,yes,0,0,0,weapon,2, SG_MOON_WARM,Warmth of the Moon -430,1,6,4,-1,0x2,1,3,1,yes,0,0,0,weapon,2, SG_STAR_WARM,Warmth of the Stars -431,0,0,4,0,0x1,0,4,1,yes,0,0,0,magic,0, SG_SUN_COMFORT,Comfort of the Sun -432,0,0,4,0,0x1,0,4,1,yes,0,0,0,magic,0, SG_MOON_COMFORT,Comfort of the Moon -433,0,0,4,0,0x1,0,4,1,yes,0,0,0,magic,0, SG_STAR_COMFORT,Comfort of the Stars -434,10,6,1,0,0x1,0,3,1,yes,0,0,0,magic,0, SG_HATE,Hatred of the Sun Moon and Stars -435,0,0,0,0,0,0,3,0,no,0,0,0,none,0, SG_SUN_ANGER,Anger of the Sun -436,0,0,0,0,0,0,3,0,no,0,0,0,none,0, SG_MOON_ANGER,Anger of the Moon -437,0,0,0,0,0,0,3,0,no,0,0,0,none,0, SG_STAR_ANGER,Anger of the Stars -438,0,0,0,0,0,0,5,0,no,0,0,0,none,0, SG_SUN_BLESS,Blessing of the Sun -439,0,0,0,0,0,0,5,0,no,0,0,0,none,0, SG_MOON_BLESS,Blessing of the Moon -440,0,0,0,0,0,0,5,0,no,0,0,0,none,0, SG_STAR_BLESS,Blessing of the Stars -441,0,0,0,0,0,0,10,0,no,0,0,0,none,0, SG_DEVIL,Demon of the Sun Moon and Stars -442,0,0,0,0,0,0,3,0,no,0,0,0,none,0, SG_FRIEND,Friend of the Sun Moon and Stars -443,0,0,0,0,0,0,10,0,no,0,0,0,none,0, SG_KNOWLEDGE,Knowledge of the Sun Moon and Stars -444,0,6,4,0,0x1,0,1,1,no,0,0,0,misc,0, SG_FUSION,Union of the Sun Moon and Stars -445,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_ALCHEMIST,Spirit of the Alchemist -446,9,6,16,0,0x1,0,1,1,yes,0,0xC08,0,none,0, AM_BERSERKPITCHER,Aid Berserk Potion -447,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_MONK,Spirit of the Monk -448,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_STAR,Spirit of the Star Gladiator -449,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_SAGE,Spirit of the Sage -450,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_CRUSADER,Spirit of the Crusader -451,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_SUPERNOVICE,Spirit of the Supernovice -452,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_KNIGHT,Spirit of the Knight -453,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_WIZARD,Spirit of the Wizard -454,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_PRIEST,Spirit of the Priest -455,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_BARDDANCER,Spirit of the Artist -456,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_ROGUE,Spirit of the Rogue -457,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_ASSASIN,Spirit of the Assasin -458,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_BLACKSMITH,Spirit of the Blacksmith -459,0,6,4,0,0x3,-1,1,1,no,0,0x8,0,weapon,0 , BS_ADRENALINE2,Advanced Adrenaline Rush -460,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_HUNTER,Spirit of the Hunter -461,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_SOULLINKER,Spirit of the Soul Linker -462,9,6,16,0,0x1,0,7,1,yes,0,0,0,magic,0, SL_KAIZEL,Kaizel -463,9,6,16,0,0x1,0,7,1,yes,0,0,0,magic,0, SL_KAAHI,Kaahi -464,9,6,16,0,0x1,0,3,1,yes,0,0,0,magic,0, SL_KAUPE,Kaupe -465,9,6,16,0,0x1,0,7,1,yes,0,0,0,magic,0, SL_KAITE,Kaite -466,0,0,0,0,0,0,7,0,yes,0,0,0,magic,0, SL_KAINA,Kaina -467,9,6,1,-2,0,0,7,1,no,0,0,0,magic,2, SL_STIN,Estin -468,9,6,1,-2,0,0,7,1,no,0,0,0,magic,0, SL_STUN,Estun -469,9,8,1,-2,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, SL_SMA,Esma -470,9,6,1,0,0x1,0,7,1,no,0,0,0,magic,0, SL_SWOO,Eswoo -471,9,6,1,0,0x1,0,3,1,no,0,0,0,magic,0, SL_SKE,Eske -472,9,6,1,0,0x1,0,3,1,no,0,0,0,magic,0, SL_SKA,Eska -473,0,6,4,0,0,0,1,1,no,0,0,0,none,0, SM_SELFPROVOKE,Provoke Self -474,0,0,4,0,0x1,0,10,1,no,0,0x2,0,none,0, NPC_EMOTION_ON,Emotion ON -475,0,0,4,0,0x1,0,1,1,yes,0,0,0,none,0, ST_PRESERVE,Preserve -476,1,6,1,0,0x1,0,5,1,yes,0,0,0,weapon,0, ST_FULLSTRIP,Divest All -477,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, WS_WEAPONREFINE,Upgrade Weapon -478,3,6,2,0,0x3,3,10,1,no,0,0,0,none,0, CR_SLIMPITCHER,Aid Condensed Potion -479,1,6,16,0,0x1,0,5,1,yes,0,0,0,weapon,0, CR_FULLPROTECTION,Full Protection -480,5,8,1,0,0,0,5,5,no,0,0,0,weapon,0, PA_SHIELDCHAIN,Shield Chain -481,0,0,0,0,0,0,5,0,no,0,0,0,none,0, HP_MANARECHARGE,Mana Recharge -482,0,6,4,0,0x1,0,5,1,no,0,0,0,magic,0, PF_DOUBLECASTING,Double Casting -483,16,6,2,0,0x1,1:2:3:4:5,1,1,no,0,0,0,none,0, HW_GANBANTEIN,Ganbantein -484,9,6,2,2,0x91,0,5,1,yes,0,0,0,misc,0, HW_GRAVITATION,Gravitation Field -485,-2,6,1,-1,0x8,0,10,1,no,0,0,0,weapon,0, WS_CARTTERMINATION,Cart Termination -486,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, WS_OVERTHRUSTMAX,Maximum Power Thrust -487,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, CG_LONGINGFREEDOM,Longing for Freedom -488,0,6,4,0,0x1,1,5,1,no,0,0x40,0,misc,0, CG_HERMODE,Wand of Hermode -489,9,6,1,0,0x41,0,5,1,no,0,0,0,misc,0, CG_TAROTCARD,Tarot Card of Fate -490,9,8,1,0,0x40,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,misc,0, CR_ACIDDEMONSTRATION,Acid Demonstration -491,1,6,2,0,0x1,0,2,1,no,0,0,0,none,0, CR_CULTIVATION,Plant Cultivation -492,0,6,4,0:1:2:3:4:5:6:7:8:9,0x1,0,10,1,no,0,0x2,0,none,0, ITEM_ENCHANTARMS,Weapon Enchantment -493,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, TK_MISSION,Taekwon Mission -494,9,6,16,0,0x1,0,5,1,yes,0,0,0,magic,0, SL_HIGH,Spirit of Rebirth -495,0,6,4,0,0x1,0,1,1,no,0,0x8,0,weapon,0, KN_ONEHAND,Onehand Quicken -496,0,6,4,0,0x1,0,1,0,no,0,0x8,0,none,0, AM_TWILIGHT1,Twilight Alchemy 1 -497,0,6,4,0,0x1,0,1,0,no,0,0x8,0,none,0, AM_TWILIGHT2,Twilight Alchemy 2 -498,0,6,4,0,0x1,0,1,0,no,0,0x8,0,none,0, AM_TWILIGHT3,Twilight Alchemy 3 -499,-9,8,4,-1,0,0,1,2,no,0,0x208,0,weapon,0, HT_POWER,Beast Strafing -500,0,6,4,0,0x40,0,5,1,no,0,0,0,misc,0, GS_GLITTERING,Flip the Coin -501,9,6,1,-1,0x50,0,1,1,no,0,0,0,misc,0, GS_FLING,Fling -502,-9,8,1,-1,0,0,1,3,no,0,0,0,weapon,0, GS_TRIPLEACTION,Triple Action -503,-9,6,1,-1,0x8,0,1,1,no,0,0,0,weapon,0, GS_BULLSEYE,Bulls Eye -504,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, GS_MADNESSCANCEL,Madness Canceller -505,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, GS_ADJUSTMENT,AdJustment -506,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, GS_INCREASING,Increasing Accuracy -507,-9,6,1,8,0,0,1,1,no,0,0,0,weapon,0, GS_MAGICALBULLET,Magical Bullet -508,-9,6,1,-1,0x1,0,1,1,no,0,0,0,weapon,0, GS_CRACKER,Cracker -509,0,0,0,0,0,0,10,0,no,0,0,0,none,0, GS_SINGLEACTION,Single Action -510,0,0,0,0,0,0,10,0,no,0,0,0,none,0, GS_SNAKEEYE,Snake Eye -511,-9,8,0,-1,0,0,10,2,no,0,0,0,weapon,0, GS_CHAINACTION,Chain Action -512,-9,6,1,-1,0,0,10,1,yes,0,0,0,weapon,0, GS_TRACKING,Tracking -513,-9,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, GS_DISARM,Disarm -514,-9,6,1,-1,0x20,0,5,1,no,0,0,0,weapon,0, GS_PIERCINGSHOT,Piercing Shot -515,-9,8,1,-1,0,0,10,5,no,0,0,0,weapon,0, GS_RAPIDSHOWER,Rapid Shower -516,0,8,4,-1,0x2,3,10,1,no,0,0,0,weapon,0, GS_DESPERADO,Desperado -517,0,6,4,-1,0x1,0,10,1,no,0,0,0,weapon,0, GS_GATLINGFEVER,Gatling Fever -518,2,6,1,-1,0,0,10,1,no,0,0,0,weapon,5, GS_DUST,Dust -519,-9,6,1,-1,0,0,10,1,yes,0,0,0,weapon,0, GS_FULLBUSTER,Full Buster -520,-9,6,1,-1,0x2,1:1:1:2:2:2:3:3:3:4,10,1,no,0,0,0,weapon,0, GS_SPREADATTACK,Spread Attack -521,-9,6,2,-1,0x40,1,10,1,no,0,0,0,weapon,3, GS_GROUNDDRIFT,Ground Drift -522,0,0,0,0,0,0,10,1,no,0,0,0,weapon,0, NJ_TOBIDOUGU,Shuriken Training -523,9,6,1,-1,0x40,0,10,1,no,0,0,0,weapon,0, NJ_SYURIKEN,Throw Shuriken -524,9,8,1,-1,0x40,0,5,3,no,0,0,0,weapon,0, NJ_KUNAI,Throw Kunai -525,9,8,1,-1,0x6,1,5,-3:-3:-4:-4:-5,yes,0,0,0,weapon,0, NJ_HUUMA,Throw Huuma Shuriken -526,9,6,1,0,0x50,0,10,1,no,0,0,0,misc,0, NJ_ZENYNAGE,Throw Zeny -527,0,6,4,-1,0,0,5,1,no,0,0,0,weapon,3, NJ_TATAMIGAESHI,Improvised Defense -528,-1,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, NJ_KASUMIKIRI,Vanishing Slash -529,7:9:11:13:15,6,2,0,0x1,0,5,1,no,0,0,0,none,0, NJ_SHADOWJUMP,Shadow Leap -530,7:9:11:13:15,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, NJ_KIRIKAGE,Shadow Slash -531,0,6,4,0,0x1,0,5,1,no,0,0,0,none,7, NJ_UTSUSEMI,Cicada Skin Sheeding -532,0,6,4,0,0x1,0,10,1,yes,0,0,0,magic,0, NJ_BUNSINJYUTSU,Mirror Image -533,0,0,0,0,0,0,10,0,no,0,0,0,none,0, NJ_NINPOU,Spirit of the Blade -534,9,8,1,3,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, NJ_KOUENKA,Crimson Fire Petal -535,0,8,4,3,0,0,10,1,yes,0,0,0,magic,1, NJ_KAENSIN,Crimson Fire Formation -536,9,8,1,3,0x2,2,5,3,yes,0,0,0,magic,0, NJ_BAKUENRYU,Raging Fire Dragon -537,9,8,1,1,0,0,10,3:4:5:6:7:8:9:10:11:12,yes,0,0,0,magic,0, NJ_HYOUSENSOU,Spear of Ice -538,9,6,2,1,0x1,0,10,1,yes,0,0,0,magic,0, NJ_SUITON,Hidden Water -539,0,6,4,1,0x2,3,5,1,yes,0,0,0,magic,0, NJ_HYOUSYOURAKU,Ice Meteor -540,9,8,1,4,0,0,10,1:2:2:3:3:4:4:5:5:6,yes,0,0,0,magic,0, NJ_HUUJIN,Wind Blade -541,9,6,4,4,0x2,2:2:3:3:4,5,1,yes,0,0,0,magic,0, NJ_RAIGEKISAI,Lightning Strike of Destruction -542,9,8,1,4,0,3,5,1,yes,0,0,5:6:7:8:9,magic,0, NJ_KAMAITACHI,Kamaitachi -543,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, NJ_NEN,Soul -544,-5,6,1,0,0x40,0,10,1,no,0,0,0,weapon,0, NJ_ISSEN,Final Strike - -// Additional NPC Skills (Episode 11.3) -653,0,8,4,0,0x6,5:7:9:11:13:5:7:9:11:13,10,1,no,0,0x2,0,magic,0, NPC_EARTHQUAKE,Earthquake -654,9,6,1,3,0,5,10,1,no,0,0x2,14,weapon,0, NPC_FIREBREATH,Fire Breath -655,9,6,1,1,0,5,10,1,no,0,0x2,14,weapon,0, NPC_ICEBREATH,Ice Breath -656,9,6,1,4,0,5,10,1,no,0,0x2,14,weapon,0, NPC_THUNDERBREATH,Thunder Breath -657,9,6,1,5,0,5,10,1,no,0,0x2,14,weapon,0, NPC_ACIDBREATH,Acid Breath -658,9,6,1,7,0,5,10,1,no,0,0x2,14,weapon,0, NPC_DARKNESSBREATH,Darkness Breath -659,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_DRAGONFEAR,Dragon Fear -660,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_BLEEDING,Bleeding -661,0,6,4,0,0x2,7,5,1,no,0,0x2,0,weapon,7, NPC_PULSESTRIKE,Pulse Strike -662,0,6,4,0,0x2,14,10,1,no,0,0x2,0,weapon,0, NPC_HELLJUDGEMENT,Hell's Judgement -663,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDESILENCE,Wide Silence -664,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDEFREEZE,Wide Freeze -665,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDEBLEEDING,Wide Bleeding -666,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDESTONE,Wide Petrify -667,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDECONFUSE,Wide Confusion -668,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDESLEEP,Wide Sleep -669,0,6,4,3,0x3,5,1,1,no,0,0x2,0,magic,0, NPC_WIDESIGHT,Wide Sight -670,9,6,2,7,0x91,0,10,1,no,0,0x2,0,magic,0, NPC_EVILLAND,Evil Land -671,0,6,4,0,0x1,0,5,1,no,0,0x2,0,magic,0, NPC_MAGICMIRROR,Magic Mirror -672,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_SLOWCAST,Slow Cast -673,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_CRITICALWOUND,Critical Wounds -674,-9,6,1,-1,0x1,0,1,1,no,0,0x2,0,none,0, NPC_EXPULSION,Expulsion -675,0,6,4,0,0x1,0,5,1,no,0,0x2,0,magic,0, NPC_STONESKIN,Stone Skin -676,0,6,4,0,0x1,0,5,1,no,0,0x2,0,magic,0, NPC_ANTIMAGIC,Anti Magic -677,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDECURSE,Wide Curse -678,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDESTUN,Wide Stun -679,0,6,4,0,0x2,5:7:9:11:13:13:13:13:13:13,10,1,no,0,0x2,0,weapon,0, NPC_VAMPIRE_GIFT,Vampire Gift -680,0,6,4,0,0x3,5:7:9:11:13:13:13:13:13:13,10,1,no,0,0x2,0,none,0, NPC_WIDESOULDRAIN,Wide Soul Drain - -// Cash Shop Skill -681,0,0,0,0,0,0,10,0,no,0,0x1,0,none,0, ALL_INCCARRY,Increase Weight Limit R - -// Additional NPC skill (Episode 12) -682,0,0,4,0,0x1,0,1,1,no,0,0x2,0,none,0, NPC_TALK,Talk -683,-9,6,1,-1,0,0,1,1,no,0,0x2,0,none,0, NPC_HELLPOWER,Hell Power -684,0,6,4,0,0x3,-1,1,1,no,0,0x2,0,none,0, NPC_WIDEHELLDIGNITY,Hell Dignity -685,0,0,4,0,0x1,0,1,1,no,0,0x2,0,none,0, NPC_INVINCIBLE,Invincible -686,0,0,4,0,0x1,0,1,1,no,0,0x2,0,none,0, NPC_INVINCIBLEOFF,Invincible off -687,0,6,4,0,0x1,0,1,1,yes,0,0x2,0,none,0, NPC_ALLHEAL,Full Heal - -// Additional Skill (??) -688,9,6,16,0,0x1,0,10,0,no,0,0x200,0,none,0, GM_SANDMAN,GM Sandman -689,0,6,4,0,0x3,-1,10,1,yes,0,0x2,0,magic,0, CASH_BLESSING,Party Blessing -690,0,6,4,0,0x3,-1,10,1,yes,0,0x2,0,magic,0, CASH_INCAGI,Party Increase AGI -691,0,6,4,0,0x3,-1,5,1,yes,0,0x2,0,magic,0, CASH_ASSUMPTIO,Party Assumptio -//692,0,0,0,0,0,0,9,0,no,0,0x2,0,none,0, ALL_CATCRY,Cat Cry -693,0,6,4,0,0x3,-1,1,1,yes,0,0x2,0,magic,0, ALL_PARTYFLEE,Party Flee -//694,0,0,0,0,0,0,9,0,no,0,0x2,0,none,0, ALL_ANGEL_PROTECT,Angel's Protection -//695,0,0,0,0,0,0,9,0,no,0,0x2,0,none,0, ALL_DREAM_SUMMERNIGHT,Summer Night Dream -//696,0,0,0,0,0,0,9,0,no,0,0x2,0,none,0, NPC_CHANGEUNDEAD2,Change Undead -//697,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, ALL_REVERSEORCISH,Reverse Orcish -698,0,6,4,0,0x01,0,1,1,no,0,0x2,0,none,0, ALL_WEWISH,Christmas Carol -//699,0,0,0,0,0,0,9,0,no,0,0x2,0,none,0, ALL_SONKRAN,ALL_SONKRAN - -// New NPC Wide Status AoE Skills And Others -//700,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDEHEALTHFEAR,Wide Health Fear -//701,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDEBODYBURNNING,Wide Body Burnning -//702,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDEFROSTMISTY,Wide Freezing -//703,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDECOLD,Wide Crystalize -//704,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDE_DEEP_SLEEP,Wide Deep Sleep -//705,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDESIREN,Wide Siren's Voice -//706,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_VENOMFOG,Venom Fog -//707,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_MILLENNIUMSHIELD,Millenium Shield 2 -//708,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_COMET,Comet 2 - -1001,9,6,1,-1,0,0,1,1,no,0,0x1,0,weapon,0, KN_CHARGEATK,Charge Attack -1002,0,6,4,0,0x1,0,1,0,no,0,0x1,0,weapon,2, CR_SHRINK,Shrink -1003,0,0,0,0,0,0,1,0,no,0,0x1,0,weapon,0, AS_SONICACCEL,Sonic Acceleration -1004,9,8,1,0,0x8,0,1,1,no,0,0x1,0,weapon,0, AS_VENOMKNIFE,Throw Venom Knife -1005,1,6,1,0,0x1,0,1,1,no,0,0x1,0,weapon,0, RG_CLOSECONFINE,Close Confine -1006,0,6,4,3,0,2,1,1,yes,0,0x1,0,magic,3, WZ_SIGHTBLASTER,Sight Blaster -1007,0,6,4,0,0x1,0,1,0,no,0,0x1,0,none,0, SA_CREATECON,Create Elemental Converter -1008,9,6,1,1,0x1,0,1,1,yes,0,0x1,0,magic,0, SA_ELEMENTWATER,Elemental Change Water -1009,-9,6,1,0,0,0,1,1,no,0,0x1,0,weapon,3, HT_PHANTASMIC,Phantasmic Arrow -1010,9,6,1,0,0x1,0,1,0,no,0,0x1,0,misc,0, BA_PANGVOICE,Pang Voice -1011,9,6,1,0,0x1,0,1,0,no,0,0x1,0,misc,0, DC_WINKCHARM,Wink of Charm -1012,0,0,0,0,0,0,1,0,no,0,0x1,0,weapon,0, BS_UNFAIRLYTRICK,Unfair Trick -1013,0,6,4,0,0x3,2,1,0,no,0,0x1,0,weapon,0, BS_GREED,Greed -1014,0,6,4,6,0x3,14,1,0,yes,0,0x1,0,magic,0, PR_REDEMPTIO,Redemptio -1015,9,6,16,0,0x1,0,1,1,no,0,0x401,0,weapon,0, MO_KITRANSLATION,Ki Translation -1016,-1,6,1,-1,0x2,1,1,1,no,0,0x1,0,weapon,5, MO_BALKYOUNG,Ki Explosion -1017,9,6,1,2,0x1,0,1,1,yes,0,0x1,0,magic,0, SA_ELEMENTGROUND,Elemental Change Earth -1018,9,6,1,3,0x1,0,1,1,yes,0,0x1,0,magic,0, SA_ELEMENTFIRE,Elemental Change Fire -1019,9,6,1,4,0x1,0,1,1,yes,0,0x1,0,magic,0, SA_ELEMENTWIND,Elemental Change Wind - -//**** -// RK Rune Knight -//**** -2001,1,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, RK_ENCHANTBLADE,Enchant Blade -2002,7:8:9:10:11,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, RK_SONICWAVE,Sonic Wave -2003,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, RK_DEATHBOUND,Death Bound -2004,1,8,1,-1,0,0,10,-5,no,0,0,0,weapon,0, RK_HUNDREDSPEAR,Hundred Spear -2005,1,6,2,4,0x2,2,5,1,no,0,0,0,weapon,3, RK_WINDCUTTER,Wind Cutter -2006,0,6,4,-1,0x2,5,5,1,no,0,0,0,weapon,0, RK_IGNITIONBREAK,Ignition Break -2007,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, RK_DRAGONTRAINING,Dragon Training -2008,9,6,2,3,0xC2,1:1:1:2:2:2:3:3:4:4,10,1,no,0,0,0,misc,0, RK_DRAGONBREATH,Dragon Breath //CHECK May have to change this back to a weapon type attack. -2009,0,6,4,0,0x3,3:4:5:6:7,5,1,yes,0,0,0,weapon,0, RK_DRAGONHOWLING,Dragon Howling -2010,0,0,0,0,0,0,10,0,no,0,0,0,none,0, RK_RUNEMASTERY,Rune Mastery -2011,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, RK_MILLENNIUMSHIELD,Millenium Shield -2012,1,6,4,-1,0,0x8,1,1,yes,0,0,0,weapon,0, RK_CRUSHSTRIKE,Crush Strike -2013,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, RK_REFRESH,Refresh -2014,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, RK_GIANTGROWTH,Giant Growth -2015,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, RK_STONEHARDSKIN,Stone Hard Skin -2016,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, RK_VITALITYACTIVATION,Vitality Activation -2017,0,6,4,-1,0x2,3,1,1,no,0,0,0,weapon,7, RK_STORMBLAST,Storm Blast -2018,0,6,4,0,0x3,-1,1,1,yes,0,0,0,none,0, RK_FIGHTINGSPIRIT,Fighting Spirit //CHECK Is this splash needed? -2019,9,6,4,6,0x1,0,1,1,yes,0,0,0,none,0, RK_ABUNDANCE,Abundance -2020,5:6:7:8:9,6,1,-1,0,0,5,1,yes,0,0,0,weapon,0, RK_PHANTOMTHRUST,Phantom Thrust - -//**** -// WL Warlock -//**** -2201,11,6,16,0,0,0,5,1,yes,0,0,0,magic,0, WL_WHITEIMPRISON,White Imprison -2202,11,8,1,8,0x2,1:1:1:2:2,5,-2,yes,0,0,0,magic,0, WL_SOULEXPANSION,Soul Expansion -2203,0,8,4,1,0x2,13,5,-3:-4:-5:-6:-7,yes,0,0,0,magic,0, WL_FROSTMISTY,Frosty Misty -2204,0,8,4,1,0x2,13,5,-5,yes,0,0,0,magic,0, WL_JACKFROST,Jack Frost -2205,11,6,1,0,0x1,0,5,1,yes,0,0,0,magic,0, WL_MARSHOFABYSS,Marsh of Abyss -2206,0,6,4,0,0x1,0,5,1,yes,0,0,0,magic,0, WL_RECOGNIZEDSPELL,Recognized Spell -2207,7,6,1,2,0x3,1:2:2:3:3,5,1,yes,0,0,0,magic,0, WL_SIENNAEXECRATE,Sienna Execrate -2208,0,0,0,0,0,0,3,0,no,0,0,0,none,0, WL_RADIUS,Radius -2209,0,6,4,0,0x3,9:10:11:12:13,5,1,yes,0,0,0,magic,0, WL_STASIS,Stasis -2210,11,6,1,0,0,0,5,1,yes,0,0,0,magic,0, WL_DRAINLIFE,Drain Life -2211,11,8,1,3,0x2,3,5,-7,yes,0,0,0,magic,3, WL_CRIMSONROCK,Crimson Rock -2212,11,6,1,3,0,0,5,1,yes,0,0,0,magic,0, WL_HELLINFERNO,Hell Inferno -2213,11,8,2,0,0x2,15,5,-20,yes,0,0,0,magic,2, WL_COMET,Comet //CHECK AoE in official code appears to be 15 x 15, yet casting circle is much bigger then that. -2214,11,6,1,0,0,3,5,1,yes,0,0,0,magic,0, WL_CHAINLIGHTNING,Chain Lightning //CHECK Is the splash being used for the target search? -2215,11,6,1,4,0,0,5,1,no,0,0,0,magic,0, WL_CHAINLIGHTNING_ATK,Chain Lightning Attack -2216,3,8,2,2,0,0,5,-6:-7:-8:-9:-10,yes,0,0,0,magic,0, WL_EARTHSTRAIN,Earth Strain -2217,11,6,1,0,0,0,5,1,yes,0,0,0,magic,0, WL_TETRAVORTEX,Tetra Vortex -2218,11,6,1,3,0,0,5,1,no,0,0,0,magic,0, WL_TETRAVORTEX_FIRE,Tetra Vortex Fire -2219,11,6,1,1,0,0,5,1,no,0,0,0,magic,0, WL_TETRAVORTEX_WATER,Tetra Vortex Water -2220,11,6,1,4,0,0,5,1,no,0,0,0,magic,0, WL_TETRAVORTEX_WIND,Tetra Vortex Wind -2221,11,6,1,2,0,0,5,1,no,0,0,0,magic,0, WL_TETRAVORTEX_GROUND,Tetra Vortex Earth -2222,0,6,4,3,0x1,0,5,1,yes,0,0,0,magic,0, WL_SUMMONFB,Summon Fire Ball -2223,0,6,4,4,0x1,0,5,1,yes,0,0,0,magic,0, WL_SUMMONBL,Summon Lightning Ball -2224,0,6,4,1,0x1,0,5,1,yes,0,0,0,magic,0, WL_SUMMONWB,Summon Water Ball -2225,11,6,1,3,0,0,5,1,no,0,0,1,magic,0, WL_SUMMON_ATK_FIRE,Summon Attack Fire //CHECK Summon attack ID's dont appear to have a range. -2226,11,6,1,4,0,0,5,1,no,0,0,1,magic,0, WL_SUMMON_ATK_WIND,Summon Attack Wind -2227,11,6,1,1,0,0,5,1,no,0,0,1,magic,0, WL_SUMMON_ATK_WATER,Summon Attack Water -2228,11,6,1,2,0,0,5,1,no,0,0,1,magic,0, WL_SUMMON_ATK_GROUND,Summon Attack Earth -2229,0,6,4,2,0x1,0,5,1,yes,0,0,0,magic,0, WL_SUMMONSTONE,Summon Stone -2230,11,8,1,0,0,0,2,1,yes,0,0,0,magic,0, WL_RELEASE,Release //CHECK Should it be left to do multi hit or single hit? -2231,0,6,4,0,0x1,0,1,1,yes,0,0,0,magic,0, WL_READING_SB,Reading Spellbook -2232,0,0,0,0,0,0,5,0,no,0,0,0,none,0, WL_FREEZE_SP,Freeze Spell - -//**** -// GC Guillotine Cross -//**** -2021,5,6,1,0,0x1,0,5,1,no,0,0,0,none,0, GC_VENOMIMPRESS,Venom Impress -2022,3,8,1,-1,0,0,5,-7,no,0,0,0,weapon,0, GC_CROSSIMPACT,Cross Impact -2023,3:4:5:6:7,6,1,-1,0,0,5,1,no,0,0,0,weapon,0,GC_DARKILLUSION,Dark Illusion -2024,0,0,0,0,0,0,10,0,no,0,0,0,none,0, GC_RESEARCHNEWPOISON,Research New Poison -2025,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, GC_CREATENEWPOISON,Create New Poison -2026,5,6,16,0,0x1,0,1,1,no,0,0,0,none,0, GC_ANTIDOTE,Antidote -2027,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, GC_POISONINGWEAPON,Poisoning Weapon -2028,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, GC_WEAPONBLOCKING,Weapon Blocking -2029,-2,6,4,-1,0x2,1,5,1,no,0,0,0,weapon,3, GC_COUNTERSLASH,Counter Slash -2030,-2,6,4,-1,0x1,0,5,1,no,0,0x200,0,weapon,0, GC_WEAPONCRUSH,Weapon Crush //CHECK SHould this and the above skill have INF2 0x200? -2031,1,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, GC_VENOMPRESSURE,Venom Pressure -2032,5,6,2,0,0x1,0,5,1,yes,0,0,1,none,0, GC_POISONSMOKE,Poison Smoke -2033,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, GC_CLOAKINGEXCEED,Cloaking Exceed -2034,0,6,4,-1,0x2,3,1,1,no,0,0,0,weapon,0, GC_PHANTOMMENACE,Phantom Menace -2035,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, GC_HALLUCINATIONWALK,Hallucination Walk -2036,0,6,4,-1,0x2,1:1:1:1:2,5,1,no,0,0,0,weapon,0, GC_ROLLINGCUTTER,Rolling Cutter -2037,9:10:11:12:13,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, GC_CROSSRIPPERSLASHER,Cross Ripper Slasher - -//**** -// AB Arch Bishop -//**** -2038,11,8,1,6,0x2,3,5,-3,yes,0,0,0,magic,0, AB_JUDEX,Judex -2039,0,6,4,0,0x1,0,1,1,yes,0,0,0,magic,0, AB_ANCILLA,Ancilla -2040,11,8,1,6,0,0,10,-10,yes,0,0,0,magic,0, AB_ADORAMUS,Adoramus -2041,0,6,4,6,0x3,3:7:15,3,1,yes,0,0,0,magic,0, AB_CLEMENTIA,Crementia -2042,0,6,4,6,0x3,3:7:15,3,1,yes,0,0,0,magic,0, AB_CANTO,Canto Candidus -2043,0,6,4,6,0x3,3:7:15,3,1,yes,0,0,0,magic,0, AB_CHEAL,Coluceo Heal -2044,11,6,2,6,0x1,0,5,1,yes,0,0,1,magic,0, AB_EPICLESIS,Epiclesis -2045,0,6,4,6,0x3,15,10,1,yes,0,0,0,magic,0, AB_PRAEFATIO,Praefatio -2046,0,6,4,6,0x3,15,10,1,yes,0,0,0,magic,0, AB_ORATIO,Oratio -2047,0,6,4,6,0x3,15,4,1,yes,0,0,0,magic,0, AB_LAUDAAGNUS,Lauda Agnus -2048,0,6,4,6,0x3,15,4,1,yes,0,0,0,magic,0, AB_LAUDARAMUS,Lauda Ramus -2049,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AB_EUCHARISTICA,Eucharistica -2050,11,6,16,6,0x1,0,1,1,yes,0,0,0,magic,0, AB_RENOVATIO,Renovatio -2051,11,6,16,6,0x21,0,5,1,yes,0,0,0,magic,0, AB_HIGHNESSHEAL,Highness Heal //CHECK Info shows this has magic attack. -2052,11,6,1,0,0x1,0,5,1,yes,0,0xA00,0,magic,0, AB_CLEARANCE,Clearance //CHECK Also shows this as a magic attack. Why? -2053,0,6,16,0,0x1,0,5,1,yes,0,0,0,magic,0, AB_EXPIATIO,Expiatio //CHECK Does this also give the buff to party members? -2054,0,6,4,6,0x1,0,10,1,yes,0,0,0,none,0, AB_DUPLELIGHT,Duple Light //CHECK Had issues adding a skill level check to make the % go higher with the skills level. Will do later. -2055,-1,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, AB_DUPLELIGHT_MELEE,Duple Light Melee -2056,-1,6,1,0,0,0,10,1,no,0,0,0,magic,0, AB_DUPLELIGHT_MAGIC,Duple Light Magic -2057,0,6,4,6,0x3,4:5:6:7:8,5,1,yes,0,0,0,magic,0, AB_SILENTIUM,Silentium //CHECk Marked magic attack as well. Hmmmm.... - -2515,11,6,16,0,0x1,0,5,1,yes,0,0,0,magic,0, AB_SECRAMENT,Secrament - -//**** -// RA Ranger -//**** -2233,9,8,1,-1,0x2,3:3:3:3:3:4:4:4:4:5,10,-3,yes,0,0,0,weapon,0, RA_ARROWSTORM,Arrow Storm -2234,0,6,4,0,0,0,5,1,yes,0,0,0,none,0, RA_FEARBREEZE,Fear Breeze -2235,0,0,0,0,0,0,10,0,no,0,0,0,none,0, RA_RANGERMAIN,Ranger Main -2236,9,8,1,-1,0,0,10,1,yes,0,0,0,weapon,0, RA_AIMEDBOLT,Aimed Bolt -2237,9,6,2,0,0x3,3,1,1,no,0,0,0,none,0, RA_DETONATOR,Detonator -2238,3,6,2,0,0x3,2,5,1,no,0,0x80,3,misc,0, RA_ELECTRICSHOCKER,Electric Shocker -2239,3,6,2,0,0x42,3,5,1,no,0,0x80,3,misc,0, RA_CLUSTERBOMB,Cluster Bomb -2240,0,6,4,0,0,0,1,1,no,0,0,0,none,0, RA_WUGMASTERY,Warg Mastery -2241,0,6,4,0,0,0,3,1,no,0,0,0,none,0, RA_WUGRIDER,Warg Rider -2242,0,6,4,-1,0x2,1,1,0,no,0,0,0,weapon,0, RA_WUGDASH,Warg Dash -2243,9,6,1,0,0,0,5,1,no,0,0,0,weapon,0, RA_WUGSTRIKE,Warg Strike -2244,9,6,1,0,0,0,5,1,no,0,0,0,weapon,0, RA_WUGBITE,Warg Bite -2245,0,0,0,0,0,0,10,0,no,0,0,0,none,0, RA_TOOTHOFWUG,Tooth of Warg -2246,0,6,4,0,0x2,3:4:5:6:7,5,1,no,0,0,0,weapon,0, RA_SENSITIVEKEEN,Sensitive Keen -2247,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, RA_CAMOUFLAGE,Camouflage -2248,0,0,0,0,0,0,5,0,no,0,0,0,none,0, RA_RESEARCHTRAP,Research Trap -2249,3,6,2,3,0x43,2,1,1,no,0,0x80,1,misc,0, RA_MAGENTATRAP,Magenta Trap -2250,3,6,2,1,0x43,2,1,1,no,0,0x80,1,misc,0, RA_COBALTTRAP,Cobalt Trap -2251,3,6,2,2,0x43,2,1,1,no,0,0x80,1,misc,0, RA_MAIZETRAP,Maize Trap -2252,3,6,2,4,0x43,2,1,1,no,0,0x80,1,misc,0, RA_VERDURETRAP,Verdure Trap -2253,3,6,2,0,0x42,2,5,1,no,0,0x80,2,misc,0, RA_FIRINGTRAP,Firing Trap -2254,3,6,2,0,0x42,2,5,1,no,0,0x80,2,misc,0, RA_ICEBOUNDTRAP,Icebound Trap - -//**** -// NC Mechanic -2255,0,0,0,0,0,0,5,0,no,0,0,0,none,0, NC_MADOLICENCE,Mado License -2256,11,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, NC_BOOSTKNUCKLE,Boost Knuckle -2257,3,6,1,-1,0,0,3,1,no,0,0,0,weapon,0, NC_PILEBUNKER,Pile Bunker -2258,13,6,1,-1,0,0,3,1,no,0,0,0,weapon,0, NC_VULCANARM,Vulcan Arm -2259,5,6,1,3,0,2,3,1,no,0,0,5,weapon,0, NC_FLAMELAUNCHER,Flame Launcher -2260,7,6,2,1,0x2,2:3:4,3,1,no,0,0,0,weapon,0, NC_COLDSLOWER,Cold Slower -2261,7,6,2,-1,0x42,3:2:1,3,1,no,0,0,0,weapon,0, NC_ARMSCANNON,Arm Cannon -2262,0,6,4,0,0x1,0,3,1,no,0,0,0,none,0, NC_ACCELERATION,Acceleration -2263,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, NC_HOVERING,Hovering -2264,0,6,4,0,0x1,0,1,1,no,0,0,0,none,7, NC_F_SIDESLIDE,Front-Side Slide -2265,0,6,4,0,0x1,0,1,1,no,0,0,0,none,7, NC_B_SIDESLIDE,Back-Side Slide -2266,0,0,0,0,0,0,4,0,no,0,0,0,none,0, NC_MAINFRAME,Mainframe Restructure // Check me. Part of the code notes translated to "The amount of fuel have". -2267,0,6,4,-1,0x42,2:3:4,3,1,no,0,0,0,misc,5, NC_SELFDESTRUCTION,Self Destruction -2268,0,6,4,0,0x1,0,4,1,yes,0,0,0,none,0, NC_SHAPESHIFT,Shape Shift -2269,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, NC_EMERGENCYCOOL,Emergency Cool -2270,0,6,4,0,0x3,7,1,1,yes,0,0,0,none,0, NC_INFRAREDSCAN,Infrared Scan -2271,9,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, NC_ANALYZE,Analyze -2272,0,6,4,0,0x3,1:2:3,3,1,yes,0,0,0,none,0, NC_MAGNETICFIELD,Magnetic Field -2273,0,6,4,0,0x1,0,3,1,yes,0,0,0,none,0, NC_NEUTRALBARRIER,Neutral Barrier -2274,0,6,4,0,0x1,0,3,1,yes,0,0,0,none,0, NC_STEALTHFIELD,Stealth Field -2275,5,6,16,0,0x1,0,5,1,yes,0,0,0,magic,0, NC_REPAIR,Repair -2276,0,0,0,0,0,0,10,0,no,0,0,0,none,0, NC_TRAININGAXE,Axe Training -2277,0,0,0,0,0,0,5,0,no,0,0,0,none,0, NC_RESEARCHFE,Research Fire/Earth -2278,4:5:6:7:8,6,1,-1,0,0,5,1,no,0,0,0,weapon,2:3:4:5:6, NC_AXEBOOMERANG,Axe Boomerang -2279,1,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, NC_POWERSWING,Power Swing -2280,0,8,4,-1,0x2,2:2:3:3:3,5,-6,no,0,0,0,weapon,0, NC_AXETORNADO,Axe Tornado // Check me. Takes 20 * Skill LV amount of HP each use. -2281,2,6,2,0,0x1,0,5,1,yes,0,0,2,none,0, NC_SILVERSNIPER,FAW - Silver Sniper -2282,2,6,2,0,0x1,0,5,1,yes,0,0,2,none,0, NC_MAGICDECOY,FAW - Magic Decoy //CHECK FIX ME!!!! Wind and Earth stones spawning opposite decoys. -2283,2,6,1,0,0x1,0,1,1,no,0,0,0,none,0, NC_DISJOINT,FAW Removal - -//**** -// SC Shadow Chaser -2284,1,6,1,-1,0x2,1,5,1,no,0,0,0,weapon,0, SC_FATALMENACE,Fatal Menace -2285,0,6,4,0,0x1,0,10,1,no,0,0,0,none,0, SC_REPRODUCE,Reproduce -2286,0,6,4,0,0x1,0,10,1,yes,0,0,0,none,0, SC_AUTOSHADOWSPELL,Auto Shadow Spell -2287,5,6,1,0,0x1,0,5,1,no,0,0,0,none,0, SC_SHADOWFORM,Shadow Form -2288,7:7:7:9:9:9:9:11:11:11,8,1,-1,0,0,10,-3,yes,0,0,0,weapon,3, SC_TRIANGLESHOT,Triangle Shot -2289,0,6,4,0,0x3,2,5,1,no,0,0,0,none,0, SC_BODYPAINT,Body Painting -2290,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, SC_INVISIBILITY,Invisibility -2291,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, SC_DEADLYINFECT,Deadly Infect -2292,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_ENERVATION,Masquerade - Enervation -2293,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_GROOMY,Masquerade - Gloomy -2294,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_IGNORANCE,Masquerade - Ignorance -2295,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_LAZINESS,Masquerade - Laziness -2296,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_UNLUCKY,Masquerade - Unlucky -2297,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_WEAKNESS,Masquerade - Weakness -2298,3,6,1,0,0x1,0,5,1,yes,0,0,0,weapon,0, SC_STRIPACCESSARY,Strip Accessory //CHECK Is weapon attack type needed? -2299,7,6,2,0,0x1,0,3,1,yes,0,0,3,none,0, SC_MANHOLE,Man Hole -2300,7,6,2,0,0x1,0,3,1,yes,0,0,1,none,0, SC_DIMENSIONDOOR,Dimension Door -2301,7,6,2,0,0x1,0,3,1,yes,0,0,0,none,0, SC_CHAOSPANIC,Chaos Panic -2302,7,6,2,0,0x1,0,3,1,yes,0,0,0,none,0, SC_MAELSTROM,Maelstrom -2303,7,6,2,0,0x1,0,3,1,yes,0,0,0,none,0, SC_BLOODYLUST,Bloody Lust -2304,0,6,4,-1,0,0,3,1,no,0,0,0,weapon,0, SC_FEINTBOMB,Feint Bomb - -//**** -// LG Royal Guard -2307,11,8,1,-1,0,2,5,1,no,0,0,10,weapon,0, LG_CANNONSPEAR,Cannon Spear -2308,7,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, LG_BANISHINGPOINT,Banishing Point -2309,0,6,4,0,0x3,2,3,1,no,0,0,0,none,0, LG_TRAMPLE,Trample -2310,1,6,1,0,0,0,5,1,no,0,0,0,weapon,0, LG_SHIELDPRESS,Shield Press -2311,0,6,4,0,0x3,3,5,1,no,0,0,0,none,0, LG_REFLECTDAMAGE,Reflect Damage -2312,5,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, LG_PINPOINTATTACK,Pinpoint Attack -2313,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, LG_FORCEOFVANGUARD,Force of Vanguard -2314,1,6,1,-1,0,0,1,1,no,0,0,0,weapon,0, LG_RAGEBURST,Rage Burst -2315,0,6,4,0,0x2,3,3,1,yes,0,0,0,none,2, LG_SHIELDSPELL,Shield Spell -2316,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, LG_EXEEDBREAK,Exceed Break -2317,1,6,2,-1,0x2,5,5,1,yes,0,0,0,none,3:4:5:6:7, LG_OVERBRAND,Over Brand //CHECK I know the splash is needed somehow for the strange AoE it gives. -2318,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, LG_PRESTIGE,Prestige -2319,0,6,4,0,0x1,3,5,1,no,0,0,0,weapon,0, LG_BANDING,Banding //CHECK Splash isnt needed right? Banding has its own UNIT ID. -2320,0,6,4,-1,0x2,3,5,1,yes,0,0,0,weapon,0, LG_MOONSLASHER,Moon Slasher -2321,1,8,2,6,0x2,5,5,-7,yes,0,0,0,weapon,0, LG_RAYOFGENESIS,Ray of Genesis -2322,0,6,16,0,0x3,1,5,1,yes,0,0,0,none,0, LG_PIETY,Piety -2323,0,8,4,2,0x2,1:1:2:2:3,5,-5,yes,0,0,0,weapon,0, LG_EARTHDRIVE,Earth Drive -2324,3,8,1,-1,0,0,5,3,yes,0,0,0,weapon,0, LG_HESPERUSLIT,Hesperus Lit -2325,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, LG_INSPIRATION,Inspiration - -//**** -// SR Sura -2326,-2,8,1,-1,0,0,10,2,no,0,0,0,weapon,0, SR_DRAGONCOMBO,Dragon Combo //CHECK Is this 2 regular hits or sub hits? Yes its 2 sub hits. -2327,0,8,4,-1,0x2,2,5,-3,no,0,0,0,weapon,3, SR_SKYNETBLOW,Sky Net Blow //CHECK Video shows 3 hits. Its sub hits right? Data check shows no sub, one source shows 3 hits, another shows 5. -2328,0,6,4,-1,0x2,1:2:3:4:5,5,1,no,0,0,0,weapon,0, SR_EARTHSHAKER,Earth Shaker //CHECK Must add a check in battle.c to triple damage if hitting a hidden target. -2329,-2,8,4,-1,0,0,5,-2,no,0,0x200,0,weapon,0, SR_FALLENEMPIRE,Fallen Empire //CHECK Video shows 2 hits. Is it sub hits? Yes its divided between 2 hits. -2330,-2,6,1,-1,0x42,1:1:1:1:1:2:2:2:2:2,10,1,yes,0,0,0,weapon,0, SR_TIGERCANNON,Tiger Cannon //CHECK Need to fix to be enemy targeted and also combo after Fallen Empire. -2331,0,0,0,0,0,0,10,0,no,0,0,0,none,0, SR_HELLGATE,Hell Gate -2332,5,6,4,-1,0x2,3,5,1,no,0,0,0,weapon,0, SR_RAMPAGEBLASTER,Rampage Blaster -2333,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, SR_CRESCENTELBOW,Crescent Elbow //CHECK Check the autospell ID. -2334,0,6,4,0,0x3,1:1:2:2:3,5,1,no,0,0,0,none,0, SR_CURSEDCIRCLE,Cursed Circle //CHECK Code shows it takes up to 5% of your HP upon use? -2335,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, SR_LIGHTNINGWALK,Lightning Walk -2336,7:8:9:10:11,6,1,-1,0,0,5,1,no,0,0,0,weapon,2:3:4:5:6, SR_KNUCKLEARROW,Knuckle Arrow -2337,0,6,4,-1,0x2,2,1,1,yes,0,0,0,weapon,0, SR_WINDMILL,Windmill -2338,0,6,4,0,0x1,0,10,1,no,0,0,0,none,0, SR_RAISINGDRAGON,Raising Dragon -2339,0,0,0,0,0,0,5,1,no,0,0,0,none,0, SR_GENTLETOUCH,Gentle Touch -2340,0,6,4,0,0x3,2,1,1,no,0,0,0,none,0, SR_ASSIMILATEPOWER,Assimilate Power -2341,3,6,16,0,0x1,0,1,1,yes,0,0x200,0,none,0, SR_POWERVELOCITY,Power Velocity -2342,1,6,1,-1,0x20,0,5,1,no,0,0,0,weapon,3, SR_CRESCENTELBOW_AUTOSPELL,Crescent Elbow Autospell //CHECK Does this ignore defense? -2343,1:2:3:3:4:4:5:5:6:7,8,1,0,0,0,10,-7,yes,0,0,0,weapon,0, SR_GATEOFHELL,Gate of Hell //CHECK Need to fix to be enemy targeted and also combo after Fallen Empire -2344,2,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, SR_GENTLETOUCH_QUIET,Gentle Touch - Quiet -2345,2,6,16,0,0x1,0,5,1,no,0,0,0,magic,0, SR_GENTLETOUCH_CURE,Gentle Touch - Cure //CHECK Its a healing skill. Guessing it has to be magic type? Healing isnt working. -2346,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, SR_GENTLETOUCH_ENERGYGAIN,Gentle Touch - Energy Gain -2347,2,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, SR_GENTLETOUCH_CHANGE,Gentle Touch - Change -2348,2,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, SR_GENTLETOUCH_REVITALIZE,Gentle Touch - Revitalize -//More from Sura but not following ID order -2517,0,6,4,-1,0x2,3:4:5:6:7,5,1,no,0,0,0,weapon,0, SR_HOWLINGOFLION,Howling of Lion -2518,11,6,2,-1,0x2,2:2:3:3:4,5,1,no,0,0,0,weapon,0, SR_RIDEINLIGHTNING,Ride In Lightening - -//**** -// WA Wanderer -2350,0,6,4,0,0x3,7:8:9:10:11,5,1,yes,0,0,0,none,0, WA_SWING_DANCE,Swing Dance -2351,0,6,4,0,0x3,7:8:9:10:11,5,1,yes,0,0,0,none,0, WA_SYMPHONY_OF_LOVER,Symphony of Lovers -2352,0,6,4,0,0x3,7:8:9:10:11,5,1,yes,0,0,0,none,0, WA_MOONLIT_SERENADE,Moonlit Serenade - -//**** -// MI Minstrel -2381,0,6,4,0,0x3,7:8:9:10:11,5,1,yes,0,0,0,none,0, MI_RUSH_WINDMILL,Windmill Rush Attack -2382,0,6,4,0,0x3,7:8:9:10:11,5,1,yes,0,0,0,none,0, MI_ECHOSONG,Echo Song -2383,9,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, MI_HARMONIZE,Harmonize - -//**** -// WM Wanderer/Minstrel -2412,0,0,0,0,0,0,10,0,no,0,0,0,none,0, WM_LESSON,Lesson -2413,9,8,1,-1,0,0,5,-2:-2:-3:-3:-4,yes,0,0,0,magic,0, WM_METALICSOUND,Metallic Sound -2414,9,6,2,-1,0x3,1,5,1,yes,0,0x80,3,none,0, WM_REVERBERATION,Reverberation -2415,0,6,1,-1,0x6,1,5,1,no,0,0,0,weapon,0, WM_REVERBERATION_MELEE,Reverberation Melee -2416,0,6,1,0,0x6,1,5,1,no,0,0,0,magic,0, WM_REVERBERATION_MAGIC,Reverberation Magic -2417,11,6,2,0,0x3,5,1,1,no,0,0,0,none,0, WM_DOMINION_IMPULSE,Dominion Impulse -2418,9,6,2,-1,0x1,0,5,1,yes,0,0,0,none,0, WM_SEVERE_RAINSTORM,Severe Rainstorm -2419,9,6,2,0,0x3,1,5,1,yes,0,0x80,5,none,0, WM_POEMOFNETHERWORLD,Poem of The Netherworld //CHECK May need to recode too. -2420,0,6,4,0,0x2,2:3:4:5:6,5,1,yes,0,0,0,none,0, WM_VOICEOFSIREN,Voice of Siren -2421,7,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, WM_DEADHILLHERE,Valley of Death -2422,7,6,2,0,0x3,5:6:7:8:9,5,1,yes,0,0,0,none,0, WM_LULLABY_DEEPSLEEP,Deep Sleep Lullaby -2423,0,6,4,0,0x3,3:4:5:6:7,5,1,yes,0,0,0,none,0, WM_SIRCLEOFNATURE,Circle of Nature's Sound -2424,9,6,4,0,0x1,0,5,1,yes,0,0,0,magic,0, WM_RANDOMIZESPELL,Improvised Song -2425,9,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, WM_GLOOMYDAY,Gloomy Day -2426,9,6,2,0,0x2,2:3:3:4:4,5,1,yes,0,0x4000,0,weapon,0, WM_GREAT_ECHO,Great Echo -2427,0,6,4,0,0x3,5:6:7:8:9,5,1,yes,0,0x4000,0,none,0, WM_SONG_OF_MANA,Song of Mana -2428,0,6,4,0,0x3,5:6:7:8:9,5,1,yes,0,0x4000,0,none,0, WM_DANCE_WITH_WUG,Dance With A Warg -2429,9,6,1,0,0x2,2:2:3:3:4,5,1,yes,0,0x4000,0,weapon,0, WM_SOUND_OF_DESTRUCTION,Sound of Destruction //CHECK Source shows its magic attack. Need to confirm before changing. -2430,0,6,4,0,0x3,3:4:5:6:7,5,1,yes,0,0x4000,0,none,0, WM_SATURDAY_NIGHT_FEVER,Saturday Night Fever -2431,0,6,4,0,0x3,5,5,1,yes,0,0x4000,0,magic,0, WM_LERADS_DEW,Lerad's Dew -2432,0,6,4,0,0x3,5,5,1,yes,0,0x4000,0,none,0, WM_MELODYOFSINK,Melody of Sink -2433,0,6,4,0,0x3,5,5,1,yes,0,0x4000,0,none,0, WM_BEYOND_OF_WARCRY,Warcry of Beyond -2434,0,6,4,0,0x3,5,5,1,yes,0,0x4000,0,none,0, WM_UNLIMITED_HUMMING_VOICE,Unlimited Humming Voice -2516,11,6,1,-1,0x2,5,5,1,no,0,0,0,weapon,0, WM_SEVERE_RAINSTORM_MELEE,Severe Rainstorm Melee - -//**** -// SO Sorcerer -2443,0,6,4,3,0,0,5,1,yes,0,0,8:10:12:14:16,magic,0, SO_FIREWALK,Fire Walk //CHECK Video and data shows each cell only hits once. -2444,0,6,4,4,0,0,5,1,yes,0,0,8:10:12:14:16,magic,0, SO_ELECTRICWALK,Electric Walk -2445,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, SO_SPELLFIST,Spell Fist -2446,9,6,2,2,0,0,5,-3,yes,0,0,0,magic,0, SO_EARTHGRAVE,Earth Grave -2447,9,6,2,1,0,0,5,-5,yes,0,0,0,magic,0, SO_DIAMONDDUST,Diamond Dust -2448,9,6,1,5,0x2,1:1:1:1:2,5,1,yes,0,0,0,magic,0, SO_POISON_BUSTER,Poison Buster -2449,9,6,2,0,0,0,5,1,yes,0,0,0,magic,0, SO_PSYCHIC_WAVE,Psychic Wave -2450,9,6,2,5,0,0,5,1,yes,0,0,0,magic,0, SO_CLOUD_KILL,Cloud Kill -2451,9,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, SO_STRIKING,Striking //CHECK Data shows a % for increased successful refine rate. Is this true? -2452,9,6,2,3,0x1,0,5,1,yes,0,0,0,magic,0, SO_WARMER,Warmer -2453,9,6,2,0,0x1,0,5,1,yes,0,0,0,magic,0, SO_VACUUM_EXTREME,Vacuum Extreme -2454,9,6,1,4,0x2,1:1:2:2:3,5,1,yes,0,0,0,magic,0, SO_VARETYR_SPEAR,Varetyr Spear -2455,9,6,1,0,0x3,1:1:2:2:3,5,1,yes,0,0,0,magic,0, SO_ARRULLO,Arrullo -2456,0,6,4,0,0x1,0,4,1,yes,0,0,0,none,0, SO_EL_CONTROL,Spirit Control -2457,0,6,4,3,0x1,0,3,1,yes,0,0,0,none,0, SO_SUMMON_AGNI,Summon Fire Spirit Agni -2458,0,6,4,1,0x1,0,3,1,yes,0,0,0,none,0, SO_SUMMON_AQUA,Summon Water Spirit Aqua -2459,0,6,4,4,0x1,0,3,1,yes,0,0,0,none,0, SO_SUMMON_VENTUS,Summon Wind Spirit Ventus -2460,0,6,4,2,0x1,0,3,1,yes,0,0,0,none,0, SO_SUMMON_TERA,Summon Earth Spirit Tera -2461,5,6,1,0,0x1,0,1,1,no,0,0,0,none,0, SO_EL_ACTION,Elemental Action -2462,0,6,4,0,0x1,0,2,1,yes,0,0,0,none,0, SO_EL_ANALYSIS,Four Spirit Analysis -2463,0,0,0,0,0,0,5,0,no,0,0,0,none,0, SO_EL_SYMPATHY,Spirit Sympathy -2464,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, SO_EL_CURE,Spirit Recovery -2465,9,6,2,3,0x1,0,3,1,yes,0,0,1,magic,0, SO_FIRE_INSIGNIA,Fire Insignia //CHECK All 4 insignia skills can be targeted and animations work -2466,9,6,2,1,0x1,0,3,1,yes,0,0,1,magic,0, SO_WATER_INSIGNIA,Water Insignia // but its effects havent been coded yet. -2467,9,6,2,4,0x1,0,3,1,yes,0,0,1,magic,0, SO_WIND_INSIGNIA,Wind Insignia -2468,9,6,2,2,0x1,0,3,1,yes,0,0,1,magic,0, SO_EARTH_INSIGNIA,Earth Insignia - -//**** -// GN Genetic -2474,0,0,0,0,0,0,5,0,no,0,0,0,none,0, GN_TRAINING_SWORD,Sword Training -2475,0,0,0,0,0,0,5,0,no,0,0,0,none,0, GN_REMODELING_CART,Cart Remodeling -2476,0,6,4,-1,0x2,2,5,1,no,0,0,0,weapon,2, GN_CART_TORNADO,Cart Tornado -2477,7:8:9:10:11,6,1,-1,0x2,1:1:2:2:3,5,1,yes,0,0,0,weapon,0, GN_CARTCANNON,Cart Cannon -2478,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, GN_CARTBOOST,Cart Boost -2479,9,6,2,0,0,0,5,1,yes,0,0x80,5,misc,0, GN_THORNS_TRAP,Thorn Trap -2480,11,6,1,0,0x1,0,5,1,yes,0,0,3,misc,0, GN_BLOOD_SUCKER,Blood Sucker //CHECK Data says its a magic attack. Hmmmm.... -2481,11,6,1,-1,0x2,1:2:3:4:5,5,1,yes,0,0,0,weapon,0, GN_SPORE_EXPLOSION,Spore Explosion //CHECK Data says its element is set to neutral. Need to confirm. -2482,11,6,16,0,0,0,5,1,yes,0,0,1,weapon,2, GN_WALLOFTHORN,Wall of Thorns -2483,11,6,2,0,0x3,4,10,1,yes,0,0x2000,0,weapon,0, GN_CRAZYWEED,Crazy Weed -2484,0,6,2,2,0x2,3,10,1,no,0,0x2000,0,weapon,0, GN_CRAZYWEED_ATK,Crazy Weed Attack -2485,9,6,2,3,0,0,5,1,yes,0,0,0,magic,0, GN_DEMONIC_FIRE,Demonic Fire -2486,9,6,2,0,0,0,5,1,yes,0,0,0,none,0, GN_FIRE_EXPANSION,Fire Expansion //CHECK FIX ME!!!! Level 1 is reducing the damage. Should increase it by 50% -2487,9,6,2,0,0,0,1,1,no,0,0,0,none,0, GN_FIRE_EXPANSION_SMOKE_POWDER,Fire Expansion Smoke Powder -2488,9,6,2,0,0,0,1,1,no,0,0,0,none,0, GN_FIRE_EXPANSION_TEAR_GAS,Fire Expansion Tear Gas -2489,11,6,1,0,0,0,10,1:2:3:4:5:6:7:8:9:10,no,0,0,0,weapon,0, GN_FIRE_EXPANSION_ACID,Fire Expansion Acid -2490,9,6,2,0,0x3,1,5,1,yes,0,0x80,2:3:4:5:6,none,0, GN_HELLS_PLANT,Hell's Plant -2491,0,6,1,0,0xC0,0,5,1,no,0,0,0,misc,0, GN_HELLS_PLANT_ATK,Hell's Plant Attack -2492,0,6,4,0,0x3,6:7:8:9:10,5,1,yes,0,0,0,none,0, GN_MANDRAGORA,Howling of Mandragora -2493,11,6,16,0,0x1,0,1,1,yes,0,0,0,none,0, GN_SLINGITEM,Sling Item -2494,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, GN_CHANGEMATERIAL,Change Material -2495,0,6,4,0,0x1,0,2,1,no,0,0,0,none,0, GN_MIX_COOKING,Mix Cooking -2496,0,6,4,0,0x1,0,2,1,no,0,0,0,none,0, GN_MAKEBOMB,Create Bomb -2497,0,6,4,0,0x1,0,10,1,no,0,0,0,none,0, GN_S_PHARMACY,Special Pharmacy -2498,11,6,1,0,0,0,1,1,no,0,0,0,weapon,0, GN_SLINGITEM_RANGEMELEEATK,Sling Item Attack - -// Episode 13.3 -//2533,0,0,4,0,0x1,0,1,0,no,0,0,0,none,0, ALL_ODINS_RECALL,Odin's Recall -2534,0,0,4,0,0x1,0,1,0,no,0,0,0,none,0, RETURN_TO_ELDICASTES,Return To Eldicastes -2535,0,0,4,0,0x1,0,1,0,no,0,0x1,0,none,0, ALL_BUYING_STORE,Open Buying Store -2536,0,0,4,0,0x1,0,1,0,no,0,0,0,none,0, ALL_GUARDIAN_RECALL,Guardian's Recall -//2537,9,6,16,0,0x1,0,2,1,yes,0,0,0,magic,0, ALL_ODINS_POWER,Odin's Power -//2538,0,0,0,0,0,0,??,0,no,0,0,0,none,0, BEER_BOTTLE_CAP,Beer Bottle Cap -//2539,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_ASSASSINCROSS,Assassin Cross of Sunset 2 -//2540,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_DISSONANCE,Dissonance 2 -//2541,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_UGLYDANCE,Ugly Dance 2 -//2542,0,0,0,0,0,0,??,0,no,0,0,0,none,0, ALL_TETANY,Tetany -//2543,0,0,0,0,0,0,??,0,no,0,0,0,none,0, ALL_RAY_OF_PROTECTION,Ray of Protection -//2544,0,0,0,0,0,0,??,0,no,0,0,0,none,0, MC_CARTDECORATE,Decorate Cart - -//**** -// Kagerou & Oboro -3001,0,6,4,0,0,0,1,1,no,0,0,0,none,0, KO_YAMIKUMO,Yamikumo -3002,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, KO_RIGHT,Right Hand Mastery -3003,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, KO_LEFT,Left Hand Mastery -3004,3:4:5:6:7,8,1,-1,0,0,5,-2,no,0,0,0,weapon,0, KO_JYUMONJIKIRI,Cross Strike -3005,2,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, KO_SETSUDAN,Soul Sever -3006,7:8:9:10:11,6,2,0,0x2,2,5,0,no,0,0,0,weapon,0, KO_BAKURETSU,Bakuretsu Kunai -3007,0,6,4,-1,0x42,4:4:4:4:5,5,0,no,0,0,0,misc,0, KO_HAPPOKUNAI,Happo Kunai -3008,9,8,2,0,0x52,2,10,-10,no,0,0,0,misc,0, KO_MUCHANAGE,Mucha Nage -3009,9:10:11:12:13,8,2,-1,0x2,3,5,2,no,0,0,0,weapon,0, KO_HUUMARANKA,Huuma Shuriken Ranka -3010,3,6,4,0,0x43,0,5,1,no,0,0x80,0,misc,0, KO_MAKIBISHI,Makibishi -3011,0,6,4,0,0x1,0,5,0,yes,0,0,0,none,0, KO_MEIKYOUSISUI,Meikyo Shisui -3012,0,6,4,0,0x1,0,5,0,no,0,0,1,none,7, KO_ZANZOU,Zanzou -3013,5,6,1,0,0x1,0,5,0,no,0,0,0,none,0, KO_KYOUGAKU,Kyougaku -3014,5,6,1,0,0x1,0,5,0,no,0,0,0,none,0, KO_JYUSATSU,Jyusatsu -3015,0,6,4,3,0x1,0,1,1,no,0,0,0,none,0, KO_KAHU_ENTEN,Kahu Enten -3016,0,6,4,1,0x1,0,1,1,no,0,0,0,none,0, KO_HYOUHU_HUBUKI,Hyouhu Hubuki -3017,0,6,4,4,0x1,0,1,1,no,0,0,0,none,0, KO_KAZEHU_SEIRAN,Kazehu Seiran -3018,0,6,4,2,0x1,0,1,1,no,0,0,0,none,0, KO_DOHU_KOUKAI,Dohu Koukai -3019,11,6,1,0,0,0,5,0,no,0,0,0,weapon,0, KO_KAIHOU,Technique Kaihou -3020,7,6,2,0,0,0,1,3,yes,0,0,0,magic,0, KO_ZENKAI,Zenkai -3021,5:6:7:8:9,6,16,0,0x1,0,5,1,no,0,0,0,none,0, KO_GENWAKU,Genwaku -3022,0,6,4,0,0x1,0,5,0,no,0,0,0,none,0, KO_IZAYOI,Izayoi -3023,0,6,4,0,0x3,2:3:4:5:6,5,0,no,0,0,0,none,0, KG_KAGEHUMI,Kagehumi -3024,7,6,1,0,0x1,0,5,1,no,0,0,0,none,0, KG_KYOMU,Kyomu -3025,7,6,16,0,0x1,0,5,1,no,0,0,0,none,0, KG_KAGEMUSYA,Kagemusha -3026,7,6,16,0,0x1,0,5,1,no,0,0,0,none,0, OB_ZANGETSU,Zangetsu -3027,7,6,16,0,0x1,0,5,1,no,0,0,0,none,0, OB_OBOROGENSOU,Oboro Gensou -3028,1,6,4,0,0x2,3,1,1,no,0,0,0,weapon,0, OB_OBOROGENSOU_TRANSITION_ATK, -3029,7,6,1,0,0x1,0,5,0,no,0,0,0,none,0, OB_AKAITSUKI,Akaitsuki - -8001,9,6,4,0,0x1,0,5,1,no,0,0,0,magic,0, HLIF_HEAL,Healing Touch -8002,0,6,4,0,0x3,-1,5,1,no,0,0,0,none,0, HLIF_AVOID,Avoid -8003,0,0,0,0,0,1,5,0,no,0,0,0,none,0, HLIF_BRAIN,Brain Surgery -8004,0,6,4,0,0x1,0,3,0,no,0,0,0,none,0, HLIF_CHANGE,Change -8005,0,6,4,0,0x1,0,5,0,no,0,0,0,none,0, HAMI_CASTLE,Castling -8006,0,6,4,0,0x1,0,5,0,no,0,0,0,none,0, HAMI_DEFENCE,Defense -8007,0,0,0,0,0x1,0,5,0,no,0,0,0,none,0, HAMI_SKIN,Adamantium Skin -8008,0,6,4,0,0x1,0,3,0,no,0,0,0,none,0, HAMI_BLOODLUST,Bloodlust -8009,1,8,1,0,0,0,5,-1:-2:-2:-2:-3,no,0,0,0,weapon,0, HFLI_MOON,Moonlight -8010,0,6,4,0,0x1,0,5,0,no,0,0,0,none,0, HFLI_FLEET,Fleeting Move -8011,0,6,4,0,0x1,0,5,0,yes,0,0,0,misc,0, HFLI_SPEED,Speed -8012,1,6,1,0,0,0,3,0,no,0,0,0,none,0, HFLI_SBR44,S.B.R.44 -8013,9,6,1,0,0,0,5,1:2:3:4:5,no,0,0,0,magic,0, HVAN_CAPRICE,Caprice -8014,0,6,4,0,0x1,0,5,0,no,0,0,0,none,0, HVAN_CHAOTIC,Benediction of Chaos -8015,0,0,0,0,0x1,0,5,0,no,0,0,0,none,0, HVAN_INSTRUCT,Instruct -8016,4,6,4,-1,0xD2,4,3,1,no,0,0,0,misc,0, HVAN_EXPLOSION,Bio Explosion -// -8018,9,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MH_SUMMON_LEGION,Summon Legion -8019,5,6,1,5,0,0,5,1,no,0,0,0,weapon,0, MH_NEEDLE_OF_PARALYZE,Needle of Paralyze -8020,5,6,2,5,0,0,5,1,no,0,0,1,weapon,0, MH_POISON_MIST,Poison Mist -8021,1,6,1,0,0x1,0,5,1,no,0,0,0,none,0, MH_PAIN_KILLER,Pain Killer -8022,0,6,4,0,0,0x1,5,1,no,0,0,0,none,0, MH_LIGHT_OF_REGENE,Light of Regene -8023,0,6,4,0,0,0x1,5,1,no,0,0,0,none,0, MH_OVERED_BOOST,Overed Boost -8024,7,6,1,4:0:4:0:4,0,0,5,1,no,0,0,0,magic,0, MH_ERASER_CUTTER,Eraser Cutter -8025,7,6,2,4:0:4:0:4,0,0,5,1,no,0,0,0,magic,0, MH_XENO_SLASHER,Xeno Slasher -8026,5:5:7:7:9,6,16,0,0x1,0,5,1,no,0,0,0,magic,0, MH_SILENT_BREEZE,Silent Breeze -8027,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, MH_STYLE_CHANGE,Style Change -8028,1,8,1,0,0,0,5,1,no,0,0,0,weapon,0, MH_SONIC_CRAW,Sonic Claw -8029,1,6,4,0,0,0,5,1,no,0,0x200,0,weapon,0, MH_SILVERVEIN_RUSH,Silver Bain Rush -8030,1,6,4,0,0,0,5,1,no,0,0x200,0,weapon,0, MH_MIDNIGHT_FRENZY,Midnight Frenzy -8031,5:6:7:8:9,6,1,0,0,0,5,1,no,0,0,0,weapon,3, MH_STAHL_HORN,Steel Horn -8032,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MH_GOLDENE_FERSE,Golden Heel -8033,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MH_STEINWAND,Stone Wall -8034,9,6,1,6,0x2,1:1:1:1:2,5,1,no,0,0,0,magic,0, MH_HEILIGE_STANGE,Holy Pole -8035,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MH_ANGRIFFS_MODUS,Attack Mode -8036,3:4:5:6:7,6,1,0,0,0,5,1,no,0,0,0,weapon,0, MH_TINDER_BREAKER,Tinder Breaker -8037,1,6,4,0,0,0,5,1,no,0,0x200,0,weapon,0, MH_CBC,Continual Break Combo -8038,1,6,4,0,0,0,5,1,no,0,0x200,0,weapon,0, MH_EQC,Eternal Quick Combo -8039,0,6,4,3,0x2,1:1:1:2:2,5,1,no,0,0,0,weapon,0, MH_MAGMA_FLOW,Magma Flow -8040,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MH_GRANITIC_ARMOR,Granitic Armor -8041,7,6,2,3,0x2,0,5,1,no,0,0,1,weapon,0, MH_LAVA_SLIDE,Lava Slide -8042,0,6,4,3,0x1,0,5,1,no,0,0,0,none,0, MH_PYROCLASTIC,Pyroclastic -8043,7,6,2,0,0x1,0,5,1,no,0,0,3,none,0, MH_VOLCANIC_ASH,Volcanic Ash - -// Mercenary Skill Place holders -8201,-1,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, MS_BASH,Bash -8202,0,6,4,3,0x2,2,10,1,no,0,0,0,weapon,2, MS_MAGNUM,Magnum_Break -8203,-2,6,1,-1,0x2,1,10,1,no,33,0,0,weapon,1, MS_BOWLINGBASH,Bowling_Bash -8204,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, MS_PARRYING,Parry -8205,0,6,4,0,0,0,10,1,no,0,0,0,weapon,0, MS_REFLECTSHIELD,Shield_Reflect -8206,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, MS_BERSERK,Frenzy -8207,-9,8,1,-1,0,0,10,2,no,0,0,0,weapon,0, MA_DOUBLE,Double_Strafe -8208,-9,6,2,-1,0x2,2,10,1,no,0,0x2000,0,weapon,2, MA_SHOWER,Arrow_Shower -8209,3,6,2,0,0x1,0,5,1,no,0,0x80,0,misc,6:7:8:9:10, MA_SKIDTRAP,Skid_Trap -8210,3,6,2,2,0x40,0,5,1,no,0,0x80,0,misc,0, MA_LANDMINE,Land_Mine -8211,3,6,2,0,0x3,2,5,1,no,0,0x80,0,misc,0, MA_SANDMAN,Sandman -8212,3,6,2,1,0x42,1,5,1,no,0,0x80,0,weapon,0, MA_FREEZINGTRAP,Freezing_Trap -8213,2,6,32,0,0x1,0,1,1,no,0,0,0,misc,0, MA_REMOVETRAP,Remove_Trap -8214,-9,6,1,-1,0x2,0,1,1,no,0,0x1,0,weapon,6, MA_CHARGEARROW,Arrow_Repel -8215,9,8,1,-1,0,2,5,1,yes,0,0,13,weapon,0, MA_SHARPSHOOTING,Focused_Arrow_Strike -8216,-2,8,1,-1,0,0,10,3,no,0,0,0,weapon,0, ML_PIERCE,Pierce -8217,-2,6,1,-1,0x1,0,10,1,no,33,0,0,weapon,3, ML_BRANDISH,Brandish_Spear -8218,5,8,1,-1,0x20,0,5,5,no,0,0,0,weapon,0, ML_SPIRALPIERCE,Spiral_Pierce -8219,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, ML_DEFENDER,Defending_Aura -8220,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, ML_AUTOGUARD,Guard -8221,7:8:9:10:11,6,16,0,0x1,0,5,1,yes,0,0x600,0,none,0, ML_DEVOTION,Sacrifice -8222,0,6,4,0,0x3,-1,5,1,yes,0,0,0,magic,0, MER_MAGNIFICAT,Magnificat -8223,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, MER_QUICKEN,Two-Hand_Quicken -8224,0,6,4,3,0x3,3,1,1,yes,0,0,0,magic,0, MER_SIGHT,Sight -8225,1,8,1,-1,0,0,5,3,no,0,0,0,weapon,0, MER_CRASH,Crash -8226,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_REGAIN,Regain -8227,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_TENDER,Tender -8228,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_BENEDICTION,Benediction -8229,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_RECUPERATE,Recuperate -8230,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_MENTALCURE,Mental_Cure -8231,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_COMPRESS,Compress -8232,9,6,1,0,0x1,0,10,1,no,0,0,0,none,0, MER_PROVOKE,Provoke -8233,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, MER_AUTOBERSERK,Berserk -8234,9,6,1,0,0x1,0,10,1,yes,0,0,0,magic,0, MER_DECAGI,Decrease_AGI -8235,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, MER_SCAPEGOAT,Scapegoat -8236,5,6,1,0,0x1,0,10,0,yes,0,0,0,magic,0, MER_LEXDIVINA,Lex_Divina -8237,9,6,1,0,0x1,0,1,1,yes,0,0,0,magic,0, MER_ESTIMATION,Sense -8238,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, MER_KYRIE,Kyrie Eleison -8239,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, MER_BLESSING,Blessing -8240,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, MER_INCAGI,Increase Agility - -// Elemental Spirits Skills -8401,0,6,4,3,0,0,1,1,no,0,0,0,weapon,2, EL_CIRCLE_OF_FIRE,Circle of Fire -8402,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_FIRE_CLOAK,Fire Cloak -8403,0,6,4,3,0,0,1,1,no,0,0,3,magic,2, EL_FIRE_MANTLE,Fire Mantle -8404,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_WATER_SCREEN,Water Screen -8405,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_WATER_DROP,Water Drop -8406,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_WATER_BARRIER,Water Barrier -8407,0,6,4,0,0x1,0,1,1,no,0,0,0,none,5, EL_WIND_STEP,Wind Step -8408,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_WIND_CURTAIN,Wind Curtain -8409,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_ZEPHYR,Zephyr -8410,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_SOLID_SKIN,Solid Skin -8411,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_STONE_SHIELD,Stone Shield -8412,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_POWER_OF_GAIA,Power of Gaia -8413,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_PYROTECHNIC,Pyrotechnic -8414,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_HEATER,Heater -8415,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_TROPIC,Tropic -8416,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_AQUAPLAY,Aqua Play -8417,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_COOLER,Cooler -8418,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_CHILLY_AIR,Cool Air -8419,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_GUST,Gust -8420,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_BLAST,Blast -8421,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_WILD_STORM,Wild Storm -8422,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_PETROLOGY,Petrology -8423,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_CURSED_SOIL,Cursed Soil -8424,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_UPHEAVAL,Upheaval -8425,6,6,1,3,0,0,1,1,no,0,0,0,magic,0, EL_FIRE_ARROW,Fire Arrow -8426,6,6,1,3,0,1,1,1,no,0,0,0,magic,0, EL_FIRE_BOMB,Fire Bomb -8427,6,6,1,3,0,1,1,1,no,0,0,0,weapon,0, EL_FIRE_BOMB_ATK,Fire Bomb Attack -8428,6,6,1,3,0,1,1,1,no,0,0,0,magic,0, EL_FIRE_WAVE,Fire Wave -8429,6,6,1,3,0,1,1,1,no,0,0,0,weapon,0, EL_FIRE_WAVE_ATK,Fire Wave Attack -8430,9,6,1,1,0,0,1,1,no,0,0,0,magic,0, EL_ICE_NEEDLE,Ice Needle -8431,9,6,1,1,0,1,1,1,no,0,0,0,magic,0, EL_WATER_SCREW,Water Screw -8432,9,6,1,1,0,1,1,1,no,0,0,0,weapon,0, EL_WATER_SCREW_ATK,Water Screw Attack -8433,9,6,1,1,0,1,1,1,no,0,0,0,weapon,0, EL_TIDAL_WEAPON,Tidal Weapon -8434,11,6,1,4,0,0,1,1,no,0,0,0,weapon,0, EL_WIND_SLASH,Wind Slasher -8435,11,6,1,4,0,1,1,1,no,0,0,0,weapon,0, EL_HURRICANE,Hurricane Rage -8436,7,6,1,4,0,0,1,1,no,0,0,0,magic,0, EL_HURRICANE_ATK,Hurricane Rage Attack -8437,11,8,1,4,0,1,1,-3,no,0,0,0,weapon,0, EL_TYPOON_MIS,Typhoon Missile -8438,11,8,1,4,0,1,1,-3,no,0,0,0,magic,0, EL_TYPOON_MIS_ATK,Typhoon Missile Attack -8439,5,6,1,2,0,0,1,1,no,0,0,0,weapon,0, EL_STONE_HAMMER,Stone Hammer -8440,3,6,1,2,0,1,1,1,no,0,0,0,weapon,0, EL_ROCK_CRUSHER,Rock Launcher -8441,5,6,1,2,0,1,1,1,no,0,0,0,magic,0, EL_ROCK_CRUSHER_ATK,Rock Launcher Attack -8442,9,6,1,2,0,1,1,-5,no,0,0,0,weapon,0, EL_STONE_RAIN,Stone Rain - -10000,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_APPROVAL,Official Guild Approval -10001,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_KAFRACONTRACT,Kafra Contract -10002,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_GUARDRESEARCH,Guardian Research -10003,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_GUARDUP,Strengthen Guardians -10004,0,0,0,0,0,0,10,0,no,0,0x10,0,none,0, GD_EXTENSION,Guild Extension -10005,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_GLORYGUILD,Guild's Glory -10006,0,0,0,0,0,2,1,0,no,0,0x10,0,none,0, GD_LEADERSHIP,Great Leadership -10007,0,0,0,0,0,2,1,0,no,0,0x10,0,none,0, GD_GLORYWOUNDS,Glorious Wounds -10008,0,0,0,0,0,2,1,0,no,0,0x10,0,none,0, GD_SOULCOLD,Cold Heart -10009,0,0,0,0,0,2,1,0,no,0,0x10,0,none,0, GD_HAWKEYES,Sharp Gaze -10010,0,0,4,0,0x3,15,1,0,yes,0,0x10,0,none,0, GD_BATTLEORDER,Battle Orders -10011,0,0,4,0,0x3,15,3,0,yes,0,0x10,0,none,0, GD_REGENERATION,Regeneration -10012,0,0,4,0,0x3,15,1,0,yes,0,0x10,0,none,0, GD_RESTORE,Restoration -10013,0,0,4,0,0x3,0,1,0,yes,0,0x10,0,none,0, GD_EMERGENCYCALL,Urgent Call -10014,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_DEVELOPMENT,Permanent Development -//10015,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_ITEMEMERGENCYCALL,Unknown Skill +//id,range,hit,inf,element,nk,splash,max,list_num,castcancel,cast_defence_rate,inf2,maxcount,skill_type,blow_count,name,description +// 01 ID +// 02 range (combo skills do not check for range when used, +// if range is < 5, the skill is considered melee-range) +// 03 hit (8- repeated hitting, 6- single-hit) +// 04 inf (0- passive, 1- enemy, 2- place, 4- self, 16- friend, 32- trap) +// 05 element (0 - neutral, 1 - water, 2 - earth, 3 - fire, 4 - wind, 5 - poison, +// 6 - holy, 7 - dark, 8 - ghost, 9 - undead, -1 - use weapon element +// -2 - use endowed element, -3 - use random element.) +// 06 nk (skill damage properties): +// 0x01 - No damage skill +// 0x02 - Has splash area +// 0x04 - Damage should be split among targets +// 0x08 - Skill ignores caster's % damage cards (misc type always ignores) +// 0x10 - Skill ignores elemental adjustments +// 0x20 - Skill ignores target's defense (misc type always ignores) +// 0x40 - Skill ignores target's flee (magic type always ignores) +// 0x80 - Skill ignores target's def cards +// 07 splash/effect range (-1 for screen-wide) +// 08 MaxLv +// 09 Number of hits (when positive, damage is increased by hits, +// negative values just show number of hits without increasing total damage) +// 10 Cast interrupted when hit? +// 11 defense-reduction rate during cast. +// 12 inf2 (skill information 2): +// 0x0001- quest skill +// 0x0002- npc skill +// 0x0004- wedding skill +// 0x0008- spirit skill +// 0x0010- guild skill +// 0x0020- song/dance +// 0x0040- ensemble skill +// 0x0080- trap +// 0x0100- skill that damages/targets yourself +// 0x0200- cannot be casted on self (if inf = 4, auto-select target skill) +// 0x0400- usable only on party-members (and enemies if skill is offensive) +// 0x0800- usable only on guild-mates (and enemies if skill is offensive) +// 0x1000- disable usage on enemies (for non-offensive skills). +// 0x2000- skill ignores land protector (e.g. arrow shower) +// 0x4000- chorus skill +// 13 maxcount: max amount of skill instances to place on the ground when +// player_land_skill_limit/monster_land_skill_limit is enabled. For skills +// that attack using a path, this is the path length to be used. +// 14 attack type (none, weapon, magic, misc) +// 15 Blowcount (amount of tiles skill knockbacks) +// 16 Name +// 17 Description +1,0,0,0,0,0,0,9,0,no,0,0,0,none,0, NV_BASIC,Basic Skill +2,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, SM_SWORD,Sword Mastery +3,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, SM_TWOHAND,Two-Handed Sword Mastery +4,0,0,0,0,0,0,10,0,no,0,0,0,none,0, SM_RECOVERY,Increase HP Recovery +5,-1,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, SM_BASH,Bash +6,9,6,1,0,1,0,10,1,no,0,0,0,none,0, SM_PROVOKE,Provoke +7,0,6,4,3,0x2,2,10,1,no,0,0,0,weapon,2, SM_MAGNUM,Magnum Break +8,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, SM_ENDURE,Endure +9,0,0,0,0,0,0,10,0,no,0,0,0,none,0, MG_SRECOVERY,Increase SP Recovery +10,0,6,4,3,0x3,3,1,1,yes,0,0,0,magic,0, MG_SIGHT,Sight +11,9,6,1,8,0x6,1,10,1,yes,0,0,0,magic,0, MG_NAPALMBEAT,Napalm Beat +12,9,8,2,8,0x1,0,10,1,yes,0,0,0,magic,0, MG_SAFETYWALL,Safety Wall +13,9,8,1,8,0,0,10,1:1:2:2:3:3:4:4:5:5,yes,0,0,0,magic,0, MG_SOULSTRIKE,Soul Strike +14,9,8,1,1,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, MG_COLDBOLT,Cold Bolt +15,9,6,1,1,0,0,10,1,yes,0,0,0,magic,0, MG_FROSTDIVER,Frost Diver +16,2,6,1,2,0x1,0,10,1,yes,0,0,0,magic,0, MG_STONECURSE,Stone Curse +17,9,6,1,3,0x2,2,10,1,yes,0,0,0,magic,0, MG_FIREBALL,Fire Ball +18,9,6,2,3,0,0,10,1,yes,0,0,3,magic,2, MG_FIREWALL,Fire Wall +19,9,8,1,3,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, MG_FIREBOLT,Fire Bolt +20,9,8,1,4,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, MG_LIGHTNINGBOLT,Lightning Bolt +21,9,8,2,4,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, MG_THUNDERSTORM,Thunderstorm +22,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, AL_DP,Divine Protection +23,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, AL_DEMONBANE,Demon Bane +24,0,6,4,6,0x3,2,1,1,yes,0,0,0,magic,0, AL_RUWACH,Ruwach +25,9,6,2,0,0x1,0,1,1,yes,0,0,0,magic,0, AL_PNEUMA,Pneuma +26,0,6,4,0,0x1,0,2,1,yes,0,0,0,magic,0, AL_TELEPORT,Teleport +27,9,6,2,0,0x1,0,4,1,yes,0,0,3,magic,0, AL_WARP,Warp Portal +28,9,6,16,6,0x21,0,10,1,yes,0,0,0,magic,0, AL_HEAL,Heal +29,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, AL_INCAGI,Increase AGI +30,9,6,1,0,0x1,0,10,1,yes,0,0,0,magic,0, AL_DECAGI,Decrease AGI +31,0,6,4,0,0x1,0,1,1,yes,0,0,0,magic,0, AL_HOLYWATER,Aqua Benedicta +32,0,6,4,0,0x3,15,10,1,yes,0,0,0,magic,0, AL_CRUCIS,Signum Crucis +33,0,6,4,0,0x3,-1,10,1,yes,0,0,0,magic,0, AL_ANGELUS,Angelus +34,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, AL_BLESSING,Blessing +35,9,6,16,0,0x1,0,1,1,yes,0,0,0,magic,0, AL_CURE,Cure +36,0,0,0,0,0,0,10,0,no,0,0,0,none,0, MC_INCCARRY,Enlarge Weight Limit +37,0,0,0,0,0,0,10,0,no,0,0,0,none,0, MC_DISCOUNT,Discount +38,0,0,0,0,0,0,10,0,no,0,0,0,none,0, MC_OVERCHARGE,Overcharge +39,1,0,0,0,0,0,10,0,no,0,0,0,none,0, MC_PUSHCART,Pushcart +40,1,6,4,0,0x1,0,1,1,no,0,0,0,none,0, MC_IDENTIFY,Item Appraisal +41,1,6,4,0,0x1,0,10,1,no,0,0,0,none,0, MC_VENDING,Vending +42,-1,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, MC_MAMMONITE,Mammonite +43,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AC_OWL,Owl's Eye +44,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AC_VULTURE,Vulture's Eye +45,0,6,4,0,0x3,3,10,1,no,0,0,0,weapon,0, AC_CONCENTRATION,Improve Concentration +46,-9,8,1,-1,0,0,10,2,no,0,0,0,weapon,0, AC_DOUBLE,Double Strafe +47,-9,6,2,-1,0x2,2,10,1,no,0,0x2000,0,weapon,2, AC_SHOWER,Arrow Shower +48,-1,8,0,-1,0,0,10,2,no,0,0,0,weapon,0, TF_DOUBLE,Double Attack +49,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, TF_MISS,Improve Dodge +50,1,6,1,0,1,0,10,1,no,0,0,0,weapon,0, TF_STEAL,Steal +51,1,6,4,0,1,0,10,1,no,0,0,0,none,0, TF_HIDING,Hiding +52,-2,6,1,5,0,0,10,1,no,0,0,0,weapon,0, TF_POISON,Envenom +53,9,6,16,5,0x1,0,1,1,no,0,0,0,weapon,0, TF_DETOXIFY,Detoxify +54,9,6,16,6,0x1,0,4,1,yes,0,0,0,magic,0, ALL_RESURRECTION,Resurrection +55,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, KN_SPEARMASTERY,Spear Mastery +56,-2,8,1,-1,0,0,10,3,no,0,0,0,weapon,0, KN_PIERCE,Pierce +57,-2,6,1,-1,0x1,0,10,1,no,33,0,0,weapon,3, KN_BRANDISHSPEAR,Brandish Spear +58,-4,6,1,-1,0x2,0,10,1,no,0,0,0,weapon,6, KN_SPEARSTAB,Spear Stab +59,3:5:7:9:11,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, KN_SPEARBOOMERANG,Spear Boomerang +60,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, KN_TWOHANDQUICKEN,Twohand Quicken +61,0,6,4,-1,0x20,0,5,1,no,0,0,0,weapon,0, KN_AUTOCOUNTER,Counter Attack +62,-2,6,1,-1,0x2,1,10,1,no,33,0,0,weapon,1, KN_BOWLINGBASH,Bowling Bash +63,0,0,0,0,0,0,1,0,no,0,0,0,weapon,0, KN_RIDING,Peco Peco Riding +64,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, KN_CAVALIERMASTERY,Cavalier Mastery +65,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, PR_MACEMASTERY,Mace Mastery +66,9,6,16,0,0x1,0,5,1,yes,0,0,0,magic,0, PR_IMPOSITIO,Impositio Manus +67,9,6,16,0,0x1,0,3,1,yes,0,0x200,0,magic,0, PR_SUFFRAGIUM,Suffragium +68,9,6,16,6,0x31,0,5,1,yes,0,0,0,magic,0, PR_ASPERSIO,Aspersio +69,9,6,2,0,0x23,1,5,1,yes,0,0x40,0,magic,0, PR_BENEDICTIO,B.S. Sacramenti +70,9,6,2,6,0x21,0,10,1,yes,0,0,0,magic,1, PR_SANCTUARY,Sanctuary +71,9,6,16,0,0x1,0,4,1,yes,0,0,0,magic,0, PR_SLOWPOISON,Slow Poison +72,9,6,16,0,0x1,0,1,1,yes,0,0,0,magic,0, PR_STRECOVERY,Status Recovery +73,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, PR_KYRIE,Kyrie Eleison +74,0,6,4,0,0x3,-1,5,1,yes,0,0,0,magic,0, PR_MAGNIFICAT,Magnificat +75,0,6,4,0,0x3,-1,5,1,yes,0,0,0,magic,0, PR_GLORIA,Gloria +76,5,6,1,0,0x1,0,10,0,yes,0,0,0,magic,0, PR_LEXDIVINA,Lex Divina +77,5,6,1,6,0x28,0,10,1,yes,0,0,0,magic,0, PR_TURNUNDEAD,Turn Undead +78,9,6,1,0,0x1,0,1,0,yes,0,0,0,magic,0, PR_LEXAETERNA,Lex Aeterna +79,9,8,2,6,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, PR_MAGNUS,Magnus Exorcismus +80,9,8,2,3,0x20,1:1:1:1:1:2:2:2:2:2:2,10,3:4:5:6:7:8:9:10:11:12:12,yes,0,0x80,5,magic,0, WZ_FIREPILLAR,Fire Pillar +81,0,6,4,3,0,3,10,1,yes,0,0,0,magic,5, WZ_SIGHTRASHER,Sightrasher +83,9,8,2,3,0,3:3:3:3:3:3:3:3:3:3:14,10,1:1:2:2:3:3:4:4:5:5:15,yes,0,0,0,magic,0, WZ_METEOR,Meteor Storm +84,9,8,1,4,0,0,10,3:4:5:6:7:8:9:10:11:12,yes,0,0,0,magic,2:3:3:4:4:5:5:6:6:7, WZ_JUPITEL,Jupitel Thunder +85,9,8,2,4,0,0,10,-10,yes,0,0,0,magic,0, WZ_VERMILION,Lord of Vermilion +86,9,8,1,1,0,0,5,1,yes,0,0,0,magic,0, WZ_WATERBALL,Water Ball +87,9,6,2,1,0x1,0,10,1,yes,0,0,0,magic,0, WZ_ICEWALL,Ice Wall +88,0,6,4,1,0x2,2,10,1,yes,0,0,0,magic,0, WZ_FROSTNOVA,Frost Nova +89,9,6,2,1,0,0,10,1,yes,0,0,0,magic,2, WZ_STORMGUST,Storm Gust +90,9,8,1,2,0,0,5,1:2:3:4:5,yes,0,0,0,magic,0, WZ_EARTHSPIKE,Earth Spike +91,9,8,2,2,0,0,5,1:2:3:4:5,yes,0,0,0,magic,0, WZ_HEAVENDRIVE,Heaven's Drive +92,9,6,2,2,0x1,0,5,1,yes,0,0,3,magic,0, WZ_QUAGMIRE,Quagmire +93,9,6,1,0,0x1,0,1,1,yes,0,0,0,magic,0, WZ_ESTIMATION,Sense +94,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, BS_IRON,Iron Tempering +95,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, BS_STEEL,Steel Tempering +96,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, BS_ENCHANTEDSTONE,Enchanted Stone Craft +97,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, BS_ORIDEOCON,Oridecon Research +98,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_DAGGER,Smith Dagger +99,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_SWORD,Smith Sword +100,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_TWOHANDSWORD,Smith Two-handed Sword +101,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_AXE,Smith Axe +102,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_MACE,Smith Mace +103,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_KNUCKLE,Smith Knucklebrace +104,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_SPEAR,Smith Spear +105,0,0,0,0,0,0,1,0,no,0,0,0,weapon,0, BS_HILTBINDING,Hilt Binding +106,0,0,0,0,0,0,1,0,no,0,0,0,weapon,0, BS_FINDINGORE,Ore Discovery +107,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, BS_WEAPONRESEARCH,Weaponry Research +108,2,6,16,0,0x1,0,1,1,yes,0,0,0,weapon,0, BS_REPAIRWEAPON,Weapon Repair +109,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, BS_SKINTEMPER,Skin Tempering +110,1,6,2,0,0x3,2:2:2:2:2:14,5,1,no,0,0,0,weapon,0, BS_HAMMERFALL,Hammer Fall +111,0,6,4,0,0x3,-1,5,1,no,0,0,0,weapon,0, BS_ADRENALINE,Adrenaline Rush +112,0,6,4,0,0x3,-1,5,1,no,0,0,0,weapon,0, BS_WEAPONPERFECT,Weapon Perfection +113,0,6,4,0,0x3,-1,5,1,no,0,0,0,weapon,0, BS_OVERTHRUST,Power-Thrust +114,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, BS_MAXIMIZE,Maximize Power +115,3,6,2,0,0x1,0,5,1,no,0,0x80,0,misc,6:7:8:9:10, HT_SKIDTRAP,Skid Trap +116,3,6,2,2,0x42,1,5,1,no,0,0x80,0,misc,0, HT_LANDMINE,Land Mine +117,3,6,2,0,0x1,0,5,1,no,0,0x80,0,misc,0, HT_ANKLESNARE,Ankle Snare +118,3,6,2,0,0x2,1,5,1,no,0,0x80,0,misc,0, HT_SHOCKWAVE,Shockwave Trap +119,3,6,2,0,0x3,2,5,1,no,0,0x80,0,misc,0, HT_SANDMAN,Sandman +120,3,6,2,0,0x3,1,5,1,no,0,0x80,0,misc,0, HT_FLASHER,Flasher +121,3,6,2,1,0x42,1,5,1,no,0,0x80,0,weapon,0, HT_FREEZINGTRAP,Freezing Trap +122,3,6,2,4,0x42,1,5,1,no,0,0x80,0,misc,0, HT_BLASTMINE,Blast Mine +123,3,6,2,3,0x42,2,5,1,no,0,0x80,0,misc,0, HT_CLAYMORETRAP,Claymore Trap +124,2,6,32,0,0x1,0,1,1,no,0,0,0,misc,0, HT_REMOVETRAP,Remove Trap +125,3,6,2,0,0x1,0,1,1,no,0,0x80,0,misc,0, HT_TALKIEBOX,Talkie Box +126,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, HT_BEASTBANE,Beast Bane +127,0,0,0,0,0,0,1,0,no,0,0,0,misc,0, HT_FALCON,Falconry Mastery +128,0,0,0,0,0,0,10,0,no,0,0,0,misc,0, HT_STEELCROW,Steel Crow +129,5,8,1,0,0x42,1,5,1:2:3:4:5,yes,0,0,0,misc,0, HT_BLITZBEAT,Blitz Beat +130,3:5:7:9,6,2,0,0x3,3,4,1,no,0,0,0,misc,0, HT_DETECTING,Detect +131,4:5:6:7:8,6,32,0,0x1,0,5,1,no,0,0,0,misc,0, HT_SPRINGTRAP,Spring Trap +132,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, AS_RIGHT,Righthand Mastery +133,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, AS_LEFT,Lefthand Mastery +134,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, AS_KATAR,Katar Mastery +135,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, AS_CLOAKING,Cloaking +136,-1,8,1,-1,0,0,10,8,no,0,0,0,weapon,0, AS_SONICBLOW,Sonic Blow +137,3:4:5:6:7,6,1,-1,0x2,1,5,1,no,0,0,0,weapon,0,AS_GRIMTOOTH,Grimtooth +138,1,6,16,5,0x1,0,10,1,no,0,0x400,0,weapon,0, AS_ENCHANTPOISON,Enchant Poison +139,0,6,4,0,0,0,10,1,no,0,0,0,weapon,0, AS_POISONREACT,Poison React +140,2,6,2,5,0x1,0,10,1,no,0,0,0,weapon,0, AS_VENOMDUST,Venom Dust +141,1,6,1,-1,0x51,2,10,1,yes,0,0,0,weapon,0, AS_SPLASHER,Venom Splasher +142,0,6,4,0,0x1,0,1,1,no,0,0x1,0,none,0, NV_FIRSTAID,First Aid +143,0,6,4,0,0x1,0,1,1,no,0,0x1,0,none,0, NV_TRICKDEAD,Play Dead +144,0,0,0,0,0,0,1,0,no,0,0x1,0,none,0, SM_MOVINGRECOVERY,Moving HP-Recovery +145,0,0,0,0,0,0,1,0,no,0,0x1,0,weapon,0, SM_FATALBLOW,Fatal Blow +146,0,6,4,0,0x1,0,1,1,no,0,0x1,0,weapon,0, SM_AUTOBERSERK,Auto Berserk +147,0,0,4,0,0x1,0,1,0,no,0,0x1,0,weapon,0, AC_MAKINGARROW,Arrow Crafting +148,-9,6,1,-1,0x2,0,1,1,no,0,0x1,0,weapon,6, AC_CHARGEARROW,Arrow Repel +149,1,6,1,2,0,0,1,1,no,0,0x1,0,weapon,0, TF_SPRINKLESAND,Sand Attack +150,0,6,4,0,0x1,0,1,1,no,0,0x1,0,weapon,5, TF_BACKSLIDING,Back Slide +151,0,6,4,0,0x1,0,1,1,no,0,0x1,0,none,0, TF_PICKSTONE,Find Stone +152,7,6,1,0,0x40,0,1,1,no,0,0x1,0,misc,0, TF_THROWSTONE,Stone Fling +153,1,6,1,-1,0x2,1,1,1,no,0,0x1,0,weapon,2, MC_CARTREVOLUTION,Cart Revolution +154,0,6,4,0,0x1,0,1,1,no,0,0x1,0,none,0, MC_CHANGECART,Change Cart +155,0,6,4,0,0x1,0,1,1,no,0,0x1,0,weapon,0, MC_LOUD,Crazy Uproar +156,9,6,1,6,0,0,1,1,yes,0,0x1,0,magic,0, AL_HOLYLIGHT,Holy Light +157,0,6,4,0,0x1,0,1,1,yes,0,0x1,0,magic,0, MG_ENERGYCOAT,Energy Coat +158,3,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_PIERCINGATT,Piercing Attack +159,-1,6,1,-1,0x40,0,5,1,no,0,0x2,0,weapon,0, NPC_MENTALBREAKER,Spirit Destruction +160,9,6,1,0,0,0,10,1,no,0,0x2,0,weapon,0, NPC_RANGEATTACK,Stand off attack +161,0,0,4,0,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_ATTRICHANGE,Attribute Change +162,0,0,4,1,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEWATER,Water Attribute Change +163,0,0,4,2,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEGROUND,Earth Attribute Change +164,0,0,4,3,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEFIRE,Fire Attribute Change +165,0,0,4,4,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEWIND,Wind Attribute Change +166,0,0,4,5,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEPOISON,Poison Attribute Change +167,0,0,4,6,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEHOLY,Holy Attribute Change +168,0,0,4,7,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEDARKNESS,Shadow Attribute Change +169,0,0,4,8,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGETELEKINESIS,Ghost Attribute Change +170,-9,6,1,-1,0x20,0,10,1,no,0,0x2,0,weapon,0, NPC_CRITICALSLASH,Defense disregard attack +171,-9,8,1,-1,0,0,10,-2:-3:-4:-5:-6:-7:-8:-9:-10:-11,no,0,0x2,0,weapon,0, NPC_COMBOATTACK,Multi-stage Attack +172,-9,6,1,-1,0x40,0,10,1,no,0,0x2,0,weapon,0, NPC_GUIDEDATTACK,Guided Attack +173,5,6,4,3,0xE2,5,10,1,no,0,0x2,0,misc,3, NPC_SELFDESTRUCTION,Suicide bombing +174,-9,6,1,-1,0x2,3,1,1,no,0,0x2,0,weapon,0, NPC_SPLASHATTACK,Splash attack +175,0,0,4,0,0x41,0,10,1,no,0,0x2,0,misc,0, NPC_SUICIDE,Suicide +176,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_POISON,Poison Attack +177,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_BLINDATTACK,Blind Attack +178,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_SILENCEATTACK,Silence Attack +179,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_STUNATTACK,Stun Attack +180,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_PETRIFYATTACK,Petrify Attack +181,-9,6,1,7,0,0,5,1,no,0,0x2,0,weapon,0, NPC_CURSEATTACK,Curse Attack +182,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_SLEEPATTACK,Sleep attack +183,-9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_RANDOMATTACK,Random Attack +184,-9,6,1,1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_WATERATTACK,Water Attribute Attack +185,-9,6,1,2,0,0,10,1,no,0,0x2,0,weapon,0, NPC_GROUNDATTACK,Earth Attribute Attack +186,-9,6,1,3,0,0,10,1,no,0,0x2,0,weapon,0, NPC_FIREATTACK,Fire Attribute Attack +187,-9,6,1,4,0,0,10,1,no,0,0x2,0,weapon,0, NPC_WINDATTACK,Wind Attribute Attack +188,-9,6,1,5,0,0,10,1,no,0,0x2,0,weapon,0, NPC_POISONATTACK,Poison Attribute Attack +189,-9,6,1,6,0,0,10,1,no,0,0x2,0,weapon,0, NPC_HOLYATTACK,Holy Attribute Attack +190,-9,6,1,7,0,0,10,1,no,0,0x2,0,weapon,0, NPC_DARKNESSATTACK,Shadow Attribute Attack +191,-9,6,1,8,0,0,10,1,no,0,0x2,0,weapon,0, NPC_TELEKINESISATTACK,Ghost Attribute Attack +192,-9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_MAGICALATTACK,Demon Shock Attack +193,0,0,4,0,0x1,0,10,1,no,0,0x2,0,none,0, NPC_METAMORPHOSIS,Metamorphosis +194,0,0,4,0,0x1,0,10,1,no,0,0x2,0,none,0, NPC_PROVOCATION,Provocation +195,0,6,4,0,0x50,0,10,1,no,0,0x2,0,misc,0, NPC_SMOKING,Smoking +196,0,0,4,0,0x1,0,10,1,no,0,0x2,0,magic,0, NPC_SUMMONSLAVE,Follower Summons +197,0,0,4,0,0x1,0,10,1,no,0,0x2,0,none,0, NPC_EMOTION,Emotion +198,0,0,4,0,0x1,0,10,1,no,0,0x2,0,magic,0, NPC_TRANSFORMATION,Transformation +199,9,6,1,7,0x40,0,1,1,no,0,0x2,0,weapon,0, NPC_BLOODDRAIN,Sucking Blood +200,9,6,1,7,0,0,1,1,no,0,0x2,0,magic,0, NPC_ENERGYDRAIN,Energy Drain +201,0,0,4,0,0x1,0,1,1,no,0,0x2,0,weapon,0, NPC_KEEPING,Keeping +202,9,6,1,7,0,0,5,1,no,0,0x2,0,misc,0, NPC_DARKBREATH,Dark Breath +203,9,6,1,7,0x1,0,10,1,no,0,0x2,0,magic,0, NPC_DARKBLESSING,Dark Blessing +204,0,0,4,0,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_BARRIER,Barrier +205,0,0,4,0,0x1,0,1,1,no,0,0x2,0,weapon,0, NPC_DEFENDER,Defender +206,1,6,1,-1,0x1,0,5,1,no,0,0x2,0,weapon,0, NPC_LICK,Lick +207,9,0,1,0,0x1,0,10,1,no,0,0x2,0,magic,0, NPC_HALLUCINATION,Hallucination +208,0,0,4,0,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_REBIRTH,Rebirth +209,0,0,4,0,0x1,0,10,1,no,0,0x2,0,magic,0, NPC_SUMMONMONSTER,Monster Summons +210,0,0,0,-1,0,0,10,0,no,0,0,0,weapon,0, RG_SNATCHER,Gank +211,1,6,1,0,0x1,0,10,1,no,0,0,0,weapon,0, RG_STEALCOIN,Mug +212,-1,6,1,-1,0x40,0,10,1,no,0,0,0,weapon,0, RG_BACKSTAP,Back Stab +213,0,0,0,0,0,0,5,0,no,0,0,0,none,0, RG_TUNNELDRIVE,Stalk +214,0,6,4,-1,0x2,1,5,1,no,0,0,0,weapon,0, RG_RAID,Sightless Mind +215,1,6,1,0,0x1,0,5,1,no,0,0,0,weapon,0, RG_STRIPWEAPON,Divest Weapon +216,1,6,1,0,0x1,0,5,1,no,0,0,0,weapon,0, RG_STRIPSHIELD,Divest Shield +217,1,6,1,0,0x1,0,5,1,no,0,0,0,weapon,0, RG_STRIPARMOR,Divest Armor +218,1,6,1,0,0x1,0,5,1,no,0,0,0,weapon,0, RG_STRIPHELM,Divest Helm +219,1,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, RG_INTIMIDATE,Snatch +220,1,6,2,0,0x1,0,1,1,no,0,0,0,none,0, RG_GRAFFITI,Scribble +221,0,6,2,0,0x1,0,5,1,no,0,0,0,none,0, RG_FLAGGRAFFITI,Piece +222,1,6,2,0,0x3,5,1,1,no,0,0,0,none,0, RG_CLEANER,Remover +223,0,0,0,0,0,1,1,0,no,0,0,0,none,0, RG_GANGSTER,Slyness +224,0,0,0,0,0,0,5,0,no,0,0,0,none,0, RG_COMPULSION,Haggle +225,0,0,0,0,0,0,10,0,no,0,0,0,none,0, RG_PLAGIARISM,Intimidate +226,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, AM_AXEMASTERY,Axe Mastery +227,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AM_LEARNINGPOTION,Potion Research +228,0,6,4,0,0x1,0,10,0,no,0,0,0,none,0, AM_PHARMACY,Prepare Potion +229,9,6,2,3,0x9,0,5,1,yes,0,0,0,weapon,0, AM_DEMONSTRATION,Bomb +230,9,6,1,0,0x48,0,5,1,yes,0,0,0,weapon,0, AM_ACIDTERROR,Acid Terror +231,9,6,16,0,0x1,0,5,1,yes,0,0xC00,0,none,0, AM_POTIONPITCHER,Aid Potion +232,4,6,2,0,0x1,0,5,1,no,0,0,5,none,0, AM_CANNIBALIZE,Summon Flora +233,1,6,2,0,0x1,0,5,1,no,0,0,3,none,0, AM_SPHEREMINE,Summon Marine Sphere +234,1,6,16,0,0x1,0,5,1,yes,0,0,0,weapon,0, AM_CP_WEAPON,Alchemical Weapon +235,1,6,16,0,0x1,0,5,1,yes,0,0,0,weapon,0, AM_CP_SHIELD,Synthesized Shield +236,1,6,16,0,0x1,0,5,1,yes,0,0,0,weapon,0, AM_CP_ARMOR,Synthetic Armor +237,1,6,16,0,0x1,0,5,1,yes,0,0,0,weapon,0, AM_CP_HELM,Biochemical Helm +238,0,0,0,0,0,0,1,0,no,0,0x1,0,none,0, AM_BIOETHICS,Bioethics +//239,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AM_BIOTECHNOLOGY,Biotechnology +//240,0,0,0,0,0,0,5,0,no,0,0,0,none,0, AM_CREATECREATURE,Life Creation +//241,0,0,0,0,0,0,5,0,no,0,0,0,none,0, AM_CULTIVATION,Cultivation +//242,0,0,0,0,0,0,5,0,no,0,0,0,none,0, AM_FLAMECONTROL,Flame Control +243,0,0,4,0,0x1,1,1,0,no,0,0,0,none,0, AM_CALLHOMUN,Call Homunculus +244,0,0,4,0,0x1,0,1,0,no,0,0,0,none,0, AM_REST,Vaporize +//245,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AM_DRILLMASTER,Drillmaster +//246,9,0,0,0,0,0,10,0,no,0,0,0,none,0, AM_HEALHOMUN,Heal Homunculus +247,9,6,4,0,0x1,1,5,0,no,0,0,0,none,0, AM_RESURRECTHOMUN,Homunculus Resurrection +248,0,0,0,0,0,0,10,0,no,0,0,0,none,0, CR_TRUST,Faith +249,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, CR_AUTOGUARD,Guard +250,3,6,1,0,0,0,5,1,no,0,0,0,weapon,5:6:7:8:9, CR_SHIELDCHARGE,Smite +251,3:5:7:9:11,6,1,0,0,0,5,1,no,0,0,0,weapon,0, CR_SHIELDBOOMERANG,Shield Boomerang +252,0,6,4,0,0,0,10,1,no,0,0,0,weapon,0, CR_REFLECTSHIELD,Shield Reflect +253,-2,8,1,6,0,0,10,-2,no,0,0,0,weapon,0, CR_HOLYCROSS,Holy Cross +254,5,6,4,6,0x48,0,10,1,no,33,0x100,0,magic,0, CR_GRANDCROSS,Grand Cross +255,7:8:9:10:11,6,16,0,0x1,0,5,1,yes,0,0x600,0,none,0, CR_DEVOTION,Sacrifice +256,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,none,0, CR_PROVIDENCE,Resistant Souls +257,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, CR_DEFENDER,Defending Aura +258,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, CR_SPEARQUICKEN,Spear Quicken +259,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, MO_IRONHAND,Iron Fists +260,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, MO_SPIRITSRECOVERY,Spiritual Cadence +261,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MO_CALLSPIRITS,Summon Spirit Sphere +262,9,6,16,0,0x1,0,1,1,yes,0,0,0,weapon,0, MO_ABSORBSPIRITS,Absorb Spirit Sphere +263,-1,8,0,-1,0,0,10,-3,no,0,0,0,weapon,0, MO_TRIPLEATTACK,Raging Trifecta Blow +264,18,6,2,0,0x1,0,1,1,no,0,0,0,none,0, MO_BODYRELOCATION,Snap +265,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, MO_DODGE,Dodge +266,2,6,1,0,0x40,0,5,1,no,0,0,0,weapon,0, MO_INVESTIGATE,Occult Impaction +267,9,8,1,-1,0,0,5,1:2:3:4:5,no,0,0,0,weapon,0, MO_FINGEROFFENSIVE,Throw Spirit Sphere +268,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, MO_STEELBODY,Mental Strength +269,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, MO_BLADESTOP,Root +270,0,6,4,0,0x1,0,5,0,no,0,0,0,weapon,0, MO_EXPLOSIONSPIRITS,Fury +271,-2,6,1,0,0x60,0,5,1,yes,0,0,0,weapon,0, MO_EXTREMITYFIST,Asura Strike +272,-2,8,4,-1,0,0,5,-4,no,0,0x200,0,weapon,0, MO_CHAINCOMBO,Raging Quadruple Blow +273,-2,6,4,-1,0x2,2,5,1,no,0,0x200,0,weapon,0, MO_COMBOFINISH,Raging Thrust +274,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, SA_ADVANCEDBOOK,Study +275,0,6,4,0,0x1,0,5,1,no,0,0,0,magic,0, SA_CASTCANCEL,Cast Cancel +276,0,6,4,0,0x1,0,5,1,yes,0,0,0,magic,0, SA_MAGICROD,Magic Rod +277,9,6,1,0,0x1,0,5,1,yes,0,0,0,magic,0, SA_SPELLBREAKER,Spell Breaker +278,0,0,0,0,0,0,10,0,no,0,0,0,magic,0, SA_FREECAST,Free Cast +279,0,6,4,0,0x1,0,10,1,yes,0,0,0,magic,0, SA_AUTOSPELL,Hindsight +280,9,6,16,3,0x1,0,5,1,yes,0,0xC00,0,magic,0, SA_FLAMELAUNCHER,Endow Blaze +281,9,6,16,1,0x1,0,5,1,yes,0,0xC00,0,magic,0, SA_FROSTWEAPON,Endow Tsunami +282,9,6,16,4,0x1,0,5,1,yes,0,0xC00,0,magic,0, SA_LIGHTNINGLOADER,Endow Tornado +283,9,6,16,2,0x1,0,5,1,yes,0,0xC00,0,magic,0, SA_SEISMICWEAPON,Endow Quake +284,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, SA_DRAGONOLOGY,Dragonology +285,2,6,2,3,0x1,0,5,1,yes,0,0,0,magic,0, SA_VOLCANO,Volcano +286,2,6,2,1,0x1,0,5,1,yes,0,0,0,magic,0, SA_DELUGE,Deluge +287,2,6,2,4,0x1,0,5,1,yes,0,0,0,magic,0, SA_VIOLENTGALE,Whirlwind +288,2,6,2,0,0x1,0,5,1,yes,0,0,0,magic,0, SA_LANDPROTECTOR,Magnetic Earth +289,9,6,1,0,0x1,0:0:0:0:0:-1,5,1,yes,0,0xE00,0,magic,0, SA_DISPELL,Dispell +290,0,6,4,0,0x1,0,10,1,yes,0,0,0,magic,0, SA_ABRACADABRA,Hocus-pocus +291,9,6,1,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_MONOCELL,Monocell +292,9,6,1,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_CLASSCHANGE,Class Change +293,0,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_SUMMONMONSTER,Monster Chant +294,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_REVERSEORCISH,Grampus Morph +295,9,6,1,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_DEATH,Grim Reaper +296,9,6,1,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_FORTUNE,Gold Digger +297,9,6,1,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_TAMINGMONSTER,Beastly Hypnosis +298,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_QUESTION,Questioning +299,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_GRAVITY,Gravity +300,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_LEVELUP,Leveling +301,9,6,4,0,0,0,1,1,yes,0,0x2,0,magic,0, SA_INSTANTDEATH,Suicide +302,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_FULLRECOVERY,Rejuvenation +303,9,6,4,0,0,0,1,1,yes,0,0x2,0,magic,0, SA_COMA,Coma +304,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, BD_ADAPTATION,Amp +305,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, BD_ENCORE,Encore +306,0,6,4,0,0x1,0,1,1,no,0,0x40,0,misc,0, BD_LULLABY,Lullaby +307,0,6,4,0,0x1,0,5,1,no,0,0x40,0,misc,0, BD_RICHMANKIM,Mental Sensing +308,0,6,4,0,0x1,0,1,1,no,0,0x40,0,misc,0, BD_ETERNALCHAOS,Down Tempo +309,0,6,4,0,0x1,0,5,1,no,0,0x40,0,misc,0, BD_DRUMBATTLEFIELD,Battle Theme +310,0,6,4,0,0x1,0,5,1,no,0,0x40,0,misc,0, BD_RINGNIBELUNGEN,Harmonic Lick +311,0,6,4,0,0x1,0,1,1,no,0,0x40,0,misc,0, BD_ROKISWEIL,Classical Pluck +312,0,6,4,0,0x1,0,1,1,no,0,0x40,0,misc,0, BD_INTOABYSS,Power Chord +313,0,6,4,0,0x1,0,5,1,no,0,0x40,0,misc,0, BD_SIEGFRIED,Acoustic Rhythm +//314,0,0,0,0,0,0,1,1,no,0,0x40,0,misc,0, BD_RAGNAROK,Ragnarok +315,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, BA_MUSICALLESSON,Music Lessons +316,9,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, BA_MUSICALSTRIKE,Melody Strike +317,0,8,4,0,0x41,0,5,1,no,0,0x20,0,misc,0, BA_DISSONANCE,Unchained Serenade +318,0,6,4,0,0x3,-1,5,1,no,0,0,0,misc,0, BA_FROSTJOKER,Unbarring Octave +319,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, BA_WHISTLE,Perfect Tablature +320,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, BA_ASSASSINCROSS,Impressive Riff +321,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, BA_POEMBRAGI,Magic Strings +322,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, BA_APPLEIDUN,Song of Lutie +323,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, DC_DANCINGLESSON,Dance Lessons +324,9,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, DC_THROWARROW,Slinging Arrow +325,0,8,4,0,0x1,0,5,1,no,0,0x20,0,misc,0, DC_UGLYDANCE,Hip Shaker +326,0,6,4,0,0x3,-1,5,1,no,0,0,0,misc,0, DC_SCREAM,Dazzler +327,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, DC_HUMMING,Focus Ballet +328,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, DC_DONTFORGETME,Slow Grace +329,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, DC_FORTUNEKISS,Lady Luck +330,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, DC_SERVICEFORYOU,Gypsy's Kiss +331,0,6,4,0,0x1,0,10,0,no,0,0x2,0,none,0, NPC_RANDOMMOVE,Random Move +332,0,6,4,0,0x1,0,10,0,no,0,0x2,0,none,0, NPC_SPEEDUP,Speed UP +333,0,6,4,0,0x1,0,1,0,no,0,0x2,0,none,0, NPC_REVENGE,Revenge +334,9,6,4,0,0x1,0,1,1,yes,0,0x4,0,none,0, WE_MALE,I Will Protect You +335,9,6,4,0,0x1,0,1,1,yes,0,0x4,0,none,0, WE_FEMALE,I Look up to You +336,9,6,4,0,0x1,3,1,1,yes,0,0x4,1,none,0, WE_CALLPARTNER,I miss You +337,9,6,1,-1,0,0,1,1,no,0,0x2,0,weapon,0, ITM_TOMAHAWK,Throw Tomahawk +338,-1,8,1,7,0,0,10,-2,no,0,0x2,0,weapon,0, NPC_DARKCROSS,Cross of Darkness +339,5,6,4,7,0x48,0,10,1,no,33,0x102,0,magic,0, NPC_GRANDDARKNESS,Grand cross of Darkness +340,9,8,1,7,0,0,10,1:1:2:2:3:3:4:4:5:5,yes,0,0x2,0,magic,0, NPC_DARKSTRIKE,Soul Strike of Darkness +341,9,8,1,7,0,0,10,3:4:5:6:7:8:9:10:11:12,yes,0,0x2,0,magic,2:3:3:4:4:5:5:6:6:7, NPC_DARKTHUNDER,Darkness Jupitel +342,9,6,1,0,0x1,0,1,0,no,0,0x2,0,none,0, NPC_STOP,Stop +343,9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_WEAPONBRAKER,Break weapon +344,9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_ARMORBRAKE,Break armor +345,9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_HELMBRAKE,Break helm +346,9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_SHIELDBRAKE,Break shield +347,-9,6,1,9,0,0,10,1,no,0,0x2,0,weapon,0, NPC_UNDEADATTACK,Undead Element Attack +348,9,0,1,9,0x1,0,5,1,no,0,0x2,0,magic,0, NPC_CHANGEUNDEAD,Undead Attribute Change +349,0,6,4,0,0x1,0,10,0,no,0,0x2,0,weapon,0, NPC_POWERUP,Power Up +350,0,6,4,0,0x1,0,10,0,no,0,0x2,0,none,0, NPC_AGIUP,Agility UP +351,0,0,0,0,0x1,0,1,0,no,0,0x2,0,none,0, NPC_SIEGEMODE,Siege Mode +352,2,0,4,0,0x1,0,1,0,no,0,0x2,0,none,0, NPC_CALLSLAVE,Recall Slaves +353,0,0,0,0,0x1,0,1,0,no,0,0x2,0,none,0, NPC_INVISIBLE,Invisible +354,2,6,4,0,0x1,0,20,0,no,0,0x2,0,misc,0, NPC_RUN,Run +355,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, LK_AURABLADE,Aura Blade +356,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, LK_PARRYING,Parrying +357,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, LK_CONCENTRATION,Concentration +358,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, LK_TENSIONRELAX,Relax +359,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, LK_BERSERK,Frenzy +//360,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, LK_FURY,Fury +361,9,6,16,0,0x1,1,5,1,yes,0,0,0,magic,0, HP_ASSUMPTIO,Assumptio +362,4,6,4,0,0x1,0,5,1,yes,0,0,0,magic,2, HP_BASILICA,Basilica +363,0,0,0,0,0,0,10,0,no,0,0,0,magic,0, HP_MEDITATIO,Meditatio +364,0,0,0,0,0,0,10,1,no,0,0,0,magic,0, HW_SOULDRAIN,Soul Drain +365,9,8,1,-1,0,0,1,1,yes,0,0,0,weapon,0, HW_MAGICCRASHER,Stave Crasher +366,0,6,4,0,0x1,0,10,1,no,0,0,0,magic,0, HW_MAGICPOWER,Mystical Amplification +367,9,8,1,0,0xD0,0,5,1,no,0,0,0,misc,0, PA_PRESSURE,Gloria Domini +368,0,6,4,0,0x61,0,5,1,yes,0,0,0,weapon,0, PA_SACRIFICE, Martyr's Reckoning +369,0,6,4,0,0x41,0,10,1,yes,0,0,0,misc,0, PA_GOSPEL,Battle Chant +370,-2,6,1,-1,0,0,5,1,yes,0,0,0,weapon,3, CH_PALMSTRIKE,Raging Palm Strike +371,-2,8,4,-1,0,0,5,1,no,0,0x200,0,weapon,0, CH_TIGERFIST,Glacier Fist +372,-2,8,4,-1,0,0,10,-1:-1:-2:-2:-3:-3:-4:-4:-5:-5,no,0,0x200,0,weapon,0, CH_CHAINCRUSH,Chain Crush Combo +373,0,6,4,0,0x1,0,5,1,no,0,0,0,magic,0, PF_HPCONVERSION,Indulge +374,9,6,1,0,0x1,0,1,1,yes,0,0xE00,0,none,0, PF_SOULCHANGE,Soul Exhale +375,9,6,1,0,0x98,0,5,1,yes,0,0,0,magic,0, PF_SOULBURN,Soul Siphon +376,0,0,0,0,0x1,0,5,1,no,0,0,0,weapon,0, ASC_KATAR,Advanced Katar Mastery +//377,0,0,4,0,0x1,0,10,1,no,0,0,0,misc,0, ASC_HALLUCINATION,Hallucination Walk +378,0,6,4,5,0x1,0,5,1,no,0,0,0,weapon,0, ASC_EDP,Enchant Deadly Poison +379,7,6,1,-1,0x8,0,10,1,yes,0,0,0,weapon,0, ASC_BREAKER,Soul Destroyer +380,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, SN_SIGHT,Falcon Eyes +381,5,8,1,0,0x40,0,5,1,yes,0,0,0,misc,0, SN_FALCONASSAULT,Falcon Assault +382,9,8,1,-1,0,2,5,1,yes,0,0,13,weapon,0, SN_SHARPSHOOTING,Focused Arrow Strike +383,0,6,4,0,0x3,-1,10,1,yes,0,0,0,weapon,0, SN_WINDWALK,Wind Walker +384,0,0,4,0,0x1,0,10,1,yes,0,0,0,weapon,0, WS_MELTDOWN,Shattering Strike +//385,0,0,4,0,0x1,0,1,1,yes,0,0,0,none,0, WS_CREATECOIN,Create Coins +//386,0,0,4,0,0x1,0,1,1,yes,0,0,0,none,0, WS_CREATENUGGET,Create Nuggets +387,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, WS_CARTBOOST,Cart Boost +//388,9,6,2,0,0x1,0,5,1,no,0,0,0,none,0, WS_SYSTEMCREATE,Auto Attack System +389,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, ST_CHASEWALK,Stealth +390,0,0,4,0,0,0,5,1,yes,0,0,0,weapon,0, ST_REJECTSWORD,Counter Instinct +//391,0,0,4,0,1,0,1,1,yes,0,0,0,magic,0, ST_STEALBACKPACK,Steal Backpack +392,0,0,4,0,0x1,0,1,1,yes,0,0,0,none,0, CR_ALCHEMY,Alchemy +393,0,0,4,0,0x1,0,1,1,yes,0,0,0,none,0, CR_SYNTHESISPOTION,Potion Synthesis +394,9,8,1,-1,0,0,10,-9,yes,0,0,0,weapon,0, CG_ARROWVULCAN,Vulcan Arrow +395,0,0,4,0,0x1,3,1,1,yes,0,0x40,0,misc,2, CG_MOONLIT,Sheltering Bliss +396,1,6,16,0,0x1,0,1,1,yes,0,0x600,0,none,0, CG_MARIONETTE,Marionette Control +397,5,8,1,-1,0x20,0,5,5,no,0,0,0,weapon,0, LK_SPIRALPIERCE,Spiral Pierce +398,4,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, LK_HEADCRUSH,Traumatic Blow +399,4,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, LK_JOINTBEAT,Vital Strike +400,9,8,1,8,0x6,1,5,1:2:3:4:5,yes,0,0,0,magic,0, HW_NAPALMVULCAN,Napalm Vulcan +401,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, CH_SOULCOLLECT,Zen +402,9,6,1,0,0x1,0,5,1,no,0,0,0,none,0, PF_MINDBREAKER,Mind Breaker +403,0,0,4,0,0x1,0,1,1,yes,0,0,0,magic,0, PF_MEMORIZE,Foresight +404,9,6,2,2,0x1,0,5,1,yes,0,0x100,2,magic,0, PF_FOGWALL,Blinding Mist +405,7,6,1,0,0x1,0,1,1,no,0,0,3,magic,0, PF_SPIDERWEB,Fiber Lock +406,0,6,4,-1,0xA,2,10,1,no,33,0,0,weapon,0, ASC_METEORASSAULT,Meteor Assault +407,0,6,4,0,0x1,0,1,0,no,0,0,0,none,0, ASC_CDP,Create Deadly Poison +408,9,6,4,0,0x1,0,1,1,yes,0,0x4,0,none,0, WE_BABY,Baby +409,9,6,4,0,0x1,3,1,1,yes,0,0x4,1,none,0, WE_CALLPARENT,Call Parent +410,9,6,4,0,0x1,3,1,1,yes,0,0x4,1,none,0, WE_CALLBABY,Call Baby +411,0,6,4,0,0x1,0,10,1,yes,0,0,0,misc,4, TK_RUN,Running +412,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, TK_READYSTORM,Tornado Stance +413,-2,8,4,-1,0x2,2,7,-3,no,0,0x200,0,weapon,0, TK_STORMKICK,Tornado Kick +414,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, TK_READYDOWN,Heel Drop Stance +415,-2,8,4,-1,0,0,7,-3,no,0,0x200,0,weapon,0, TK_DOWNKICK,Heel Drop +416,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, TK_READYTURN,Roundhouse Stance +417,-2,8,4,-1,0x2,1,7,-3,no,0,0x200,0,weapon,2, TK_TURNKICK,Roundhouse Kick +418,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, TK_READYCOUNTER,Counter Kick Stance +419,-2,8,4,-1,0x40,0,7,-3,no,0,0x200,0,weapon,0, TK_COUNTER,Counter Kick +420,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, TK_DODGE,Tumbling +421,9,8,16,-1,0x1,0,7,-3,no,0,0,0,weapon,0, TK_JUMPKICK,Flying Kick +422,0,0,0,0,0,1,10,0,no,0,0,0,none,0, TK_HPTIME,Peaceful Break +423,0,0,0,0,0,1,10,0,no,0,0,0,none,0, TK_SPTIME,Happy Break +424,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, TK_POWER,Kihop +425,0,6,4,2:4:1:3:8:7:6,0x1,0,7,1,no,0,0,0,weapon,0, TK_SEVENWIND,Mild Wind +426,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, TK_HIGHJUMP,Taekwon Jump +427,0,6,4,0,0x1,0,3,1,yes,0,0,0,magic,0, SG_FEEL,Feeling the Sun Moon and Stars +428,1,6,4,-1,0x2,1,3,1,yes,0,0,0,weapon,2, SG_SUN_WARM,Warmth of the Sun +429,1,6,4,-1,0x2,1,3,1,yes,0,0,0,weapon,2, SG_MOON_WARM,Warmth of the Moon +430,1,6,4,-1,0x2,1,3,1,yes,0,0,0,weapon,2, SG_STAR_WARM,Warmth of the Stars +431,0,0,4,0,0x1,0,4,1,yes,0,0,0,magic,0, SG_SUN_COMFORT,Comfort of the Sun +432,0,0,4,0,0x1,0,4,1,yes,0,0,0,magic,0, SG_MOON_COMFORT,Comfort of the Moon +433,0,0,4,0,0x1,0,4,1,yes,0,0,0,magic,0, SG_STAR_COMFORT,Comfort of the Stars +434,10,6,1,0,0x1,0,3,1,yes,0,0,0,magic,0, SG_HATE,Hatred of the Sun Moon and Stars +435,0,0,0,0,0,0,3,0,no,0,0,0,none,0, SG_SUN_ANGER,Anger of the Sun +436,0,0,0,0,0,0,3,0,no,0,0,0,none,0, SG_MOON_ANGER,Anger of the Moon +437,0,0,0,0,0,0,3,0,no,0,0,0,none,0, SG_STAR_ANGER,Anger of the Stars +438,0,0,0,0,0,0,5,0,no,0,0,0,none,0, SG_SUN_BLESS,Blessing of the Sun +439,0,0,0,0,0,0,5,0,no,0,0,0,none,0, SG_MOON_BLESS,Blessing of the Moon +440,0,0,0,0,0,0,5,0,no,0,0,0,none,0, SG_STAR_BLESS,Blessing of the Stars +441,0,0,0,0,0,0,10,0,no,0,0,0,none,0, SG_DEVIL,Demon of the Sun Moon and Stars +442,0,0,0,0,0,0,3,0,no,0,0,0,none,0, SG_FRIEND,Friend of the Sun Moon and Stars +443,0,0,0,0,0,0,10,0,no,0,0,0,none,0, SG_KNOWLEDGE,Knowledge of the Sun Moon and Stars +444,0,6,4,0,0x1,0,1,1,no,0,0,0,misc,0, SG_FUSION,Union of the Sun Moon and Stars +445,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_ALCHEMIST,Spirit of the Alchemist +446,9,6,16,0,0x1,0,1,1,yes,0,0xC08,0,none,0, AM_BERSERKPITCHER,Aid Berserk Potion +447,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_MONK,Spirit of the Monk +448,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_STAR,Spirit of the Star Gladiator +449,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_SAGE,Spirit of the Sage +450,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_CRUSADER,Spirit of the Crusader +451,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_SUPERNOVICE,Spirit of the Supernovice +452,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_KNIGHT,Spirit of the Knight +453,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_WIZARD,Spirit of the Wizard +454,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_PRIEST,Spirit of the Priest +455,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_BARDDANCER,Spirit of the Artist +456,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_ROGUE,Spirit of the Rogue +457,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_ASSASIN,Spirit of the Assasin +458,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_BLACKSMITH,Spirit of the Blacksmith +459,0,6,4,0,0x3,-1,1,1,no,0,0x8,0,weapon,0 , BS_ADRENALINE2,Advanced Adrenaline Rush +460,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_HUNTER,Spirit of the Hunter +461,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_SOULLINKER,Spirit of the Soul Linker +462,9,6,16,0,0x1,0,7,1,yes,0,0,0,magic,0, SL_KAIZEL,Kaizel +463,9,6,16,0,0x1,0,7,1,yes,0,0,0,magic,0, SL_KAAHI,Kaahi +464,9,6,16,0,0x1,0,3,1,yes,0,0,0,magic,0, SL_KAUPE,Kaupe +465,9,6,16,0,0x1,0,7,1,yes,0,0,0,magic,0, SL_KAITE,Kaite +466,0,0,0,0,0,0,7,0,yes,0,0,0,magic,0, SL_KAINA,Kaina +467,9,6,1,-2,0,0,7,1,no,0,0,0,magic,2, SL_STIN,Estin +468,9,6,1,-2,0,0,7,1,no,0,0,0,magic,0, SL_STUN,Estun +469,9,8,1,-2,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, SL_SMA,Esma +470,9,6,1,0,0x1,0,7,1,no,0,0,0,magic,0, SL_SWOO,Eswoo +471,9,6,1,0,0x1,0,3,1,no,0,0,0,magic,0, SL_SKE,Eske +472,9,6,1,0,0x1,0,3,1,no,0,0,0,magic,0, SL_SKA,Eska +473,0,6,4,0,0,0,1,1,no,0,0,0,none,0, SM_SELFPROVOKE,Provoke Self +474,0,0,4,0,0x1,0,10,1,no,0,0x2,0,none,0, NPC_EMOTION_ON,Emotion ON +475,0,0,4,0,0x1,0,1,1,yes,0,0,0,none,0, ST_PRESERVE,Preserve +476,1,6,1,0,0x1,0,5,1,yes,0,0,0,weapon,0, ST_FULLSTRIP,Divest All +477,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, WS_WEAPONREFINE,Upgrade Weapon +478,3,6,2,0,0x3,3,10,1,no,0,0,0,none,0, CR_SLIMPITCHER,Aid Condensed Potion +479,1,6,16,0,0x1,0,5,1,yes,0,0,0,weapon,0, CR_FULLPROTECTION,Full Protection +480,5,8,1,0,0,0,5,5,no,0,0,0,weapon,0, PA_SHIELDCHAIN,Shield Chain +481,0,0,0,0,0,0,5,0,no,0,0,0,none,0, HP_MANARECHARGE,Mana Recharge +482,0,6,4,0,0x1,0,5,1,no,0,0,0,magic,0, PF_DOUBLECASTING,Double Casting +483,16,6,2,0,0x1,1:2:3:4:5,1,1,no,0,0,0,none,0, HW_GANBANTEIN,Ganbantein +484,9,6,2,2,0x91,0,5,1,yes,0,0,0,misc,0, HW_GRAVITATION,Gravitation Field +485,-2,6,1,-1,0x8,0,10,1,no,0,0,0,weapon,0, WS_CARTTERMINATION,Cart Termination +486,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, WS_OVERTHRUSTMAX,Maximum Power Thrust +487,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, CG_LONGINGFREEDOM,Longing for Freedom +488,0,6,4,0,0x1,1,5,1,no,0,0x40,0,misc,0, CG_HERMODE,Wand of Hermode +489,9,6,1,0,0x41,0,5,1,no,0,0,0,misc,0, CG_TAROTCARD,Tarot Card of Fate +490,9,8,1,0,0x40,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,misc,0, CR_ACIDDEMONSTRATION,Acid Demonstration +491,1,6,2,0,0x1,0,2,1,no,0,0,0,none,0, CR_CULTIVATION,Plant Cultivation +492,0,6,4,0:1:2:3:4:5:6:7:8:9,0x1,0,10,1,no,0,0x2,0,none,0, ITEM_ENCHANTARMS,Weapon Enchantment +493,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, TK_MISSION,Taekwon Mission +494,9,6,16,0,0x1,0,5,1,yes,0,0,0,magic,0, SL_HIGH,Spirit of Rebirth +495,0,6,4,0,0x1,0,1,1,no,0,0x8,0,weapon,0, KN_ONEHAND,Onehand Quicken +496,0,6,4,0,0x1,0,1,0,no,0,0x8,0,none,0, AM_TWILIGHT1,Twilight Alchemy 1 +497,0,6,4,0,0x1,0,1,0,no,0,0x8,0,none,0, AM_TWILIGHT2,Twilight Alchemy 2 +498,0,6,4,0,0x1,0,1,0,no,0,0x8,0,none,0, AM_TWILIGHT3,Twilight Alchemy 3 +499,-9,8,4,-1,0,0,1,2,no,0,0x208,0,weapon,0, HT_POWER,Beast Strafing +500,0,6,4,0,0x40,0,5,1,no,0,0,0,misc,0, GS_GLITTERING,Flip the Coin +501,9,6,1,-1,0x50,0,1,1,no,0,0,0,misc,0, GS_FLING,Fling +502,-9,8,1,-1,0,0,1,3,no,0,0,0,weapon,0, GS_TRIPLEACTION,Triple Action +503,-9,6,1,-1,0x8,0,1,1,no,0,0,0,weapon,0, GS_BULLSEYE,Bulls Eye +504,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, GS_MADNESSCANCEL,Madness Canceller +505,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, GS_ADJUSTMENT,AdJustment +506,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, GS_INCREASING,Increasing Accuracy +507,-9,6,1,8,0,0,1,1,no,0,0,0,weapon,0, GS_MAGICALBULLET,Magical Bullet +508,-9,6,1,-1,0x1,0,1,1,no,0,0,0,weapon,0, GS_CRACKER,Cracker +509,0,0,0,0,0,0,10,0,no,0,0,0,none,0, GS_SINGLEACTION,Single Action +510,0,0,0,0,0,0,10,0,no,0,0,0,none,0, GS_SNAKEEYE,Snake Eye +511,-9,8,0,-1,0,0,10,2,no,0,0,0,weapon,0, GS_CHAINACTION,Chain Action +512,-9,6,1,-1,0,0,10,1,yes,0,0,0,weapon,0, GS_TRACKING,Tracking +513,-9,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, GS_DISARM,Disarm +514,-9,6,1,-1,0x20,0,5,1,no,0,0,0,weapon,0, GS_PIERCINGSHOT,Piercing Shot +515,-9,8,1,-1,0,0,10,5,no,0,0,0,weapon,0, GS_RAPIDSHOWER,Rapid Shower +516,0,8,4,-1,0x2,3,10,1,no,0,0,0,weapon,0, GS_DESPERADO,Desperado +517,0,6,4,-1,0x1,0,10,1,no,0,0,0,weapon,0, GS_GATLINGFEVER,Gatling Fever +518,2,6,1,-1,0,0,10,1,no,0,0,0,weapon,5, GS_DUST,Dust +519,-9,6,1,-1,0,0,10,1,yes,0,0,0,weapon,0, GS_FULLBUSTER,Full Buster +520,-9,6,1,-1,0x2,1:1:1:2:2:2:3:3:3:4,10,1,no,0,0,0,weapon,0, GS_SPREADATTACK,Spread Attack +521,-9,6,2,-1,0x40,1,10,1,no,0,0,0,weapon,3, GS_GROUNDDRIFT,Ground Drift +522,0,0,0,0,0,0,10,1,no,0,0,0,weapon,0, NJ_TOBIDOUGU,Shuriken Training +523,9,6,1,-1,0x40,0,10,1,no,0,0,0,weapon,0, NJ_SYURIKEN,Throw Shuriken +524,9,8,1,-1,0x40,0,5,3,no,0,0,0,weapon,0, NJ_KUNAI,Throw Kunai +525,9,8,1,-1,0x6,1,5,-3:-3:-4:-4:-5,yes,0,0,0,weapon,0, NJ_HUUMA,Throw Huuma Shuriken +526,9,6,1,0,0x50,0,10,1,no,0,0,0,misc,0, NJ_ZENYNAGE,Throw Zeny +527,0,6,4,-1,0,0,5,1,no,0,0,0,weapon,3, NJ_TATAMIGAESHI,Improvised Defense +528,-1,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, NJ_KASUMIKIRI,Vanishing Slash +529,7:9:11:13:15,6,2,0,0x1,0,5,1,no,0,0,0,none,0, NJ_SHADOWJUMP,Shadow Leap +530,7:9:11:13:15,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, NJ_KIRIKAGE,Shadow Slash +531,0,6,4,0,0x1,0,5,1,no,0,0,0,none,7, NJ_UTSUSEMI,Cicada Skin Sheeding +532,0,6,4,0,0x1,0,10,1,yes,0,0,0,magic,0, NJ_BUNSINJYUTSU,Mirror Image +533,0,0,0,0,0,0,10,0,no,0,0,0,none,0, NJ_NINPOU,Spirit of the Blade +534,9,8,1,3,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, NJ_KOUENKA,Crimson Fire Petal +535,0,8,4,3,0,0,10,1,yes,0,0,0,magic,1, NJ_KAENSIN,Crimson Fire Formation +536,9,8,1,3,0x2,2,5,3,yes,0,0,0,magic,0, NJ_BAKUENRYU,Raging Fire Dragon +537,9,8,1,1,0,0,10,3:4:5:6:7:8:9:10:11:12,yes,0,0,0,magic,0, NJ_HYOUSENSOU,Spear of Ice +538,9,6,2,1,0x1,0,10,1,yes,0,0,0,magic,0, NJ_SUITON,Hidden Water +539,0,6,4,1,0x2,3,5,1,yes,0,0,0,magic,0, NJ_HYOUSYOURAKU,Ice Meteor +540,9,8,1,4,0,0,10,1:2:2:3:3:4:4:5:5:6,yes,0,0,0,magic,0, NJ_HUUJIN,Wind Blade +541,9,6,4,4,0x2,2:2:3:3:4,5,1,yes,0,0,0,magic,0, NJ_RAIGEKISAI,Lightning Strike of Destruction +542,9,8,1,4,0,3,5,1,yes,0,0,5:6:7:8:9,magic,0, NJ_KAMAITACHI,Kamaitachi +543,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, NJ_NEN,Soul +544,-5,6,1,0,0x40,0,10,1,no,0,0,0,weapon,0, NJ_ISSEN,Final Strike + +// Additional NPC Skills (Episode 11.3) +653,0,8,4,0,0x6,5:7:9:11:13:5:7:9:11:13,10,1,no,0,0x2,0,magic,0, NPC_EARTHQUAKE,Earthquake +654,9,6,1,3,0,5,10,1,no,0,0x2,14,weapon,0, NPC_FIREBREATH,Fire Breath +655,9,6,1,1,0,5,10,1,no,0,0x2,14,weapon,0, NPC_ICEBREATH,Ice Breath +656,9,6,1,4,0,5,10,1,no,0,0x2,14,weapon,0, NPC_THUNDERBREATH,Thunder Breath +657,9,6,1,5,0,5,10,1,no,0,0x2,14,weapon,0, NPC_ACIDBREATH,Acid Breath +658,9,6,1,7,0,5,10,1,no,0,0x2,14,weapon,0, NPC_DARKNESSBREATH,Darkness Breath +659,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_DRAGONFEAR,Dragon Fear +660,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_BLEEDING,Bleeding +661,0,6,4,0,0x2,7,5,1,no,0,0x2,0,weapon,7, NPC_PULSESTRIKE,Pulse Strike +662,0,6,4,0,0x2,14,10,1,no,0,0x2,0,weapon,0, NPC_HELLJUDGEMENT,Hell's Judgement +663,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDESILENCE,Wide Silence +664,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDEFREEZE,Wide Freeze +665,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDEBLEEDING,Wide Bleeding +666,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDESTONE,Wide Petrify +667,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDECONFUSE,Wide Confusion +668,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDESLEEP,Wide Sleep +669,0,6,4,3,0x3,5,1,1,no,0,0x2,0,magic,0, NPC_WIDESIGHT,Wide Sight +670,9,6,2,7,0x91,0,10,1,no,0,0x2,0,magic,0, NPC_EVILLAND,Evil Land +671,0,6,4,0,0x1,0,5,1,no,0,0x2,0,magic,0, NPC_MAGICMIRROR,Magic Mirror +672,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_SLOWCAST,Slow Cast +673,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_CRITICALWOUND,Critical Wounds +674,-9,6,1,-1,0x1,0,1,1,no,0,0x2,0,none,0, NPC_EXPULSION,Expulsion +675,0,6,4,0,0x1,0,5,1,no,0,0x2,0,magic,0, NPC_STONESKIN,Stone Skin +676,0,6,4,0,0x1,0,5,1,no,0,0x2,0,magic,0, NPC_ANTIMAGIC,Anti Magic +677,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDECURSE,Wide Curse +678,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDESTUN,Wide Stun +679,0,6,4,0,0x2,5:7:9:11:13:13:13:13:13:13,10,1,no,0,0x2,0,weapon,0, NPC_VAMPIRE_GIFT,Vampire Gift +680,0,6,4,0,0x3,5:7:9:11:13:13:13:13:13:13,10,1,no,0,0x2,0,none,0, NPC_WIDESOULDRAIN,Wide Soul Drain + +// Cash Shop Skill +681,0,0,0,0,0,0,10,0,no,0,0x1,0,none,0, ALL_INCCARRY,Increase Weight Limit R + +// Additional NPC skill (Episode 12) +682,0,0,4,0,0x1,0,1,1,no,0,0x2,0,none,0, NPC_TALK,Talk +683,-9,6,1,-1,0,0,1,1,no,0,0x2,0,none,0, NPC_HELLPOWER,Hell Power +684,0,6,4,0,0x3,-1,1,1,no,0,0x2,0,none,0, NPC_WIDEHELLDIGNITY,Hell Dignity +685,0,0,4,0,0x1,0,1,1,no,0,0x2,0,none,0, NPC_INVINCIBLE,Invincible +686,0,0,4,0,0x1,0,1,1,no,0,0x2,0,none,0, NPC_INVINCIBLEOFF,Invincible off +687,0,6,4,0,0x1,0,1,1,yes,0,0x2,0,none,0, NPC_ALLHEAL,Full Heal + +// Additional Skill (??) +688,9,6,16,0,0x1,0,10,0,no,0,0x200,0,none,0, GM_SANDMAN,GM Sandman +689,0,6,4,0,0x3,-1,10,1,yes,0,0x2,0,magic,0, CASH_BLESSING,Party Blessing +690,0,6,4,0,0x3,-1,10,1,yes,0,0x2,0,magic,0, CASH_INCAGI,Party Increase AGI +691,0,6,4,0,0x3,-1,5,1,yes,0,0x2,0,magic,0, CASH_ASSUMPTIO,Party Assumptio +//692,0,0,0,0,0,0,9,0,no,0,0x2,0,none,0, ALL_CATCRY,Cat Cry +693,0,6,4,0,0x3,-1,1,1,yes,0,0x2,0,magic,0, ALL_PARTYFLEE,Party Flee +//694,0,0,0,0,0,0,9,0,no,0,0x2,0,none,0, ALL_ANGEL_PROTECT,Angel's Protection +//695,0,0,0,0,0,0,9,0,no,0,0x2,0,none,0, ALL_DREAM_SUMMERNIGHT,Summer Night Dream +//696,0,0,0,0,0,0,9,0,no,0,0x2,0,none,0, NPC_CHANGEUNDEAD2,Change Undead +//697,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, ALL_REVERSEORCISH,Reverse Orcish +698,0,6,4,0,0x01,0,1,1,no,0,0x2,0,none,0, ALL_WEWISH,Christmas Carol +//699,0,0,0,0,0,0,9,0,no,0,0x2,0,none,0, ALL_SONKRAN,ALL_SONKRAN + +// New NPC Wide Status AoE Skills And Others +//700,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDEHEALTHFEAR,Wide Health Fear +//701,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDEBODYBURNNING,Wide Body Burnning +//702,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDEFROSTMISTY,Wide Freezing +//703,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDECOLD,Wide Crystalize +//704,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDE_DEEP_SLEEP,Wide Deep Sleep +//705,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDESIREN,Wide Siren's Voice +//706,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_VENOMFOG,Venom Fog +//707,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_MILLENNIUMSHIELD,Millenium Shield 2 +//708,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_COMET,Comet 2 + +1001,9,6,1,-1,0,0,1,1,no,0,0x1,0,weapon,0, KN_CHARGEATK,Charge Attack +1002,0,6,4,0,0x1,0,1,0,no,0,0x1,0,weapon,2, CR_SHRINK,Shrink +1003,0,0,0,0,0,0,1,0,no,0,0x1,0,weapon,0, AS_SONICACCEL,Sonic Acceleration +1004,9,8,1,0,0x8,0,1,1,no,0,0x1,0,weapon,0, AS_VENOMKNIFE,Throw Venom Knife +1005,1,6,1,0,0x1,0,1,1,no,0,0x1,0,weapon,0, RG_CLOSECONFINE,Close Confine +1006,0,6,4,3,0,2,1,1,yes,0,0x1,0,magic,3, WZ_SIGHTBLASTER,Sight Blaster +1007,0,6,4,0,0x1,0,1,0,no,0,0x1,0,none,0, SA_CREATECON,Create Elemental Converter +1008,9,6,1,1,0x1,0,1,1,yes,0,0x1,0,magic,0, SA_ELEMENTWATER,Elemental Change Water +1009,-9,6,1,0,0,0,1,1,no,0,0x1,0,weapon,3, HT_PHANTASMIC,Phantasmic Arrow +1010,9,6,1,0,0x1,0,1,0,no,0,0x1,0,misc,0, BA_PANGVOICE,Pang Voice +1011,9,6,1,0,0x1,0,1,0,no,0,0x1,0,misc,0, DC_WINKCHARM,Wink of Charm +1012,0,0,0,0,0,0,1,0,no,0,0x1,0,weapon,0, BS_UNFAIRLYTRICK,Unfair Trick +1013,0,6,4,0,0x3,2,1,0,no,0,0x1,0,weapon,0, BS_GREED,Greed +1014,0,6,4,6,0x3,14,1,0,yes,0,0x1,0,magic,0, PR_REDEMPTIO,Redemptio +1015,9,6,16,0,0x1,0,1,1,no,0,0x401,0,weapon,0, MO_KITRANSLATION,Ki Translation +1016,-1,6,1,-1,0x2,1,1,1,no,0,0x1,0,weapon,5, MO_BALKYOUNG,Ki Explosion +1017,9,6,1,2,0x1,0,1,1,yes,0,0x1,0,magic,0, SA_ELEMENTGROUND,Elemental Change Earth +1018,9,6,1,3,0x1,0,1,1,yes,0,0x1,0,magic,0, SA_ELEMENTFIRE,Elemental Change Fire +1019,9,6,1,4,0x1,0,1,1,yes,0,0x1,0,magic,0, SA_ELEMENTWIND,Elemental Change Wind + +//**** +// RK Rune Knight +//**** +2001,1,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, RK_ENCHANTBLADE,Enchant Blade +2002,7:8:9:10:11,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, RK_SONICWAVE,Sonic Wave +2003,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, RK_DEATHBOUND,Death Bound +2004,1,8,1,-1,0,0,10,-5,no,0,0,0,weapon,0, RK_HUNDREDSPEAR,Hundred Spear +2005,1,6,2,4,0x2,2,5,1,no,0,0,0,weapon,3, RK_WINDCUTTER,Wind Cutter +2006,0,6,4,-1,0x2,5,5,1,no,0,0,0,weapon,0, RK_IGNITIONBREAK,Ignition Break +2007,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, RK_DRAGONTRAINING,Dragon Training +2008,9,6,2,3,0xC2,1:1:1:2:2:2:3:3:4:4,10,1,no,0,0,0,misc,0, RK_DRAGONBREATH,Dragon Breath //CHECK May have to change this back to a weapon type attack. +2009,0,6,4,0,0x3,3:4:5:6:7,5,1,yes,0,0,0,weapon,0, RK_DRAGONHOWLING,Dragon Howling +2010,0,0,0,0,0,0,10,0,no,0,0,0,none,0, RK_RUNEMASTERY,Rune Mastery +2011,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, RK_MILLENNIUMSHIELD,Millenium Shield +2012,1,6,4,-1,0,0x8,1,1,yes,0,0,0,weapon,0, RK_CRUSHSTRIKE,Crush Strike +2013,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, RK_REFRESH,Refresh +2014,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, RK_GIANTGROWTH,Giant Growth +2015,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, RK_STONEHARDSKIN,Stone Hard Skin +2016,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, RK_VITALITYACTIVATION,Vitality Activation +2017,0,6,4,-1,0x2,3,1,1,no,0,0,0,weapon,7, RK_STORMBLAST,Storm Blast +2018,0,6,4,0,0x3,-1,1,1,yes,0,0,0,none,0, RK_FIGHTINGSPIRIT,Fighting Spirit //CHECK Is this splash needed? +2019,9,6,4,6,0x1,0,1,1,yes,0,0,0,none,0, RK_ABUNDANCE,Abundance +2020,5:6:7:8:9,6,1,-1,0,0,5,1,yes,0,0,0,weapon,0, RK_PHANTOMTHRUST,Phantom Thrust + +//**** +// WL Warlock +//**** +2201,11,6,16,0,0,0,5,1,yes,0,0,0,magic,0, WL_WHITEIMPRISON,White Imprison +2202,11,8,1,8,0x2,1:1:1:2:2,5,-2,yes,0,0,0,magic,0, WL_SOULEXPANSION,Soul Expansion +2203,0,8,4,1,0x2,13,5,-3:-4:-5:-6:-7,yes,0,0,0,magic,0, WL_FROSTMISTY,Frosty Misty +2204,0,8,4,1,0x2,13,5,-5,yes,0,0,0,magic,0, WL_JACKFROST,Jack Frost +2205,11,6,1,0,0x1,0,5,1,yes,0,0,0,magic,0, WL_MARSHOFABYSS,Marsh of Abyss +2206,0,6,4,0,0x1,0,5,1,yes,0,0,0,magic,0, WL_RECOGNIZEDSPELL,Recognized Spell +2207,7,6,1,2,0x3,1:2:2:3:3,5,1,yes,0,0,0,magic,0, WL_SIENNAEXECRATE,Sienna Execrate +2208,0,0,0,0,0,0,3,0,no,0,0,0,none,0, WL_RADIUS,Radius +2209,0,6,4,0,0x3,9:10:11:12:13,5,1,yes,0,0,0,magic,0, WL_STASIS,Stasis +2210,11,6,1,0,0,0,5,1,yes,0,0,0,magic,0, WL_DRAINLIFE,Drain Life +2211,11,8,1,3,0x2,3,5,-7,yes,0,0,0,magic,3, WL_CRIMSONROCK,Crimson Rock +2212,11,6,1,3,0,0,5,1,yes,0,0,0,magic,0, WL_HELLINFERNO,Hell Inferno +2213,11,8,2,0,0x2,15,5,-20,yes,0,0,0,magic,2, WL_COMET,Comet //CHECK AoE in official code appears to be 15 x 15, yet casting circle is much bigger then that. +2214,11,6,1,0,0,3,5,1,yes,0,0,0,magic,0, WL_CHAINLIGHTNING,Chain Lightning //CHECK Is the splash being used for the target search? +2215,11,6,1,4,0,0,5,1,no,0,0,0,magic,0, WL_CHAINLIGHTNING_ATK,Chain Lightning Attack +2216,3,8,2,2,0,0,5,-6:-7:-8:-9:-10,yes,0,0,0,magic,0, WL_EARTHSTRAIN,Earth Strain +2217,11,6,1,0,0,0,5,1,yes,0,0,0,magic,0, WL_TETRAVORTEX,Tetra Vortex +2218,11,6,1,3,0,0,5,1,no,0,0,0,magic,0, WL_TETRAVORTEX_FIRE,Tetra Vortex Fire +2219,11,6,1,1,0,0,5,1,no,0,0,0,magic,0, WL_TETRAVORTEX_WATER,Tetra Vortex Water +2220,11,6,1,4,0,0,5,1,no,0,0,0,magic,0, WL_TETRAVORTEX_WIND,Tetra Vortex Wind +2221,11,6,1,2,0,0,5,1,no,0,0,0,magic,0, WL_TETRAVORTEX_GROUND,Tetra Vortex Earth +2222,0,6,4,3,0x1,0,5,1,yes,0,0,0,magic,0, WL_SUMMONFB,Summon Fire Ball +2223,0,6,4,4,0x1,0,5,1,yes,0,0,0,magic,0, WL_SUMMONBL,Summon Lightning Ball +2224,0,6,4,1,0x1,0,5,1,yes,0,0,0,magic,0, WL_SUMMONWB,Summon Water Ball +2225,11,6,1,3,0,0,5,1,no,0,0,1,magic,0, WL_SUMMON_ATK_FIRE,Summon Attack Fire //CHECK Summon attack ID's dont appear to have a range. +2226,11,6,1,4,0,0,5,1,no,0,0,1,magic,0, WL_SUMMON_ATK_WIND,Summon Attack Wind +2227,11,6,1,1,0,0,5,1,no,0,0,1,magic,0, WL_SUMMON_ATK_WATER,Summon Attack Water +2228,11,6,1,2,0,0,5,1,no,0,0,1,magic,0, WL_SUMMON_ATK_GROUND,Summon Attack Earth +2229,0,6,4,2,0x1,0,5,1,yes,0,0,0,magic,0, WL_SUMMONSTONE,Summon Stone +2230,11,8,1,0,0,0,2,1,yes,0,0,0,magic,0, WL_RELEASE,Release //CHECK Should it be left to do multi hit or single hit? +2231,0,6,4,0,0x1,0,1,1,yes,0,0,0,magic,0, WL_READING_SB,Reading Spellbook +2232,0,0,0,0,0,0,5,0,no,0,0,0,none,0, WL_FREEZE_SP,Freeze Spell + +//**** +// GC Guillotine Cross +//**** +2021,10,6,1,0,0x1,0,5,1,no,0,0,0,none,0, GC_VENOMIMPRESS,Venom Impress +2022,3,8,1,-1,0,0,5,-7,no,0,0,0,weapon,0, GC_CROSSIMPACT,Cross Impact +2023,3:4:5:6:7,6,1,-1,0,0,5,1,no,0,0,0,weapon,0,GC_DARKILLUSION,Dark Illusion +2024,0,0,0,0,0,0,10,0,no,0,0,0,none,0, GC_RESEARCHNEWPOISON,Research New Poison +2025,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, GC_CREATENEWPOISON,Create New Poison +2026,5,6,16,0,0x1,0,1,1,no,0,0,0,none,0, GC_ANTIDOTE,Antidote +2027,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, GC_POISONINGWEAPON,Poisoning Weapon +2028,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, GC_WEAPONBLOCKING,Weapon Blocking +2029,-2,6,4,-1,0x2,1,5,1,no,0,0,0,weapon,3, GC_COUNTERSLASH,Counter Slash +2030,-2,6,4,-1,0x1,0,5,1,no,0,0x200,0,weapon,0, GC_WEAPONCRUSH,Weapon Crush //CHECK SHould this and the above skill have INF2 0x200? +2031,1,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, GC_VENOMPRESSURE,Venom Pressure +2032,5,6,2,0,0x1,0,5,1,yes,0,0,1,none,0, GC_POISONSMOKE,Poison Smoke +2033,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, GC_CLOAKINGEXCEED,Cloaking Exceed +2034,0,6,4,-1,0x2,3,1,1,no,0,0,0,weapon,0, GC_PHANTOMMENACE,Phantom Menace +2035,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, GC_HALLUCINATIONWALK,Hallucination Walk +2036,0,6,4,-1,0x2,1:1:1:1:2,5,1,no,0,0,0,weapon,0, GC_ROLLINGCUTTER,Rolling Cutter +2037,9:10:11:12:13,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, GC_CROSSRIPPERSLASHER,Cross Ripper Slasher + +//**** +// AB Arch Bishop +//**** +2038,11,8,1,6,0x2,3,5,-3,yes,0,0,0,magic,0, AB_JUDEX,Judex +2039,0,6,4,0,0x1,0,1,1,yes,0,0,0,magic,0, AB_ANCILLA,Ancilla +2040,11,8,1,6,0,0,10,-10,yes,0,0,0,magic,0, AB_ADORAMUS,Adoramus +2041,0,6,4,6,0x3,3:7:15,3,1,yes,0,0,0,magic,0, AB_CLEMENTIA,Crementia +2042,0,6,4,6,0x3,3:7:15,3,1,yes,0,0,0,magic,0, AB_CANTO,Canto Candidus +2043,0,6,4,6,0x3,3:7:15,3,1,yes,0,0,0,magic,0, AB_CHEAL,Coluceo Heal +2044,11,6,2,6,0x1,0,5,1,yes,0,0,1,magic,0, AB_EPICLESIS,Epiclesis +2045,0,6,4,6,0x3,15,10,1,yes,0,0,0,magic,0, AB_PRAEFATIO,Praefatio +2046,0,6,4,6,0x3,15,10,1,yes,0,0,0,magic,0, AB_ORATIO,Oratio +2047,0,6,4,6,0x3,15,4,1,yes,0,0,0,magic,0, AB_LAUDAAGNUS,Lauda Agnus +2048,0,6,4,6,0x3,15,4,1,yes,0,0,0,magic,0, AB_LAUDARAMUS,Lauda Ramus +2049,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AB_EUCHARISTICA,Eucharistica +2050,11,6,16,6,0x1,0,1,1,yes,0,0,0,magic,0, AB_RENOVATIO,Renovatio +2051,11,6,16,6,0x21,0,5,1,yes,0,0,0,magic,0, AB_HIGHNESSHEAL,Highness Heal //CHECK Info shows this has magic attack. +2052,11,6,1,0,0x1,0,5,1,yes,0,0xA00,0,magic,0, AB_CLEARANCE,Clearance //CHECK Also shows this as a magic attack. Why? +2053,0,6,16,0,0x1,0,5,1,yes,0,0,0,magic,0, AB_EXPIATIO,Expiatio //CHECK Does this also give the buff to party members? +2054,0,6,4,6,0x1,0,10,1,yes,0,0,0,none,0, AB_DUPLELIGHT,Duple Light //CHECK Had issues adding a skill level check to make the % go higher with the skills level. Will do later. +2055,-1,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, AB_DUPLELIGHT_MELEE,Duple Light Melee +2056,-1,6,1,0,0,0,10,1,no,0,0,0,magic,0, AB_DUPLELIGHT_MAGIC,Duple Light Magic +2057,0,6,4,6,0x3,4:5:6:7:8,5,1,yes,0,0,0,magic,0, AB_SILENTIUM,Silentium //CHECk Marked magic attack as well. Hmmmm.... + +2515,11,6,16,0,0x1,0,5,1,yes,0,0,0,magic,0, AB_SECRAMENT,Secrament + +//**** +// RA Ranger +//**** +2233,9,8,1,-1,0x2,3:3:3:3:3:4:4:4:4:5,10,-3,yes,0,0,0,weapon,0, RA_ARROWSTORM,Arrow Storm +2234,0,6,4,0,0,0,5,1,yes,0,0,0,none,0, RA_FEARBREEZE,Fear Breeze +2235,0,0,0,0,0,0,10,0,no,0,0,0,none,0, RA_RANGERMAIN,Ranger Main +2236,9,8,1,-1,0,0,10,1,yes,0,0,0,weapon,0, RA_AIMEDBOLT,Aimed Bolt +2237,9,6,2,0,0x3,3,1,1,no,0,0,0,none,0, RA_DETONATOR,Detonator +2238,3,6,2,0,0x3,2,5,1,no,0,0x80,3,misc,0, RA_ELECTRICSHOCKER,Electric Shocker +2239,3,6,2,0,0x42,3,5,1,no,0,0x80,3,misc,0, RA_CLUSTERBOMB,Cluster Bomb +2240,0,6,4,0,0,0,1,1,no,0,0,0,none,0, RA_WUGMASTERY,Warg Mastery +2241,0,6,4,0,0,0,3,1,no,0,0,0,none,0, RA_WUGRIDER,Warg Rider +2242,0,6,4,-1,0x2,1,1,0,no,0,0,0,weapon,0, RA_WUGDASH,Warg Dash +2243,9,6,1,0,0,0,5,1,no,0,0,0,weapon,0, RA_WUGSTRIKE,Warg Strike +2244,9,6,1,0,0,0,5,1,no,0,0,0,weapon,0, RA_WUGBITE,Warg Bite +2245,0,0,0,0,0,0,10,0,no,0,0,0,none,0, RA_TOOTHOFWUG,Tooth of Warg +2246,0,6,4,0,0x2,3:4:5:6:7,5,1,no,0,0,0,weapon,0, RA_SENSITIVEKEEN,Sensitive Keen +2247,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, RA_CAMOUFLAGE,Camouflage +2248,0,0,0,0,0,0,5,0,no,0,0,0,none,0, RA_RESEARCHTRAP,Research Trap +2249,3,6,2,3,0x43,2,1,1,no,0,0x80,1,misc,0, RA_MAGENTATRAP,Magenta Trap +2250,3,6,2,1,0x43,2,1,1,no,0,0x80,1,misc,0, RA_COBALTTRAP,Cobalt Trap +2251,3,6,2,2,0x43,2,1,1,no,0,0x80,1,misc,0, RA_MAIZETRAP,Maize Trap +2252,3,6,2,4,0x43,2,1,1,no,0,0x80,1,misc,0, RA_VERDURETRAP,Verdure Trap +2253,3,6,2,0,0x42,2,5,1,no,0,0x80,2,misc,0, RA_FIRINGTRAP,Firing Trap +2254,3,6,2,0,0x42,2,5,1,no,0,0x80,2,misc,0, RA_ICEBOUNDTRAP,Icebound Trap + +//**** +// NC Mechanic +2255,0,0,0,0,0,0,5,0,no,0,0,0,none,0, NC_MADOLICENCE,Mado License +2256,11,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, NC_BOOSTKNUCKLE,Boost Knuckle +2257,3,6,1,-1,0,0,3,1,no,0,0,0,weapon,0, NC_PILEBUNKER,Pile Bunker +2258,13,6,1,-1,0,0,3,1,no,0,0,0,weapon,0, NC_VULCANARM,Vulcan Arm +2259,5,6,1,3,0,2,3,1,no,0,0,5,weapon,0, NC_FLAMELAUNCHER,Flame Launcher +2260,7,6,2,1,0x2,2:3:4,3,1,no,0,0,0,weapon,0, NC_COLDSLOWER,Cold Slower +2261,7,6,2,-1,0x42,3:2:1,3,1,no,0,0,0,weapon,0, NC_ARMSCANNON,Arm Cannon +2262,0,6,4,0,0x1,0,3,1,no,0,0,0,none,0, NC_ACCELERATION,Acceleration +2263,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, NC_HOVERING,Hovering +2264,0,6,4,0,0x1,0,1,1,no,0,0,0,none,7, NC_F_SIDESLIDE,Front-Side Slide +2265,0,6,4,0,0x1,0,1,1,no,0,0,0,none,7, NC_B_SIDESLIDE,Back-Side Slide +2266,0,0,0,0,0,0,4,0,no,0,0,0,none,0, NC_MAINFRAME,Mainframe Restructure // Check me. Part of the code notes translated to "The amount of fuel have". +2267,0,6,4,-1,0x42,2:3:4,3,1,no,0,0,0,misc,5, NC_SELFDESTRUCTION,Self Destruction +2268,0,6,4,0,0x1,0,4,1,yes,0,0,0,none,0, NC_SHAPESHIFT,Shape Shift +2269,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, NC_EMERGENCYCOOL,Emergency Cool +2270,0,6,4,0,0x3,7,1,1,yes,0,0,0,none,0, NC_INFRAREDSCAN,Infrared Scan +2271,9,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, NC_ANALYZE,Analyze +2272,0,6,4,0,0x3,1:2:3,3,1,yes,0,0,0,none,0, NC_MAGNETICFIELD,Magnetic Field +2273,0,6,4,0,0x1,0,3,1,yes,0,0,0,none,0, NC_NEUTRALBARRIER,Neutral Barrier +2274,0,6,4,0,0x1,0,3,1,yes,0,0,0,none,0, NC_STEALTHFIELD,Stealth Field +2275,5,6,16,0,0x1,0,5,1,yes,0,0,0,magic,0, NC_REPAIR,Repair +2276,0,0,0,0,0,0,10,0,no,0,0,0,none,0, NC_TRAININGAXE,Axe Training +2277,0,0,0,0,0,0,5,0,no,0,0,0,none,0, NC_RESEARCHFE,Research Fire/Earth +2278,4:5:6:7:8,6,1,-1,0,0,5,1,no,0,0,0,weapon,2:3:4:5:6, NC_AXEBOOMERANG,Axe Boomerang +2279,1,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, NC_POWERSWING,Power Swing +2280,0,8,4,-1,0x2,2:2:3:3:3,5,-6,no,0,0,0,weapon,0, NC_AXETORNADO,Axe Tornado // Check me. Takes 20 * Skill LV amount of HP each use. +2281,2,6,2,0,0x1,0,5,1,yes,0,0,2,none,0, NC_SILVERSNIPER,FAW - Silver Sniper +2282,2,6,2,0,0x1,0,5,1,yes,0,0,2,none,0, NC_MAGICDECOY,FAW - Magic Decoy //CHECK FIX ME!!!! Wind and Earth stones spawning opposite decoys. +2283,2,6,1,0,0x1,0,1,1,no,0,0,0,none,0, NC_DISJOINT,FAW Removal + +//**** +// SC Shadow Chaser +2284,1,6,1,-1,0x2,1,5,1,no,0,0,0,weapon,0, SC_FATALMENACE,Fatal Menace +2285,0,6,4,0,0x1,0,10,1,no,0,0,0,none,0, SC_REPRODUCE,Reproduce +2286,0,6,4,0,0x1,0,10,1,yes,0,0,0,none,0, SC_AUTOSHADOWSPELL,Auto Shadow Spell +2287,5,6,1,0,0x1,0,5,1,no,0,0,0,none,0, SC_SHADOWFORM,Shadow Form +2288,7:7:7:9:9:9:9:11:11:11,8,1,-1,0,0,10,-3,yes,0,0,0,weapon,3, SC_TRIANGLESHOT,Triangle Shot +2289,0,6,4,0,0x3,2,5,1,no,0,0,0,none,0, SC_BODYPAINT,Body Painting +2290,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, SC_INVISIBILITY,Invisibility +2291,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, SC_DEADLYINFECT,Deadly Infect +2292,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_ENERVATION,Masquerade - Enervation +2293,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_GROOMY,Masquerade - Gloomy +2294,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_IGNORANCE,Masquerade - Ignorance +2295,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_LAZINESS,Masquerade - Laziness +2296,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_UNLUCKY,Masquerade - Unlucky +2297,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_WEAKNESS,Masquerade - Weakness +2298,3,6,1,0,0x1,0,5,1,yes,0,0,0,weapon,0, SC_STRIPACCESSARY,Strip Accessory //CHECK Is weapon attack type needed? +2299,7,6,2,0,0x1,0,3,1,yes,0,0,3,none,0, SC_MANHOLE,Man Hole +2300,7,6,2,0,0x1,0,3,1,yes,0,0,1,none,0, SC_DIMENSIONDOOR,Dimension Door +2301,7,6,2,0,0x1,0,3,1,yes,0,0,0,none,0, SC_CHAOSPANIC,Chaos Panic +2302,7,6,2,0,0x1,0,3,1,yes,0,0,0,none,0, SC_MAELSTROM,Maelstrom +2303,7,6,2,0,0x1,0,3,1,yes,0,0,0,none,0, SC_BLOODYLUST,Bloody Lust +2304,0,6,4,-1,0,0,3,1,no,0,0,0,weapon,0, SC_FEINTBOMB,Feint Bomb + +//**** +// LG Royal Guard +2307,11,8,1,-1,0,2,5,1,no,0,0,10,weapon,0, LG_CANNONSPEAR,Cannon Spear +2308,7,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, LG_BANISHINGPOINT,Banishing Point +2309,0,6,4,0,0x3,2,3,1,no,0,0,0,none,0, LG_TRAMPLE,Trample +2310,1,6,1,0,0,0,5,1,no,0,0,0,weapon,0, LG_SHIELDPRESS,Shield Press +2311,0,6,4,0,0x3,3,5,1,no,0,0,0,none,0, LG_REFLECTDAMAGE,Reflect Damage +2312,5,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, LG_PINPOINTATTACK,Pinpoint Attack +2313,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, LG_FORCEOFVANGUARD,Force of Vanguard +2314,1,6,1,-1,0,0,1,1,no,0,0,0,weapon,0, LG_RAGEBURST,Rage Burst +2315,0,6,4,0,0x2,3,3,1,yes,0,0,0,none,2, LG_SHIELDSPELL,Shield Spell +2316,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, LG_EXEEDBREAK,Exceed Break +2317,1,6,2,-1,0x2,5,5,1,yes,0,0,0,none,3:4:5:6:7, LG_OVERBRAND,Over Brand //CHECK I know the splash is needed somehow for the strange AoE it gives. +2318,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, LG_PRESTIGE,Prestige +2319,0,6,4,0,0x1,3,5,1,no,0,0,0,weapon,0, LG_BANDING,Banding //CHECK Splash isnt needed right? Banding has its own UNIT ID. +2320,0,6,4,-1,0x2,3,5,1,yes,0,0,0,weapon,0, LG_MOONSLASHER,Moon Slasher +2321,1,8,2,6,0x2,5,5,-7,yes,0,0,0,weapon,0, LG_RAYOFGENESIS,Ray of Genesis +2322,0,6,16,0,0x3,1,5,1,yes,0,0,0,none,0, LG_PIETY,Piety +2323,0,8,4,2,0x2,1:1:2:2:3,5,-5,yes,0,0,0,weapon,0, LG_EARTHDRIVE,Earth Drive +2324,3,8,1,-1,0,0,5,3,yes,0,0,0,weapon,0, LG_HESPERUSLIT,Hesperus Lit +2325,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, LG_INSPIRATION,Inspiration + +//**** +// SR Sura +2326,-2,8,1,-1,0,0,10,2,no,0,0,0,weapon,0, SR_DRAGONCOMBO,Dragon Combo //CHECK Is this 2 regular hits or sub hits? Yes its 2 sub hits. +2327,0,8,4,-1,0x2,2,5,-3,no,0,0,0,weapon,3, SR_SKYNETBLOW,Sky Net Blow //CHECK Video shows 3 hits. Its sub hits right? Data check shows no sub, one source shows 3 hits, another shows 5. +2328,0,6,4,-1,0x2,1:2:3:4:5,5,1,no,0,0,0,weapon,0, SR_EARTHSHAKER,Earth Shaker //CHECK Must add a check in battle.c to triple damage if hitting a hidden target. +2329,-2,8,4,-1,0,0,5,-2,no,0,0x200,0,weapon,0, SR_FALLENEMPIRE,Fallen Empire //CHECK Video shows 2 hits. Is it sub hits? Yes its divided between 2 hits. +2330,-2,6,1,-1,0x42,1:1:1:1:1:2:2:2:2:2,10,1,yes,0,0,0,weapon,0, SR_TIGERCANNON,Tiger Cannon //CHECK Need to fix to be enemy targeted and also combo after Fallen Empire. +2331,0,0,0,0,0,0,10,0,no,0,0,0,none,0, SR_HELLGATE,Hell Gate +2332,5,6,4,-1,0x2,3,5,1,no,0,0,0,weapon,0, SR_RAMPAGEBLASTER,Rampage Blaster +2333,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, SR_CRESCENTELBOW,Crescent Elbow //CHECK Check the autospell ID. +2334,0,6,4,0,0x3,1:1:2:2:3,5,1,no,0,0,0,none,0, SR_CURSEDCIRCLE,Cursed Circle //CHECK Code shows it takes up to 5% of your HP upon use? +2335,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, SR_LIGHTNINGWALK,Lightning Walk +2336,7:8:9:10:11,6,1,-1,0,0,5,1,no,0,0,0,weapon,2:3:4:5:6, SR_KNUCKLEARROW,Knuckle Arrow +2337,0,6,4,-1,0x2,2,1,1,yes,0,0,0,weapon,0, SR_WINDMILL,Windmill +2338,0,6,4,0,0x1,0,10,1,no,0,0,0,none,0, SR_RAISINGDRAGON,Raising Dragon +2339,0,0,0,0,0,0,5,1,no,0,0,0,none,0, SR_GENTLETOUCH,Gentle Touch +2340,0,6,4,0,0x3,2,1,1,no,0,0,0,none,0, SR_ASSIMILATEPOWER,Assimilate Power +2341,3,6,16,0,0x1,0,1,1,yes,0,0x200,0,none,0, SR_POWERVELOCITY,Power Velocity +2342,1,6,1,-1,0x20,0,5,1,no,0,0,0,weapon,3, SR_CRESCENTELBOW_AUTOSPELL,Crescent Elbow Autospell //CHECK Does this ignore defense? +2343,1:2:3:3:4:4:5:5:6:7,8,1,0,0,0,10,-7,yes,0,0,0,weapon,0, SR_GATEOFHELL,Gate of Hell //CHECK Need to fix to be enemy targeted and also combo after Fallen Empire +2344,2,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, SR_GENTLETOUCH_QUIET,Gentle Touch - Quiet +2345,2,6,16,0,0x1,0,5,1,no,0,0,0,magic,0, SR_GENTLETOUCH_CURE,Gentle Touch - Cure //CHECK Its a healing skill. Guessing it has to be magic type? Healing isnt working. +2346,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, SR_GENTLETOUCH_ENERGYGAIN,Gentle Touch - Energy Gain +2347,2,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, SR_GENTLETOUCH_CHANGE,Gentle Touch - Change +2348,2,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, SR_GENTLETOUCH_REVITALIZE,Gentle Touch - Revitalize +//More from Sura but not following ID order +2517,0,6,4,-1,0x2,3:4:5:6:7,5,1,no,0,0,0,weapon,0, SR_HOWLINGOFLION,Howling of Lion +2518,11,6,2,-1,0x2,2:2:3:3:4,5,1,no,0,0,0,weapon,0, SR_RIDEINLIGHTNING,Ride In Lightening + +//**** +// WA Wanderer +2350,0,6,4,0,0x3,7:8:9:10:11,5,1,yes,0,0,0,none,0, WA_SWING_DANCE,Swing Dance +2351,0,6,4,0,0x3,7:8:9:10:11,5,1,yes,0,0,0,none,0, WA_SYMPHONY_OF_LOVER,Symphony of Lovers +2352,0,6,4,0,0x3,7:8:9:10:11,5,1,yes,0,0,0,none,0, WA_MOONLIT_SERENADE,Moonlit Serenade + +//**** +// MI Minstrel +2381,0,6,4,0,0x3,7:8:9:10:11,5,1,yes,0,0,0,none,0, MI_RUSH_WINDMILL,Windmill Rush Attack +2382,0,6,4,0,0x3,7:8:9:10:11,5,1,yes,0,0,0,none,0, MI_ECHOSONG,Echo Song +2383,9,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, MI_HARMONIZE,Harmonize + +//**** +// WM Wanderer/Minstrel +2412,0,0,0,0,0,0,10,0,no,0,0,0,none,0, WM_LESSON,Lesson +2413,9,8,1,-1,0,0,5,-2:-2:-3:-3:-4,yes,0,0,0,magic,0, WM_METALICSOUND,Metallic Sound +2414,9,6,2,-1,0x3,1,5,1,yes,0,0x80,3,none,0, WM_REVERBERATION,Reverberation +2415,0,6,1,-1,0x6,1,5,1,no,0,0,0,weapon,0, WM_REVERBERATION_MELEE,Reverberation Melee +2416,0,6,1,0,0x6,1,5,1,no,0,0,0,magic,0, WM_REVERBERATION_MAGIC,Reverberation Magic +2417,11,6,2,0,0x3,5,1,1,no,0,0,0,none,0, WM_DOMINION_IMPULSE,Dominion Impulse +2418,9,6,2,-1,0x1,0,5,1,yes,0,0,0,none,0, WM_SEVERE_RAINSTORM,Severe Rainstorm +2419,9,6,2,0,0x3,1,5,1,yes,0,0x80,5,none,0, WM_POEMOFNETHERWORLD,Poem of The Netherworld //CHECK May need to recode too. +2420,0,6,4,0,0x2,2:3:4:5:6,5,1,yes,0,0,0,none,0, WM_VOICEOFSIREN,Voice of Siren +2421,7,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, WM_DEADHILLHERE,Valley of Death +2422,7,6,4,0,0x3,5:6:7:8:9,5,1,yes,0,0,0,none,0, WM_LULLABY_DEEPSLEEP,Deep Sleep Lullaby +2423,0,6,4,0,0x3,3:4:5:6:7,5,1,yes,0,0,0,none,0, WM_SIRCLEOFNATURE,Circle of Nature's Sound +2424,9,6,4,0,0x1,0,5,1,yes,0,0,0,magic,0, WM_RANDOMIZESPELL,Improvised Song +2425,9,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, WM_GLOOMYDAY,Gloomy Day +2426,9,6,2,0,0x2,2:3:3:4:4,5,1,yes,0,0x4000,0,weapon,0, WM_GREAT_ECHO,Great Echo +2427,0,6,4,0,0x3,5:6:7:8:9,5,1,yes,0,0x4000,0,none,0, WM_SONG_OF_MANA,Song of Mana +2428,0,6,4,0,0x3,5:6:7:8:9,5,1,yes,0,0x4000,0,none,0, WM_DANCE_WITH_WUG,Dance With A Warg +2429,9,6,1,0,0x2,2:2:3:3:4,5,1,yes,0,0x4000,0,weapon,0, WM_SOUND_OF_DESTRUCTION,Sound of Destruction //CHECK Source shows its magic attack. Need to confirm before changing. +2430,0,6,4,0,0x3,3:4:5:6:7,5,1,yes,0,0x4000,0,none,0, WM_SATURDAY_NIGHT_FEVER,Saturday Night Fever +2431,0,6,4,0,0x3,5,5,1,yes,0,0x4000,0,magic,0, WM_LERADS_DEW,Lerad's Dew +2432,0,6,4,0,0x3,5,5,1,yes,0,0x4000,0,none,0, WM_MELODYOFSINK,Melody of Sink +2433,0,6,4,0,0x3,5,5,1,yes,0,0x4000,0,none,0, WM_BEYOND_OF_WARCRY,Warcry of Beyond +2434,0,6,4,0,0x3,5,5,1,yes,0,0x4000,0,none,0, WM_UNLIMITED_HUMMING_VOICE,Unlimited Humming Voice +2516,11,6,1,-1,0x2,5,5,1,no,0,0,0,weapon,0, WM_SEVERE_RAINSTORM_MELEE,Severe Rainstorm Melee + +//**** +// SO Sorcerer +2443,0,6,4,3,0,0,5,1,yes,0,0,8:10:12:14:16,magic,0, SO_FIREWALK,Fire Walk //CHECK Video and data shows each cell only hits once. +2444,0,6,4,4,0,0,5,1,yes,0,0,8:10:12:14:16,magic,0, SO_ELECTRICWALK,Electric Walk +2445,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, SO_SPELLFIST,Spell Fist +2446,9,6,2,2,0,0,5,-3,yes,0,0,0,magic,0, SO_EARTHGRAVE,Earth Grave +2447,9,6,2,1,0,0,5,-5,yes,0,0,0,magic,0, SO_DIAMONDDUST,Diamond Dust +2448,9,6,1,5,0x2,1:1:1:1:2,5,1,yes,0,0,0,magic,0, SO_POISON_BUSTER,Poison Buster +2449,9,6,2,0,0,0,5,1,yes,0,0,0,magic,0, SO_PSYCHIC_WAVE,Psychic Wave +2450,9,6,2,5,0,0,5,1,yes,0,0,0,magic,0, SO_CLOUD_KILL,Cloud Kill +2451,9,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, SO_STRIKING,Striking //CHECK Data shows a % for increased successful refine rate. Is this true? +2452,9,6,2,3,0x1,0,5,1,yes,0,0,0,magic,0, SO_WARMER,Warmer +2453,9,6,2,0,0x1,0,5,1,yes,0,0,0,magic,0, SO_VACUUM_EXTREME,Vacuum Extreme +2454,9,6,1,4,0x2,1:1:2:2:3,5,1,yes,0,0,0,magic,0, SO_VARETYR_SPEAR,Varetyr Spear +2455,9,6,2,0,0x3,1:1:2:2:3,5,1,yes,0,0,0,magic,0, SO_ARRULLO,Arrullo +2456,0,6,4,0,0x1,0,4,1,yes,0,0,0,none,0, SO_EL_CONTROL,Spirit Control +2457,0,6,4,3,0x1,0,3,1,yes,0,0,0,none,0, SO_SUMMON_AGNI,Summon Fire Spirit Agni +2458,0,6,4,1,0x1,0,3,1,yes,0,0,0,none,0, SO_SUMMON_AQUA,Summon Water Spirit Aqua +2459,0,6,4,4,0x1,0,3,1,yes,0,0,0,none,0, SO_SUMMON_VENTUS,Summon Wind Spirit Ventus +2460,0,6,4,2,0x1,0,3,1,yes,0,0,0,none,0, SO_SUMMON_TERA,Summon Earth Spirit Tera +2461,5,6,1,0,0x1,0,1,1,no,0,0,0,none,0, SO_EL_ACTION,Elemental Action +2462,0,6,4,0,0x1,0,2,1,yes,0,0,0,none,0, SO_EL_ANALYSIS,Four Spirit Analysis +2463,0,0,0,0,0,0,5,0,no,0,0,0,none,0, SO_EL_SYMPATHY,Spirit Sympathy +2464,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, SO_EL_CURE,Spirit Recovery +2465,9,6,2,3,0x1,0,3,1,yes,0,0,1,magic,0, SO_FIRE_INSIGNIA,Fire Insignia //CHECK All 4 insignia skills can be targeted and animations work +2466,9,6,2,1,0x1,0,3,1,yes,0,0,1,magic,0, SO_WATER_INSIGNIA,Water Insignia // but its effects havent been coded yet. +2467,9,6,2,4,0x1,0,3,1,yes,0,0,1,magic,0, SO_WIND_INSIGNIA,Wind Insignia +2468,9,6,2,2,0x1,0,3,1,yes,0,0,1,magic,0, SO_EARTH_INSIGNIA,Earth Insignia + +//**** +// GN Genetic +2474,0,0,0,0,0,0,5,0,no,0,0,0,none,0, GN_TRAINING_SWORD,Sword Training +2475,0,0,0,0,0,0,5,0,no,0,0,0,none,0, GN_REMODELING_CART,Cart Remodeling +2476,0,6,4,-1,0x2,2,5,1,no,0,0,0,weapon,2, GN_CART_TORNADO,Cart Tornado +2477,7:8:9:10:11,6,1,-1,0x2,1:1:2:2:3,5,1,yes,0,0,0,weapon,0, GN_CARTCANNON,Cart Cannon +2478,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, GN_CARTBOOST,Cart Boost +2479,9,6,2,0,0,0,5,1,yes,0,0x80,5,misc,0, GN_THORNS_TRAP,Thorn Trap +2480,11,6,1,0,0x1,0,5,1,yes,0,0,3,misc,0, GN_BLOOD_SUCKER,Blood Sucker //CHECK Data says its a magic attack. Hmmmm.... +2481,11,6,1,-1,0x2,1:2:3:4:5,5,1,yes,0,0,0,weapon,0, GN_SPORE_EXPLOSION,Spore Explosion //CHECK Data says its element is set to neutral. Need to confirm. +2482,11,6,16,0,0,0,5,1,yes,0,0,1,weapon,2, GN_WALLOFTHORN,Wall of Thorns +2483,11,6,2,0,0x3,4,10,1,yes,0,0x2000,0,weapon,0, GN_CRAZYWEED,Crazy Weed +2484,0,6,2,2,0x2,3,10,1,no,0,0x2000,0,weapon,0, GN_CRAZYWEED_ATK,Crazy Weed Attack +2485,9,6,2,3,0,0,5,1,yes,0,0,0,magic,0, GN_DEMONIC_FIRE,Demonic Fire +2486,9,6,2,0,0,0,5,1,yes,0,0,0,none,0, GN_FIRE_EXPANSION,Fire Expansion //CHECK FIX ME!!!! Level 1 is reducing the damage. Should increase it by 50% +2487,9,6,2,0,0,0,1,1,no,0,0,0,none,0, GN_FIRE_EXPANSION_SMOKE_POWDER,Fire Expansion Smoke Powder +2488,9,6,2,0,0,0,1,1,no,0,0,0,none,0, GN_FIRE_EXPANSION_TEAR_GAS,Fire Expansion Tear Gas +2489,11,6,1,0,0,0,10,1:2:3:4:5:6:7:8:9:10,no,0,0,0,weapon,0, GN_FIRE_EXPANSION_ACID,Fire Expansion Acid +2490,9,6,2,0,0x3,1,5,1,yes,0,0x80,2:3:4:5:6,none,0, GN_HELLS_PLANT,Hell's Plant +2491,0,6,1,0,0xC0,0,5,1,no,0,0,0,misc,0, GN_HELLS_PLANT_ATK,Hell's Plant Attack +2492,0,6,4,0,0x3,6:7:8:9:10,5,1,yes,0,0,0,none,0, GN_MANDRAGORA,Howling of Mandragora +2493,11,6,16,0,0x1,0,1,1,yes,0,0,0,none,0, GN_SLINGITEM,Sling Item +2494,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, GN_CHANGEMATERIAL,Change Material +2495,0,6,4,0,0x1,0,2,1,no,0,0,0,none,0, GN_MIX_COOKING,Mix Cooking +2496,0,6,4,0,0x1,0,2,1,no,0,0,0,none,0, GN_MAKEBOMB,Create Bomb +2497,0,6,4,0,0x1,0,10,1,no,0,0,0,none,0, GN_S_PHARMACY,Special Pharmacy +2498,11,6,1,0,0,0,1,1,no,0,0,0,weapon,0, GN_SLINGITEM_RANGEMELEEATK,Sling Item Attack + +// Episode 13.3 +//2533,0,0,4,0,0x1,0,1,0,no,0,0,0,none,0, ALL_ODINS_RECALL,Odin's Recall +2534,0,0,4,0,0x1,0,1,0,no,0,0,0,none,0, RETURN_TO_ELDICASTES,Return To Eldicastes +2535,0,0,4,0,0x1,0,1,0,no,0,0x1,0,none,0, ALL_BUYING_STORE,Open Buying Store +2536,0,0,4,0,0x1,0,1,0,no,0,0,0,none,0, ALL_GUARDIAN_RECALL,Guardian's Recall +//2537,9,6,16,0,0x1,0,2,1,yes,0,0,0,magic,0, ALL_ODINS_POWER,Odin's Power +//2538,0,0,0,0,0,0,??,0,no,0,0,0,none,0, BEER_BOTTLE_CAP,Beer Bottle Cap +//2539,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_ASSASSINCROSS,Assassin Cross of Sunset 2 +//2540,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_DISSONANCE,Dissonance 2 +//2541,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_UGLYDANCE,Ugly Dance 2 +//2542,0,0,0,0,0,0,??,0,no,0,0,0,none,0, ALL_TETANY,Tetany +//2543,0,0,0,0,0,0,??,0,no,0,0,0,none,0, ALL_RAY_OF_PROTECTION,Ray of Protection +//2544,0,0,0,0,0,0,??,0,no,0,0,0,none,0, MC_CARTDECORATE,Decorate Cart + +//**** +// Kagerou & Oboro +3001,0,6,4,0,0,0,1,1,no,0,0,0,none,0, KO_YAMIKUMO,Yamikumo +3002,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, KO_RIGHT,Right Hand Mastery +3003,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, KO_LEFT,Left Hand Mastery +3004,3:4:5:6:7,8,1,-1,0,0,5,-2,no,0,0,0,weapon,0, KO_JYUMONJIKIRI,Cross Strike +3005,2,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, KO_SETSUDAN,Soul Sever +3006,7:8:9:10:11,6,2,0,0x2,2,5,0,no,0,0,0,weapon,0, KO_BAKURETSU,Bakuretsu Kunai +3007,0,6,4,-1,0x42,4:4:4:4:5,5,0,no,0,0,0,misc,0, KO_HAPPOKUNAI,Happo Kunai +3008,9,8,2,0,0x52,2,10,-10,no,0,0,0,misc,0, KO_MUCHANAGE,Mucha Nage +3009,9:10:11:12:13,8,2,-1,0x2,3,5,2,no,0,0,0,weapon,0, KO_HUUMARANKA,Huuma Shuriken Ranka +3010,3,6,4,0,0x43,0,5,1,no,0,0x80,0,misc,0, KO_MAKIBISHI,Makibishi +3011,0,6,4,0,0x1,0,5,0,yes,0,0,0,none,0, KO_MEIKYOUSISUI,Meikyo Shisui +3012,0,6,4,0,0x1,0,5,0,no,0,0,1,none,7, KO_ZANZOU,Zanzou +3013,5,6,1,0,0x1,0,5,0,no,0,0,0,none,0, KO_KYOUGAKU,Kyougaku +3014,5,6,1,0,0x1,0,5,0,no,0,0,0,none,0, KO_JYUSATSU,Jyusatsu +3015,0,6,4,3,0x1,0,1,1,no,0,0,0,none,0, KO_KAHU_ENTEN,Kahu Enten +3016,0,6,4,1,0x1,0,1,1,no,0,0,0,none,0, KO_HYOUHU_HUBUKI,Hyouhu Hubuki +3017,0,6,4,4,0x1,0,1,1,no,0,0,0,none,0, KO_KAZEHU_SEIRAN,Kazehu Seiran +3018,0,6,4,2,0x1,0,1,1,no,0,0,0,none,0, KO_DOHU_KOUKAI,Dohu Koukai +3019,11,6,1,0,0,0,5,0,no,0,0,0,weapon,0, KO_KAIHOU,Technique Kaihou +3020,7,6,2,0,0,0,1,3,yes,0,0,0,magic,0, KO_ZENKAI,Zenkai +3021,5:6:7:8:9,6,16,0,0x1,0,5,1,no,0,0,0,none,0, KO_GENWAKU,Genwaku +3022,0,6,4,0,0x1,0,5,0,no,0,0,0,none,0, KO_IZAYOI,Izayoi +3023,0,6,4,0,0x3,2:3:4:5:6,5,0,no,0,0,0,none,0, KG_KAGEHUMI,Kagehumi +3024,7,6,1,0,0x1,0,5,1,no,0,0,0,none,0, KG_KYOMU,Kyomu +3025,7,6,16,0,0x1,0,5,1,no,0,0,0,none,0, KG_KAGEMUSYA,Kagemusha +3026,7,6,16,0,0x1,0,5,1,no,0,0,0,none,0, OB_ZANGETSU,Zangetsu +3027,7,6,16,0,0x1,0,5,1,no,0,0,0,none,0, OB_OBOROGENSOU,Oboro Gensou +3028,1,6,4,0,0x2,3,1,1,no,0,0,0,weapon,0, OB_OBOROGENSOU_TRANSITION_ATK, +3029,7,6,1,0,0x1,0,5,0,no,0,0,0,none,0, OB_AKAITSUKI,Akaitsuki + +8001,9,6,4,0,0x1,0,5,1,no,0,0,0,magic,0, HLIF_HEAL,Healing Touch +8002,0,6,4,0,0x3,-1,5,1,no,0,0,0,none,0, HLIF_AVOID,Avoid +8003,0,0,0,0,0,1,5,0,no,0,0,0,none,0, HLIF_BRAIN,Brain Surgery +8004,0,6,4,0,0x1,0,3,0,no,0,0,0,none,0, HLIF_CHANGE,Change +8005,0,6,4,0,0x1,0,5,0,no,0,0,0,none,0, HAMI_CASTLE,Castling +8006,0,6,4,0,0x1,0,5,0,no,0,0,0,none,0, HAMI_DEFENCE,Defense +8007,0,0,0,0,0x1,0,5,0,no,0,0,0,none,0, HAMI_SKIN,Adamantium Skin +8008,0,6,4,0,0x1,0,3,0,no,0,0,0,none,0, HAMI_BLOODLUST,Bloodlust +8009,1,8,1,0,0,0,5,-1:-2:-2:-2:-3,no,0,0,0,weapon,0, HFLI_MOON,Moonlight +8010,0,6,4,0,0x1,0,5,0,no,0,0,0,none,0, HFLI_FLEET,Fleeting Move +8011,0,6,4,0,0x1,0,5,0,yes,0,0,0,misc,0, HFLI_SPEED,Speed +8012,1,6,1,0,0,0,3,0,no,0,0,0,none,0, HFLI_SBR44,S.B.R.44 +8013,9,6,1,0,0,0,5,1:2:3:4:5,no,0,0,0,magic,0, HVAN_CAPRICE,Caprice +8014,0,6,4,0,0x1,0,5,0,no,0,0,0,none,0, HVAN_CHAOTIC,Benediction of Chaos +8015,0,0,0,0,0x1,0,5,0,no,0,0,0,none,0, HVAN_INSTRUCT,Instruct +8016,4,6,4,-1,0xD2,4,3,1,no,0,0,0,misc,0, HVAN_EXPLOSION,Bio Explosion +// +8018,9,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MH_SUMMON_LEGION,Summon Legion +8019,5,6,1,5,0,0,5,1,no,0,0,0,weapon,0, MH_NEEDLE_OF_PARALYZE,Needle of Paralyze +8020,5,6,2,5,0,0,5,1,no,0,0,1,weapon,0, MH_POISON_MIST,Poison Mist +8021,1,6,1,0,0x1,0,5,1,no,0,0,0,none,0, MH_PAIN_KILLER,Pain Killer +8022,0,6,4,0,0,0x1,5,1,no,0,0,0,none,0, MH_LIGHT_OF_REGENE,Light of Regene +8023,0,6,4,0,0,0x1,5,1,no,0,0,0,none,0, MH_OVERED_BOOST,Overed Boost +8024,7,6,1,4:0:4:0:4,0,0,5,1,no,0,0,0,magic,0, MH_ERASER_CUTTER,Eraser Cutter +8025,7,6,2,4:0:4:0:4,0,0,5,1,no,0,0,0,magic,0, MH_XENO_SLASHER,Xeno Slasher +8026,5:5:7:7:9,6,16,0,0x1,0,5,1,no,0,0,0,magic,0, MH_SILENT_BREEZE,Silent Breeze +8027,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, MH_STYLE_CHANGE,Style Change +8028,1,8,1,0,0,0,5,1,no,0,0,0,weapon,0, MH_SONIC_CRAW,Sonic Claw +8029,1,6,4,0,0,0,5,1,no,0,0x200,0,weapon,0, MH_SILVERVEIN_RUSH,Silver Bain Rush +8030,1,6,4,0,0,0,5,1,no,0,0x200,0,weapon,0, MH_MIDNIGHT_FRENZY,Midnight Frenzy +8031,5:6:7:8:9,6,1,0,0,0,5,1,no,0,0,0,weapon,3, MH_STAHL_HORN,Steel Horn +8032,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MH_GOLDENE_FERSE,Golden Heel +8033,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MH_STEINWAND,Stone Wall +8034,9,6,1,6,0x2,1:1:1:1:2,5,1,no,0,0,0,magic,0, MH_HEILIGE_STANGE,Holy Pole +8035,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MH_ANGRIFFS_MODUS,Attack Mode +8036,3:4:5:6:7,6,1,0,0,0,5,1,no,0,0,0,weapon,0, MH_TINDER_BREAKER,Tinder Breaker +8037,1,6,4,0,0,0,5,1,no,0,0x200,0,weapon,0, MH_CBC,Continual Break Combo +8038,1,6,4,0,0,0,5,1,no,0,0x200,0,weapon,0, MH_EQC,Eternal Quick Combo +8039,0,6,4,3,0x2,1:1:1:2:2,5,1,no,0,0,0,weapon,0, MH_MAGMA_FLOW,Magma Flow +8040,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MH_GRANITIC_ARMOR,Granitic Armor +8041,7,6,2,3,0x2,0,5,1,no,0,0,1,weapon,0, MH_LAVA_SLIDE,Lava Slide +8042,0,6,4,3,0x1,0,5,1,no,0,0,0,none,0, MH_PYROCLASTIC,Pyroclastic +8043,7,6,2,0,0x1,0,5,1,no,0,0,3,none,0, MH_VOLCANIC_ASH,Volcanic Ash + +// Mercenary Skill Place holders +8201,-1,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, MS_BASH,Bash +8202,0,6,4,3,0x2,2,10,1,no,0,0,0,weapon,2, MS_MAGNUM,Magnum_Break +8203,-2,6,1,-1,0x2,1,10,1,no,33,0,0,weapon,1, MS_BOWLINGBASH,Bowling_Bash +8204,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, MS_PARRYING,Parry +8205,0,6,4,0,0,0,10,1,no,0,0,0,weapon,0, MS_REFLECTSHIELD,Shield_Reflect +8206,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, MS_BERSERK,Frenzy +8207,-9,8,1,-1,0,0,10,2,no,0,0,0,weapon,0, MA_DOUBLE,Double_Strafe +8208,-9,6,2,-1,0x2,2,10,1,no,0,0x2000,0,weapon,2, MA_SHOWER,Arrow_Shower +8209,3,6,2,0,0x1,0,5,1,no,0,0x80,0,misc,6:7:8:9:10, MA_SKIDTRAP,Skid_Trap +8210,3,6,2,2,0x40,0,5,1,no,0,0x80,0,misc,0, MA_LANDMINE,Land_Mine +8211,3,6,2,0,0x3,2,5,1,no,0,0x80,0,misc,0, MA_SANDMAN,Sandman +8212,3,6,2,1,0x42,1,5,1,no,0,0x80,0,weapon,0, MA_FREEZINGTRAP,Freezing_Trap +8213,2,6,32,0,0x1,0,1,1,no,0,0,0,misc,0, MA_REMOVETRAP,Remove_Trap +8214,-9,6,1,-1,0x2,0,1,1,no,0,0x1,0,weapon,6, MA_CHARGEARROW,Arrow_Repel +8215,9,8,1,-1,0,2,5,1,yes,0,0,13,weapon,0, MA_SHARPSHOOTING,Focused_Arrow_Strike +8216,-2,8,1,-1,0,0,10,3,no,0,0,0,weapon,0, ML_PIERCE,Pierce +8217,-2,6,1,-1,0x1,0,10,1,no,33,0,0,weapon,3, ML_BRANDISH,Brandish_Spear +8218,5,8,1,-1,0x20,0,5,5,no,0,0,0,weapon,0, ML_SPIRALPIERCE,Spiral_Pierce +8219,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, ML_DEFENDER,Defending_Aura +8220,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, ML_AUTOGUARD,Guard +8221,7:8:9:10:11,6,16,0,0x1,0,5,1,yes,0,0x600,0,none,0, ML_DEVOTION,Sacrifice +8222,0,6,4,0,0x3,-1,5,1,yes,0,0,0,magic,0, MER_MAGNIFICAT,Magnificat +8223,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, MER_QUICKEN,Two-Hand_Quicken +8224,0,6,4,3,0x3,3,1,1,yes,0,0,0,magic,0, MER_SIGHT,Sight +8225,1,8,1,-1,0,0,5,3,no,0,0,0,weapon,0, MER_CRASH,Crash +8226,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_REGAIN,Regain +8227,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_TENDER,Tender +8228,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_BENEDICTION,Benediction +8229,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_RECUPERATE,Recuperate +8230,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_MENTALCURE,Mental_Cure +8231,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_COMPRESS,Compress +8232,9,6,1,0,0x1,0,10,1,no,0,0,0,none,0, MER_PROVOKE,Provoke +8233,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, MER_AUTOBERSERK,Berserk +8234,9,6,1,0,0x1,0,10,1,yes,0,0,0,magic,0, MER_DECAGI,Decrease_AGI +8235,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, MER_SCAPEGOAT,Scapegoat +8236,5,6,1,0,0x1,0,10,0,yes,0,0,0,magic,0, MER_LEXDIVINA,Lex_Divina +8237,9,6,1,0,0x1,0,1,1,yes,0,0,0,magic,0, MER_ESTIMATION,Sense +8238,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, MER_KYRIE,Kyrie Eleison +8239,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, MER_BLESSING,Blessing +8240,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, MER_INCAGI,Increase Agility + +// Elemental Spirits Skills +8401,0,6,4,3,0,0,1,1,no,0,0,0,weapon,2, EL_CIRCLE_OF_FIRE,Circle of Fire +8402,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_FIRE_CLOAK,Fire Cloak +8403,0,6,4,3,0,0,1,1,no,0,0,3,magic,2, EL_FIRE_MANTLE,Fire Mantle +8404,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_WATER_SCREEN,Water Screen +8405,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_WATER_DROP,Water Drop +8406,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_WATER_BARRIER,Water Barrier +8407,0,6,4,0,0x1,0,1,1,no,0,0,0,none,5, EL_WIND_STEP,Wind Step +8408,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_WIND_CURTAIN,Wind Curtain +8409,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_ZEPHYR,Zephyr +8410,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_SOLID_SKIN,Solid Skin +8411,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_STONE_SHIELD,Stone Shield +8412,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_POWER_OF_GAIA,Power of Gaia +8413,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_PYROTECHNIC,Pyrotechnic +8414,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_HEATER,Heater +8415,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_TROPIC,Tropic +8416,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_AQUAPLAY,Aqua Play +8417,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_COOLER,Cooler +8418,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_CHILLY_AIR,Cool Air +8419,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_GUST,Gust +8420,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_BLAST,Blast +8421,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_WILD_STORM,Wild Storm +8422,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_PETROLOGY,Petrology +8423,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_CURSED_SOIL,Cursed Soil +8424,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_UPHEAVAL,Upheaval +8425,6,6,1,3,0,0,1,1,no,0,0,0,magic,0, EL_FIRE_ARROW,Fire Arrow +8426,6,6,1,3,0,1,1,1,no,0,0,0,magic,0, EL_FIRE_BOMB,Fire Bomb +8427,6,6,1,3,0,1,1,1,no,0,0,0,weapon,0, EL_FIRE_BOMB_ATK,Fire Bomb Attack +8428,6,6,1,3,0,1,1,1,no,0,0,0,magic,0, EL_FIRE_WAVE,Fire Wave +8429,6,6,1,3,0,1,1,1,no,0,0,0,weapon,0, EL_FIRE_WAVE_ATK,Fire Wave Attack +8430,9,6,1,1,0,0,1,1,no,0,0,0,magic,0, EL_ICE_NEEDLE,Ice Needle +8431,9,6,1,1,0,1,1,1,no,0,0,0,magic,0, EL_WATER_SCREW,Water Screw +8432,9,6,1,1,0,1,1,1,no,0,0,0,weapon,0, EL_WATER_SCREW_ATK,Water Screw Attack +8433,9,6,1,1,0,1,1,1,no,0,0,0,weapon,0, EL_TIDAL_WEAPON,Tidal Weapon +8434,11,6,1,4,0,0,1,1,no,0,0,0,weapon,0, EL_WIND_SLASH,Wind Slasher +8435,11,6,1,4,0,1,1,1,no,0,0,0,weapon,0, EL_HURRICANE,Hurricane Rage +8436,7,6,1,4,0,0,1,1,no,0,0,0,magic,0, EL_HURRICANE_ATK,Hurricane Rage Attack +8437,11,8,1,4,0,1,1,-3,no,0,0,0,weapon,0, EL_TYPOON_MIS,Typhoon Missile +8438,11,8,1,4,0,1,1,-3,no,0,0,0,magic,0, EL_TYPOON_MIS_ATK,Typhoon Missile Attack +8439,5,6,1,2,0,0,1,1,no,0,0,0,weapon,0, EL_STONE_HAMMER,Stone Hammer +8440,3,6,1,2,0,1,1,1,no,0,0,0,weapon,0, EL_ROCK_CRUSHER,Rock Launcher +8441,5,6,1,2,0,1,1,1,no,0,0,0,magic,0, EL_ROCK_CRUSHER_ATK,Rock Launcher Attack +8442,9,6,1,2,0,1,1,-5,no,0,0,0,weapon,0, EL_STONE_RAIN,Stone Rain + +10000,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_APPROVAL,Official Guild Approval +10001,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_KAFRACONTRACT,Kafra Contract +10002,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_GUARDRESEARCH,Guardian Research +10003,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_GUARDUP,Strengthen Guardians +10004,0,0,0,0,0,0,10,0,no,0,0x10,0,none,0, GD_EXTENSION,Guild Extension +10005,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_GLORYGUILD,Guild's Glory +10006,0,0,0,0,0,2,1,0,no,0,0x10,0,none,0, GD_LEADERSHIP,Great Leadership +10007,0,0,0,0,0,2,1,0,no,0,0x10,0,none,0, GD_GLORYWOUNDS,Glorious Wounds +10008,0,0,0,0,0,2,1,0,no,0,0x10,0,none,0, GD_SOULCOLD,Cold Heart +10009,0,0,0,0,0,2,1,0,no,0,0x10,0,none,0, GD_HAWKEYES,Sharp Gaze +10010,0,0,4,0,0x3,15,1,0,yes,0,0x10,0,none,0, GD_BATTLEORDER,Battle Orders +10011,0,0,4,0,0x3,15,3,0,yes,0,0x10,0,none,0, GD_REGENERATION,Regeneration +10012,0,0,4,0,0x3,15,1,0,yes,0,0x10,0,none,0, GD_RESTORE,Restoration +10013,0,0,4,0,0x3,0,1,0,yes,0,0x10,0,none,0, GD_EMERGENCYCALL,Urgent Call +10014,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_DEVELOPMENT,Permanent Development +//10015,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_ITEMEMERGENCYCALL,Unknown Skill diff --git a/db/re/skill_db.txt b/db/re/skill_db.txt index f81cd5b05..13ade9c02 100644 --- a/db/re/skill_db.txt +++ b/db/re/skill_db.txt @@ -1,1206 +1,1206 @@ -//id,range,hit,inf,element,nk,splash,max,list_num,castcancel,cast_defence_rate,inf2,maxcount,skill_type,blow_count,name,description -// 01 ID -// 02 range (combo skills do not check for range when used, -// if range is < 5, the skill is considered melee-range) -// 03 hit (8- repeated hitting, 6- single-hit) -// 04 inf (0- passive, 1- enemy, 2- place, 4- self, 16- friend, 32- trap) -// 05 element (0 - neutral, 1 - water, 2 - earth, 3 - fire, 4 - wind, 5 - poison, -// 6 - holy, 7 - dark, 8 - ghost, 9 - undead, -1 - use weapon element -// -2 - use endowed element, -3 - use random element.) -// 06 nk (skill damage properties): -// 0x01 - No damage skill -// 0x02 - Has splash area -// 0x04 - Damage should be split among targets -// 0x08 - Skill ignores caster's % damage cards (misc type always ignores) -// 0x10 - Skill ignores elemental adjustments -// 0x20 - Skill ignores target's defense (misc type always ignores) -// 0x40 - Skill ignores target's flee (magic type always ignores) -// 0x80 - Skill ignores target's def cards -// 07 splash/effect range (-1 for screen-wide) -// 08 MaxLv -// 09 Number of hits (when positive, damage is increased by hits, -// negative values just show number of hits without increasing total damage) -// 10 Cast interrupted when hit? -// 11 defense-reduction rate during cast. -// 12 inf2 (skill information 2): -// 0x0001- quest skill -// 0x0002- npc skill -// 0x0004- wedding skill -// 0x0008- spirit skill -// 0x0010- guild skill -// 0x0020- song/dance -// 0x0040- ensemble skill -// 0x0080- trap -// 0x0100- skill that damages/targets yourself -// 0x0200- cannot be casted on self (if inf = 4, auto-select target skill) -// 0x0400- usable only on party-members (and enemies if skill is offensive) -// 0x0800- usable only on guild-mates (and enemies if skill is offensive) -// 0x1000- disable usage on enemies (for non-offensive skills). -// 0x2000- skill ignores land protector (e.g. arrow shower) -// 0x4000- chorus skill -// 13 maxcount: max amount of skill instances to place on the ground when -// player_land_skill_limit/monster_land_skill_limit is enabled. For skills -// that attack using a path, this is the path length to be used. -// 14 attack type (none, weapon, magic, misc) -// 15 Blowcount (amount of tiles skill knockbacks) -// 16 Name -// 17 Description -1,0,0,0,0,0,0,9,0,no,0,0,0,none,0, NV_BASIC,Basic Skill -2,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, SM_SWORD,Sword Mastery -3,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, SM_TWOHAND,Two-Handed Sword Mastery -4,0,0,0,0,0,0,10,0,no,0,0,0,none,0, SM_RECOVERY,Increase HP Recovery -5,-1,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, SM_BASH,Bash -6,9,6,1,0,1,0,10,1,no,0,0,0,none,0, SM_PROVOKE,Provoke -7,0,6,4,3,0x2,2,10,1,no,0,0,0,weapon,2, SM_MAGNUM,Magnum Break -8,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, SM_ENDURE,Endure -9,0,0,0,0,0,0,10,0,no,0,0,0,none,0, MG_SRECOVERY,Increase SP Recovery -10,0,6,4,3,0x3,3,1,1,yes,0,0,0,magic,0, MG_SIGHT,Sight -11,9,6,1,8,0x6,1,10,1,yes,0,0,0,magic,0, MG_NAPALMBEAT,Napalm Beat -12,9,8,2,8,0x1,0,10,1,yes,0,0,0,magic,0, MG_SAFETYWALL,Safety Wall -13,9,8,1,8,0,0,10,1:1:2:2:3:3:4:4:5:5,yes,0,0,0,magic,0, MG_SOULSTRIKE,Soul Strike -14,9,8,1,1,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, MG_COLDBOLT,Cold Bolt -15,9,6,1,1,0,0,10,1,yes,0,0,0,magic,0, MG_FROSTDIVER,Frost Diver -16,2,6,1,2,0x1,0,10,1,yes,0,0,0,magic,0, MG_STONECURSE,Stone Curse -17,9,6,1,3,0x2,2,10,1,yes,0,0,0,magic,0, MG_FIREBALL,Fire Ball -18,9,6,2,3,0,0,10,1,yes,0,0,3,magic,2, MG_FIREWALL,Fire Wall -19,9,8,1,3,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, MG_FIREBOLT,Fire Bolt -20,9,8,1,4,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, MG_LIGHTNINGBOLT,Lightning Bolt -21,9,8,2,4,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, MG_THUNDERSTORM,Thunderstorm -22,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, AL_DP,Divine Protection -23,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, AL_DEMONBANE,Demon Bane -24,0,6,4,6,0x3,2,1,1,yes,0,0,0,magic,0, AL_RUWACH,Ruwach -25,9,6,2,0,0x1,0,1,1,yes,0,0,0,magic,0, AL_PNEUMA,Pneuma -26,0,6,4,0,0x1,0,2,1,yes,0,0,0,magic,0, AL_TELEPORT,Teleport -27,9,6,2,0,0x1,0,4,1,yes,0,0,3,magic,0, AL_WARP,Warp Portal -28,9,6,16,6,0x21,0,10,1,yes,0,0,0,magic,0, AL_HEAL,Heal -29,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, AL_INCAGI,Increase AGI -30,9,6,1,0,0x1,0,10,1,yes,0,0,0,magic,0, AL_DECAGI,Decrease AGI -31,0,6,4,0,0x1,0,1,1,yes,0,0,0,magic,0, AL_HOLYWATER,Aqua Benedicta -32,0,6,4,0,0x3,15,10,1,yes,0,0,0,magic,0, AL_CRUCIS,Signum Crucis -33,0,6,4,0,0x3,-1,10,1,yes,0,0,0,magic,0, AL_ANGELUS,Angelus -34,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, AL_BLESSING,Blessing -35,9,6,16,0,0x1,0,1,1,yes,0,0,0,magic,0, AL_CURE,Cure -36,0,0,0,0,0,0,10,0,no,0,0,0,none,0, MC_INCCARRY,Enlarge Weight Limit -37,0,0,0,0,0,0,10,0,no,0,0,0,none,0, MC_DISCOUNT,Discount -38,0,0,0,0,0,0,10,0,no,0,0,0,none,0, MC_OVERCHARGE,Overcharge -39,1,0,0,0,0,0,10,0,no,0,0,0,none,0, MC_PUSHCART,Pushcart -40,1,6,4,0,0x1,0,1,1,no,0,0,0,none,0, MC_IDENTIFY,Item Appraisal -41,1,6,4,0,0x1,0,10,1,no,0,0,0,none,0, MC_VENDING,Vending -42,-1,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, MC_MAMMONITE,Mammonite -43,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AC_OWL,Owl's Eye -44,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AC_VULTURE,Vulture's Eye -45,0,6,4,0,0x3,3,10,1,no,0,0,0,weapon,0, AC_CONCENTRATION,Improve Concentration -46,-9,8,1,-1,0,0,10,2,no,0,0,0,weapon,0, AC_DOUBLE,Double Strafe -47,-9,6,2,-1,0x2,2,10,1,no,0,0x2000,0,weapon,2, AC_SHOWER,Arrow Shower -48,-1,8,0,-1,0,0,10,2,no,0,0,0,weapon,0, TF_DOUBLE,Double Attack -49,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, TF_MISS,Improve Dodge -50,1,6,1,0,1,0,10,1,no,0,0,0,weapon,0, TF_STEAL,Steal -51,1,6,4,0,1,0,10,1,no,0,0,0,none,0, TF_HIDING,Hiding -52,-2,6,1,5,0,0,10,1,no,0,0,0,weapon,0, TF_POISON,Envenom -53,9,6,16,5,0x1,0,1,1,no,0,0,0,weapon,0, TF_DETOXIFY,Detoxify -54,9,6,16,6,0x1,0,4,1,yes,0,0,0,magic,0, ALL_RESURRECTION,Resurrection -55,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, KN_SPEARMASTERY,Spear Mastery -56,-2,8,1,-1,0,0,10,3,no,0,0,0,weapon,0, KN_PIERCE,Pierce -57,-2,6,1,-1,0x1,0,10,1,no,33,0,0,weapon,3, KN_BRANDISHSPEAR,Brandish Spear -58,-4,6,1,-1,0x2,0,10,1,no,0,0,0,weapon,6, KN_SPEARSTAB,Spear Stab -59,3:5:7:9:11,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, KN_SPEARBOOMERANG,Spear Boomerang -60,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, KN_TWOHANDQUICKEN,Twohand Quicken -61,0,6,4,-1,0x20,0,5,1,no,0,0,0,weapon,0, KN_AUTOCOUNTER,Counter Attack -62,-2,6,1,-1,0x2,1,10,1,no,33,0,0,weapon,1, KN_BOWLINGBASH,Bowling Bash -63,0,0,0,0,0,0,1,0,no,0,0,0,weapon,0, KN_RIDING,Peco Peco Riding -64,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, KN_CAVALIERMASTERY,Cavalier Mastery -65,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, PR_MACEMASTERY,Mace Mastery -66,9,6,16,0,0x1,0,5,1,yes,0,0,0,magic,0, PR_IMPOSITIO,Impositio Manus -67,9,6,16,0,0x1,0,3,1,yes,0,0x200,0,magic,0, PR_SUFFRAGIUM,Suffragium -68,9,6,16,6,0x31,0,5,1,yes,0,0,0,magic,0, PR_ASPERSIO,Aspersio -69,9,6,2,0,0x23,1,5,1,yes,0,0x40,0,magic,0, PR_BENEDICTIO,B.S. Sacramenti -70,9,6,2,6,0x21,0,10,1,yes,0,0,0,magic,1, PR_SANCTUARY,Sanctuary -71,9,6,16,0,0x1,0,4,1,yes,0,0,0,magic,0, PR_SLOWPOISON,Slow Poison -72,9,6,16,0,0x1,0,1,1,yes,0,0,0,magic,0, PR_STRECOVERY,Status Recovery -73,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, PR_KYRIE,Kyrie Eleison -74,0,6,4,0,0x3,-1,5,1,yes,0,0,0,magic,0, PR_MAGNIFICAT,Magnificat -75,0,6,4,0,0x3,-1,5,1,yes,0,0,0,magic,0, PR_GLORIA,Gloria -76,5,6,1,0,0x1,0,10,0,yes,0,0,0,magic,0, PR_LEXDIVINA,Lex Divina -77,5,6,1,6,0x28,0,10,1,yes,0,0,0,magic,0, PR_TURNUNDEAD,Turn Undead -78,9,6,1,0,0x1,0,1,0,yes,0,0,0,magic,0, PR_LEXAETERNA,Lex Aeterna -79,9,8,2,6,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, PR_MAGNUS,Magnus Exorcismus -80,9,8,2,3,0x20,1:1:1:1:1:2:2:2:2:2:2,10,3:4:5:6:7:8:9:10:11:12:12,yes,0,0x80,5,magic,0, WZ_FIREPILLAR,Fire Pillar -81,0,6,4,3,0,3,10,1,yes,0,0,0,magic,5, WZ_SIGHTRASHER,Sightrasher -83,9,8,2,3,0,3:3:3:3:3:3:3:3:3:3:14,10,1:1:2:2:3:3:4:4:5:5:15,yes,0,0,0,magic,0, WZ_METEOR,Meteor Storm -84,9,8,1,4,0,0,10,3:4:5:6:7:8:9:10:11:12,yes,0,0,0,magic,2:3:3:4:4:5:5:6:6:7, WZ_JUPITEL,Jupitel Thunder -85,9,8,2,4,0,0,10,-10,yes,0,0,0,magic,0, WZ_VERMILION,Lord of Vermilion -86,9,8,1,1,0,0,5,1,yes,0,0,0,magic,0, WZ_WATERBALL,Water Ball -87,9,6,2,1,0x1,0,10,1,yes,0,0,0,magic,0, WZ_ICEWALL,Ice Wall -88,0,6,4,1,0x2,2,10,1,yes,0,0,0,magic,0, WZ_FROSTNOVA,Frost Nova -89,9,6,2,1,0,0,10,1,yes,0,0,0,magic,2, WZ_STORMGUST,Storm Gust -90,9,8,1,2,0,0,5,1:2:3:4:5,yes,0,0,0,magic,0, WZ_EARTHSPIKE,Earth Spike -91,9,8,2,2,0,0,5,1:2:3:4:5,yes,0,0,0,magic,0, WZ_HEAVENDRIVE,Heaven's Drive -92,9,6,2,2,0x1,0,5,1,yes,0,0,3,magic,0, WZ_QUAGMIRE,Quagmire -93,9,6,1,0,0x1,0,1,1,yes,0,0,0,magic,0, WZ_ESTIMATION,Sense -94,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, BS_IRON,Iron Tempering -95,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, BS_STEEL,Steel Tempering -96,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, BS_ENCHANTEDSTONE,Enchanted Stone Craft -97,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, BS_ORIDEOCON,Oridecon Research -98,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_DAGGER,Smith Dagger -99,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_SWORD,Smith Sword -100,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_TWOHANDSWORD,Smith Two-handed Sword -101,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_AXE,Smith Axe -102,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_MACE,Smith Mace -103,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_KNUCKLE,Smith Knucklebrace -104,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_SPEAR,Smith Spear -105,0,0,0,0,0,0,1,0,no,0,0,0,weapon,0, BS_HILTBINDING,Hilt Binding -106,0,0,0,0,0,0,1,0,no,0,0,0,weapon,0, BS_FINDINGORE,Ore Discovery -107,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, BS_WEAPONRESEARCH,Weaponry Research -108,2,6,16,0,0x1,0,1,1,yes,0,0,0,weapon,0, BS_REPAIRWEAPON,Weapon Repair -109,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, BS_SKINTEMPER,Skin Tempering -110,1,6,2,0,0x3,2:2:2:2:2:14,5,1,no,0,0,0,weapon,0, BS_HAMMERFALL,Hammer Fall -111,0,6,4,0,0x3,-1,5,1,no,0,0,0,weapon,0, BS_ADRENALINE,Adrenaline Rush -112,0,6,4,0,0x3,-1,5,1,no,0,0,0,weapon,0, BS_WEAPONPERFECT,Weapon Perfection -113,0,6,4,0,0x3,-1,5,1,no,0,0,0,weapon,0, BS_OVERTHRUST,Power-Thrust -114,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, BS_MAXIMIZE,Maximize Power -115,3,6,2,0,0x1,0,5,1,no,0,0x80,0,misc,6:7:8:9:10, HT_SKIDTRAP,Skid Trap -116,3,6,2,2,0x42,1,5,1,no,0,0x80,0,misc,0, HT_LANDMINE,Land Mine -117,3,6,2,0,0x1,0,5,1,no,0,0x80,0,misc,0, HT_ANKLESNARE,Ankle Snare -118,3,6,2,0,0x2,1,5,1,no,0,0x80,0,misc,0, HT_SHOCKWAVE,Shockwave Trap -119,3,6,2,0,0x3,2,5,1,no,0,0x80,0,misc,0, HT_SANDMAN,Sandman -120,3,6,2,0,0x3,1,5,1,no,0,0x80,0,misc,0, HT_FLASHER,Flasher -121,3,6,2,1,0x42,1,5,1,no,0,0x80,0,weapon,0, HT_FREEZINGTRAP,Freezing Trap -122,3,6,2,4,0x42,1,5,1,no,0,0x80,0,misc,0, HT_BLASTMINE,Blast Mine -123,3,6,2,3,0x42,2,5,1,no,0,0x80,0,misc,0, HT_CLAYMORETRAP,Claymore Trap -124,2,6,32,0,0x1,0,1,1,no,0,0,0,misc,0, HT_REMOVETRAP,Remove Trap -125,3,6,2,0,0x1,0,1,1,no,0,0x80,0,misc,0, HT_TALKIEBOX,Talkie Box -126,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, HT_BEASTBANE,Beast Bane -127,0,0,0,0,0,0,1,0,no,0,0,0,misc,0, HT_FALCON,Falconry Mastery -128,0,0,0,0,0,0,10,0,no,0,0,0,misc,0, HT_STEELCROW,Steel Crow -129,5,8,1,0,0x42,1,5,1:2:3:4:5,yes,0,0,0,misc,0, HT_BLITZBEAT,Blitz Beat -130,3:5:7:9,6,2,0,0x3,3,4,1,no,0,0,0,misc,0, HT_DETECTING,Detect -131,4:5:6:7:8,6,32,0,0x1,0,5,1,no,0,0,0,misc,0, HT_SPRINGTRAP,Spring Trap -132,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, AS_RIGHT,Righthand Mastery -133,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, AS_LEFT,Lefthand Mastery -134,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, AS_KATAR,Katar Mastery -135,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, AS_CLOAKING,Cloaking -136,-1,8,1,-1,0,0,10,8,no,0,0,0,weapon,0, AS_SONICBLOW,Sonic Blow -137,3:4:5:6:7,6,1,-1,0x2,1,5,1,no,0,0,0,weapon,0,AS_GRIMTOOTH,Grimtooth -138,1,6,16,5,0x1,0,10,1,no,0,0x400,0,weapon,0, AS_ENCHANTPOISON,Enchant Poison -139,0,6,4,0,0,0,10,1,no,0,0,0,weapon,0, AS_POISONREACT,Poison React -140,2,6,2,5,0x1,0,10,1,no,0,0,0,weapon,0, AS_VENOMDUST,Venom Dust -141,1,6,1,-1,0x51,2,10,1,yes,0,0,0,weapon,0, AS_SPLASHER,Venom Splasher -142,0,6,4,0,0x1,0,1,1,no,0,0x1,0,none,0, NV_FIRSTAID,First Aid -143,0,6,4,0,0x1,0,1,1,no,0,0x1,0,none,0, NV_TRICKDEAD,Play Dead -144,0,0,0,0,0,0,1,0,no,0,0x1,0,none,0, SM_MOVINGRECOVERY,Moving HP-Recovery -145,0,0,0,0,0,0,1,0,no,0,0x1,0,weapon,0, SM_FATALBLOW,Fatal Blow -146,0,6,4,0,0x1,0,1,1,no,0,0x1,0,weapon,0, SM_AUTOBERSERK,Auto Berserk -147,0,0,4,0,0x1,0,1,0,no,0,0x1,0,weapon,0, AC_MAKINGARROW,Arrow Crafting -148,-9,6,1,-1,0x2,0,1,1,no,0,0x1,0,weapon,6, AC_CHARGEARROW,Arrow Repel -149,1,6,1,2,0,0,1,1,no,0,0x1,0,weapon,0, TF_SPRINKLESAND,Sand Attack -150,0,6,4,0,0x1,0,1,1,no,0,0x1,0,weapon,5, TF_BACKSLIDING,Back Slide -151,0,6,4,0,0x1,0,1,1,no,0,0x1,0,none,0, TF_PICKSTONE,Find Stone -152,7,6,1,0,0x40,0,1,1,no,0,0x1,0,misc,0, TF_THROWSTONE,Stone Fling -153,1,6,1,-1,0x2,1,1,1,no,0,0x1,0,weapon,2, MC_CARTREVOLUTION,Cart Revolution -154,0,6,4,0,0x1,0,1,1,no,0,0x1,0,none,0, MC_CHANGECART,Change Cart -155,0,6,4,0,0x1,0,1,1,no,0,0x1,0,weapon,0, MC_LOUD,Crazy Uproar -156,9,6,1,6,0,0,1,1,yes,0,0x1,0,magic,0, AL_HOLYLIGHT,Holy Light -157,0,6,4,0,0x1,0,1,1,yes,0,0x1,0,magic,0, MG_ENERGYCOAT,Energy Coat -158,3,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_PIERCINGATT,Piercing Attack -159,-1,6,1,-1,0x40,0,5,1,no,0,0x2,0,weapon,0, NPC_MENTALBREAKER,Spirit Destruction -160,9,6,1,0,0,0,10,1,no,0,0x2,0,weapon,0, NPC_RANGEATTACK,Stand off attack -161,0,0,4,0,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_ATTRICHANGE,Attribute Change -162,0,0,4,1,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEWATER,Water Attribute Change -163,0,0,4,2,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEGROUND,Earth Attribute Change -164,0,0,4,3,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEFIRE,Fire Attribute Change -165,0,0,4,4,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEWIND,Wind Attribute Change -166,0,0,4,5,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEPOISON,Poison Attribute Change -167,0,0,4,6,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEHOLY,Holy Attribute Change -168,0,0,4,7,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEDARKNESS,Shadow Attribute Change -169,0,0,4,8,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGETELEKINESIS,Ghost Attribute Change -170,-9,6,1,-1,0x20,0,10,1,no,0,0x2,0,weapon,0, NPC_CRITICALSLASH,Defense disregard attack -171,-9,8,1,-1,0,0,10,-2:-3:-4:-5:-6:-7:-8:-9:-10:-11,no,0,0x2,0,weapon,0, NPC_COMBOATTACK,Multi-stage Attack -172,-9,6,1,-1,0x40,0,10,1,no,0,0x2,0,weapon,0, NPC_GUIDEDATTACK,Guided Attack -173,5,6,4,3,0xE2,5,10,1,no,0,0x2,0,misc,3, NPC_SELFDESTRUCTION,Suicide bombing -174,-9,6,1,-1,0x2,3,1,1,no,0,0x2,0,weapon,0, NPC_SPLASHATTACK,Splash attack -175,0,0,4,0,0x41,0,10,1,no,0,0x2,0,misc,0, NPC_SUICIDE,Suicide -176,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_POISON,Poison Attack -177,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_BLINDATTACK,Blind Attack -178,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_SILENCEATTACK,Silence Attack -179,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_STUNATTACK,Stun Attack -180,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_PETRIFYATTACK,Petrify Attack -181,-9,6,1,7,0,0,5,1,no,0,0x2,0,weapon,0, NPC_CURSEATTACK,Curse Attack -182,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_SLEEPATTACK,Sleep attack -183,-9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_RANDOMATTACK,Random Attack -184,-9,6,1,1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_WATERATTACK,Water Attribute Attack -185,-9,6,1,2,0,0,10,1,no,0,0x2,0,weapon,0, NPC_GROUNDATTACK,Earth Attribute Attack -186,-9,6,1,3,0,0,10,1,no,0,0x2,0,weapon,0, NPC_FIREATTACK,Fire Attribute Attack -187,-9,6,1,4,0,0,10,1,no,0,0x2,0,weapon,0, NPC_WINDATTACK,Wind Attribute Attack -188,-9,6,1,5,0,0,10,1,no,0,0x2,0,weapon,0, NPC_POISONATTACK,Poison Attribute Attack -189,-9,6,1,6,0,0,10,1,no,0,0x2,0,weapon,0, NPC_HOLYATTACK,Holy Attribute Attack -190,-9,6,1,7,0,0,10,1,no,0,0x2,0,weapon,0, NPC_DARKNESSATTACK,Shadow Attribute Attack -191,-9,6,1,8,0,0,10,1,no,0,0x2,0,weapon,0, NPC_TELEKINESISATTACK,Ghost Attribute Attack -192,-9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_MAGICALATTACK,Demon Shock Attack -193,0,0,4,0,0x1,0,10,1,no,0,0x2,0,none,0, NPC_METAMORPHOSIS,Metamorphosis -194,0,0,4,0,0x1,0,10,1,no,0,0x2,0,none,0, NPC_PROVOCATION,Provocation -195,0,6,4,0,0x50,0,10,1,no,0,0x2,0,misc,0, NPC_SMOKING,Smoking -196,0,0,4,0,0x1,0,10,1,no,0,0x2,0,magic,0, NPC_SUMMONSLAVE,Follower Summons -197,0,0,4,0,0x1,0,10,1,no,0,0x2,0,none,0, NPC_EMOTION,Emotion -198,0,0,4,0,0x1,0,10,1,no,0,0x2,0,magic,0, NPC_TRANSFORMATION,Transformation -199,9,6,1,7,0x40,0,1,1,no,0,0x2,0,weapon,0, NPC_BLOODDRAIN,Sucking Blood -200,9,6,1,7,0,0,1,1,no,0,0x2,0,magic,0, NPC_ENERGYDRAIN,Energy Drain -201,0,0,4,0,0x1,0,1,1,no,0,0x2,0,weapon,0, NPC_KEEPING,Keeping -202,9,6,1,7,0,0,5,1,no,0,0x2,0,misc,0, NPC_DARKBREATH,Dark Breath -203,9,6,1,7,0x1,0,10,1,no,0,0x2,0,magic,0, NPC_DARKBLESSING,Dark Blessing -204,0,0,4,0,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_BARRIER,Barrier -205,0,0,4,0,0x1,0,1,1,no,0,0x2,0,weapon,0, NPC_DEFENDER,Defender -206,1,6,1,-1,0x1,0,5,1,no,0,0x2,0,weapon,0, NPC_LICK,Lick -207,9,0,1,0,0x1,0,10,1,no,0,0x2,0,magic,0, NPC_HALLUCINATION,Hallucination -208,0,0,4,0,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_REBIRTH,Rebirth -209,0,0,4,0,0x1,0,10,1,no,0,0x2,0,magic,0, NPC_SUMMONMONSTER,Monster Summons -210,0,0,0,-1,0,0,10,0,no,0,0,0,weapon,0, RG_SNATCHER,Gank -211,1,6,1,0,0x1,0,10,1,no,0,0,0,weapon,0, RG_STEALCOIN,Mug -212,-1,6,1,-1,0x40,0,10,1,no,0,0,0,weapon,0, RG_BACKSTAP,Back Stab -213,0,0,0,0,0,0,5,0,no,0,0,0,none,0, RG_TUNNELDRIVE,Stalk -214,0,6,4,-1,0x2,1,5,1,no,0,0,0,weapon,0, RG_RAID,Sightless Mind -215,1,6,1,0,0x1,0,5,1,no,0,0,0,weapon,0, RG_STRIPWEAPON,Divest Weapon -216,1,6,1,0,0x1,0,5,1,no,0,0,0,weapon,0, RG_STRIPSHIELD,Divest Shield -217,1,6,1,0,0x1,0,5,1,no,0,0,0,weapon,0, RG_STRIPARMOR,Divest Armor -218,1,6,1,0,0x1,0,5,1,no,0,0,0,weapon,0, RG_STRIPHELM,Divest Helm -219,1,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, RG_INTIMIDATE,Snatch -220,1,6,2,0,0x1,0,1,1,no,0,0,0,none,0, RG_GRAFFITI,Scribble -221,0,6,2,0,0x1,0,5,1,no,0,0,0,none,0, RG_FLAGGRAFFITI,Piece -222,1,6,2,0,0x3,5,1,1,no,0,0,0,none,0, RG_CLEANER,Remover -223,0,0,0,0,0,1,1,0,no,0,0,0,none,0, RG_GANGSTER,Slyness -224,0,0,0,0,0,0,5,0,no,0,0,0,none,0, RG_COMPULSION,Haggle -225,0,0,0,0,0,0,10,0,no,0,0,0,none,0, RG_PLAGIARISM,Intimidate -226,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, AM_AXEMASTERY,Axe Mastery -227,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AM_LEARNINGPOTION,Potion Research -228,0,6,4,0,0x1,0,10,0,no,0,0,0,none,0, AM_PHARMACY,Prepare Potion -229,9,6,2,3,0x9,0,5,1,yes,0,0,0,weapon,0, AM_DEMONSTRATION,Bomb -230,9,6,1,0,0x48,0,5,1,yes,0,0,0,weapon,0, AM_ACIDTERROR,Acid Terror -231,9,6,16,0,0x1,0,5,1,yes,0,0xC00,0,none,0, AM_POTIONPITCHER,Aid Potion -232,4,6,2,0,0x1,0,5,1,no,0,0,5,none,0, AM_CANNIBALIZE,Summon Flora -233,1,6,2,0,0x1,0,5,1,no,0,0,3,none,0, AM_SPHEREMINE,Summon Marine Sphere -234,1,6,16,0,0x1,0,5,1,yes,0,0,0,weapon,0, AM_CP_WEAPON,Alchemical Weapon -235,1,6,16,0,0x1,0,5,1,yes,0,0,0,weapon,0, AM_CP_SHIELD,Synthesized Shield -236,1,6,16,0,0x1,0,5,1,yes,0,0,0,weapon,0, AM_CP_ARMOR,Synthetic Armor -237,1,6,16,0,0x1,0,5,1,yes,0,0,0,weapon,0, AM_CP_HELM,Biochemical Helm -238,0,0,0,0,0,0,1,0,no,0,0x1,0,none,0, AM_BIOETHICS,Bioethics -//239,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AM_BIOTECHNOLOGY,Biotechnology -//240,0,0,0,0,0,0,5,0,no,0,0,0,none,0, AM_CREATECREATURE,Life Creation -//241,0,0,0,0,0,0,5,0,no,0,0,0,none,0, AM_CULTIVATION,Cultivation -//242,0,0,0,0,0,0,5,0,no,0,0,0,none,0, AM_FLAMECONTROL,Flame Control -243,0,0,4,0,0x1,1,1,0,no,0,0,0,none,0, AM_CALLHOMUN,Call Homunculus -244,0,0,4,0,0x1,0,1,0,no,0,0,0,none,0, AM_REST,Vaporize -//245,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AM_DRILLMASTER,Drillmaster -//246,9,0,0,0,0,0,10,0,no,0,0,0,none,0, AM_HEALHOMUN,Heal Homunculus -247,9,6,4,0,0x1,1,5,0,no,0,0,0,none,0, AM_RESURRECTHOMUN,Homunculus Resurrection -248,0,0,0,0,0,0,10,0,no,0,0,0,none,0, CR_TRUST,Faith -249,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, CR_AUTOGUARD,Guard -250,3,6,1,0,0,0,5,1,no,0,0,0,weapon,5:6:7:8:9, CR_SHIELDCHARGE,Smite -251,3:5:7:9:11,6,1,0,0,0,5,1,no,0,0,0,weapon,0, CR_SHIELDBOOMERANG,Shield Boomerang -252,0,6,4,0,0,0,10,1,no,0,0,0,weapon,0, CR_REFLECTSHIELD,Shield Reflect -253,-2,8,1,6,0,0,10,-2,no,0,0,0,weapon,0, CR_HOLYCROSS,Holy Cross -254,5,6,4,6,0x48,0,10,1,no,33,0x100,0,magic,0, CR_GRANDCROSS,Grand Cross -255,7:8:9:10:11,6,16,0,0x1,0,5,1,yes,0,0x600,0,none,0, CR_DEVOTION,Sacrifice -256,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,none,0, CR_PROVIDENCE,Resistant Souls -257,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, CR_DEFENDER,Defending Aura -258,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, CR_SPEARQUICKEN,Spear Quicken -259,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, MO_IRONHAND,Iron Fists -260,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, MO_SPIRITSRECOVERY,Spiritual Cadence -261,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MO_CALLSPIRITS,Summon Spirit Sphere -262,9,6,16,0,0x1,0,1,1,yes,0,0,0,weapon,0, MO_ABSORBSPIRITS,Absorb Spirit Sphere -263,-1,8,0,-1,0,0,10,-3,no,0,0,0,weapon,0, MO_TRIPLEATTACK,Raging Trifecta Blow -264,18,6,2,0,0x1,0,1,1,no,0,0,0,none,0, MO_BODYRELOCATION,Snap -265,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, MO_DODGE,Dodge -266,2,6,1,0,0x40,0,5,1,no,0,0,0,weapon,0, MO_INVESTIGATE,Occult Impaction -267,9,8,1,-1,0,0,5,1:2:3:4:5,no,0,0,0,weapon,0, MO_FINGEROFFENSIVE,Throw Spirit Sphere -268,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, MO_STEELBODY,Mental Strength -269,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, MO_BLADESTOP,Root -270,0,6,4,0,0x1,0,5,0,no,0,0,0,weapon,0, MO_EXPLOSIONSPIRITS,Fury -271,-2,6,1,0,0x60,0,5,1,yes,0,0,0,weapon,0, MO_EXTREMITYFIST,Asura Strike -272,-2,8,4,-1,0,0,5,-4,no,0,0x200,0,weapon,0, MO_CHAINCOMBO,Raging Quadruple Blow -273,-2,6,4,-1,0x2,2,5,1,no,0,0x200,0,weapon,0, MO_COMBOFINISH,Raging Thrust -274,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, SA_ADVANCEDBOOK,Study -275,0,6,4,0,0x1,0,5,1,no,0,0,0,magic,0, SA_CASTCANCEL,Cast Cancel -276,0,6,4,0,0x1,0,5,1,yes,0,0,0,magic,0, SA_MAGICROD,Magic Rod -277,9,6,1,0,0x1,0,5,1,yes,0,0,0,magic,0, SA_SPELLBREAKER,Spell Breaker -278,0,0,0,0,0,0,10,0,no,0,0,0,magic,0, SA_FREECAST,Free Cast -279,0,6,4,0,0x1,0,10,1,yes,0,0,0,magic,0, SA_AUTOSPELL,Hindsight -280,9,6,16,3,0x1,0,5,1,yes,0,0xC00,0,magic,0, SA_FLAMELAUNCHER,Endow Blaze -281,9,6,16,1,0x1,0,5,1,yes,0,0xC00,0,magic,0, SA_FROSTWEAPON,Endow Tsunami -282,9,6,16,4,0x1,0,5,1,yes,0,0xC00,0,magic,0, SA_LIGHTNINGLOADER,Endow Tornado -283,9,6,16,2,0x1,0,5,1,yes,0,0xC00,0,magic,0, SA_SEISMICWEAPON,Endow Quake -284,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, SA_DRAGONOLOGY,Dragonology -285,2,6,2,3,0x1,0,5,1,yes,0,0,0,magic,0, SA_VOLCANO,Volcano -286,2,6,2,1,0x1,0,5,1,yes,0,0,0,magic,0, SA_DELUGE,Deluge -287,2,6,2,4,0x1,0,5,1,yes,0,0,0,magic,0, SA_VIOLENTGALE,Whirlwind -288,2,6,2,0,0x1,0,5,1,yes,0,0,0,magic,0, SA_LANDPROTECTOR,Magnetic Earth -289,9,6,1,0,0x1,0:0:0:0:0:-1,5,1,yes,0,0xE00,0,magic,0, SA_DISPELL,Dispell -290,0,6,4,0,0x1,0,10,1,yes,0,0,0,magic,0, SA_ABRACADABRA,Hocus-pocus -291,9,6,1,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_MONOCELL,Monocell -292,9,6,1,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_CLASSCHANGE,Class Change -293,0,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_SUMMONMONSTER,Monster Chant -294,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_REVERSEORCISH,Grampus Morph -295,9,6,1,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_DEATH,Grim Reaper -296,9,6,1,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_FORTUNE,Gold Digger -297,9,6,1,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_TAMINGMONSTER,Beastly Hypnosis -298,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_QUESTION,Questioning -299,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_GRAVITY,Gravity -300,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_LEVELUP,Leveling -301,9,6,4,0,0,0,1,1,yes,0,0x2,0,magic,0, SA_INSTANTDEATH,Suicide -302,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_FULLRECOVERY,Rejuvenation -303,9,6,4,0,0,0,1,1,yes,0,0x2,0,magic,0, SA_COMA,Coma -304,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, BD_ADAPTATION,Amp -305,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, BD_ENCORE,Encore -306,0,6,4,0,0x1,0,1,1,no,0,0x40,0,misc,0, BD_LULLABY,Lullaby -307,0,6,4,0,0x1,0,5,1,no,0,0x40,0,misc,0, BD_RICHMANKIM,Mental Sensing -308,0,6,4,0,0x1,0,1,1,no,0,0x40,0,misc,0, BD_ETERNALCHAOS,Down Tempo -309,0,6,4,0,0x1,0,5,1,no,0,0x40,0,misc,0, BD_DRUMBATTLEFIELD,Battle Theme -310,0,6,4,0,0x1,0,5,1,no,0,0x40,0,misc,0, BD_RINGNIBELUNGEN,Harmonic Lick -311,0,6,4,0,0x1,0,1,1,no,0,0x40,0,misc,0, BD_ROKISWEIL,Classical Pluck -312,0,6,4,0,0x1,0,1,1,no,0,0x40,0,misc,0, BD_INTOABYSS,Power Chord -313,0,6,4,0,0x1,0,5,1,no,0,0x40,0,misc,0, BD_SIEGFRIED,Acoustic Rhythm -//314,0,0,0,0,0,0,1,1,no,0,0x40,0,misc,0, BD_RAGNAROK,Ragnarok -315,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, BA_MUSICALLESSON,Music Lessons -316,9,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, BA_MUSICALSTRIKE,Melody Strike -317,0,8,4,0,0x41,0,5,1,no,0,0x20,0,misc,0, BA_DISSONANCE,Unchained Serenade -318,0,6,4,0,0x3,-1,5,1,no,0,0,0,misc,0, BA_FROSTJOKER,Unbarring Octave -319,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, BA_WHISTLE,Perfect Tablature -320,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, BA_ASSASSINCROSS,Impressive Riff -321,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, BA_POEMBRAGI,Magic Strings -322,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, BA_APPLEIDUN,Song of Lutie -323,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, DC_DANCINGLESSON,Dance Lessons -324,9,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, DC_THROWARROW,Slinging Arrow -325,0,8,4,0,0x1,0,5,1,no,0,0x20,0,misc,0, DC_UGLYDANCE,Hip Shaker -326,0,6,4,0,0x3,-1,5,1,no,0,0,0,misc,0, DC_SCREAM,Dazzler -327,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, DC_HUMMING,Focus Ballet -328,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, DC_DONTFORGETME,Slow Grace -329,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, DC_FORTUNEKISS,Lady Luck -330,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, DC_SERVICEFORYOU,Gypsy's Kiss -331,0,6,4,0,0x1,0,10,0,no,0,0x2,0,none,0, NPC_RANDOMMOVE,Random Move -332,0,6,4,0,0x1,0,10,0,no,0,0x2,0,none,0, NPC_SPEEDUP,Speed UP -333,0,6,4,0,0x1,0,1,0,no,0,0x2,0,none,0, NPC_REVENGE,Revenge -334,9,6,4,0,0x1,0,1,1,yes,0,0x4,0,none,0, WE_MALE,I Will Protect You -335,9,6,4,0,0x1,0,1,1,yes,0,0x4,0,none,0, WE_FEMALE,I Look up to You -336,9,6,4,0,0x1,3,1,1,yes,0,0x4,1,none,0, WE_CALLPARTNER,I miss You -337,9,6,1,-1,0,0,1,1,no,0,0x2,0,weapon,0, ITM_TOMAHAWK,Throw Tomahawk -338,-1,8,1,7,0,0,10,-2,no,0,0x2,0,weapon,0, NPC_DARKCROSS,Cross of Darkness -339,5,6,4,7,0x48,0,10,1,no,33,0x102,0,magic,0, NPC_GRANDDARKNESS,Grand cross of Darkness -340,9,8,1,7,0,0,10,1:1:2:2:3:3:4:4:5:5,yes,0,0x2,0,magic,0, NPC_DARKSTRIKE,Soul Strike of Darkness -341,9,8,1,7,0,0,10,3:4:5:6:7:8:9:10:11:12,yes,0,0x2,0,magic,2:3:3:4:4:5:5:6:6:7, NPC_DARKTHUNDER,Darkness Jupitel -342,9,6,1,0,0x1,0,1,0,no,0,0x2,0,none,0, NPC_STOP,Stop -343,9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_WEAPONBRAKER,Break weapon -344,9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_ARMORBRAKE,Break armor -345,9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_HELMBRAKE,Break helm -346,9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_SHIELDBRAKE,Break shield -347,-9,6,1,9,0,0,10,1,no,0,0x2,0,weapon,0, NPC_UNDEADATTACK,Undead Element Attack -348,9,0,1,9,0x1,0,5,1,no,0,0x2,0,magic,0, NPC_CHANGEUNDEAD,Undead Attribute Change -349,0,6,4,0,0x1,0,10,0,no,0,0x2,0,weapon,0, NPC_POWERUP,Power Up -350,0,6,4,0,0x1,0,10,0,no,0,0x2,0,none,0, NPC_AGIUP,Agility UP -351,0,0,0,0,0x1,0,1,0,no,0,0x2,0,none,0, NPC_SIEGEMODE,Siege Mode -352,2,0,4,0,0x1,0,1,0,no,0,0x2,0,none,0, NPC_CALLSLAVE,Recall Slaves -353,0,0,0,0,0x1,0,1,0,no,0,0x2,0,none,0, NPC_INVISIBLE,Invisible -354,2,6,4,0,0x1,0,20,0,no,0,0x2,0,misc,0, NPC_RUN,Run -355,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, LK_AURABLADE,Aura Blade -356,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, LK_PARRYING,Parrying -357,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, LK_CONCENTRATION,Concentration -358,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, LK_TENSIONRELAX,Relax -359,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, LK_BERSERK,Frenzy -//360,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, LK_FURY,Fury -361,9,6,16,0,0x1,1,5,1,yes,0,0,0,magic,0, HP_ASSUMPTIO,Assumptio -362,4,6,4,0,0x1,0,5,1,yes,0,0,0,magic,2, HP_BASILICA,Basilica -363,0,0,0,0,0,0,10,0,no,0,0,0,magic,0, HP_MEDITATIO,Meditatio -364,0,0,0,0,0,0,10,1,no,0,0,0,magic,0, HW_SOULDRAIN,Soul Drain -365,9,8,1,-1,0,0,1,1,yes,0,0,0,weapon,0, HW_MAGICCRASHER,Stave Crasher -366,0,6,4,0,0x1,0,10,1,no,0,0,0,magic,0, HW_MAGICPOWER,Mystical Amplification -367,9,8,1,0,0xD0,0,5,1,no,0,0,0,misc,0, PA_PRESSURE,Gloria Domini -368,0,6,4,0,0x69,0,5,1,yes,0,0,0,weapon,0, PA_SACRIFICE, Martyr's Reckoning -369,0,6,4,0,0x41,0,10,1,yes,0,0,0,misc,0, PA_GOSPEL,Battle Chant -370,-2,6,1,-1,0,0,5,1,yes,0,0,0,weapon,3, CH_PALMSTRIKE,Raging Palm Strike -371,-2,8,4,-1,0,0,5,1,no,0,0x200,0,weapon,0, CH_TIGERFIST,Glacier Fist -372,-2,8,4,-1,0,0,10,-1:-1:-2:-2:-3:-3:-4:-4:-5:-5,no,0,0x200,0,weapon,0, CH_CHAINCRUSH,Chain Crush Combo -373,0,6,4,0,0x1,0,5,1,no,0,0,0,magic,0, PF_HPCONVERSION,Indulge -374,9,6,1,0,0x1,0,1,1,yes,0,0xE00,0,none,0, PF_SOULCHANGE,Soul Exhale -375,9,6,1,0,0x98,0,5,1,yes,0,0,0,magic,0, PF_SOULBURN,Soul Siphon -376,0,0,0,0,0x1,0,5,1,no,0,0,0,weapon,0, ASC_KATAR,Advanced Katar Mastery -//377,0,0,4,0,0x1,0,10,1,no,0,0,0,misc,0, ASC_HALLUCINATION,Hallucination Walk -378,0,6,4,5,0x1,0,5,1,no,0,0,0,weapon,0, ASC_EDP,Enchant Deadly Poison -379,7,6,1,-1,0x8,0,10,1,yes,0,0,0,weapon,0, ASC_BREAKER,Soul Destroyer -380,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, SN_SIGHT,Falcon Eyes -381,5,8,1,0,0x40,0,5,1,yes,0,0,0,misc,0, SN_FALCONASSAULT,Falcon Assault -382,9,8,1,-1,0,2,5,1,yes,0,0,13,weapon,0, SN_SHARPSHOOTING,Focused Arrow Strike -383,0,6,4,0,0x3,-1,10,1,yes,0,0,0,weapon,0, SN_WINDWALK,Wind Walker -384,0,0,4,0,0x1,0,10,1,yes,0,0,0,weapon,0, WS_MELTDOWN,Shattering Strike -//385,0,0,4,0,0x1,0,1,1,yes,0,0,0,none,0, WS_CREATECOIN,Create Coins -//386,0,0,4,0,0x1,0,1,1,yes,0,0,0,none,0, WS_CREATENUGGET,Create Nuggets -387,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, WS_CARTBOOST,Cart Boost -//388,9,6,2,0,0x1,0,5,1,no,0,0,0,none,0, WS_SYSTEMCREATE,Auto Attack System -389,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, ST_CHASEWALK,Stealth -390,0,0,4,0,0,0,5,1,yes,0,0,0,weapon,0, ST_REJECTSWORD,Counter Instinct -//391,0,0,4,0,1,0,1,1,yes,0,0,0,magic,0, ST_STEALBACKPACK,Steal Backpack -392,0,0,4,0,0x1,0,1,1,yes,0,0,0,none,0, CR_ALCHEMY,Alchemy -393,0,0,4,0,0x1,0,1,1,yes,0,0,0,none,0, CR_SYNTHESISPOTION,Potion Synthesis -394,9,8,1,-1,0,0,10,-9,yes,0,0,0,weapon,0, CG_ARROWVULCAN,Vulcan Arrow -395,0,0,4,0,0x1,3,1,1,yes,0,0x40,0,misc,2, CG_MOONLIT,Sheltering Bliss -396,1,6,16,0,0x1,0,1,1,yes,0,0x600,0,none,0, CG_MARIONETTE,Marionette Control -397,5,8,1,-1,0x20,0,5,5,no,0,0,0,weapon,0, LK_SPIRALPIERCE,Spiral Pierce -398,4,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, LK_HEADCRUSH,Traumatic Blow -399,4,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, LK_JOINTBEAT,Vital Strike -400,9,8,1,8,0x6,1,5,1:2:3:4:5,yes,0,0,0,magic,0, HW_NAPALMVULCAN,Napalm Vulcan -401,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, CH_SOULCOLLECT,Zen -402,9,6,1,0,0x1,0,5,1,no,0,0,0,none,0, PF_MINDBREAKER,Mind Breaker -403,0,0,4,0,0x1,0,1,1,yes,0,0,0,magic,0, PF_MEMORIZE,Foresight -404,9,6,2,2,0x1,0,5,1,yes,0,0x100,2,magic,0, PF_FOGWALL,Blinding Mist -405,7,6,1,0,0x1,0,1,1,no,0,0,3,magic,0, PF_SPIDERWEB,Fiber Lock -406,0,6,4,-1,0xA,2,10,1,no,33,0,0,weapon,0, ASC_METEORASSAULT,Meteor Assault -407,0,6,4,0,0x1,0,1,0,no,0,0,0,none,0, ASC_CDP,Create Deadly Poison -408,9,6,4,0,0x1,0,1,1,yes,0,0x4,0,none,0, WE_BABY,Baby -409,9,6,4,0,0x1,3,1,1,yes,0,0x4,1,none,0, WE_CALLPARENT,Call Parent -410,9,6,4,0,0x1,3,1,1,yes,0,0x4,1,none,0, WE_CALLBABY,Call Baby -411,0,6,4,0,0x1,0,10,1,yes,0,0,0,misc,4, TK_RUN,Running -412,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, TK_READYSTORM,Tornado Stance -413,-2,8,4,-1,0x2,2,7,-3,no,0,0x200,0,weapon,0, TK_STORMKICK,Tornado Kick -414,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, TK_READYDOWN,Heel Drop Stance -415,-2,8,4,-1,0,0,7,-3,no,0,0x200,0,weapon,0, TK_DOWNKICK,Heel Drop -416,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, TK_READYTURN,Roundhouse Stance -417,-2,8,4,-1,0x2,1,7,-3,no,0,0x200,0,weapon,2, TK_TURNKICK,Roundhouse Kick -418,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, TK_READYCOUNTER,Counter Kick Stance -419,-2,8,4,-1,0x40,0,7,-3,no,0,0x200,0,weapon,0, TK_COUNTER,Counter Kick -420,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, TK_DODGE,Tumbling -421,9,8,16,-1,0x1,0,7,-3,no,0,0,0,weapon,0, TK_JUMPKICK,Flying Kick -422,0,0,0,0,0,1,10,0,no,0,0,0,none,0, TK_HPTIME,Peaceful Break -423,0,0,0,0,0,1,10,0,no,0,0,0,none,0, TK_SPTIME,Happy Break -424,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, TK_POWER,Kihop -425,0,6,4,2:4:1:3:8:7:6,0x1,0,7,1,no,0,0,0,weapon,0, TK_SEVENWIND,Mild Wind -426,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, TK_HIGHJUMP,Taekwon Jump -427,0,6,4,0,0x1,0,3,1,yes,0,0,0,magic,0, SG_FEEL,Feeling the Sun Moon and Stars -428,1,6,4,-1,0x2,1,3,1,yes,0,0,0,weapon,2, SG_SUN_WARM,Warmth of the Sun -429,1,6,4,-1,0x2,1,3,1,yes,0,0,0,weapon,2, SG_MOON_WARM,Warmth of the Moon -430,1,6,4,-1,0x2,1,3,1,yes,0,0,0,weapon,2, SG_STAR_WARM,Warmth of the Stars -431,0,0,4,0,0x1,0,4,1,yes,0,0,0,magic,0, SG_SUN_COMFORT,Comfort of the Sun -432,0,0,4,0,0x1,0,4,1,yes,0,0,0,magic,0, SG_MOON_COMFORT,Comfort of the Moon -433,0,0,4,0,0x1,0,4,1,yes,0,0,0,magic,0, SG_STAR_COMFORT,Comfort of the Stars -434,10,6,1,0,0x1,0,3,1,yes,0,0,0,magic,0, SG_HATE,Hatred of the Sun Moon and Stars -435,0,0,0,0,0,0,3,0,no,0,0,0,none,0, SG_SUN_ANGER,Anger of the Sun -436,0,0,0,0,0,0,3,0,no,0,0,0,none,0, SG_MOON_ANGER,Anger of the Moon -437,0,0,0,0,0,0,3,0,no,0,0,0,none,0, SG_STAR_ANGER,Anger of the Stars -438,0,0,0,0,0,0,5,0,no,0,0,0,none,0, SG_SUN_BLESS,Blessing of the Sun -439,0,0,0,0,0,0,5,0,no,0,0,0,none,0, SG_MOON_BLESS,Blessing of the Moon -440,0,0,0,0,0,0,5,0,no,0,0,0,none,0, SG_STAR_BLESS,Blessing of the Stars -441,0,0,0,0,0,0,10,0,no,0,0,0,none,0, SG_DEVIL,Demon of the Sun Moon and Stars -442,0,0,0,0,0,0,3,0,no,0,0,0,none,0, SG_FRIEND,Friend of the Sun Moon and Stars -443,0,0,0,0,0,0,10,0,no,0,0,0,none,0, SG_KNOWLEDGE,Knowledge of the Sun Moon and Stars -444,0,6,4,0,0x1,0,1,1,no,0,0,0,misc,0, SG_FUSION,Union of the Sun Moon and Stars -445,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_ALCHEMIST,Spirit of the Alchemist -446,9,6,16,0,0x1,0,1,1,yes,0,0xC08,0,none,0, AM_BERSERKPITCHER,Aid Berserk Potion -447,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_MONK,Spirit of the Monk -448,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_STAR,Spirit of the Star Gladiator -449,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_SAGE,Spirit of the Sage -450,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_CRUSADER,Spirit of the Crusader -451,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_SUPERNOVICE,Spirit of the Supernovice -452,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_KNIGHT,Spirit of the Knight -453,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_WIZARD,Spirit of the Wizard -454,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_PRIEST,Spirit of the Priest -455,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_BARDDANCER,Spirit of the Artist -456,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_ROGUE,Spirit of the Rogue -457,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_ASSASIN,Spirit of the Assasin -458,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_BLACKSMITH,Spirit of the Blacksmith -459,0,6,4,0,0x3,-1,1,1,no,0,0x8,0,weapon,0 , BS_ADRENALINE2,Advanced Adrenaline Rush -460,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_HUNTER,Spirit of the Hunter -461,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_SOULLINKER,Spirit of the Soul Linker -462,9,6,16,0,0x1,0,7,1,yes,0,0,0,magic,0, SL_KAIZEL,Kaizel -463,9,6,16,0,0x1,0,7,1,yes,0,0,0,magic,0, SL_KAAHI,Kaahi -464,9,6,16,0,0x1,0,3,1,yes,0,0,0,magic,0, SL_KAUPE,Kaupe -465,9,6,16,0,0x1,0,7,1,yes,0,0,0,magic,0, SL_KAITE,Kaite -466,0,0,0,0,0,0,7,0,yes,0,0,0,magic,0, SL_KAINA,Kaina -467,9,6,1,-2,0,0,7,1,no,0,0,0,magic,2, SL_STIN,Estin -468,9,6,1,-2,0,0,7,1,no,0,0,0,magic,0, SL_STUN,Estun -469,9,8,1,-2,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, SL_SMA,Esma -470,9,6,1,0,0x1,0,7,1,no,0,0,0,magic,0, SL_SWOO,Eswoo -471,9,6,1,0,0x1,0,3,1,no,0,0,0,magic,0, SL_SKE,Eske -472,9,6,1,0,0x1,0,3,1,no,0,0,0,magic,0, SL_SKA,Eska -473,0,6,4,0,0,0,1,1,no,0,0,0,none,0, SM_SELFPROVOKE,Provoke Self -474,0,0,4,0,0x1,0,10,1,no,0,0x2,0,none,0, NPC_EMOTION_ON,Emotion ON -475,0,0,4,0,0x1,0,1,1,yes,0,0,0,none,0, ST_PRESERVE,Preserve -476,1,6,1,0,0x1,0,5,1,yes,0,0,0,weapon,0, ST_FULLSTRIP,Divest All -477,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, WS_WEAPONREFINE,Upgrade Weapon -478,3,6,2,0,0x3,3,10,1,no,0,0,0,none,0, CR_SLIMPITCHER,Aid Condensed Potion -479,1,6,16,0,0x1,0,5,1,yes,0,0,0,weapon,0, CR_FULLPROTECTION,Full Protection -480,5,8,1,-1,0,0,5,5,no,0,0,0,weapon,0, PA_SHIELDCHAIN,Shield Chain -481,0,0,0,0,0,0,5,0,no,0,0,0,none,0, HP_MANARECHARGE,Mana Recharge -482,0,6,4,0,0x1,0,5,1,no,0,0,0,magic,0, PF_DOUBLECASTING,Double Casting -483,16,6,2,0,0x1,1:2:3:4:5,1,1,no,0,0,0,none,0, HW_GANBANTEIN,Ganbantein -484,9,6,2,2,0x91,0,5,1,yes,0,0,0,misc,0, HW_GRAVITATION,Gravitation Field -485,-2,6,1,-1,0x8,0,10,1,no,0,0,0,weapon,0, WS_CARTTERMINATION,Cart Termination -486,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, WS_OVERTHRUSTMAX,Maximum Power Thrust -487,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, CG_LONGINGFREEDOM,Longing for Freedom -488,0,6,4,0,0x1,1,5,1,no,0,0x40,0,misc,0, CG_HERMODE,Wand of Hermode -489,9,6,1,0,0x41,0,5,1,no,0,0,0,misc,0, CG_TAROTCARD,Tarot Card of Fate -490,9,8,1,0,0x40,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,misc,0, CR_ACIDDEMONSTRATION,Acid Demonstration -491,1,6,2,0,0x1,0,2,1,no,0,0,0,none,0, CR_CULTIVATION,Plant Cultivation -492,0,6,4,0:1:2:3:4:5:6:7:8:9,0x1,0,10,1,no,0,0x2,0,none,0, ITEM_ENCHANTARMS,Weapon Enchantment -493,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, TK_MISSION,Taekwon Mission -494,9,6,16,0,0x1,0,5,1,yes,0,0,0,magic,0, SL_HIGH,Spirit of Rebirth -495,0,6,4,0,0x1,0,1,1,no,0,0x8,0,weapon,0, KN_ONEHAND,Onehand Quicken -496,0,6,4,0,0x1,0,1,0,no,0,0x8,0,none,0, AM_TWILIGHT1,Twilight Alchemy 1 -497,0,6,4,0,0x1,0,1,0,no,0,0x8,0,none,0, AM_TWILIGHT2,Twilight Alchemy 2 -498,0,6,4,0,0x1,0,1,0,no,0,0x8,0,none,0, AM_TWILIGHT3,Twilight Alchemy 3 -499,-9,8,4,-1,0,0,1,2,no,0,0x208,0,weapon,0, HT_POWER,Beast Strafing -500,0,6,4,0,0x40,0,5,1,no,0,0,0,misc,0, GS_GLITTERING,Flip the Coin -501,9,6,1,-1,0x50,0,1,1,no,0,0,0,misc,0, GS_FLING,Fling -502,-9,8,1,-1,0,0,1,3,no,0,0,0,weapon,0, GS_TRIPLEACTION,Triple Action -503,-9,6,1,-1,0x8,0,1,1,no,0,0,0,weapon,0, GS_BULLSEYE,Bulls Eye -504,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, GS_MADNESSCANCEL,Madness Canceller -505,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, GS_ADJUSTMENT,AdJustment -506,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, GS_INCREASING,Increasing Accuracy -507,-9,6,1,8,0,0,1,1,no,0,0,0,weapon,0, GS_MAGICALBULLET,Magical Bullet -508,-9,6,1,-1,0x1,0,1,1,no,0,0,0,weapon,0, GS_CRACKER,Cracker -509,0,0,0,0,0,0,10,0,no,0,0,0,none,0, GS_SINGLEACTION,Single Action -510,0,0,0,0,0,0,10,0,no,0,0,0,none,0, GS_SNAKEEYE,Snake Eye -511,-9,8,0,-1,0,0,10,2,no,0,0,0,weapon,0, GS_CHAINACTION,Chain Action -512,-9,6,1,-1,0,0,10,1,yes,0,0,0,weapon,0, GS_TRACKING,Tracking -513,-9,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, GS_DISARM,Disarm -514,-9,6,1,-1,0x20,0,5,1,no,0,0,0,weapon,0, GS_PIERCINGSHOT,Piercing Shot -515,-9,8,1,-1,0,0,10,5,no,0,0,0,weapon,0, GS_RAPIDSHOWER,Rapid Shower -516,0,8,4,-1,0x2,3,10,1,no,0,0,0,weapon,0, GS_DESPERADO,Desperado -517,0,6,4,-1,0x1,0,10,1,no,0,0,0,weapon,0, GS_GATLINGFEVER,Gatling Fever -518,2,6,1,-1,0,0,10,1,no,0,0,0,weapon,5, GS_DUST,Dust -519,-9,6,1,-1,0,0,10,1,yes,0,0,0,weapon,0, GS_FULLBUSTER,Full Buster -520,-9,6,1,-1,0x2,1:1:1:2:2:2:3:3:3:4,10,1,no,0,0,0,weapon,0, GS_SPREADATTACK,Spread Attack -521,-9,6,2,-1,0x40,1,10,1,no,0,0,0,weapon,3, GS_GROUNDDRIFT,Ground Drift -522,0,0,0,0,0,0,10,1,no,0,0,0,weapon,0, NJ_TOBIDOUGU,Shuriken Training -523,9,6,1,-1,0x40,0,10,1,no,0,0,0,weapon,0, NJ_SYURIKEN,Throw Shuriken -524,9,8,1,-1,0x40,0,5,3,no,0,0,0,weapon,0, NJ_KUNAI,Throw Kunai -525,9,8,2,-1,0x2,0,5,-3:-3:-4:-4:-5,no,0,0,0,weapon,0, NJ_HUUMA,Throw Huuma Shuriken -526,9,6,1,0,0x50,0,10,1,no,0,0,0,misc,0, NJ_ZENYNAGE,Throw Zeny -527,0,6,4,-1,0,0,5,1,no,0,0,0,weapon,4, NJ_TATAMIGAESHI,Improvised Defense -528,-1,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, NJ_KASUMIKIRI,Vanishing Slash -529,7:9:11:13:15,6,2,0,0x1,0,5,1,no,0,0,0,none,0, NJ_SHADOWJUMP,Shadow Leap -530,7:9:11:13:15,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, NJ_KIRIKAGE,Shadow Slash -531,0,6,4,0,0x1,0,5,1,no,0,0,0,none,7, NJ_UTSUSEMI,Cicada Skin Sheeding -532,0,6,4,0,0x1,0,10,1,yes,0,0,0,magic,0, NJ_BUNSINJYUTSU,Mirror Image -533,0,0,0,0,0,0,10,0,no,0,0,0,none,0, NJ_NINPOU,Spirit of the Blade -534,9,8,1,3,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, NJ_KOUENKA,Crimson Fire Petal -535,0,8,4,3,0,0,10,1,yes,0,0,0,magic,1, NJ_KAENSIN,Crimson Fire Formation -536,9,8,1,3,0x2,2,5,3,yes,0,0,0,magic,0, NJ_BAKUENRYU,Raging Fire Dragon -537,9,8,1,1,0,0,10,3:4:5:6:7:8:9:10:11:12,yes,0,0,0,magic,0, NJ_HYOUSENSOU,Spear of Ice -538,9,6,2,1,0x1,0,10,1,yes,0,0,0,magic,0, NJ_SUITON,Hidden Water -539,0,6,4,1,0x2,3,5,1,yes,0,0,0,magic,0, NJ_HYOUSYOURAKU,Ice Meteor -540,9,8,1,4,0,0,10,1:2:2:3:3:4:4:5:5:6,yes,0,0,0,magic,0, NJ_HUUJIN,Wind Blade -541,9,6,2,4,0x2,2:2:3:3:4,5,1,yes,0,0,0,magic,0, NJ_RAIGEKISAI,Lightning Strike of Destruction -542,9,8,1,4,0,3,5,1,yes,0,0,5:6:7:8:9,magic,0, NJ_KAMAITACHI,Kamaitachi -543,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, NJ_NEN,Soul -544,-5,8,1,0,0x40,0,10,1,no,0,0,0,weapon,0, NJ_ISSEN,Final Strike - -// Additional NPC Skills (Episode 11.3) -653,0,8,4,0,0x6,5:7:9:11:13:5:7:9:11:13,10,1,no,0,0x2,0,magic,0, NPC_EARTHQUAKE,Earthquake -654,9,6,1,3,0,5,10,1,no,0,0x2,14,weapon,0, NPC_FIREBREATH,Fire Breath -655,9,6,1,1,0,5,10,1,no,0,0x2,14,weapon,0, NPC_ICEBREATH,Ice Breath -656,9,6,1,4,0,5,10,1,no,0,0x2,14,weapon,0, NPC_THUNDERBREATH,Thunder Breath -657,9,6,1,5,0,5,10,1,no,0,0x2,14,weapon,0, NPC_ACIDBREATH,Acid Breath -658,9,6,1,7,0,5,10,1,no,0,0x2,14,weapon,0, NPC_DARKNESSBREATH,Darkness Breath -659,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_DRAGONFEAR,Dragon Fear -660,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_BLEEDING,Bleeding -661,0,6,4,0,0x2,7,5,1,no,0,0x2,0,weapon,7, NPC_PULSESTRIKE,Pulse Strike -662,0,6,4,0,0x2,14,10,1,no,0,0x2,0,weapon,0, NPC_HELLJUDGEMENT,Hell's Judgement -663,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDESILENCE,Wide Silence -664,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDEFREEZE,Wide Freeze -665,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDEBLEEDING,Wide Bleeding -666,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDESTONE,Wide Petrify -667,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDECONFUSE,Wide Confusion -668,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDESLEEP,Wide Sleep -669,0,6,4,3,0x3,5,1,1,no,0,0x2,0,magic,0, NPC_WIDESIGHT,Wide Sight -670,9,6,2,7,0x91,0,10,1,no,0,0x2,0,magic,0, NPC_EVILLAND,Evil Land -671,0,6,4,0,0x1,0,5,1,no,0,0x2,0,magic,0, NPC_MAGICMIRROR,Magic Mirror -672,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_SLOWCAST,Slow Cast -673,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_CRITICALWOUND,Critical Wounds -674,-9,6,1,-1,0x1,0,1,1,no,0,0x2,0,none,0, NPC_EXPULSION,Expulsion -675,0,6,4,0,0x1,0,5,1,no,0,0x2,0,magic,0, NPC_STONESKIN,Stone Skin -676,0,6,4,0,0x1,0,5,1,no,0,0x2,0,magic,0, NPC_ANTIMAGIC,Anti Magic -677,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDECURSE,Wide Curse -678,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDESTUN,Wide Stun -679,0,6,4,0,0x2,5:7:9:11:13:13:13:13:13:13,10,1,no,0,0x2,0,weapon,0, NPC_VAMPIRE_GIFT,Vampire Gift -680,0,6,4,0,0x3,5:7:9:11:13:13:13:13:13:13,10,1,no,0,0x2,0,none,0, NPC_WIDESOULDRAIN,Wide Soul Drain - -// Cash Shop Skill -681,0,0,0,0,0,0,10,0,no,0,0x1,0,none,0, ALL_INCCARRY,Increase Weight Limit R - -// Additional NPC skill (Episode 12) -682,0,0,4,0,0x1,0,1,1,no,0,0x2,0,none,0, NPC_TALK,Talk -683,-9,6,1,-1,0,0,1,1,no,0,0x2,0,none,0, NPC_HELLPOWER,Hell Power -684,0,6,4,0,0x3,-1,1,1,no,0,0x2,0,none,0, NPC_WIDEHELLDIGNITY,Hell Dignity -685,0,0,4,0,0x1,0,1,1,no,0,0x2,0,none,0, NPC_INVINCIBLE,Invincible -686,0,0,4,0,0x1,0,1,1,no,0,0x2,0,none,0, NPC_INVINCIBLEOFF,Invincible off -687,0,6,4,0,0x1,0,1,1,yes,0,0x2,0,none,0, NPC_ALLHEAL,Full Heal - -// Additional Skill (??) -688,9,6,16,0,0x1,0,10,0,no,0,0x200,0,none,0, GM_SANDMAN,GM Sandman -689,0,6,4,0,0x3,-1,10,1,yes,0,0x2,0,magic,0, CASH_BLESSING,Party Blessing -690,0,6,4,0,0x3,-1,10,1,yes,0,0x2,0,magic,0, CASH_INCAGI,Party Increase AGI -691,0,6,4,0,0x3,-1,5,1,yes,0,0x2,0,magic,0, CASH_ASSUMPTIO,Party Assumptio -//692,0,0,0,0,0,0,9,0,no,0,0x2,0,none,0, ALL_CATCRY,Cat Cry -693,0,6,4,0,0x3,-1,1,1,yes,0,0x2,0,magic,0, ALL_PARTYFLEE,Party Flee -//694,0,0,0,0,0,0,9,0,no,0,0x2,0,none,0, ALL_ANGEL_PROTECT,Angel's Protection -//695,0,0,0,0,0,0,9,0,no,0,0x2,0,none,0, ALL_DREAM_SUMMERNIGHT,Summer Night Dream -//696,0,0,0,0,0,0,9,0,no,0,0x2,0,none,0, NPC_CHANGEUNDEAD2,Change Undead -//697,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, ALL_REVERSEORCISH,Reverse Orcish -698,0,6,4,0,0x01,0,1,1,no,0,0x2,0,none,0, ALL_WEWISH,Christmas Carol -//699,0,0,0,0,0,0,9,0,no,0,0x2,0,none,0, ALL_SONKRAN,ALL_SONKRAN - -// New NPC Wide Status AoE Skills And Others -//700,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDEHEALTHFEAR,Wide Health Fear -//701,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDEBODYBURNNING,Wide Body Burnning -//702,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDEFROSTMISTY,Wide Freezing -//703,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDECOLD,Wide Crystalize -//704,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDE_DEEP_SLEEP,Wide Deep Sleep -//705,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDESIREN,Wide Siren's Voice -//706,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_VENOMFOG,Venom Fog -//707,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_MILLENNIUMSHIELD,Millenium Shield 2 -//708,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_COMET,Comet 2 - -1001,9,6,1,-1,0,0,1,1,no,0,0x1,0,weapon,0, KN_CHARGEATK,Charge Attack -1002,0,6,4,0,0x1,0,1,0,no,0,0x1,0,weapon,2, CR_SHRINK,Shrink -1003,0,0,0,0,0,0,1,0,no,0,0x1,0,weapon,0, AS_SONICACCEL,Sonic Acceleration -1004,9,8,1,0,0x8,0,1,1,no,0,0x1,0,weapon,0, AS_VENOMKNIFE,Throw Venom Knife -1005,1,6,1,0,0x1,0,1,1,no,0,0x1,0,weapon,0, RG_CLOSECONFINE,Close Confine -1006,0,6,4,3,0,2,1,1,yes,0,0x1,0,magic,3, WZ_SIGHTBLASTER,Sight Blaster -1007,0,6,4,0,0x1,0,1,0,no,0,0x1,0,none,0, SA_CREATECON,Create Elemental Converter -1008,9,6,1,1,0x1,0,1,1,yes,0,0x1,0,magic,0, SA_ELEMENTWATER,Elemental Change Water -1009,-9,6,1,0,0,0,1,1,no,0,0x1,0,weapon,3, HT_PHANTASMIC,Phantasmic Arrow -1010,9,6,1,0,0x1,0,1,0,no,0,0x1,0,misc,0, BA_PANGVOICE,Pang Voice -1011,9,6,1,0,0x1,0,1,0,no,0,0x1,0,misc,0, DC_WINKCHARM,Wink of Charm -1012,0,0,0,0,0,0,1,0,no,0,0x1,0,weapon,0, BS_UNFAIRLYTRICK,Unfair Trick -1013,0,6,4,0,0x3,2,1,0,no,0,0x1,0,weapon,0, BS_GREED,Greed -1014,0,6,4,6,0x3,14,1,0,yes,0,0x1,0,magic,0, PR_REDEMPTIO,Redemptio -1015,9,6,16,0,0x1,0,1,1,no,0,0x401,0,weapon,0, MO_KITRANSLATION,Ki Translation -1016,-1,6,1,-1,0x2,1,1,1,no,0,0x1,0,weapon,5, MO_BALKYOUNG,Ki Explosion -1017,9,6,1,2,0x1,0,1,1,yes,0,0x1,0,magic,0, SA_ELEMENTGROUND,Elemental Change Earth -1018,9,6,1,3,0x1,0,1,1,yes,0,0x1,0,magic,0, SA_ELEMENTFIRE,Elemental Change Fire -1019,9,6,1,4,0x1,0,1,1,yes,0,0x1,0,magic,0, SA_ELEMENTWIND,Elemental Change Wind - -//**** -// RK Rune Knight -//**** -2001,1,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, RK_ENCHANTBLADE,Enchant Blade -2002,7:8:9:10:11,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, RK_SONICWAVE,Sonic Wave -2003,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, RK_DEATHBOUND,Death Bound -2004,1,8,1,-1,0,0,10,-5,no,0,0,0,weapon,0, RK_HUNDREDSPEAR,Hundred Spear -2005,1,6,2,4,0x2,2,5,1,no,0,0,0,weapon,3, RK_WINDCUTTER,Wind Cutter -2006,0,6,4,-1,0x2,5,5,1,no,0,0,0,weapon,0, RK_IGNITIONBREAK,Ignition Break -2007,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, RK_DRAGONTRAINING,Dragon Training -2008,9,6,2,3,0xC2,1:1:1:2:2:2:3:3:4:4,10,1,no,0,0,0,misc,0, RK_DRAGONBREATH,Dragon Breath //CHECK May have to change this back to a weapon type attack. -2009,0,6,4,0,0x3,3:4:5:6:7,5,1,yes,0,0,0,weapon,0, RK_DRAGONHOWLING,Dragon Howling -2010,0,0,0,0,0,0,10,0,no,0,0,0,none,0, RK_RUNEMASTERY,Rune Mastery -2011,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, RK_MILLENNIUMSHIELD,Millenium Shield -2012,1,6,4,-1,0,0x8,1,1,yes,0,0,0,weapon,0, RK_CRUSHSTRIKE,Crush Strike -2013,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, RK_REFRESH,Refresh -2014,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, RK_GIANTGROWTH,Giant Growth -2015,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, RK_STONEHARDSKIN,Stone Hard Skin -2016,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, RK_VITALITYACTIVATION,Vitality Activation -2017,0,6,4,-1,0x2,3,1,1,no,0,0,0,weapon,7, RK_STORMBLAST,Storm Blast -2018,0,6,4,0,0x3,-1,1,1,yes,0,0,0,none,0, RK_FIGHTINGSPIRIT,Fighting Spirit //CHECK Is this splash needed? -2019,9,6,4,6,0x1,0,1,1,yes,0,0,0,none,0, RK_ABUNDANCE,Abundance -2020,5:6:7:8:9,6,1,-1,0,0,5,1,yes,0,0,0,weapon,0, RK_PHANTOMTHRUST,Phantom Thrust - -//**** -// WL Warlock -//**** -2201,11,6,16,0,0,0,5,1,yes,0,0,0,magic,0, WL_WHITEIMPRISON,White Imprison -2202,11,8,1,8,0x2,1:1:1:2:2,5,-2,yes,0,0,0,magic,0, WL_SOULEXPANSION,Soul Expansion -2203,0,8,4,1,0x2,13,5,-3:-4:-5:-6:-7,yes,0,0,0,magic,0, WL_FROSTMISTY,Frosty Misty -2204,0,8,4,1,0x2,13,5,-5,yes,0,0,0,magic,0, WL_JACKFROST,Jack Frost -2205,11,6,1,0,0x1,0,5,1,yes,0,0,0,magic,0, WL_MARSHOFABYSS,Marsh of Abyss -2206,0,6,4,0,0x1,0,5,1,yes,0,0,0,magic,0, WL_RECOGNIZEDSPELL,Recognized Spell -2207,7,6,1,2,0x3,1:2:2:3:3,5,1,yes,0,0,0,magic,0, WL_SIENNAEXECRATE,Sienna Execrate -2208,0,0,0,0,0,0,3,0,no,0,0,0,none,0, WL_RADIUS,Radius -2209,0,6,4,0,0x3,9:10:11:12:13,5,1,yes,0,0,0,magic,0, WL_STASIS,Stasis -2210,11,6,1,0,0,0,5,1,yes,0,0,0,magic,0, WL_DRAINLIFE,Drain Life -2211,11,8,1,3,0x2,3,5,-7,yes,0,0,0,magic,3, WL_CRIMSONROCK,Crimson Rock -2212,11,6,1,3,0,0,5,1,yes,0,0,0,magic,0, WL_HELLINFERNO,Hell Inferno -2213,11,8,2,0,0x2,15,5,-20,yes,0,0,0,magic,2, WL_COMET,Comet //CHECK AoE in official code appears to be 15 x 15, yet casting circle is much bigger then that. -2214,11,6,1,0,0,3,5,1,yes,0,0,0,magic,0, WL_CHAINLIGHTNING,Chain Lightning //CHECK Is the splash being used for the target search? -2215,11,6,1,4,0,0,5,1,no,0,0,0,magic,0, WL_CHAINLIGHTNING_ATK,Chain Lightning Attack -2216,3,8,2,2,0,0,5,-6:-7:-8:-9:-10,yes,0,0,0,magic,0, WL_EARTHSTRAIN,Earth Strain -2217,11,6,1,0,0,0,5,1,yes,0,0,0,magic,0, WL_TETRAVORTEX,Tetra Vortex -2218,11,6,1,3,0,0,5,1,no,0,0,0,magic,0, WL_TETRAVORTEX_FIRE,Tetra Vortex Fire -2219,11,6,1,1,0,0,5,1,no,0,0,0,magic,0, WL_TETRAVORTEX_WATER,Tetra Vortex Water -2220,11,6,1,4,0,0,5,1,no,0,0,0,magic,0, WL_TETRAVORTEX_WIND,Tetra Vortex Wind -2221,11,6,1,2,0,0,5,1,no,0,0,0,magic,0, WL_TETRAVORTEX_GROUND,Tetra Vortex Earth -2222,0,6,4,3,0x1,0,5,1,yes,0,0,0,magic,0, WL_SUMMONFB,Summon Fire Ball -2223,0,6,4,4,0x1,0,5,1,yes,0,0,0,magic,0, WL_SUMMONBL,Summon Lightning Ball -2224,0,6,4,1,0x1,0,5,1,yes,0,0,0,magic,0, WL_SUMMONWB,Summon Water Ball -2225,11,6,1,3,0,0,5,1,no,0,0,1,magic,0, WL_SUMMON_ATK_FIRE,Summon Attack Fire //CHECK Summon attack ID's dont appear to have a range. -2226,11,6,1,4,0,0,5,1,no,0,0,1,magic,0, WL_SUMMON_ATK_WIND,Summon Attack Wind -2227,11,6,1,1,0,0,5,1,no,0,0,1,magic,0, WL_SUMMON_ATK_WATER,Summon Attack Water -2228,11,6,1,2,0,0,5,1,no,0,0,1,magic,0, WL_SUMMON_ATK_GROUND,Summon Attack Earth -2229,0,6,4,2,0x1,0,5,1,yes,0,0,0,magic,0, WL_SUMMONSTONE,Summon Stone -2230,11,8,1,0,0,0,2,1,yes,0,0,0,magic,0, WL_RELEASE,Release //CHECK Should it be left to do multi hit or single hit? -2231,0,6,4,0,0x1,0,1,1,yes,0,0,0,magic,0, WL_READING_SB,Reading Spellbook -2232,0,0,0,0,0,0,5,0,no,0,0,0,none,0, WL_FREEZE_SP,Freeze Spell - - -//**** -// GC Guillotine Cross -//**** -2021,5,6,1,0,0x1,0,5,1,no,0,0,0,none,0, GC_VENOMIMPRESS,Venom Impress -2022,3,8,1,-1,0,0,5,-7,no,0,0,0,weapon,0, GC_CROSSIMPACT,Cross Impact -2023,3:4:5:6:7,6,1,-1,0,0,5,1,no,0,0,0,weapon,0,GC_DARKILLUSION,Dark Illusion -2024,0,0,0,0,0,0,10,0,no,0,0,0,none,0, GC_RESEARCHNEWPOISON,Research New Poison -2025,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, GC_CREATENEWPOISON,Create New Poison -2026,5,6,16,0,0x1,0,1,1,no,0,0,0,none,0, GC_ANTIDOTE,Antidote -2027,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, GC_POISONINGWEAPON,Poisoning Weapon -2028,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, GC_WEAPONBLOCKING,Weapon Blocking -2029,-2,6,4,-1,0x2,1,5,1,no,0,0,0,weapon,3, GC_COUNTERSLASH,Counter Slash -2030,-2,6,4,-1,0x1,0,5,1,no,0,0x200,0,weapon,0, GC_WEAPONCRUSH,Weapon Crush //CHECK SHould this and the above skill have INF2 0x200? -2031,1,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, GC_VENOMPRESSURE,Venom Pressure -2032,5,6,2,0,0x1,0,5,1,yes,0,0,1,none,0, GC_POISONSMOKE,Poison Smoke -2033,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, GC_CLOAKINGEXCEED,Cloaking Exceed -2034,0,6,4,-1,0x2,3,1,1,no,0,0,0,weapon,0, GC_PHANTOMMENACE,Phantom Menace -2035,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, GC_HALLUCINATIONWALK,Hallucination Walk -2036,0,6,4,-1,0x2,1:1:1:1:2,5,1,no,0,0,0,weapon,0, GC_ROLLINGCUTTER,Rolling Cutter -2037,9:10:11:12:13,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, GC_CROSSRIPPERSLASHER,Cross Ripper Slasher - -//**** -// AB Arch Bishop -//**** -2038,11,8,1,6,0x2,3,5,-3,yes,0,0,0,magic,0, AB_JUDEX,Judex -2039,0,6,4,0,0x1,0,1,1,yes,0,0,0,magic,0, AB_ANCILLA,Ancilla -2040,11,8,1,6,0,0,10,-10,yes,0,0,0,magic,0, AB_ADORAMUS,Adoramus -2041,0,6,4,6,0x3,3:7:15,3,1,yes,0,0,0,magic,0, AB_CLEMENTIA,Crementia -2042,0,6,4,6,0x3,3:7:15,3,1,yes,0,0,0,magic,0, AB_CANTO,Canto Candidus -2043,0,6,4,6,0x3,3:7:15,3,1,yes,0,0,0,magic,0, AB_CHEAL,Coluceo Heal -2044,11,6,2,6,0x1,0,5,1,yes,0,0,1,magic,0, AB_EPICLESIS,Epiclesis -2045,0,6,4,6,0x3,15,10,1,yes,0,0,0,magic,0, AB_PRAEFATIO,Praefatio -2046,0,6,4,6,0x3,15,10,1,yes,0,0,0,magic,0, AB_ORATIO,Oratio -2047,0,6,4,6,0x3,15,4,1,yes,0,0,0,magic,0, AB_LAUDAAGNUS,Lauda Agnus -2048,0,6,4,6,0x3,15,4,1,yes,0,0,0,magic,0, AB_LAUDARAMUS,Lauda Ramus -2049,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AB_EUCHARISTICA,Eucharistica -2050,11,6,16,6,0x1,0,1,1,yes,0,0,0,magic,0, AB_RENOVATIO,Renovatio -2051,11,6,16,6,0x21,0,5,1,yes,0,0,0,magic,0, AB_HIGHNESSHEAL,Highness Heal //CHECK Info shows this has magic attack. -2052,11,6,1,0,0x1,0,5,1,yes,0,0xA00,0,magic,0, AB_CLEARANCE,Clearance //CHECK Also shows this as a magic attack. Why? -2053,0,6,16,0,0x1,0,5,1,yes,0,0,0,magic,0, AB_EXPIATIO,Expiatio //CHECK Does this also give the buff to party members? -2054,0,6,4,6,0x1,0,10,1,yes,0,0,0,none,0, AB_DUPLELIGHT,Duple Light //CHECK Had issues adding a skill level check to make the % go higher with the skills level. Will do later. -2055,-1,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, AB_DUPLELIGHT_MELEE,Duple Light Melee -2056,-1,6,1,0,0,0,10,1,no,0,0,0,magic,0, AB_DUPLELIGHT_MAGIC,Duple Light Magic -2057,0,6,4,6,0x3,4:5:6:7:8,5,1,yes,0,0,0,magic,0, AB_SILENTIUM,Silentium //CHECk Marked magic attack as well. Hmmmm.... - -2515,11,6,16,0,0x1,0,5,1,yes,0,0,0,magic,0, AB_SECRAMENT,Secrament - -//**** -// RA Ranger -//**** -2233,9,8,1,-1,0x2,3:3:3:3:3:4:4:4:4:5,10,-3,yes,0,0,0,weapon,0, RA_ARROWSTORM,Arrow Storm -2234,0,6,4,0,0,0,5,1,yes,0,0,0,none,0, RA_FEARBREEZE,Fear Breeze -2235,0,0,0,0,0,0,10,0,no,0,0,0,none,0, RA_RANGERMAIN,Ranger Main -2236,9,8,1,-1,0,0,10,1,yes,0,0,0,weapon,0, RA_AIMEDBOLT,Aimed Bolt -2237,9,6,2,0,0x3,3,1,1,no,0,0,0,none,0, RA_DETONATOR,Detonator -2238,3,6,2,0,0x3,2,5,1,no,0,0x80,3,misc,0, RA_ELECTRICSHOCKER,Electric Shocker -2239,3,6,2,0,0x42,3,5,1,no,0,0x80,3,misc,0, RA_CLUSTERBOMB,Cluster Bomb -2240,0,6,4,0,0,0,1,1,no,0,0,0,none,0, RA_WUGMASTERY,Warg Mastery -2241,0,6,4,0,0,0,3,1,no,0,0,0,none,0, RA_WUGRIDER,Warg Rider -2242,0,6,4,-1,0x2,1,1,0,no,0,0,0,weapon,0, RA_WUGDASH,Warg Dash -2243,9,6,1,0,0,0,5,1,no,0,0,0,weapon,0, RA_WUGSTRIKE,Warg Strike -2244,9,6,1,0,0,0,5,1,no,0,0,0,weapon,0, RA_WUGBITE,Warg Bite -2245,0,0,0,0,0,0,10,0,no,0,0,0,none,0, RA_TOOTHOFWUG,Tooth of Warg -2246,0,6,4,0,0x2,3:4:5:6:7,5,1,no,0,0,0,weapon,0, RA_SENSITIVEKEEN,Sensitive Keen -2247,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, RA_CAMOUFLAGE,Camouflage -2248,0,0,0,0,0,0,5,0,no,0,0,0,none,0, RA_RESEARCHTRAP,Research Trap -2249,3,6,2,3,0x43,2,1,1,no,0,0x80,1,misc,0, RA_MAGENTATRAP,Magenta Trap -2250,3,6,2,1,0x43,2,1,1,no,0,0x80,1,misc,0, RA_COBALTTRAP,Cobalt Trap -2251,3,6,2,2,0x43,2,1,1,no,0,0x80,1,misc,0, RA_MAIZETRAP,Maize Trap -2252,3,6,2,4,0x43,2,1,1,no,0,0x80,1,misc,0, RA_VERDURETRAP,Verdure Trap -2253,3,6,2,0,0x42,2,5,1,no,0,0x80,2,misc,0, RA_FIRINGTRAP,Firing Trap -2254,3,6,2,0,0x42,2,5,1,no,0,0x80,2,misc,0, RA_ICEBOUNDTRAP,Icebound Trap - -//**** -// NC Mechanic -2255,0,0,0,0,0,0,5,0,no,0,0,0,none,0, NC_MADOLICENCE,Mado License -2256,11,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, NC_BOOSTKNUCKLE,Boost Knuckle -2257,3,6,1,-1,0,0,3,1,no,0,0,0,weapon,0, NC_PILEBUNKER,Pile Bunker -2258,13,6,1,-1,0,0,3,1,no,0,0,0,weapon,0, NC_VULCANARM,Vulcan Arm -2259,5,6,1,3,0,2,3,1,no,0,0,5,weapon,0, NC_FLAMELAUNCHER,Flame Launcher -2260,7,6,2,1,0x2,2:3:4,3,1,no,0,0,0,weapon,0, NC_COLDSLOWER,Cold Slower -2261,7,6,2,-1,0x42,3:2:1,3,1,no,0,0,0,weapon,0, NC_ARMSCANNON,Arm Cannon -2262,0,6,4,0,0x1,0,3,1,no,0,0,0,none,0, NC_ACCELERATION,Acceleration -2263,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, NC_HOVERING,Hovering -2264,0,6,4,0,0x1,0,1,1,no,0,0,0,none,7, NC_F_SIDESLIDE,Front-Side Slide -2265,0,6,4,0,0x1,0,1,1,no,0,0,0,none,7, NC_B_SIDESLIDE,Back-Side Slide -2266,0,0,0,0,0,0,4,0,no,0,0,0,none,0, NC_MAINFRAME,Mainframe Restructure // Check me. Part of the code notes translated to "The amount of fuel have". -2267,0,6,4,-1,0x42,2:3:4,3,1,no,0,0,0,misc,5, NC_SELFDESTRUCTION,Self Destruction -2268,0,6,4,0,0x1,0,4,1,yes,0,0,0,none,0, NC_SHAPESHIFT,Shape Shift -2269,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, NC_EMERGENCYCOOL,Emergency Cool -2270,0,6,4,0,0x3,7,1,1,yes,0,0,0,none,0, NC_INFRAREDSCAN,Infrared Scan -2271,9,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, NC_ANALYZE,Analyze -2272,0,6,4,0,0x3,1:2:3,3,1,yes,0,0,0,none,0, NC_MAGNETICFIELD,Magnetic Field -2273,0,6,4,0,0x1,0,3,1,yes,0,0,0,none,0, NC_NEUTRALBARRIER,Neutral Barrier -2274,0,6,4,0,0x1,0,3,1,yes,0,0,0,none,0, NC_STEALTHFIELD,Stealth Field -2275,5,6,16,0,0x1,0,5,1,yes,0,0,0,magic,0, NC_REPAIR,Repair -2276,0,0,0,0,0,0,10,0,no,0,0,0,none,0, NC_TRAININGAXE,Axe Training -2277,0,0,0,0,0,0,5,0,no,0,0,0,none,0, NC_RESEARCHFE,Research Fire/Earth -2278,4:5:6:7:8,6,1,-1,0,0,5,1,no,0,0,0,weapon,2:3:4:5:6, NC_AXEBOOMERANG,Axe Boomerang -2279,1,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, NC_POWERSWING,Power Swing -2280,0,8,4,-1,0x2,2:2:3:3:3,5,-6,no,0,0,0,weapon,0, NC_AXETORNADO,Axe Tornado // Check me. Takes 20 * Skill LV amount of HP each use. -2281,2,6,2,0,0x1,0,5,1,yes,0,0,2,none,0, NC_SILVERSNIPER,FAW - Silver Sniper -2282,2,6,2,0,0x1,0,5,1,yes,0,0,2,none,0, NC_MAGICDECOY,FAW - Magic Decoy //CHECK FIX ME!!!! Wind and Earth stones spawning opposite decoys. -2283,2,6,1,0,0x1,0,1,1,no,0,0,0,none,0, NC_DISJOINT,FAW Removal - -//**** -// SC Shadow Chaser -2284,1,6,1,-1,0x2,1,5,1,no,0,0,0,weapon,0, SC_FATALMENACE,Fatal Menace -2285,0,6,4,0,0x1,0,10,1,no,0,0,0,none,0, SC_REPRODUCE,Reproduce -2286,0,6,4,0,0x1,0,10,1,yes,0,0,0,none,0, SC_AUTOSHADOWSPELL,Auto Shadow Spell -2287,5,6,1,0,0x1,0,5,1,no,0,0,0,none,0, SC_SHADOWFORM,Shadow Form -2288,7:7:7:9:9:9:9:11:11:11,8,1,-1,0,0,10,-3,yes,0,0,0,weapon,3, SC_TRIANGLESHOT,Triangle Shot -2289,0,6,4,0,0x3,2,5,1,no,0,0,0,none,0, SC_BODYPAINT,Body Painting -2290,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, SC_INVISIBILITY,Invisibility -2291,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, SC_DEADLYINFECT,Deadly Infect -2292,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_ENERVATION,Masquerade - Enervation -2293,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_GROOMY,Masquerade - Gloomy -2294,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_IGNORANCE,Masquerade - Ignorance -2295,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_LAZINESS,Masquerade - Laziness -2296,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_UNLUCKY,Masquerade - Unlucky -2297,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_WEAKNESS,Masquerade - Weakness -2298,3,6,1,0,0x1,0,5,1,yes,0,0,0,weapon,0, SC_STRIPACCESSARY,Strip Accessory //CHECK Is weapon attack type needed? -2299,7,6,2,0,0x1,0,3,1,yes,0,0,3,none,0, SC_MANHOLE,Man Hole -2300,7,6,2,0,0x1,0,3,1,yes,0,0,1,none,0, SC_DIMENSIONDOOR,Dimension Door -2301,7,6,2,0,0x1,0,3,1,yes,0,0,0,none,0, SC_CHAOSPANIC,Chaos Panic -2302,7,6,2,0,0x1,0,3,1,yes,0,0,0,none,0, SC_MAELSTROM,Maelstrom -2303,7,6,2,0,0x1,0,3,1,yes,0,0,0,none,0, SC_BLOODYLUST,Bloody Lust -2304,0,6,4,-1,0,0,3,1,no,0,0,0,weapon,0, SC_FEINTBOMB,Feint Bomb - -//**** -// LG Royal Guard -2307,11,8,1,-1,0,2,5,1,no,0,0,10,weapon,0, LG_CANNONSPEAR,Cannon Spear -2308,7,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, LG_BANISHINGPOINT,Banishing Point -2309,0,6,4,0,0x3,2,3,1,no,0,0,0,none,0, LG_TRAMPLE,Trample -2310,1,6,1,0,0,0,5,1,no,0,0,0,weapon,0, LG_SHIELDPRESS,Shield Press -2311,0,6,4,0,0x3,3,5,1,no,0,0,0,none,0, LG_REFLECTDAMAGE,Reflect Damage -2312,5,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, LG_PINPOINTATTACK,Pinpoint Attack -2313,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, LG_FORCEOFVANGUARD,Force of Vanguard -2314,1,6,1,-1,0,0,1,1,no,0,0,0,weapon,0, LG_RAGEBURST,Rage Burst -2315,0,6,4,0,0x2,3,3,1,yes,0,0,0,none,2, LG_SHIELDSPELL,Shield Spell -2316,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, LG_EXEEDBREAK,Exceed Break -2317,1,6,2,-1,0x2,5,5,1,yes,0,0,0,none,3:4:5:6:7, LG_OVERBRAND,Over Brand //CHECK I know the splash is needed somehow for the strange AoE it gives. -2318,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, LG_PRESTIGE,Prestige -2319,0,6,4,0,0x1,3,5,1,no,0,0,0,weapon,0, LG_BANDING,Banding //CHECK Splash isnt needed right? Banding has its own UNIT ID. -2320,0,6,4,-1,0x2,3,5,1,yes,0,0,0,weapon,0, LG_MOONSLASHER,Moon Slasher -2321,1,8,2,6,0x2,5,5,-7,yes,0,0,0,weapon,0, LG_RAYOFGENESIS,Ray of Genesis -2322,0,6,16,0,0x3,1,5,1,yes,0,0,0,none,0, LG_PIETY,Piety -2323,0,8,4,2,0x2,1:1:2:2:3,5,-5,yes,0,0,0,weapon,0, LG_EARTHDRIVE,Earth Drive -2324,3,8,1,-1,0,0,5,3,yes,0,0,0,weapon,0, LG_HESPERUSLIT,Hesperus Lit -2325,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, LG_INSPIRATION,Inspiration - -//**** -// SR Sura -2326,-2,8,1,-1,0,0,10,2,no,0,0,0,weapon,0, SR_DRAGONCOMBO,Dragon Combo //CHECK Is this 2 regular hits or sub hits? Yes its 2 sub hits. -2327,0,8,4,-1,0x2,2,5,-3,no,0,0,0,weapon,3, SR_SKYNETBLOW,Sky Net Blow //CHECK Video shows 3 hits. Its sub hits right? Data check shows no sub, one source shows 3 hits, another shows 5. -2328,0,6,4,-1,0x2,1:2:3:4:5,5,1,no,0,0,0,weapon,0, SR_EARTHSHAKER,Earth Shaker //CHECK Must add a check in battle.c to triple damage if hitting a hidden target. -2329,-2,8,4,-1,0,0,5,-2,no,0,0x200,0,weapon,0, SR_FALLENEMPIRE,Fallen Empire //CHECK Video shows 2 hits. Is it sub hits? Yes its divided between 2 hits. -2330,-2,6,1,-1,0x42,1:1:1:1:1:2:2:2:2:2,10,1,yes,0,0,0,weapon,0, SR_TIGERCANNON,Tiger Cannon //CHECK Need to fix to be enemy targeted and also combo after Fallen Empire. -2331,0,0,0,0,0,0,10,0,no,0,0,0,none,0, SR_HELLGATE,Hell Gate -2332,5,6,4,-1,0x2,3,5,1,no,0,0,0,weapon,0, SR_RAMPAGEBLASTER,Rampage Blaster -2333,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, SR_CRESCENTELBOW,Crescent Elbow //CHECK Check the autospell ID. -2334,0,6,4,0,0x3,1:1:2:2:3,5,1,no,0,0,0,none,0, SR_CURSEDCIRCLE,Cursed Circle //CHECK Code shows it takes up to 5% of your HP upon use? -2335,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, SR_LIGHTNINGWALK,Lightning Walk -2336,7:8:9:10:11,6,1,-1,0,0,5,1,no,0,0,0,weapon,2:3:4:5:6, SR_KNUCKLEARROW,Knuckle Arrow -2337,0,6,4,-1,0x2,2,1,1,yes,0,0,0,weapon,0, SR_WINDMILL,Windmill -2338,0,6,4,0,0x1,0,10,1,no,0,0,0,none,0, SR_RAISINGDRAGON,Raising Dragon -2339,0,0,0,0,0,0,5,1,no,0,0,0,none,0, SR_GENTLETOUCH,Gentle Touch -2340,0,6,4,0,0x3,2,1,1,no,0,0,0,none,0, SR_ASSIMILATEPOWER,Assimilate Power -2341,3,6,16,0,0x1,0,1,1,yes,0,0x200,0,none,0, SR_POWERVELOCITY,Power Velocity -2342,1,6,1,-1,0x20,0,5,1,no,0,0,0,weapon,3, SR_CRESCENTELBOW_AUTOSPELL,Crescent Elbow Autospell //CHECK Does this ignore defense? -2343,1:2:3:3:4:4:5:5:6:7,8,1,0,0,0,10,-7,yes,0,0,0,weapon,0, SR_GATEOFHELL,Gate of Hell //CHECK Need to fix to be enemy targeted and also combo after Fallen Empire -2344,2,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, SR_GENTLETOUCH_QUIET,Gentle Touch - Quiet -2345,2,6,16,0,0x1,0,5,1,no,0,0,0,magic,0, SR_GENTLETOUCH_CURE,Gentle Touch - Cure //CHECK Its a healing skill. Guessing it has to be magic type? Healing isnt working. -2346,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, SR_GENTLETOUCH_ENERGYGAIN,Gentle Touch - Energy Gain -2347,2,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, SR_GENTLETOUCH_CHANGE,Gentle Touch - Change -2348,2,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, SR_GENTLETOUCH_REVITALIZE,Gentle Touch - Revitalize -//More from Sura but not following ID order -2517,0,6,4,-1,0x2,3:4:5:6:7,5,1,no,0,0,0,weapon,0, SR_HOWLINGOFLION,Howling of Lion -2518,11,6,2,-1,0x2,2:2:3:3:4,5,1,no,0,0,0,weapon,0, SR_RIDEINLIGHTNING,Ride In Lightening - -//**** -// WA Wanderer -2350,0,6,4,0,0x3,7:8:9:10:11,5,1,yes,0,0,0,none,0, WA_SWING_DANCE,Swing Dance -2351,0,6,4,0,0x3,7:8:9:10:11,5,1,yes,0,0,0,none,0, WA_SYMPHONY_OF_LOVER,Symphony of Lovers -2352,0,6,4,0,0x3,7:8:9:10:11,5,1,yes,0,0,0,none,0, WA_MOONLIT_SERENADE,Moonlit Serenade - -//**** -// MI Minstrel -2381,0,6,4,0,0x3,7:8:9:10:11,5,1,yes,0,0,0,none,0, MI_RUSH_WINDMILL,Windmill Rush Attack -2382,0,6,4,0,0x3,7:8:9:10:11,5,1,yes,0,0,0,none,0, MI_ECHOSONG,Echo Song -2383,9,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, MI_HARMONIZE,Harmonize - -//**** -// WM Wanderer/Minstrel -2412,0,0,0,0,0,0,10,0,no,0,0,0,none,0, WM_LESSON,Lesson -2413,9,8,1,-1,0,0,5,-2:-2:-3:-3:-4,yes,0,0,0,magic,0, WM_METALICSOUND,Metallic Sound -2414,9,6,2,-1,0x3,1,5,1,yes,0,0x80,3,none,0, WM_REVERBERATION,Reverberation -2415,0,6,1,-1,0x6,1,5,1,no,0,0,0,weapon,0, WM_REVERBERATION_MELEE,Reverberation Melee -2416,0,6,1,0,0x6,1,5,1,no,0,0,0,magic,0, WM_REVERBERATION_MAGIC,Reverberation Magic -2417,11,6,2,0,0x3,5,1,1,no,0,0,0,none,0, WM_DOMINION_IMPULSE,Dominion Impulse -2418,9,6,2,-1,0x1,0,5,1,yes,0,0,0,none,0, WM_SEVERE_RAINSTORM,Severe Rainstorm -2419,9,6,2,0,0x3,1,5,1,yes,0,0x80,5,none,0, WM_POEMOFNETHERWORLD,Poem of The Netherworld //CHECK May need to recode too. -2420,0,6,4,0,0x2,2:3:4:5:6,5,1,yes,0,0,0,none,0, WM_VOICEOFSIREN,Voice of Siren -2421,7,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, WM_DEADHILLHERE,Valley of Death -2422,7,6,2,0,0x3,5:6:7:8:9,5,1,yes,0,0,0,none,0, WM_LULLABY_DEEPSLEEP,Deep Sleep Lullaby -2423,0,6,4,0,0x3,3:4:5:6:7,5,1,yes,0,0,0,none,0, WM_SIRCLEOFNATURE,Circle of Nature's Sound -2424,9,6,4,0,0x1,0,5,1,yes,0,0,0,magic,0, WM_RANDOMIZESPELL,Improvised Song -2425,9,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, WM_GLOOMYDAY,Gloomy Day -2426,9,6,2,0,0x2,2:3:3:4:4,5,1,yes,0,0x4000,0,weapon,0, WM_GREAT_ECHO,Great Echo -2427,0,6,4,0,0x3,5:6:7:8:9,5,1,yes,0,0x4000,0,none,0, WM_SONG_OF_MANA,Song of Mana -2428,0,6,4,0,0x3,5:6:7:8:9,5,1,yes,0,0x4000,0,none,0, WM_DANCE_WITH_WUG,Dance With A Warg -2429,9,6,1,0,0x2,2:2:3:3:4,5,1,yes,0,0x4000,0,weapon,0, WM_SOUND_OF_DESTRUCTION,Sound of Destruction //CHECK Source shows its magic attack. Need to confirm before changing. -2430,0,6,4,0,0x3,3:4:5:6:7,5,1,yes,0,0x4000,0,none,0, WM_SATURDAY_NIGHT_FEVER,Saturday Night Fever -2431,0,6,4,0,0x3,5,5,1,yes,0,0x4000,0,magic,0, WM_LERADS_DEW,Lerad's Dew -2432,0,6,4,0,0x3,5,5,1,yes,0,0x4000,0,none,0, WM_MELODYOFSINK,Melody of Sink -2433,0,6,4,0,0x3,5,5,1,yes,0,0x4000,0,none,0, WM_BEYOND_OF_WARCRY,Warcry of Beyond -2434,0,6,4,0,0x3,5,5,1,yes,0,0x4000,0,none,0, WM_UNLIMITED_HUMMING_VOICE,Unlimited Humming Voice -2516,11,6,1,-1,0x2,5,5,1,no,0,0,0,weapon,0, WM_SEVERE_RAINSTORM_MELEE,Severe Rainstorm Melee - -//**** -// SO Sorcerer -2443,0,6,4,3,0,0,5,1,yes,0,0,8:10:12:14:16,magic,0, SO_FIREWALK,Fire Walk //CHECK Video and data shows each cell only hits once. -2444,0,6,4,4,0,0,5,1,yes,0,0,8:10:12:14:16,magic,0, SO_ELECTRICWALK,Electric Walk -2445,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, SO_SPELLFIST,Spell Fist -2446,9,6,2,2,0,0,5,-3,yes,0,0,0,magic,0, SO_EARTHGRAVE,Earth Grave -2447,9,6,2,1,0,0,5,-5,yes,0,0,0,magic,0, SO_DIAMONDDUST,Diamond Dust -2448,9,6,1,5,0x2,1:1:1:1:2,5,1,yes,0,0,0,magic,0, SO_POISON_BUSTER,Poison Buster -2449,9,6,2,0,0,0,5,1,yes,0,0,0,magic,0, SO_PSYCHIC_WAVE,Psychic Wave -2450,9,6,2,5,0,0,5,1,yes,0,0,0,magic,0, SO_CLOUD_KILL,Cloud Kill -2451,9,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, SO_STRIKING,Striking //CHECK Data shows a % for increased successful refine rate. Is this true? -2452,9,6,2,3,0x1,0,5,1,yes,0,0,0,magic,0, SO_WARMER,Warmer -2453,9,6,2,0,0x1,0,5,1,yes,0,0,0,magic,0, SO_VACUUM_EXTREME,Vacuum Extreme -2454,9,6,1,4,0x2,1:1:2:2:3,5,1,yes,0,0,0,magic,0, SO_VARETYR_SPEAR,Varetyr Spear -2455,9,6,1,0,0x3,1:1:2:2:3,5,1,yes,0,0,0,magic,0, SO_ARRULLO,Arrullo -2456,0,6,4,0,0x1,0,4,1,yes,0,0,0,none,0, SO_EL_CONTROL,Spirit Control -2457,0,6,4,3,0x1,0,3,1,yes,0,0,0,none,0, SO_SUMMON_AGNI,Summon Fire Spirit Agni -2458,0,6,4,1,0x1,0,3,1,yes,0,0,0,none,0, SO_SUMMON_AQUA,Summon Water Spirit Aqua -2459,0,6,4,4,0x1,0,3,1,yes,0,0,0,none,0, SO_SUMMON_VENTUS,Summon Wind Spirit Ventus -2460,0,6,4,2,0x1,0,3,1,yes,0,0,0,none,0, SO_SUMMON_TERA,Summon Earth Spirit Tera -2461,5,6,1,0,0x1,0,1,1,no,0,0,0,none,0, SO_EL_ACTION,Elemental Action -2462,0,6,4,0,0x1,0,2,1,yes,0,0,0,none,0, SO_EL_ANALYSIS,Four Spirit Analysis -2463,0,0,0,0,0,0,5,0,no,0,0,0,none,0, SO_EL_SYMPATHY,Spirit Sympathy -2464,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, SO_EL_CURE,Spirit Recovery -2465,9,6,2,3,0x1,0,3,1,yes,0,0,1,magic,0, SO_FIRE_INSIGNIA,Fire Insignia //CHECK All 4 insignia skills can be targeted and animations work -2466,9,6,2,1,0x1,0,3,1,yes,0,0,1,magic,0, SO_WATER_INSIGNIA,Water Insignia // but its effects havent been coded yet. -2467,9,6,2,4,0x1,0,3,1,yes,0,0,1,magic,0, SO_WIND_INSIGNIA,Wind Insignia -2468,9,6,2,2,0x1,0,3,1,yes,0,0,1,magic,0, SO_EARTH_INSIGNIA,Earth Insignia - -//**** -// GN Genetic -2474,0,0,0,0,0,0,5,0,no,0,0,0,none,0, GN_TRAINING_SWORD,Sword Training -2475,0,0,0,0,0,0,5,0,no,0,0,0,none,0, GN_REMODELING_CART,Cart Remodeling -2476,0,6,4,-1,0x2,2,5,1,no,0,0,0,weapon,2, GN_CART_TORNADO,Cart Tornado -2477,7:8:9:10:11,6,1,-1,0x2,1:1:2:2:3,5,1,yes,0,0,0,weapon,0, GN_CARTCANNON,Cart Cannon -2478,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, GN_CARTBOOST,Cart Boost -2479,9,6,2,0,0,0,5,1,yes,0,0x80,5,misc,0, GN_THORNS_TRAP,Thorn Trap -2480,11,6,1,0,0x1,0,5,1,yes,0,0,3,misc,0, GN_BLOOD_SUCKER,Blood Sucker //CHECK Data says its a magic attack. Hmmmm.... -2481,11,6,1,-1,0x2,1:2:3:4:5,5,1,yes,0,0,0,weapon,0, GN_SPORE_EXPLOSION,Spore Explosion //CHECK Data says its element is set to neutral. Need to confirm. -2482,11,6,16,0,0,0,5,1,yes,0,0,1,weapon,2, GN_WALLOFTHORN,Wall of Thorns -2483,11,6,2,0,0x3,4,10,1,yes,0,0x2000,0,weapon,0, GN_CRAZYWEED,Crazy Weed -2484,0,6,2,2,0x2,3,10,1,no,0,0x2000,0,weapon,0, GN_CRAZYWEED_ATK,Crazy Weed Attack -2485,9,6,2,3,0,0,5,1,yes,0,0,0,magic,0, GN_DEMONIC_FIRE,Demonic Fire -2486,9,6,2,0,0,0,5,1,yes,0,0,0,none,0, GN_FIRE_EXPANSION,Fire Expansion //CHECK FIX ME!!!! Level 1 is reducing the damage. Should increase it by 50% -2487,9,6,2,0,0,0,1,1,no,0,0,0,none,0, GN_FIRE_EXPANSION_SMOKE_POWDER,Fire Expansion Smoke Powder -2488,9,6,2,0,0,0,1,1,no,0,0,0,none,0, GN_FIRE_EXPANSION_TEAR_GAS,Fire Expansion Tear Gas -2489,11,6,1,0,0,0,10,1:2:3:4:5:6:7:8:9:10,no,0,0,0,weapon,0, GN_FIRE_EXPANSION_ACID,Fire Expansion Acid -2490,9,6,2,0,0x3,1,5,1,yes,0,0x80,2:3:4:5:6,none,0, GN_HELLS_PLANT,Hell's Plant -2491,0,6,1,0,0xC0,0,5,1,no,0,0,0,misc,0, GN_HELLS_PLANT_ATK,Hell's Plant Attack -2492,0,6,4,0,0x3,6:7:8:9:10,5,1,yes,0,0,0,none,0, GN_MANDRAGORA,Howling of Mandragora -2493,11,6,16,0,0x1,0,1,1,yes,0,0,0,none,0, GN_SLINGITEM,Sling Item -2494,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, GN_CHANGEMATERIAL,Change Material -2495,0,6,4,0,0x1,0,2,1,no,0,0,0,none,0, GN_MIX_COOKING,Mix Cooking -2496,0,6,4,0,0x1,0,2,1,no,0,0,0,none,0, GN_MAKEBOMB,Create Bomb -2497,0,6,4,0,0x1,0,10,1,no,0,0,0,none,0, GN_S_PHARMACY,Special Pharmacy -2498,11,6,1,0,0,0,1,1,no,0,0,0,weapon,0, GN_SLINGITEM_RANGEMELEEATK,Sling Item Attack - -// Episode 13.3 -//2533,0,0,4,0,0x1,0,1,0,no,0,0,0,none,0, ALL_ODINS_RECALL,Odin's Recall -2534,0,0,4,0,0x1,0,1,0,no,0,0,0,none,0, RETURN_TO_ELDICASTES,Return To Eldicastes -2535,0,0,4,0,0x1,0,1,0,no,0,0x1,0,none,0, ALL_BUYING_STORE,Open Buying Store -2536,0,0,4,0,0x1,0,1,0,no,0,0,0,none,0, ALL_GUARDIAN_RECALL,Guardian's Recall -2537,9,6,16,0,0x1,0,2,1,yes,0,0,0,magic,0, ALL_ODINS_POWER,Odin's Power -//2538,0,0,0,0,0,0,??,0,no,0,0,0,none,0, BEER_BOTTLE_CAP,Beer Bottle Cap -//2539,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_ASSASSINCROSS,Assassin Cross of Sunset 2 -//2540,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_DISSONANCE,Dissonance 2 -//2541,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_UGLYDANCE,Ugly Dance 2 -//2542,0,0,0,0,0,0,??,0,no,0,0,0,none,0, ALL_TETANY,Tetany -//2543,0,0,0,0,0,0,??,0,no,0,0,0,none,0, ALL_RAY_OF_PROTECTION,Ray of Protection -//2544,0,0,0,0,0,0,??,0,no,0,0,0,none,0, MC_CARTDECORATE,Decorate Cart - -//**** -// Kagerou & Oboro -3001,0,6,4,0,0,0,1,1,no,0,0,0,none,0, KO_YAMIKUMO,Yamikumo -3002,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, KO_RIGHT,Right Hand Mastery -3003,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, KO_LEFT,Left Hand Mastery -3004,3:4:5:6:7,8,1,-1,0,0,5,-2,no,0,0,0,weapon,0, KO_JYUMONJIKIRI,Cross Strike -3005,2,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, KO_SETSUDAN,Soul Sever -3006,7:8:9:10:11,6,2,0,0x2,2,5,0,no,0,0,0,weapon,0, KO_BAKURETSU,Bakuretsu Kunai -3007,0,6,4,-1,0x42,4:4:4:4:5,5,0,no,0,0,0,misc,0, KO_HAPPOKUNAI,Happo Kunai -3008,9,8,2,0,0x52,2,10,-10,no,0,0,0,misc,0, KO_MUCHANAGE,Mucha Nage -3009,9:10:11:12:13,8,2,-1,0x2,3,5,2,no,0,0,0,weapon,0, KO_HUUMARANKA,Huuma Shuriken Ranka -3010,3,6,4,0,0x43,0,5,1,no,0,0x80,0,misc,0, KO_MAKIBISHI,Makibishi -3011,0,6,4,0,0x1,0,5,0,yes,0,0,0,none,0, KO_MEIKYOUSISUI,Meikyo Shisui -3012,0,6,4,0,0x1,0,5,0,no,0,0,1,none,7, KO_ZANZOU,Zanzou -3013,5,6,1,0,0x1,0,5,0,no,0,0,0,none,0, KO_KYOUGAKU,Kyougaku -3014,5,6,1,0,0x1,0,5,0,no,0,0,0,none,0, KO_JYUSATSU,Jyusatsu -3015,0,6,4,3,0x1,0,1,1,no,0,0,0,none,0, KO_KAHU_ENTEN,Kahu Enten -3016,0,6,4,1,0x1,0,1,1,no,0,0,0,none,0, KO_HYOUHU_HUBUKI,Hyouhu Hubuki -3017,0,6,4,4,0x1,0,1,1,no,0,0,0,none,0, KO_KAZEHU_SEIRAN,Kazehu Seiran -3018,0,6,4,2,0x1,0,1,1,no,0,0,0,none,0, KO_DOHU_KOUKAI,Dohu Koukai -3019,11,6,1,0,0,0,5,0,no,0,0,0,weapon,0, KO_KAIHOU,Technique Kaihou -3020,7,6,2,0,0,0,1,3,yes,0,0,0,magic,0, KO_ZENKAI,Zenkai -3021,5:6:7:8:9,6,16,0,0x1,0,5,1,no,0,0,0,none,0, KO_GENWAKU,Genwaku -3022,0,6,4,0,0x1,0,5,0,no,0,0,0,none,0, KO_IZAYOI,Izayoi -3023,0,6,4,0,0x3,2:3:4:5:6,5,0,no,0,0,0,none,0, KG_KAGEHUMI,Kagehumi -3024,7,6,1,0,0x1,0,5,1,no,0,0,0,none,0, KG_KYOMU,Kyomu -3025,7,6,16,0,0x1,0,5,1,no,0,0,0,none,0, KG_KAGEMUSYA,Kagemusha -3026,7,6,16,0,0x1,0,5,1,no,0,0,0,none,0, OB_ZANGETSU,Zangetsu -3027,7,6,16,0,0x1,0,5,1,no,0,0,0,none,0, OB_OBOROGENSOU,Oboro Gensou -3028,1,6,4,0,0x2,3,1,1,no,0,0,0,weapon,0, OB_OBOROGENSOU_TRANSITION_ATK, -3029,7,6,1,0,0x1,0,5,0,no,0,0,0,none,0, OB_AKAITSUKI,Akaitsuki - -8001,9,6,4,0,0x1,0,5,1,no,0,0,0,magic,0, HLIF_HEAL,Healing Touch -8002,0,6,4,0,0x3,-1,5,1,no,0,0,0,none,0, HLIF_AVOID,Avoid -8003,0,0,0,0,0,1,5,0,no,0,0,0,none,0, HLIF_BRAIN,Brain Surgery -8004,0,6,4,0,0x1,0,3,0,no,0,0,0,none,0, HLIF_CHANGE,Change -8005,0,6,4,0,0x1,0,5,0,no,0,0,0,none,0, HAMI_CASTLE,Castling -8006,0,6,4,0,0x1,0,5,0,no,0,0,0,none,0, HAMI_DEFENCE,Defense -8007,0,0,0,0,0x1,0,5,0,no,0,0,0,none,0, HAMI_SKIN,Adamantium Skin -8008,0,6,4,0,0x1,0,3,0,no,0,0,0,none,0, HAMI_BLOODLUST,Bloodlust -8009,1,8,1,0,0,0,5,-1:-2:-2:-2:-3,no,0,0,0,weapon,0, HFLI_MOON,Moonlight -8010,0,6,4,0,0x1,0,5,0,no,0,0,0,none,0, HFLI_FLEET,Fleeting Move -8011,0,6,4,0,0x1,0,5,0,yes,0,0,0,misc,0, HFLI_SPEED,Speed -8012,1,6,1,0,0,0,3,0,no,0,0,0,none,0, HFLI_SBR44,S.B.R.44 -8013,9,6,1,0,0,0,5,1:2:3:4:5,no,0,0,0,magic,0, HVAN_CAPRICE,Caprice -8014,0,6,4,0,0x1,0,5,0,no,0,0,0,none,0, HVAN_CHAOTIC,Benediction of Chaos -8015,0,0,0,0,0x1,0,5,0,no,0,0,0,none,0, HVAN_INSTRUCT,Instruct -8016,4,6,4,-1,0xD2,4,3,1,no,0,0,0,misc,0, HVAN_EXPLOSION,Bio Explosion -// -8018,9,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MH_SUMMON_LEGION,Summon Legion -8019,5,6,1,5,0,0,5,1,no,0,0,0,weapon,0, MH_NEEDLE_OF_PARALYZE,Needle of Paralyze -8020,5,6,2,5,0,0,5,1,no,0,0,1,weapon,0, MH_POISON_MIST,Poison Mist -8021,1,6,1,0,0x1,0,5,1,no,0,0,0,none,0, MH_PAIN_KILLER,Pain Killer -8022,0,6,4,0,0,0x1,5,1,no,0,0,0,none,0, MH_LIGHT_OF_REGENE,Light of Regene -8023,0,6,4,0,0,0x1,5,1,no,0,0,0,none,0, MH_OVERED_BOOST,Overed Boost -8024,7,6,1,4:0:4:0:4,0,0,5,1,no,0,0,0,magic,0, MH_ERASER_CUTTER,Eraser Cutter -8025,7,6,2,4:0:4:0:4,0,0,5,1,no,0,0,0,magic,0, MH_XENO_SLASHER,Xeno Slasher -8026,5:5:7:7:9,6,16,0,0x1,0,5,1,no,0,0,0,magic,0, MH_SILENT_BREEZE,Silent Breeze -8027,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, MH_STYLE_CHANGE,Style Change -8028,1,8,1,0,0,0,5,1,no,0,0,0,weapon,0, MH_SONIC_CRAW,Sonic Claw -8029,1,6,4,0,0,0,5,1,no,0,0x200,0,weapon,0, MH_SILVERVEIN_RUSH,Silver Bain Rush -8030,1,6,4,0,0,0,5,1,no,0,0x200,0,weapon,0, MH_MIDNIGHT_FRENZY,Midnight Frenzy -8031,5:6:7:8:9,6,1,0,0,0,5,1,no,0,0,0,weapon,3, MH_STAHL_HORN,Steel Horn -8032,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MH_GOLDENE_FERSE,Golden Heel -8033,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MH_STEINWAND,Stone Wall -8034,9,6,1,6,0x2,1:1:1:1:2,5,1,no,0,0,0,magic,0, MH_HEILIGE_STANGE,Holy Pole -8035,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MH_ANGRIFFS_MODUS,Attack Mode -8036,3:4:5:6:7,6,1,0,0,0,5,1,no,0,0,0,weapon,0, MH_TINDER_BREAKER,Tinder Breaker -8037,1,6,4,0,0,0,5,1,no,0,0x200,0,weapon,0, MH_CBC,Continual Break Combo -8038,1,6,4,0,0,0,5,1,no,0,0x200,0,weapon,0, MH_EQC,Eternal Quick Combo -8039,0,6,4,3,0x2,1:1:1:2:2,5,1,no,0,0,0,weapon,0, MH_MAGMA_FLOW,Magma Flow -8040,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MH_GRANITIC_ARMOR,Granitic Armor -8041,7,6,2,3,0x2,0,5,1,no,0,0,1,weapon,0, MH_LAVA_SLIDE,Lava Slide -8042,0,6,4,3,0x1,0,5,1,no,0,0,0,none,0, MH_PYROCLASTIC,Pyroclastic -8043,7,6,2,0,0x1,0,5,1,no,0,0,3,none,0, MH_VOLCANIC_ASH,Volcanic Ash - -// Mercenary Skill Place holders -8201,-1,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, MS_BASH,Bash -8202,0,6,4,3,0x2,2,10,1,no,0,0,0,weapon,2, MS_MAGNUM,Magnum_Break -8203,-2,6,1,-1,0x2,1,10,1,no,33,0,0,weapon,1, MS_BOWLINGBASH,Bowling_Bash -8204,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, MS_PARRYING,Parry -8205,0,6,4,0,0,0,10,1,no,0,0,0,weapon,0, MS_REFLECTSHIELD,Shield_Reflect -8206,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, MS_BERSERK,Frenzy -8207,-9,8,1,-1,0,0,10,2,no,0,0,0,weapon,0, MA_DOUBLE,Double_Strafe -8208,-9,6,2,-1,0x2,2,10,1,no,0,0x2000,0,weapon,2, MA_SHOWER,Arrow_Shower -8209,3,6,2,0,0x1,0,5,1,no,0,0x80,0,misc,6:7:8:9:10, MA_SKIDTRAP,Skid_Trap -8210,3,6,2,2,0x40,0,5,1,no,0,0x80,0,misc,0, MA_LANDMINE,Land_Mine -8211,3,6,2,0,0x3,2,5,1,no,0,0x80,0,misc,0, MA_SANDMAN,Sandman -8212,3,6,2,1,0x42,1,5,1,no,0,0x80,0,weapon,0, MA_FREEZINGTRAP,Freezing_Trap -8213,2,6,32,0,0x1,0,1,1,no,0,0,0,misc,0, MA_REMOVETRAP,Remove_Trap -8214,-9,6,1,-1,0x2,0,1,1,no,0,0x1,0,weapon,6, MA_CHARGEARROW,Arrow_Repel -8215,9,8,1,-1,0,2,5,1,yes,0,0,13,weapon,0, MA_SHARPSHOOTING,Focused_Arrow_Strike -8216,-2,8,1,-1,0,0,10,3,no,0,0,0,weapon,0, ML_PIERCE,Pierce -8217,-2,6,1,-1,0x1,0,10,1,no,33,0,0,weapon,3, ML_BRANDISH,Brandish_Spear -8218,5,8,1,-1,0x20,0,5,5,no,0,0,0,weapon,0, ML_SPIRALPIERCE,Spiral_Pierce -8219,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, ML_DEFENDER,Defending_Aura -8220,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, ML_AUTOGUARD,Guard -8221,7:8:9:10:11,6,16,0,0x1,0,5,1,yes,0,0x600,0,none,0, ML_DEVOTION,Sacrifice -8222,0,6,4,0,0x3,-1,5,1,yes,0,0,0,magic,0, MER_MAGNIFICAT,Magnificat -8223,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, MER_QUICKEN,Two-Hand_Quicken -8224,0,6,4,3,0x3,3,1,1,yes,0,0,0,magic,0, MER_SIGHT,Sight -8225,1,8,1,-1,0,0,5,3,no,0,0,0,weapon,0, MER_CRASH,Crash -8226,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_REGAIN,Regain -8227,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_TENDER,Tender -8228,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_BENEDICTION,Benediction -8229,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_RECUPERATE,Recuperate -8230,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_MENTALCURE,Mental_Cure -8231,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_COMPRESS,Compress -8232,9,6,1,0,0x1,0,10,1,no,0,0,0,none,0, MER_PROVOKE,Provoke -8233,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, MER_AUTOBERSERK,Berserk -8234,9,6,1,0,0x1,0,10,1,yes,0,0,0,magic,0, MER_DECAGI,Decrease_AGI -8235,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, MER_SCAPEGOAT,Scapegoat -8236,5,6,1,0,0x1,0,10,0,yes,0,0,0,magic,0, MER_LEXDIVINA,Lex_Divina -8237,9,6,1,0,0x1,0,1,1,yes,0,0,0,magic,0, MER_ESTIMATION,Sense -8238,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, MER_KYRIE,Kyrie Eleison -8239,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, MER_BLESSING,Blessing -8240,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, MER_INCAGI,Increase Agility - -// Elemental Spirits Skills -8401,0,6,4,3,0,0,1,1,no,0,0,0,weapon,2, EL_CIRCLE_OF_FIRE,Circle of Fire -8402,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_FIRE_CLOAK,Fire Cloak -8403,0,6,4,3,0,0,1,1,no,0,0,3,magic,2, EL_FIRE_MANTLE,Fire Mantle -8404,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_WATER_SCREEN,Water Screen -8405,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_WATER_DROP,Water Drop -8406,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_WATER_BARRIER,Water Barrier -8407,0,6,4,0,0x1,0,1,1,no,0,0,0,none,5, EL_WIND_STEP,Wind Step -8408,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_WIND_CURTAIN,Wind Curtain -8409,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_ZEPHYR,Zephyr -8410,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_SOLID_SKIN,Solid Skin -8411,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_STONE_SHIELD,Stone Shield -8412,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_POWER_OF_GAIA,Power of Gaia -8413,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_PYROTECHNIC,Pyrotechnic -8414,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_HEATER,Heater -8415,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_TROPIC,Tropic -8416,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_AQUAPLAY,Aqua Play -8417,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_COOLER,Cooler -8418,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_CHILLY_AIR,Cool Air -8419,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_GUST,Gust -8420,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_BLAST,Blast -8421,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_WILD_STORM,Wild Storm -8422,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_PETROLOGY,Petrology -8423,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_CURSED_SOIL,Cursed Soil -8424,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_UPHEAVAL,Upheaval -8425,6,6,1,3,0,0,1,1,no,0,0,0,magic,0, EL_FIRE_ARROW,Fire Arrow -8426,6,6,1,3,0,1,1,1,no,0,0,0,magic,0, EL_FIRE_BOMB,Fire Bomb -8427,6,6,1,3,0,1,1,1,no,0,0,0,weapon,0, EL_FIRE_BOMB_ATK,Fire Bomb Attack -8428,6,6,1,3,0,1,1,1,no,0,0,0,magic,0, EL_FIRE_WAVE,Fire Wave -8429,6,6,1,3,0,1,1,1,no,0,0,0,weapon,0, EL_FIRE_WAVE_ATK,Fire Wave Attack -8430,9,6,1,1,0,0,1,1,no,0,0,0,magic,0, EL_ICE_NEEDLE,Ice Needle -8431,9,6,1,1,0,1,1,1,no,0,0,0,magic,0, EL_WATER_SCREW,Water Screw -8432,9,6,1,1,0,1,1,1,no,0,0,0,weapon,0, EL_WATER_SCREW_ATK,Water Screw Attack -8433,9,6,1,1,0,1,1,1,no,0,0,0,weapon,0, EL_TIDAL_WEAPON,Tidal Weapon -8434,11,6,1,4,0,0,1,1,no,0,0,0,weapon,0, EL_WIND_SLASH,Wind Slasher -8435,11,6,1,4,0,1,1,1,no,0,0,0,weapon,0, EL_HURRICANE,Hurricane Rage -8436,7,6,1,4,0,0,1,1,no,0,0,0,magic,0, EL_HURRICANE_ATK,Hurricane Rage Attack -8437,11,8,1,4,0,1,1,-3,no,0,0,0,weapon,0, EL_TYPOON_MIS,Typhoon Missile -8438,11,8,1,4,0,1,1,-3,no,0,0,0,magic,0, EL_TYPOON_MIS_ATK,Typhoon Missile Attack -8439,5,6,1,2,0,0,1,1,no,0,0,0,weapon,0, EL_STONE_HAMMER,Stone Hammer -8440,3,6,1,2,0,1,1,1,no,0,0,0,weapon,0, EL_ROCK_CRUSHER,Rock Launcher -8441,5,6,1,2,0,1,1,1,no,0,0,0,magic,0, EL_ROCK_CRUSHER_ATK,Rock Launcher Attack -8442,9,6,1,2,0,1,1,-5,no,0,0,0,weapon,0, EL_STONE_RAIN,Stone Rain - -10000,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_APPROVAL,Official Guild Approval -10001,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_KAFRACONTRACT,Kafra Contract -10002,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_GUARDRESEARCH,Guardian Research -10003,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_GUARDUP,Strengthen Guardians -10004,0,0,0,0,0,0,10,0,no,0,0x10,0,none,0, GD_EXTENSION,Guild Extension -10005,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_GLORYGUILD,Guild's Glory -10006,0,0,0,0,0,2,1,0,no,0,0x10,0,none,0, GD_LEADERSHIP,Great Leadership -10007,0,0,0,0,0,2,1,0,no,0,0x10,0,none,0, GD_GLORYWOUNDS,Glorious Wounds -10008,0,0,0,0,0,2,1,0,no,0,0x10,0,none,0, GD_SOULCOLD,Cold Heart -10009,0,0,0,0,0,2,1,0,no,0,0x10,0,none,0, GD_HAWKEYES,Sharp Gaze -10010,0,0,4,0,0x3,15,1,0,yes,0,0x10,0,none,0, GD_BATTLEORDER,Battle Orders -10011,0,0,4,0,0x3,15,3,0,yes,0,0x10,0,none,0, GD_REGENERATION,Regeneration -10012,0,0,4,0,0x3,15,1,0,yes,0,0x10,0,none,0, GD_RESTORE,Restoration -10013,0,0,4,0,0x3,0,1,0,yes,0,0x10,0,none,0, GD_EMERGENCYCALL,Urgent Call -10014,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_DEVELOPMENT,Permanent Development -//10015,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_ITEMEMERGENCYCALL,Unknown Skill +//id,range,hit,inf,element,nk,splash,max,list_num,castcancel,cast_defence_rate,inf2,maxcount,skill_type,blow_count,name,description +// 01 ID +// 02 range (combo skills do not check for range when used, +// if range is < 5, the skill is considered melee-range) +// 03 hit (8- repeated hitting, 6- single-hit) +// 04 inf (0- passive, 1- enemy, 2- place, 4- self, 16- friend, 32- trap) +// 05 element (0 - neutral, 1 - water, 2 - earth, 3 - fire, 4 - wind, 5 - poison, +// 6 - holy, 7 - dark, 8 - ghost, 9 - undead, -1 - use weapon element +// -2 - use endowed element, -3 - use random element.) +// 06 nk (skill damage properties): +// 0x01 - No damage skill +// 0x02 - Has splash area +// 0x04 - Damage should be split among targets +// 0x08 - Skill ignores caster's % damage cards (misc type always ignores) +// 0x10 - Skill ignores elemental adjustments +// 0x20 - Skill ignores target's defense (misc type always ignores) +// 0x40 - Skill ignores target's flee (magic type always ignores) +// 0x80 - Skill ignores target's def cards +// 07 splash/effect range (-1 for screen-wide) +// 08 MaxLv +// 09 Number of hits (when positive, damage is increased by hits, +// negative values just show number of hits without increasing total damage) +// 10 Cast interrupted when hit? +// 11 defense-reduction rate during cast. +// 12 inf2 (skill information 2): +// 0x0001- quest skill +// 0x0002- npc skill +// 0x0004- wedding skill +// 0x0008- spirit skill +// 0x0010- guild skill +// 0x0020- song/dance +// 0x0040- ensemble skill +// 0x0080- trap +// 0x0100- skill that damages/targets yourself +// 0x0200- cannot be casted on self (if inf = 4, auto-select target skill) +// 0x0400- usable only on party-members (and enemies if skill is offensive) +// 0x0800- usable only on guild-mates (and enemies if skill is offensive) +// 0x1000- disable usage on enemies (for non-offensive skills). +// 0x2000- skill ignores land protector (e.g. arrow shower) +// 0x4000- chorus skill +// 13 maxcount: max amount of skill instances to place on the ground when +// player_land_skill_limit/monster_land_skill_limit is enabled. For skills +// that attack using a path, this is the path length to be used. +// 14 attack type (none, weapon, magic, misc) +// 15 Blowcount (amount of tiles skill knockbacks) +// 16 Name +// 17 Description +1,0,0,0,0,0,0,9,0,no,0,0,0,none,0, NV_BASIC,Basic Skill +2,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, SM_SWORD,Sword Mastery +3,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, SM_TWOHAND,Two-Handed Sword Mastery +4,0,0,0,0,0,0,10,0,no,0,0,0,none,0, SM_RECOVERY,Increase HP Recovery +5,-1,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, SM_BASH,Bash +6,9,6,1,0,1,0,10,1,no,0,0,0,none,0, SM_PROVOKE,Provoke +7,0,6,4,3,0x2,2,10,1,no,0,0,0,weapon,2, SM_MAGNUM,Magnum Break +8,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, SM_ENDURE,Endure +9,0,0,0,0,0,0,10,0,no,0,0,0,none,0, MG_SRECOVERY,Increase SP Recovery +10,0,6,4,3,0x3,3,1,1,yes,0,0,0,magic,0, MG_SIGHT,Sight +11,9,6,1,8,0x6,1,10,1,yes,0,0,0,magic,0, MG_NAPALMBEAT,Napalm Beat +12,9,8,2,8,0x1,0,10,1,yes,0,0,0,magic,0, MG_SAFETYWALL,Safety Wall +13,9,8,1,8,0,0,10,1:1:2:2:3:3:4:4:5:5,yes,0,0,0,magic,0, MG_SOULSTRIKE,Soul Strike +14,9,8,1,1,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, MG_COLDBOLT,Cold Bolt +15,9,6,1,1,0,0,10,1,yes,0,0,0,magic,0, MG_FROSTDIVER,Frost Diver +16,2,6,1,2,0x1,0,10,1,yes,0,0,0,magic,0, MG_STONECURSE,Stone Curse +17,9,6,1,3,0x2,2,10,1,yes,0,0,0,magic,0, MG_FIREBALL,Fire Ball +18,9,6,2,3,0,0,10,1,yes,0,0,3,magic,2, MG_FIREWALL,Fire Wall +19,9,8,1,3,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, MG_FIREBOLT,Fire Bolt +20,9,8,1,4,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, MG_LIGHTNINGBOLT,Lightning Bolt +21,9,8,2,4,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, MG_THUNDERSTORM,Thunderstorm +22,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, AL_DP,Divine Protection +23,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, AL_DEMONBANE,Demon Bane +24,0,6,4,6,0x3,2,1,1,yes,0,0,0,magic,0, AL_RUWACH,Ruwach +25,9,6,2,0,0x1,0,1,1,yes,0,0,0,magic,0, AL_PNEUMA,Pneuma +26,0,6,4,0,0x1,0,2,1,yes,0,0,0,magic,0, AL_TELEPORT,Teleport +27,9,6,2,0,0x1,0,4,1,yes,0,0,3,magic,0, AL_WARP,Warp Portal +28,9,6,16,6,0x21,0,10,1,yes,0,0,0,magic,0, AL_HEAL,Heal +29,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, AL_INCAGI,Increase AGI +30,9,6,1,0,0x1,0,10,1,yes,0,0,0,magic,0, AL_DECAGI,Decrease AGI +31,0,6,4,0,0x1,0,1,1,yes,0,0,0,magic,0, AL_HOLYWATER,Aqua Benedicta +32,0,6,4,0,0x3,15,10,1,yes,0,0,0,magic,0, AL_CRUCIS,Signum Crucis +33,0,6,4,0,0x3,-1,10,1,yes,0,0,0,magic,0, AL_ANGELUS,Angelus +34,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, AL_BLESSING,Blessing +35,9,6,16,0,0x1,0,1,1,yes,0,0,0,magic,0, AL_CURE,Cure +36,0,0,0,0,0,0,10,0,no,0,0,0,none,0, MC_INCCARRY,Enlarge Weight Limit +37,0,0,0,0,0,0,10,0,no,0,0,0,none,0, MC_DISCOUNT,Discount +38,0,0,0,0,0,0,10,0,no,0,0,0,none,0, MC_OVERCHARGE,Overcharge +39,1,0,0,0,0,0,10,0,no,0,0,0,none,0, MC_PUSHCART,Pushcart +40,1,6,4,0,0x1,0,1,1,no,0,0,0,none,0, MC_IDENTIFY,Item Appraisal +41,1,6,4,0,0x1,0,10,1,no,0,0,0,none,0, MC_VENDING,Vending +42,-1,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, MC_MAMMONITE,Mammonite +43,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AC_OWL,Owl's Eye +44,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AC_VULTURE,Vulture's Eye +45,0,6,4,0,0x3,3,10,1,no,0,0,0,weapon,0, AC_CONCENTRATION,Improve Concentration +46,-9,8,1,-1,0,0,10,2,no,0,0,0,weapon,0, AC_DOUBLE,Double Strafe +47,-9,6,2,-1,0x2,2,10,1,no,0,0x2000,0,weapon,2, AC_SHOWER,Arrow Shower +48,-1,8,0,-1,0,0,10,2,no,0,0,0,weapon,0, TF_DOUBLE,Double Attack +49,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, TF_MISS,Improve Dodge +50,1,6,1,0,1,0,10,1,no,0,0,0,weapon,0, TF_STEAL,Steal +51,1,6,4,0,1,0,10,1,no,0,0,0,none,0, TF_HIDING,Hiding +52,-2,6,1,5,0,0,10,1,no,0,0,0,weapon,0, TF_POISON,Envenom +53,9,6,16,5,0x1,0,1,1,no,0,0,0,weapon,0, TF_DETOXIFY,Detoxify +54,9,6,16,6,0x1,0,4,1,yes,0,0,0,magic,0, ALL_RESURRECTION,Resurrection +55,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, KN_SPEARMASTERY,Spear Mastery +56,-2,8,1,-1,0,0,10,3,no,0,0,0,weapon,0, KN_PIERCE,Pierce +57,-2,6,1,-1,0x1,0,10,1,no,33,0,0,weapon,3, KN_BRANDISHSPEAR,Brandish Spear +58,-4,6,1,-1,0x2,0,10,1,no,0,0,0,weapon,6, KN_SPEARSTAB,Spear Stab +59,3:5:7:9:11,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, KN_SPEARBOOMERANG,Spear Boomerang +60,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, KN_TWOHANDQUICKEN,Twohand Quicken +61,0,6,4,-1,0x20,0,5,1,no,0,0,0,weapon,0, KN_AUTOCOUNTER,Counter Attack +62,-2,6,1,-1,0x2,1,10,1,no,33,0,0,weapon,1, KN_BOWLINGBASH,Bowling Bash +63,0,0,0,0,0,0,1,0,no,0,0,0,weapon,0, KN_RIDING,Peco Peco Riding +64,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, KN_CAVALIERMASTERY,Cavalier Mastery +65,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, PR_MACEMASTERY,Mace Mastery +66,9,6,16,0,0x1,0,5,1,yes,0,0,0,magic,0, PR_IMPOSITIO,Impositio Manus +67,9,6,16,0,0x1,0,3,1,yes,0,0x200,0,magic,0, PR_SUFFRAGIUM,Suffragium +68,9,6,16,6,0x31,0,5,1,yes,0,0,0,magic,0, PR_ASPERSIO,Aspersio +69,9,6,2,0,0x23,1,5,1,yes,0,0x40,0,magic,0, PR_BENEDICTIO,B.S. Sacramenti +70,9,6,2,6,0x21,0,10,1,yes,0,0,0,magic,1, PR_SANCTUARY,Sanctuary +71,9,6,16,0,0x1,0,4,1,yes,0,0,0,magic,0, PR_SLOWPOISON,Slow Poison +72,9,6,16,0,0x1,0,1,1,yes,0,0,0,magic,0, PR_STRECOVERY,Status Recovery +73,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, PR_KYRIE,Kyrie Eleison +74,0,6,4,0,0x3,-1,5,1,yes,0,0,0,magic,0, PR_MAGNIFICAT,Magnificat +75,0,6,4,0,0x3,-1,5,1,yes,0,0,0,magic,0, PR_GLORIA,Gloria +76,5,6,1,0,0x1,0,10,0,yes,0,0,0,magic,0, PR_LEXDIVINA,Lex Divina +77,5,6,1,6,0x28,0,10,1,yes,0,0,0,magic,0, PR_TURNUNDEAD,Turn Undead +78,9,6,1,0,0x1,0,1,0,yes,0,0,0,magic,0, PR_LEXAETERNA,Lex Aeterna +79,9,8,2,6,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, PR_MAGNUS,Magnus Exorcismus +80,9,8,2,3,0x20,1:1:1:1:1:2:2:2:2:2:2,10,3:4:5:6:7:8:9:10:11:12:12,yes,0,0x80,5,magic,0, WZ_FIREPILLAR,Fire Pillar +81,0,6,4,3,0,3,10,1,yes,0,0,0,magic,5, WZ_SIGHTRASHER,Sightrasher +83,9,8,2,3,0,3:3:3:3:3:3:3:3:3:3:14,10,1:1:2:2:3:3:4:4:5:5:15,yes,0,0,0,magic,0, WZ_METEOR,Meteor Storm +84,9,8,1,4,0,0,10,3:4:5:6:7:8:9:10:11:12,yes,0,0,0,magic,2:3:3:4:4:5:5:6:6:7, WZ_JUPITEL,Jupitel Thunder +85,9,8,2,4,0,0,10,-10,yes,0,0,0,magic,0, WZ_VERMILION,Lord of Vermilion +86,9,8,1,1,0,0,5,1,yes,0,0,0,magic,0, WZ_WATERBALL,Water Ball +87,9,6,2,1,0x1,0,10,1,yes,0,0,0,magic,0, WZ_ICEWALL,Ice Wall +88,0,6,4,1,0x2,2,10,1,yes,0,0,0,magic,0, WZ_FROSTNOVA,Frost Nova +89,9,6,2,1,0,0,10,1,yes,0,0,0,magic,2, WZ_STORMGUST,Storm Gust +90,9,8,1,2,0,0,5,1:2:3:4:5,yes,0,0,0,magic,0, WZ_EARTHSPIKE,Earth Spike +91,9,8,2,2,0,0,5,1:2:3:4:5,yes,0,0,0,magic,0, WZ_HEAVENDRIVE,Heaven's Drive +92,9,6,2,2,0x1,0,5,1,yes,0,0,3,magic,0, WZ_QUAGMIRE,Quagmire +93,9,6,1,0,0x1,0,1,1,yes,0,0,0,magic,0, WZ_ESTIMATION,Sense +94,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, BS_IRON,Iron Tempering +95,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, BS_STEEL,Steel Tempering +96,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, BS_ENCHANTEDSTONE,Enchanted Stone Craft +97,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, BS_ORIDEOCON,Oridecon Research +98,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_DAGGER,Smith Dagger +99,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_SWORD,Smith Sword +100,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_TWOHANDSWORD,Smith Two-handed Sword +101,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_AXE,Smith Axe +102,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_MACE,Smith Mace +103,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_KNUCKLE,Smith Knucklebrace +104,0,0,0,0,0,0,3,0,no,0,0,0,weapon,0, BS_SPEAR,Smith Spear +105,0,0,0,0,0,0,1,0,no,0,0,0,weapon,0, BS_HILTBINDING,Hilt Binding +106,0,0,0,0,0,0,1,0,no,0,0,0,weapon,0, BS_FINDINGORE,Ore Discovery +107,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, BS_WEAPONRESEARCH,Weaponry Research +108,2,6,16,0,0x1,0,1,1,yes,0,0,0,weapon,0, BS_REPAIRWEAPON,Weapon Repair +109,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, BS_SKINTEMPER,Skin Tempering +110,1,6,2,0,0x3,2:2:2:2:2:14,5,1,no,0,0,0,weapon,0, BS_HAMMERFALL,Hammer Fall +111,0,6,4,0,0x3,-1,5,1,no,0,0,0,weapon,0, BS_ADRENALINE,Adrenaline Rush +112,0,6,4,0,0x3,-1,5,1,no,0,0,0,weapon,0, BS_WEAPONPERFECT,Weapon Perfection +113,0,6,4,0,0x3,-1,5,1,no,0,0,0,weapon,0, BS_OVERTHRUST,Power-Thrust +114,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, BS_MAXIMIZE,Maximize Power +115,3,6,2,0,0x1,0,5,1,no,0,0x80,0,misc,6:7:8:9:10, HT_SKIDTRAP,Skid Trap +116,3,6,2,2,0x42,1,5,1,no,0,0x80,0,misc,0, HT_LANDMINE,Land Mine +117,3,6,2,0,0x1,0,5,1,no,0,0x80,0,misc,0, HT_ANKLESNARE,Ankle Snare +118,3,6,2,0,0x2,1,5,1,no,0,0x80,0,misc,0, HT_SHOCKWAVE,Shockwave Trap +119,3,6,2,0,0x3,2,5,1,no,0,0x80,0,misc,0, HT_SANDMAN,Sandman +120,3,6,2,0,0x3,1,5,1,no,0,0x80,0,misc,0, HT_FLASHER,Flasher +121,3,6,2,1,0x42,1,5,1,no,0,0x80,0,weapon,0, HT_FREEZINGTRAP,Freezing Trap +122,3,6,2,4,0x42,1,5,1,no,0,0x80,0,misc,0, HT_BLASTMINE,Blast Mine +123,3,6,2,3,0x42,2,5,1,no,0,0x80,0,misc,0, HT_CLAYMORETRAP,Claymore Trap +124,2,6,32,0,0x1,0,1,1,no,0,0,0,misc,0, HT_REMOVETRAP,Remove Trap +125,3,6,2,0,0x1,0,1,1,no,0,0x80,0,misc,0, HT_TALKIEBOX,Talkie Box +126,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, HT_BEASTBANE,Beast Bane +127,0,0,0,0,0,0,1,0,no,0,0,0,misc,0, HT_FALCON,Falconry Mastery +128,0,0,0,0,0,0,10,0,no,0,0,0,misc,0, HT_STEELCROW,Steel Crow +129,5,8,1,0,0x42,1,5,1:2:3:4:5,yes,0,0,0,misc,0, HT_BLITZBEAT,Blitz Beat +130,3:5:7:9,6,2,0,0x3,3,4,1,no,0,0,0,misc,0, HT_DETECTING,Detect +131,4:5:6:7:8,6,32,0,0x1,0,5,1,no,0,0,0,misc,0, HT_SPRINGTRAP,Spring Trap +132,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, AS_RIGHT,Righthand Mastery +133,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, AS_LEFT,Lefthand Mastery +134,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, AS_KATAR,Katar Mastery +135,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, AS_CLOAKING,Cloaking +136,-1,8,1,-1,0,0,10,8,no,0,0,0,weapon,0, AS_SONICBLOW,Sonic Blow +137,3:4:5:6:7,6,1,-1,0x2,1,5,1,no,0,0,0,weapon,0,AS_GRIMTOOTH,Grimtooth +138,1,6,16,5,0x1,0,10,1,no,0,0x400,0,weapon,0, AS_ENCHANTPOISON,Enchant Poison +139,0,6,4,0,0,0,10,1,no,0,0,0,weapon,0, AS_POISONREACT,Poison React +140,2,6,2,5,0x1,0,10,1,no,0,0,0,weapon,0, AS_VENOMDUST,Venom Dust +141,1,6,1,-1,0x51,2,10,1,yes,0,0,0,weapon,0, AS_SPLASHER,Venom Splasher +142,0,6,4,0,0x1,0,1,1,no,0,0x1,0,none,0, NV_FIRSTAID,First Aid +143,0,6,4,0,0x1,0,1,1,no,0,0x1,0,none,0, NV_TRICKDEAD,Play Dead +144,0,0,0,0,0,0,1,0,no,0,0x1,0,none,0, SM_MOVINGRECOVERY,Moving HP-Recovery +145,0,0,0,0,0,0,1,0,no,0,0x1,0,weapon,0, SM_FATALBLOW,Fatal Blow +146,0,6,4,0,0x1,0,1,1,no,0,0x1,0,weapon,0, SM_AUTOBERSERK,Auto Berserk +147,0,0,4,0,0x1,0,1,0,no,0,0x1,0,weapon,0, AC_MAKINGARROW,Arrow Crafting +148,-9,6,1,-1,0x2,0,1,1,no,0,0x1,0,weapon,6, AC_CHARGEARROW,Arrow Repel +149,1,6,1,2,0,0,1,1,no,0,0x1,0,weapon,0, TF_SPRINKLESAND,Sand Attack +150,0,6,4,0,0x1,0,1,1,no,0,0x1,0,weapon,5, TF_BACKSLIDING,Back Slide +151,0,6,4,0,0x1,0,1,1,no,0,0x1,0,none,0, TF_PICKSTONE,Find Stone +152,7,6,1,0,0x40,0,1,1,no,0,0x1,0,misc,0, TF_THROWSTONE,Stone Fling +153,1,6,1,-1,0x2,1,1,1,no,0,0x1,0,weapon,2, MC_CARTREVOLUTION,Cart Revolution +154,0,6,4,0,0x1,0,1,1,no,0,0x1,0,none,0, MC_CHANGECART,Change Cart +155,0,6,4,0,0x1,0,1,1,no,0,0x1,0,weapon,0, MC_LOUD,Crazy Uproar +156,9,6,1,6,0,0,1,1,yes,0,0x1,0,magic,0, AL_HOLYLIGHT,Holy Light +157,0,6,4,0,0x1,0,1,1,yes,0,0x1,0,magic,0, MG_ENERGYCOAT,Energy Coat +158,3,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_PIERCINGATT,Piercing Attack +159,-1,6,1,-1,0x40,0,5,1,no,0,0x2,0,weapon,0, NPC_MENTALBREAKER,Spirit Destruction +160,9,6,1,0,0,0,10,1,no,0,0x2,0,weapon,0, NPC_RANGEATTACK,Stand off attack +161,0,0,4,0,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_ATTRICHANGE,Attribute Change +162,0,0,4,1,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEWATER,Water Attribute Change +163,0,0,4,2,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEGROUND,Earth Attribute Change +164,0,0,4,3,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEFIRE,Fire Attribute Change +165,0,0,4,4,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEWIND,Wind Attribute Change +166,0,0,4,5,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEPOISON,Poison Attribute Change +167,0,0,4,6,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEHOLY,Holy Attribute Change +168,0,0,4,7,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGEDARKNESS,Shadow Attribute Change +169,0,0,4,8,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_CHANGETELEKINESIS,Ghost Attribute Change +170,-9,6,1,-1,0x20,0,10,1,no,0,0x2,0,weapon,0, NPC_CRITICALSLASH,Defense disregard attack +171,-9,8,1,-1,0,0,10,-2:-3:-4:-5:-6:-7:-8:-9:-10:-11,no,0,0x2,0,weapon,0, NPC_COMBOATTACK,Multi-stage Attack +172,-9,6,1,-1,0x40,0,10,1,no,0,0x2,0,weapon,0, NPC_GUIDEDATTACK,Guided Attack +173,5,6,4,3,0xE2,5,10,1,no,0,0x2,0,misc,3, NPC_SELFDESTRUCTION,Suicide bombing +174,-9,6,1,-1,0x2,3,1,1,no,0,0x2,0,weapon,0, NPC_SPLASHATTACK,Splash attack +175,0,0,4,0,0x41,0,10,1,no,0,0x2,0,misc,0, NPC_SUICIDE,Suicide +176,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_POISON,Poison Attack +177,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_BLINDATTACK,Blind Attack +178,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_SILENCEATTACK,Silence Attack +179,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_STUNATTACK,Stun Attack +180,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_PETRIFYATTACK,Petrify Attack +181,-9,6,1,7,0,0,5,1,no,0,0x2,0,weapon,0, NPC_CURSEATTACK,Curse Attack +182,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_SLEEPATTACK,Sleep attack +183,-9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_RANDOMATTACK,Random Attack +184,-9,6,1,1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_WATERATTACK,Water Attribute Attack +185,-9,6,1,2,0,0,10,1,no,0,0x2,0,weapon,0, NPC_GROUNDATTACK,Earth Attribute Attack +186,-9,6,1,3,0,0,10,1,no,0,0x2,0,weapon,0, NPC_FIREATTACK,Fire Attribute Attack +187,-9,6,1,4,0,0,10,1,no,0,0x2,0,weapon,0, NPC_WINDATTACK,Wind Attribute Attack +188,-9,6,1,5,0,0,10,1,no,0,0x2,0,weapon,0, NPC_POISONATTACK,Poison Attribute Attack +189,-9,6,1,6,0,0,10,1,no,0,0x2,0,weapon,0, NPC_HOLYATTACK,Holy Attribute Attack +190,-9,6,1,7,0,0,10,1,no,0,0x2,0,weapon,0, NPC_DARKNESSATTACK,Shadow Attribute Attack +191,-9,6,1,8,0,0,10,1,no,0,0x2,0,weapon,0, NPC_TELEKINESISATTACK,Ghost Attribute Attack +192,-9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_MAGICALATTACK,Demon Shock Attack +193,0,0,4,0,0x1,0,10,1,no,0,0x2,0,none,0, NPC_METAMORPHOSIS,Metamorphosis +194,0,0,4,0,0x1,0,10,1,no,0,0x2,0,none,0, NPC_PROVOCATION,Provocation +195,0,6,4,0,0x50,0,10,1,no,0,0x2,0,misc,0, NPC_SMOKING,Smoking +196,0,0,4,0,0x1,0,10,1,no,0,0x2,0,magic,0, NPC_SUMMONSLAVE,Follower Summons +197,0,0,4,0,0x1,0,10,1,no,0,0x2,0,none,0, NPC_EMOTION,Emotion +198,0,0,4,0,0x1,0,10,1,no,0,0x2,0,magic,0, NPC_TRANSFORMATION,Transformation +199,9,6,1,7,0x40,0,1,1,no,0,0x2,0,weapon,0, NPC_BLOODDRAIN,Sucking Blood +200,9,6,1,7,0,0,1,1,no,0,0x2,0,magic,0, NPC_ENERGYDRAIN,Energy Drain +201,0,0,4,0,0x1,0,1,1,no,0,0x2,0,weapon,0, NPC_KEEPING,Keeping +202,9,6,1,7,0,0,5,1,no,0,0x2,0,misc,0, NPC_DARKBREATH,Dark Breath +203,9,6,1,7,0x1,0,10,1,no,0,0x2,0,magic,0, NPC_DARKBLESSING,Dark Blessing +204,0,0,4,0,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_BARRIER,Barrier +205,0,0,4,0,0x1,0,1,1,no,0,0x2,0,weapon,0, NPC_DEFENDER,Defender +206,1,6,1,-1,0x1,0,5,1,no,0,0x2,0,weapon,0, NPC_LICK,Lick +207,9,0,1,0,0x1,0,10,1,no,0,0x2,0,magic,0, NPC_HALLUCINATION,Hallucination +208,0,0,4,0,0x1,0,1,1,no,0,0x2,0,magic,0, NPC_REBIRTH,Rebirth +209,0,0,4,0,0x1,0,10,1,no,0,0x2,0,magic,0, NPC_SUMMONMONSTER,Monster Summons +210,0,0,0,-1,0,0,10,0,no,0,0,0,weapon,0, RG_SNATCHER,Gank +211,1,6,1,0,0x1,0,10,1,no,0,0,0,weapon,0, RG_STEALCOIN,Mug +212,-1,6,1,-1,0x40,0,10,1,no,0,0,0,weapon,0, RG_BACKSTAP,Back Stab +213,0,0,0,0,0,0,5,0,no,0,0,0,none,0, RG_TUNNELDRIVE,Stalk +214,0,6,4,-1,0x2,1,5,1,no,0,0,0,weapon,0, RG_RAID,Sightless Mind +215,1,6,1,0,0x1,0,5,1,no,0,0,0,weapon,0, RG_STRIPWEAPON,Divest Weapon +216,1,6,1,0,0x1,0,5,1,no,0,0,0,weapon,0, RG_STRIPSHIELD,Divest Shield +217,1,6,1,0,0x1,0,5,1,no,0,0,0,weapon,0, RG_STRIPARMOR,Divest Armor +218,1,6,1,0,0x1,0,5,1,no,0,0,0,weapon,0, RG_STRIPHELM,Divest Helm +219,1,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, RG_INTIMIDATE,Snatch +220,1,6,2,0,0x1,0,1,1,no,0,0,0,none,0, RG_GRAFFITI,Scribble +221,0,6,2,0,0x1,0,5,1,no,0,0,0,none,0, RG_FLAGGRAFFITI,Piece +222,1,6,2,0,0x3,5,1,1,no,0,0,0,none,0, RG_CLEANER,Remover +223,0,0,0,0,0,1,1,0,no,0,0,0,none,0, RG_GANGSTER,Slyness +224,0,0,0,0,0,0,5,0,no,0,0,0,none,0, RG_COMPULSION,Haggle +225,0,0,0,0,0,0,10,0,no,0,0,0,none,0, RG_PLAGIARISM,Intimidate +226,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, AM_AXEMASTERY,Axe Mastery +227,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AM_LEARNINGPOTION,Potion Research +228,0,6,4,0,0x1,0,10,0,no,0,0,0,none,0, AM_PHARMACY,Prepare Potion +229,9,6,2,3,0x9,0,5,1,yes,0,0,0,weapon,0, AM_DEMONSTRATION,Bomb +230,9,6,1,0,0x48,0,5,1,yes,0,0,0,weapon,0, AM_ACIDTERROR,Acid Terror +231,9,6,16,0,0x1,0,5,1,yes,0,0xC00,0,none,0, AM_POTIONPITCHER,Aid Potion +232,4,6,2,0,0x1,0,5,1,no,0,0,5,none,0, AM_CANNIBALIZE,Summon Flora +233,1,6,2,0,0x1,0,5,1,no,0,0,3,none,0, AM_SPHEREMINE,Summon Marine Sphere +234,1,6,16,0,0x1,0,5,1,yes,0,0,0,weapon,0, AM_CP_WEAPON,Alchemical Weapon +235,1,6,16,0,0x1,0,5,1,yes,0,0,0,weapon,0, AM_CP_SHIELD,Synthesized Shield +236,1,6,16,0,0x1,0,5,1,yes,0,0,0,weapon,0, AM_CP_ARMOR,Synthetic Armor +237,1,6,16,0,0x1,0,5,1,yes,0,0,0,weapon,0, AM_CP_HELM,Biochemical Helm +238,0,0,0,0,0,0,1,0,no,0,0x1,0,none,0, AM_BIOETHICS,Bioethics +//239,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AM_BIOTECHNOLOGY,Biotechnology +//240,0,0,0,0,0,0,5,0,no,0,0,0,none,0, AM_CREATECREATURE,Life Creation +//241,0,0,0,0,0,0,5,0,no,0,0,0,none,0, AM_CULTIVATION,Cultivation +//242,0,0,0,0,0,0,5,0,no,0,0,0,none,0, AM_FLAMECONTROL,Flame Control +243,0,0,4,0,0x1,1,1,0,no,0,0,0,none,0, AM_CALLHOMUN,Call Homunculus +244,0,0,4,0,0x1,0,1,0,no,0,0,0,none,0, AM_REST,Vaporize +//245,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AM_DRILLMASTER,Drillmaster +//246,9,0,0,0,0,0,10,0,no,0,0,0,none,0, AM_HEALHOMUN,Heal Homunculus +247,9,6,4,0,0x1,1,5,0,no,0,0,0,none,0, AM_RESURRECTHOMUN,Homunculus Resurrection +248,0,0,0,0,0,0,10,0,no,0,0,0,none,0, CR_TRUST,Faith +249,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, CR_AUTOGUARD,Guard +250,3,6,1,0,0,0,5,1,no,0,0,0,weapon,5:6:7:8:9, CR_SHIELDCHARGE,Smite +251,3:5:7:9:11,6,1,0,0,0,5,1,no,0,0,0,weapon,0, CR_SHIELDBOOMERANG,Shield Boomerang +252,0,6,4,0,0,0,10,1,no,0,0,0,weapon,0, CR_REFLECTSHIELD,Shield Reflect +253,-2,8,1,6,0,0,10,-2,no,0,0,0,weapon,0, CR_HOLYCROSS,Holy Cross +254,5,6,4,6,0x48,0,10,1,no,33,0x100,0,magic,0, CR_GRANDCROSS,Grand Cross +255,7:8:9:10:11,6,16,0,0x1,0,5,1,yes,0,0x600,0,none,0, CR_DEVOTION,Sacrifice +256,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,none,0, CR_PROVIDENCE,Resistant Souls +257,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, CR_DEFENDER,Defending Aura +258,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, CR_SPEARQUICKEN,Spear Quicken +259,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, MO_IRONHAND,Iron Fists +260,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, MO_SPIRITSRECOVERY,Spiritual Cadence +261,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MO_CALLSPIRITS,Summon Spirit Sphere +262,9,6,16,0,0x1,0,1,1,yes,0,0,0,weapon,0, MO_ABSORBSPIRITS,Absorb Spirit Sphere +263,-1,8,0,-1,0,0,10,-3,no,0,0,0,weapon,0, MO_TRIPLEATTACK,Raging Trifecta Blow +264,18,6,2,0,0x1,0,1,1,no,0,0,0,none,0, MO_BODYRELOCATION,Snap +265,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, MO_DODGE,Dodge +266,2,6,1,0,0x40,0,5,1,no,0,0,0,weapon,0, MO_INVESTIGATE,Occult Impaction +267,9,8,1,-1,0,0,5,1:2:3:4:5,no,0,0,0,weapon,0, MO_FINGEROFFENSIVE,Throw Spirit Sphere +268,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, MO_STEELBODY,Mental Strength +269,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, MO_BLADESTOP,Root +270,0,6,4,0,0x1,0,5,0,no,0,0,0,weapon,0, MO_EXPLOSIONSPIRITS,Fury +271,-2,6,1,0,0x60,0,5,1,yes,0,0,0,weapon,0, MO_EXTREMITYFIST,Asura Strike +272,-2,8,4,-1,0,0,5,-4,no,0,0x200,0,weapon,0, MO_CHAINCOMBO,Raging Quadruple Blow +273,-2,6,4,-1,0x2,2,5,1,no,0,0x200,0,weapon,0, MO_COMBOFINISH,Raging Thrust +274,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, SA_ADVANCEDBOOK,Study +275,0,6,4,0,0x1,0,5,1,no,0,0,0,magic,0, SA_CASTCANCEL,Cast Cancel +276,0,6,4,0,0x1,0,5,1,yes,0,0,0,magic,0, SA_MAGICROD,Magic Rod +277,9,6,1,0,0x1,0,5,1,yes,0,0,0,magic,0, SA_SPELLBREAKER,Spell Breaker +278,0,0,0,0,0,0,10,0,no,0,0,0,magic,0, SA_FREECAST,Free Cast +279,0,6,4,0,0x1,0,10,1,yes,0,0,0,magic,0, SA_AUTOSPELL,Hindsight +280,9,6,16,3,0x1,0,5,1,yes,0,0xC00,0,magic,0, SA_FLAMELAUNCHER,Endow Blaze +281,9,6,16,1,0x1,0,5,1,yes,0,0xC00,0,magic,0, SA_FROSTWEAPON,Endow Tsunami +282,9,6,16,4,0x1,0,5,1,yes,0,0xC00,0,magic,0, SA_LIGHTNINGLOADER,Endow Tornado +283,9,6,16,2,0x1,0,5,1,yes,0,0xC00,0,magic,0, SA_SEISMICWEAPON,Endow Quake +284,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, SA_DRAGONOLOGY,Dragonology +285,2,6,2,3,0x1,0,5,1,yes,0,0,0,magic,0, SA_VOLCANO,Volcano +286,2,6,2,1,0x1,0,5,1,yes,0,0,0,magic,0, SA_DELUGE,Deluge +287,2,6,2,4,0x1,0,5,1,yes,0,0,0,magic,0, SA_VIOLENTGALE,Whirlwind +288,2,6,2,0,0x1,0,5,1,yes,0,0,0,magic,0, SA_LANDPROTECTOR,Magnetic Earth +289,9,6,1,0,0x1,0:0:0:0:0:-1,5,1,yes,0,0xE00,0,magic,0, SA_DISPELL,Dispell +290,0,6,4,0,0x1,0,10,1,yes,0,0,0,magic,0, SA_ABRACADABRA,Hocus-pocus +291,9,6,1,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_MONOCELL,Monocell +292,9,6,1,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_CLASSCHANGE,Class Change +293,0,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_SUMMONMONSTER,Monster Chant +294,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_REVERSEORCISH,Grampus Morph +295,9,6,1,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_DEATH,Grim Reaper +296,9,6,1,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_FORTUNE,Gold Digger +297,9,6,1,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_TAMINGMONSTER,Beastly Hypnosis +298,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_QUESTION,Questioning +299,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_GRAVITY,Gravity +300,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_LEVELUP,Leveling +301,9,6,4,0,0,0,1,1,yes,0,0x2,0,magic,0, SA_INSTANTDEATH,Suicide +302,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, SA_FULLRECOVERY,Rejuvenation +303,9,6,4,0,0,0,1,1,yes,0,0x2,0,magic,0, SA_COMA,Coma +304,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, BD_ADAPTATION,Amp +305,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, BD_ENCORE,Encore +306,0,6,4,0,0x1,0,1,1,no,0,0x40,0,misc,0, BD_LULLABY,Lullaby +307,0,6,4,0,0x1,0,5,1,no,0,0x40,0,misc,0, BD_RICHMANKIM,Mental Sensing +308,0,6,4,0,0x1,0,1,1,no,0,0x40,0,misc,0, BD_ETERNALCHAOS,Down Tempo +309,0,6,4,0,0x1,0,5,1,no,0,0x40,0,misc,0, BD_DRUMBATTLEFIELD,Battle Theme +310,0,6,4,0,0x1,0,5,1,no,0,0x40,0,misc,0, BD_RINGNIBELUNGEN,Harmonic Lick +311,0,6,4,0,0x1,0,1,1,no,0,0x40,0,misc,0, BD_ROKISWEIL,Classical Pluck +312,0,6,4,0,0x1,0,1,1,no,0,0x40,0,misc,0, BD_INTOABYSS,Power Chord +313,0,6,4,0,0x1,0,5,1,no,0,0x40,0,misc,0, BD_SIEGFRIED,Acoustic Rhythm +//314,0,0,0,0,0,0,1,1,no,0,0x40,0,misc,0, BD_RAGNAROK,Ragnarok +315,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, BA_MUSICALLESSON,Music Lessons +316,9,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, BA_MUSICALSTRIKE,Melody Strike +317,0,8,4,0,0x41,0,5,1,no,0,0x20,0,misc,0, BA_DISSONANCE,Unchained Serenade +318,0,6,4,0,0x3,-1,5,1,no,0,0,0,misc,0, BA_FROSTJOKER,Unbarring Octave +319,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, BA_WHISTLE,Perfect Tablature +320,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, BA_ASSASSINCROSS,Impressive Riff +321,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, BA_POEMBRAGI,Magic Strings +322,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, BA_APPLEIDUN,Song of Lutie +323,0,0,0,0,0,0,10,0,no,0,0,0,weapon,0, DC_DANCINGLESSON,Dance Lessons +324,9,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, DC_THROWARROW,Slinging Arrow +325,0,8,4,0,0x1,0,5,1,no,0,0x20,0,misc,0, DC_UGLYDANCE,Hip Shaker +326,0,6,4,0,0x3,-1,5,1,no,0,0,0,misc,0, DC_SCREAM,Dazzler +327,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, DC_HUMMING,Focus Ballet +328,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, DC_DONTFORGETME,Slow Grace +329,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, DC_FORTUNEKISS,Lady Luck +330,0,6,4,0,0x1,0,10,1,no,0,0x20,0,misc,0, DC_SERVICEFORYOU,Gypsy's Kiss +331,0,6,4,0,0x1,0,10,0,no,0,0x2,0,none,0, NPC_RANDOMMOVE,Random Move +332,0,6,4,0,0x1,0,10,0,no,0,0x2,0,none,0, NPC_SPEEDUP,Speed UP +333,0,6,4,0,0x1,0,1,0,no,0,0x2,0,none,0, NPC_REVENGE,Revenge +334,9,6,4,0,0x1,0,1,1,yes,0,0x4,0,none,0, WE_MALE,I Will Protect You +335,9,6,4,0,0x1,0,1,1,yes,0,0x4,0,none,0, WE_FEMALE,I Look up to You +336,9,6,4,0,0x1,3,1,1,yes,0,0x4,1,none,0, WE_CALLPARTNER,I miss You +337,9,6,1,-1,0,0,1,1,no,0,0x2,0,weapon,0, ITM_TOMAHAWK,Throw Tomahawk +338,-1,8,1,7,0,0,10,-2,no,0,0x2,0,weapon,0, NPC_DARKCROSS,Cross of Darkness +339,5,6,4,7,0x48,0,10,1,no,33,0x102,0,magic,0, NPC_GRANDDARKNESS,Grand cross of Darkness +340,9,8,1,7,0,0,10,1:1:2:2:3:3:4:4:5:5,yes,0,0x2,0,magic,0, NPC_DARKSTRIKE,Soul Strike of Darkness +341,9,8,1,7,0,0,10,3:4:5:6:7:8:9:10:11:12,yes,0,0x2,0,magic,2:3:3:4:4:5:5:6:6:7, NPC_DARKTHUNDER,Darkness Jupitel +342,9,6,1,0,0x1,0,1,0,no,0,0x2,0,none,0, NPC_STOP,Stop +343,9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_WEAPONBRAKER,Break weapon +344,9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_ARMORBRAKE,Break armor +345,9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_HELMBRAKE,Break helm +346,9,6,1,-1,0,0,10,1,no,0,0x2,0,weapon,0, NPC_SHIELDBRAKE,Break shield +347,-9,6,1,9,0,0,10,1,no,0,0x2,0,weapon,0, NPC_UNDEADATTACK,Undead Element Attack +348,9,0,1,9,0x1,0,5,1,no,0,0x2,0,magic,0, NPC_CHANGEUNDEAD,Undead Attribute Change +349,0,6,4,0,0x1,0,10,0,no,0,0x2,0,weapon,0, NPC_POWERUP,Power Up +350,0,6,4,0,0x1,0,10,0,no,0,0x2,0,none,0, NPC_AGIUP,Agility UP +351,0,0,0,0,0x1,0,1,0,no,0,0x2,0,none,0, NPC_SIEGEMODE,Siege Mode +352,2,0,4,0,0x1,0,1,0,no,0,0x2,0,none,0, NPC_CALLSLAVE,Recall Slaves +353,0,0,0,0,0x1,0,1,0,no,0,0x2,0,none,0, NPC_INVISIBLE,Invisible +354,2,6,4,0,0x1,0,20,0,no,0,0x2,0,misc,0, NPC_RUN,Run +355,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, LK_AURABLADE,Aura Blade +356,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, LK_PARRYING,Parrying +357,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, LK_CONCENTRATION,Concentration +358,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, LK_TENSIONRELAX,Relax +359,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, LK_BERSERK,Frenzy +//360,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, LK_FURY,Fury +361,9,6,16,0,0x1,1,5,1,yes,0,0,0,magic,0, HP_ASSUMPTIO,Assumptio +362,4,6,4,0,0x1,0,5,1,yes,0,0,0,magic,2, HP_BASILICA,Basilica +363,0,0,0,0,0,0,10,0,no,0,0,0,magic,0, HP_MEDITATIO,Meditatio +364,0,0,0,0,0,0,10,1,no,0,0,0,magic,0, HW_SOULDRAIN,Soul Drain +365,9,8,1,-1,0,0,1,1,yes,0,0,0,weapon,0, HW_MAGICCRASHER,Stave Crasher +366,0,6,4,0,0x1,0,10,1,no,0,0,0,magic,0, HW_MAGICPOWER,Mystical Amplification +367,9,8,1,0,0xD0,0,5,1,no,0,0,0,misc,0, PA_PRESSURE,Gloria Domini +368,0,6,4,0,0x69,0,5,1,yes,0,0,0,weapon,0, PA_SACRIFICE, Martyr's Reckoning +369,0,6,4,0,0x41,0,10,1,yes,0,0,0,misc,0, PA_GOSPEL,Battle Chant +370,-2,6,1,-1,0,0,5,1,yes,0,0,0,weapon,3, CH_PALMSTRIKE,Raging Palm Strike +371,-2,8,4,-1,0,0,5,1,no,0,0x200,0,weapon,0, CH_TIGERFIST,Glacier Fist +372,-2,8,4,-1,0,0,10,-1:-1:-2:-2:-3:-3:-4:-4:-5:-5,no,0,0x200,0,weapon,0, CH_CHAINCRUSH,Chain Crush Combo +373,0,6,4,0,0x1,0,5,1,no,0,0,0,magic,0, PF_HPCONVERSION,Indulge +374,9,6,1,0,0x1,0,1,1,yes,0,0xE00,0,none,0, PF_SOULCHANGE,Soul Exhale +375,9,6,1,0,0x98,0,5,1,yes,0,0,0,magic,0, PF_SOULBURN,Soul Siphon +376,0,0,0,0,0x1,0,5,1,no,0,0,0,weapon,0, ASC_KATAR,Advanced Katar Mastery +//377,0,0,4,0,0x1,0,10,1,no,0,0,0,misc,0, ASC_HALLUCINATION,Hallucination Walk +378,0,6,4,5,0x1,0,5,1,no,0,0,0,weapon,0, ASC_EDP,Enchant Deadly Poison +379,7,6,1,-1,0x8,0,10,1,yes,0,0,0,weapon,0, ASC_BREAKER,Soul Destroyer +380,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, SN_SIGHT,Falcon Eyes +381,5,8,1,0,0x40,0,5,1,yes,0,0,0,misc,0, SN_FALCONASSAULT,Falcon Assault +382,9,8,1,-1,0,2,5,1,yes,0,0,13,weapon,0, SN_SHARPSHOOTING,Focused Arrow Strike +383,0,6,4,0,0x3,-1,10,1,yes,0,0,0,weapon,0, SN_WINDWALK,Wind Walker +384,0,0,4,0,0x1,0,10,1,yes,0,0,0,weapon,0, WS_MELTDOWN,Shattering Strike +//385,0,0,4,0,0x1,0,1,1,yes,0,0,0,none,0, WS_CREATECOIN,Create Coins +//386,0,0,4,0,0x1,0,1,1,yes,0,0,0,none,0, WS_CREATENUGGET,Create Nuggets +387,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, WS_CARTBOOST,Cart Boost +//388,9,6,2,0,0x1,0,5,1,no,0,0,0,none,0, WS_SYSTEMCREATE,Auto Attack System +389,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, ST_CHASEWALK,Stealth +390,0,0,4,0,0,0,5,1,yes,0,0,0,weapon,0, ST_REJECTSWORD,Counter Instinct +//391,0,0,4,0,1,0,1,1,yes,0,0,0,magic,0, ST_STEALBACKPACK,Steal Backpack +392,0,0,4,0,0x1,0,1,1,yes,0,0,0,none,0, CR_ALCHEMY,Alchemy +393,0,0,4,0,0x1,0,1,1,yes,0,0,0,none,0, CR_SYNTHESISPOTION,Potion Synthesis +394,9,8,1,-1,0,0,10,-9,yes,0,0,0,weapon,0, CG_ARROWVULCAN,Vulcan Arrow +395,0,0,4,0,0x1,3,1,1,yes,0,0x40,0,misc,2, CG_MOONLIT,Sheltering Bliss +396,1,6,16,0,0x1,0,1,1,yes,0,0x600,0,none,0, CG_MARIONETTE,Marionette Control +397,5,8,1,-1,0x20,0,5,5,no,0,0,0,weapon,0, LK_SPIRALPIERCE,Spiral Pierce +398,4,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, LK_HEADCRUSH,Traumatic Blow +399,4,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, LK_JOINTBEAT,Vital Strike +400,9,8,1,8,0x6,1,5,1:2:3:4:5,yes,0,0,0,magic,0, HW_NAPALMVULCAN,Napalm Vulcan +401,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, CH_SOULCOLLECT,Zen +402,9,6,1,0,0x1,0,5,1,no,0,0,0,none,0, PF_MINDBREAKER,Mind Breaker +403,0,0,4,0,0x1,0,1,1,yes,0,0,0,magic,0, PF_MEMORIZE,Foresight +404,9,6,2,2,0x1,0,5,1,yes,0,0x100,2,magic,0, PF_FOGWALL,Blinding Mist +405,7,6,1,0,0x1,0,1,1,no,0,0,3,magic,0, PF_SPIDERWEB,Fiber Lock +406,0,6,4,-1,0xA,2,10,1,no,33,0,0,weapon,0, ASC_METEORASSAULT,Meteor Assault +407,0,6,4,0,0x1,0,1,0,no,0,0,0,none,0, ASC_CDP,Create Deadly Poison +408,9,6,4,0,0x1,0,1,1,yes,0,0x4,0,none,0, WE_BABY,Baby +409,9,6,4,0,0x1,3,1,1,yes,0,0x4,1,none,0, WE_CALLPARENT,Call Parent +410,9,6,4,0,0x1,3,1,1,yes,0,0x4,1,none,0, WE_CALLBABY,Call Baby +411,0,6,4,0,0x1,0,10,1,yes,0,0,0,misc,4, TK_RUN,Running +412,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, TK_READYSTORM,Tornado Stance +413,-2,8,4,-1,0x2,2,7,-3,no,0,0x200,0,weapon,0, TK_STORMKICK,Tornado Kick +414,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, TK_READYDOWN,Heel Drop Stance +415,-2,8,4,-1,0,0,7,-3,no,0,0x200,0,weapon,0, TK_DOWNKICK,Heel Drop +416,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, TK_READYTURN,Roundhouse Stance +417,-2,8,4,-1,0x2,1,7,-3,no,0,0x200,0,weapon,2, TK_TURNKICK,Roundhouse Kick +418,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, TK_READYCOUNTER,Counter Kick Stance +419,-2,8,4,-1,0x40,0,7,-3,no,0,0x200,0,weapon,0, TK_COUNTER,Counter Kick +420,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, TK_DODGE,Tumbling +421,9,8,16,-1,0x1,0,7,-3,no,0,0,0,weapon,0, TK_JUMPKICK,Flying Kick +422,0,0,0,0,0,1,10,0,no,0,0,0,none,0, TK_HPTIME,Peaceful Break +423,0,0,0,0,0,1,10,0,no,0,0,0,none,0, TK_SPTIME,Happy Break +424,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, TK_POWER,Kihop +425,0,6,4,2:4:1:3:8:7:6,0x1,0,7,1,no,0,0,0,weapon,0, TK_SEVENWIND,Mild Wind +426,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, TK_HIGHJUMP,Taekwon Jump +427,0,6,4,0,0x1,0,3,1,yes,0,0,0,magic,0, SG_FEEL,Feeling the Sun Moon and Stars +428,1,6,4,-1,0x2,1,3,1,yes,0,0,0,weapon,2, SG_SUN_WARM,Warmth of the Sun +429,1,6,4,-1,0x2,1,3,1,yes,0,0,0,weapon,2, SG_MOON_WARM,Warmth of the Moon +430,1,6,4,-1,0x2,1,3,1,yes,0,0,0,weapon,2, SG_STAR_WARM,Warmth of the Stars +431,0,0,4,0,0x1,0,4,1,yes,0,0,0,magic,0, SG_SUN_COMFORT,Comfort of the Sun +432,0,0,4,0,0x1,0,4,1,yes,0,0,0,magic,0, SG_MOON_COMFORT,Comfort of the Moon +433,0,0,4,0,0x1,0,4,1,yes,0,0,0,magic,0, SG_STAR_COMFORT,Comfort of the Stars +434,10,6,1,0,0x1,0,3,1,yes,0,0,0,magic,0, SG_HATE,Hatred of the Sun Moon and Stars +435,0,0,0,0,0,0,3,0,no,0,0,0,none,0, SG_SUN_ANGER,Anger of the Sun +436,0,0,0,0,0,0,3,0,no,0,0,0,none,0, SG_MOON_ANGER,Anger of the Moon +437,0,0,0,0,0,0,3,0,no,0,0,0,none,0, SG_STAR_ANGER,Anger of the Stars +438,0,0,0,0,0,0,5,0,no,0,0,0,none,0, SG_SUN_BLESS,Blessing of the Sun +439,0,0,0,0,0,0,5,0,no,0,0,0,none,0, SG_MOON_BLESS,Blessing of the Moon +440,0,0,0,0,0,0,5,0,no,0,0,0,none,0, SG_STAR_BLESS,Blessing of the Stars +441,0,0,0,0,0,0,10,0,no,0,0,0,none,0, SG_DEVIL,Demon of the Sun Moon and Stars +442,0,0,0,0,0,0,3,0,no,0,0,0,none,0, SG_FRIEND,Friend of the Sun Moon and Stars +443,0,0,0,0,0,0,10,0,no,0,0,0,none,0, SG_KNOWLEDGE,Knowledge of the Sun Moon and Stars +444,0,6,4,0,0x1,0,1,1,no,0,0,0,misc,0, SG_FUSION,Union of the Sun Moon and Stars +445,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_ALCHEMIST,Spirit of the Alchemist +446,9,6,16,0,0x1,0,1,1,yes,0,0xC08,0,none,0, AM_BERSERKPITCHER,Aid Berserk Potion +447,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_MONK,Spirit of the Monk +448,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_STAR,Spirit of the Star Gladiator +449,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_SAGE,Spirit of the Sage +450,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_CRUSADER,Spirit of the Crusader +451,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_SUPERNOVICE,Spirit of the Supernovice +452,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_KNIGHT,Spirit of the Knight +453,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_WIZARD,Spirit of the Wizard +454,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_PRIEST,Spirit of the Priest +455,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_BARDDANCER,Spirit of the Artist +456,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_ROGUE,Spirit of the Rogue +457,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_ASSASIN,Spirit of the Assasin +458,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_BLACKSMITH,Spirit of the Blacksmith +459,0,6,4,0,0x3,-1,1,1,no,0,0x8,0,weapon,0 , BS_ADRENALINE2,Advanced Adrenaline Rush +460,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_HUNTER,Spirit of the Hunter +461,9,6,16,0,0x1,0,5,1,yes,0,0x200,0,magic,0, SL_SOULLINKER,Spirit of the Soul Linker +462,9,6,16,0,0x1,0,7,1,yes,0,0,0,magic,0, SL_KAIZEL,Kaizel +463,9,6,16,0,0x1,0,7,1,yes,0,0,0,magic,0, SL_KAAHI,Kaahi +464,9,6,16,0,0x1,0,3,1,yes,0,0,0,magic,0, SL_KAUPE,Kaupe +465,9,6,16,0,0x1,0,7,1,yes,0,0,0,magic,0, SL_KAITE,Kaite +466,0,0,0,0,0,0,7,0,yes,0,0,0,magic,0, SL_KAINA,Kaina +467,9,6,1,-2,0,0,7,1,no,0,0,0,magic,2, SL_STIN,Estin +468,9,6,1,-2,0,0,7,1,no,0,0,0,magic,0, SL_STUN,Estun +469,9,8,1,-2,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, SL_SMA,Esma +470,9,6,1,0,0x1,0,7,1,no,0,0,0,magic,0, SL_SWOO,Eswoo +471,9,6,1,0,0x1,0,3,1,no,0,0,0,magic,0, SL_SKE,Eske +472,9,6,1,0,0x1,0,3,1,no,0,0,0,magic,0, SL_SKA,Eska +473,0,6,4,0,0,0,1,1,no,0,0,0,none,0, SM_SELFPROVOKE,Provoke Self +474,0,0,4,0,0x1,0,10,1,no,0,0x2,0,none,0, NPC_EMOTION_ON,Emotion ON +475,0,0,4,0,0x1,0,1,1,yes,0,0,0,none,0, ST_PRESERVE,Preserve +476,1,6,1,0,0x1,0,5,1,yes,0,0,0,weapon,0, ST_FULLSTRIP,Divest All +477,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, WS_WEAPONREFINE,Upgrade Weapon +478,3,6,2,0,0x3,3,10,1,no,0,0,0,none,0, CR_SLIMPITCHER,Aid Condensed Potion +479,1,6,16,0,0x1,0,5,1,yes,0,0,0,weapon,0, CR_FULLPROTECTION,Full Protection +480,5,8,1,-1,0,0,5,5,no,0,0,0,weapon,0, PA_SHIELDCHAIN,Shield Chain +481,0,0,0,0,0,0,5,0,no,0,0,0,none,0, HP_MANARECHARGE,Mana Recharge +482,0,6,4,0,0x1,0,5,1,no,0,0,0,magic,0, PF_DOUBLECASTING,Double Casting +483,16,6,2,0,0x1,1:2:3:4:5,1,1,no,0,0,0,none,0, HW_GANBANTEIN,Ganbantein +484,9,6,2,2,0x91,0,5,1,yes,0,0,0,misc,0, HW_GRAVITATION,Gravitation Field +485,-2,6,1,-1,0x8,0,10,1,no,0,0,0,weapon,0, WS_CARTTERMINATION,Cart Termination +486,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, WS_OVERTHRUSTMAX,Maximum Power Thrust +487,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, CG_LONGINGFREEDOM,Longing for Freedom +488,0,6,4,0,0x1,1,5,1,no,0,0x40,0,misc,0, CG_HERMODE,Wand of Hermode +489,9,6,1,0,0x41,0,5,1,no,0,0,0,misc,0, CG_TAROTCARD,Tarot Card of Fate +490,9,8,1,0,0x40,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,misc,0, CR_ACIDDEMONSTRATION,Acid Demonstration +491,1,6,2,0,0x1,0,2,1,no,0,0,0,none,0, CR_CULTIVATION,Plant Cultivation +492,0,6,4,0:1:2:3:4:5:6:7:8:9,0x1,0,10,1,no,0,0x2,0,none,0, ITEM_ENCHANTARMS,Weapon Enchantment +493,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, TK_MISSION,Taekwon Mission +494,9,6,16,0,0x1,0,5,1,yes,0,0,0,magic,0, SL_HIGH,Spirit of Rebirth +495,0,6,4,0,0x1,0,1,1,no,0,0x8,0,weapon,0, KN_ONEHAND,Onehand Quicken +496,0,6,4,0,0x1,0,1,0,no,0,0x8,0,none,0, AM_TWILIGHT1,Twilight Alchemy 1 +497,0,6,4,0,0x1,0,1,0,no,0,0x8,0,none,0, AM_TWILIGHT2,Twilight Alchemy 2 +498,0,6,4,0,0x1,0,1,0,no,0,0x8,0,none,0, AM_TWILIGHT3,Twilight Alchemy 3 +499,-9,8,4,-1,0,0,1,2,no,0,0x208,0,weapon,0, HT_POWER,Beast Strafing +500,0,6,4,0,0x40,0,5,1,no,0,0,0,misc,0, GS_GLITTERING,Flip the Coin +501,9,6,1,-1,0x50,0,1,1,no,0,0,0,misc,0, GS_FLING,Fling +502,-9,8,1,-1,0,0,1,3,no,0,0,0,weapon,0, GS_TRIPLEACTION,Triple Action +503,-9,6,1,-1,0x8,0,1,1,no,0,0,0,weapon,0, GS_BULLSEYE,Bulls Eye +504,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, GS_MADNESSCANCEL,Madness Canceller +505,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, GS_ADJUSTMENT,AdJustment +506,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, GS_INCREASING,Increasing Accuracy +507,-9,6,1,8,0,0,1,1,no,0,0,0,weapon,0, GS_MAGICALBULLET,Magical Bullet +508,-9,6,1,-1,0x1,0,1,1,no,0,0,0,weapon,0, GS_CRACKER,Cracker +509,0,0,0,0,0,0,10,0,no,0,0,0,none,0, GS_SINGLEACTION,Single Action +510,0,0,0,0,0,0,10,0,no,0,0,0,none,0, GS_SNAKEEYE,Snake Eye +511,-9,8,0,-1,0,0,10,2,no,0,0,0,weapon,0, GS_CHAINACTION,Chain Action +512,-9,6,1,-1,0,0,10,1,yes,0,0,0,weapon,0, GS_TRACKING,Tracking +513,-9,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, GS_DISARM,Disarm +514,-9,6,1,-1,0x20,0,5,1,no,0,0,0,weapon,0, GS_PIERCINGSHOT,Piercing Shot +515,-9,8,1,-1,0,0,10,5,no,0,0,0,weapon,0, GS_RAPIDSHOWER,Rapid Shower +516,0,8,4,-1,0x2,3,10,1,no,0,0,0,weapon,0, GS_DESPERADO,Desperado +517,0,6,4,-1,0x1,0,10,1,no,0,0,0,weapon,0, GS_GATLINGFEVER,Gatling Fever +518,2,6,1,-1,0,0,10,1,no,0,0,0,weapon,5, GS_DUST,Dust +519,-9,6,1,-1,0,0,10,1,yes,0,0,0,weapon,0, GS_FULLBUSTER,Full Buster +520,-9,6,1,-1,0x2,1:1:1:2:2:2:3:3:3:4,10,1,no,0,0,0,weapon,0, GS_SPREADATTACK,Spread Attack +521,-9,6,2,-1,0x40,1,10,1,no,0,0,0,weapon,3, GS_GROUNDDRIFT,Ground Drift +522,0,0,0,0,0,0,10,1,no,0,0,0,weapon,0, NJ_TOBIDOUGU,Shuriken Training +523,9,6,1,-1,0x40,0,10,1,no,0,0,0,weapon,0, NJ_SYURIKEN,Throw Shuriken +524,9,8,1,-1,0x40,0,5,3,no,0,0,0,weapon,0, NJ_KUNAI,Throw Kunai +525,9,8,2,-1,0x2,0,5,-3:-3:-4:-4:-5,no,0,0,0,weapon,0, NJ_HUUMA,Throw Huuma Shuriken +526,9,6,1,0,0x50,0,10,1,no,0,0,0,misc,0, NJ_ZENYNAGE,Throw Zeny +527,0,6,4,-1,0,0,5,1,no,0,0,0,weapon,4, NJ_TATAMIGAESHI,Improvised Defense +528,-1,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, NJ_KASUMIKIRI,Vanishing Slash +529,7:9:11:13:15,6,2,0,0x1,0,5,1,no,0,0,0,none,0, NJ_SHADOWJUMP,Shadow Leap +530,7:9:11:13:15,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, NJ_KIRIKAGE,Shadow Slash +531,0,6,4,0,0x1,0,5,1,no,0,0,0,none,7, NJ_UTSUSEMI,Cicada Skin Sheeding +532,0,6,4,0,0x1,0,10,1,yes,0,0,0,magic,0, NJ_BUNSINJYUTSU,Mirror Image +533,0,0,0,0,0,0,10,0,no,0,0,0,none,0, NJ_NINPOU,Spirit of the Blade +534,9,8,1,3,0,0,10,1:2:3:4:5:6:7:8:9:10,yes,0,0,0,magic,0, NJ_KOUENKA,Crimson Fire Petal +535,0,8,4,3,0,0,10,1,yes,0,0,0,magic,1, NJ_KAENSIN,Crimson Fire Formation +536,9,8,1,3,0x2,2,5,3,yes,0,0,0,magic,0, NJ_BAKUENRYU,Raging Fire Dragon +537,9,8,1,1,0,0,10,3:4:5:6:7:8:9:10:11:12,yes,0,0,0,magic,0, NJ_HYOUSENSOU,Spear of Ice +538,9,6,2,1,0x1,0,10,1,yes,0,0,0,magic,0, NJ_SUITON,Hidden Water +539,0,6,4,1,0x2,3,5,1,yes,0,0,0,magic,0, NJ_HYOUSYOURAKU,Ice Meteor +540,9,8,1,4,0,0,10,1:2:2:3:3:4:4:5:5:6,yes,0,0,0,magic,0, NJ_HUUJIN,Wind Blade +541,9,6,2,4,0x2,2:2:3:3:4,5,1,yes,0,0,0,magic,0, NJ_RAIGEKISAI,Lightning Strike of Destruction +542,9,8,1,4,0,3,5,1,yes,0,0,5:6:7:8:9,magic,0, NJ_KAMAITACHI,Kamaitachi +543,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, NJ_NEN,Soul +544,-5,8,1,0,0x40,0,10,1,no,0,0,0,weapon,0, NJ_ISSEN,Final Strike + +// Additional NPC Skills (Episode 11.3) +653,0,8,4,0,0x6,5:7:9:11:13:5:7:9:11:13,10,1,no,0,0x2,0,magic,0, NPC_EARTHQUAKE,Earthquake +654,9,6,1,3,0,5,10,1,no,0,0x2,14,weapon,0, NPC_FIREBREATH,Fire Breath +655,9,6,1,1,0,5,10,1,no,0,0x2,14,weapon,0, NPC_ICEBREATH,Ice Breath +656,9,6,1,4,0,5,10,1,no,0,0x2,14,weapon,0, NPC_THUNDERBREATH,Thunder Breath +657,9,6,1,5,0,5,10,1,no,0,0x2,14,weapon,0, NPC_ACIDBREATH,Acid Breath +658,9,6,1,7,0,5,10,1,no,0,0x2,14,weapon,0, NPC_DARKNESSBREATH,Darkness Breath +659,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_DRAGONFEAR,Dragon Fear +660,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_BLEEDING,Bleeding +661,0,6,4,0,0x2,7,5,1,no,0,0x2,0,weapon,7, NPC_PULSESTRIKE,Pulse Strike +662,0,6,4,0,0x2,14,10,1,no,0,0x2,0,weapon,0, NPC_HELLJUDGEMENT,Hell's Judgement +663,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDESILENCE,Wide Silence +664,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDEFREEZE,Wide Freeze +665,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDEBLEEDING,Wide Bleeding +666,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDESTONE,Wide Petrify +667,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDECONFUSE,Wide Confusion +668,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDESLEEP,Wide Sleep +669,0,6,4,3,0x3,5,1,1,no,0,0x2,0,magic,0, NPC_WIDESIGHT,Wide Sight +670,9,6,2,7,0x91,0,10,1,no,0,0x2,0,magic,0, NPC_EVILLAND,Evil Land +671,0,6,4,0,0x1,0,5,1,no,0,0x2,0,magic,0, NPC_MAGICMIRROR,Magic Mirror +672,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_SLOWCAST,Slow Cast +673,-9,6,1,-1,0,0,5,1,no,0,0x2,0,weapon,0, NPC_CRITICALWOUND,Critical Wounds +674,-9,6,1,-1,0x1,0,1,1,no,0,0x2,0,none,0, NPC_EXPULSION,Expulsion +675,0,6,4,0,0x1,0,5,1,no,0,0x2,0,magic,0, NPC_STONESKIN,Stone Skin +676,0,6,4,0,0x1,0,5,1,no,0,0x2,0,magic,0, NPC_ANTIMAGIC,Anti Magic +677,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDECURSE,Wide Curse +678,0,6,4,0,0x3,2:5:8:11:14,5,1,no,0,0x2,0,none,0, NPC_WIDESTUN,Wide Stun +679,0,6,4,0,0x2,5:7:9:11:13:13:13:13:13:13,10,1,no,0,0x2,0,weapon,0, NPC_VAMPIRE_GIFT,Vampire Gift +680,0,6,4,0,0x3,5:7:9:11:13:13:13:13:13:13,10,1,no,0,0x2,0,none,0, NPC_WIDESOULDRAIN,Wide Soul Drain + +// Cash Shop Skill +681,0,0,0,0,0,0,10,0,no,0,0x1,0,none,0, ALL_INCCARRY,Increase Weight Limit R + +// Additional NPC skill (Episode 12) +682,0,0,4,0,0x1,0,1,1,no,0,0x2,0,none,0, NPC_TALK,Talk +683,-9,6,1,-1,0,0,1,1,no,0,0x2,0,none,0, NPC_HELLPOWER,Hell Power +684,0,6,4,0,0x3,-1,1,1,no,0,0x2,0,none,0, NPC_WIDEHELLDIGNITY,Hell Dignity +685,0,0,4,0,0x1,0,1,1,no,0,0x2,0,none,0, NPC_INVINCIBLE,Invincible +686,0,0,4,0,0x1,0,1,1,no,0,0x2,0,none,0, NPC_INVINCIBLEOFF,Invincible off +687,0,6,4,0,0x1,0,1,1,yes,0,0x2,0,none,0, NPC_ALLHEAL,Full Heal + +// Additional Skill (??) +688,9,6,16,0,0x1,0,10,0,no,0,0x200,0,none,0, GM_SANDMAN,GM Sandman +689,0,6,4,0,0x3,-1,10,1,yes,0,0x2,0,magic,0, CASH_BLESSING,Party Blessing +690,0,6,4,0,0x3,-1,10,1,yes,0,0x2,0,magic,0, CASH_INCAGI,Party Increase AGI +691,0,6,4,0,0x3,-1,5,1,yes,0,0x2,0,magic,0, CASH_ASSUMPTIO,Party Assumptio +//692,0,0,0,0,0,0,9,0,no,0,0x2,0,none,0, ALL_CATCRY,Cat Cry +693,0,6,4,0,0x3,-1,1,1,yes,0,0x2,0,magic,0, ALL_PARTYFLEE,Party Flee +//694,0,0,0,0,0,0,9,0,no,0,0x2,0,none,0, ALL_ANGEL_PROTECT,Angel's Protection +//695,0,0,0,0,0,0,9,0,no,0,0x2,0,none,0, ALL_DREAM_SUMMERNIGHT,Summer Night Dream +//696,0,0,0,0,0,0,9,0,no,0,0x2,0,none,0, NPC_CHANGEUNDEAD2,Change Undead +//697,9,6,4,0,0x1,0,1,1,yes,0,0x2,0,magic,0, ALL_REVERSEORCISH,Reverse Orcish +698,0,6,4,0,0x01,0,1,1,no,0,0x2,0,none,0, ALL_WEWISH,Christmas Carol +//699,0,0,0,0,0,0,9,0,no,0,0x2,0,none,0, ALL_SONKRAN,ALL_SONKRAN + +// New NPC Wide Status AoE Skills And Others +//700,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDEHEALTHFEAR,Wide Health Fear +//701,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDEBODYBURNNING,Wide Body Burnning +//702,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDEFROSTMISTY,Wide Freezing +//703,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDECOLD,Wide Crystalize +//704,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDE_DEEP_SLEEP,Wide Deep Sleep +//705,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_WIDESIREN,Wide Siren's Voice +//706,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_VENOMFOG,Venom Fog +//707,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_MILLENNIUMSHIELD,Millenium Shield 2 +//708,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_COMET,Comet 2 + +1001,9,6,1,-1,0,0,1,1,no,0,0x1,0,weapon,0, KN_CHARGEATK,Charge Attack +1002,0,6,4,0,0x1,0,1,0,no,0,0x1,0,weapon,2, CR_SHRINK,Shrink +1003,0,0,0,0,0,0,1,0,no,0,0x1,0,weapon,0, AS_SONICACCEL,Sonic Acceleration +1004,9,8,1,0,0x8,0,1,1,no,0,0x1,0,weapon,0, AS_VENOMKNIFE,Throw Venom Knife +1005,1,6,1,0,0x1,0,1,1,no,0,0x1,0,weapon,0, RG_CLOSECONFINE,Close Confine +1006,0,6,4,3,0,2,1,1,yes,0,0x1,0,magic,3, WZ_SIGHTBLASTER,Sight Blaster +1007,0,6,4,0,0x1,0,1,0,no,0,0x1,0,none,0, SA_CREATECON,Create Elemental Converter +1008,9,6,1,1,0x1,0,1,1,yes,0,0x1,0,magic,0, SA_ELEMENTWATER,Elemental Change Water +1009,-9,6,1,0,0,0,1,1,no,0,0x1,0,weapon,3, HT_PHANTASMIC,Phantasmic Arrow +1010,9,6,1,0,0x1,0,1,0,no,0,0x1,0,misc,0, BA_PANGVOICE,Pang Voice +1011,9,6,1,0,0x1,0,1,0,no,0,0x1,0,misc,0, DC_WINKCHARM,Wink of Charm +1012,0,0,0,0,0,0,1,0,no,0,0x1,0,weapon,0, BS_UNFAIRLYTRICK,Unfair Trick +1013,0,6,4,0,0x3,2,1,0,no,0,0x1,0,weapon,0, BS_GREED,Greed +1014,0,6,4,6,0x3,14,1,0,yes,0,0x1,0,magic,0, PR_REDEMPTIO,Redemptio +1015,9,6,16,0,0x1,0,1,1,no,0,0x401,0,weapon,0, MO_KITRANSLATION,Ki Translation +1016,-1,6,1,-1,0x2,1,1,1,no,0,0x1,0,weapon,5, MO_BALKYOUNG,Ki Explosion +1017,9,6,1,2,0x1,0,1,1,yes,0,0x1,0,magic,0, SA_ELEMENTGROUND,Elemental Change Earth +1018,9,6,1,3,0x1,0,1,1,yes,0,0x1,0,magic,0, SA_ELEMENTFIRE,Elemental Change Fire +1019,9,6,1,4,0x1,0,1,1,yes,0,0x1,0,magic,0, SA_ELEMENTWIND,Elemental Change Wind + +//**** +// RK Rune Knight +//**** +2001,1,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, RK_ENCHANTBLADE,Enchant Blade +2002,7:8:9:10:11,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, RK_SONICWAVE,Sonic Wave +2003,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, RK_DEATHBOUND,Death Bound +2004,1,8,1,-1,0,0,10,-5,no,0,0,0,weapon,0, RK_HUNDREDSPEAR,Hundred Spear +2005,1,6,2,4,0x2,2,5,1,no,0,0,0,weapon,3, RK_WINDCUTTER,Wind Cutter +2006,0,6,4,-1,0x2,5,5,1,no,0,0,0,weapon,0, RK_IGNITIONBREAK,Ignition Break +2007,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, RK_DRAGONTRAINING,Dragon Training +2008,9,6,2,3,0xC2,1:1:1:2:2:2:3:3:4:4,10,1,no,0,0,0,misc,0, RK_DRAGONBREATH,Dragon Breath //CHECK May have to change this back to a weapon type attack. +2009,0,6,4,0,0x3,3:4:5:6:7,5,1,yes,0,0,0,weapon,0, RK_DRAGONHOWLING,Dragon Howling +2010,0,0,0,0,0,0,10,0,no,0,0,0,none,0, RK_RUNEMASTERY,Rune Mastery +2011,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, RK_MILLENNIUMSHIELD,Millenium Shield +2012,1,6,4,-1,0,0x8,1,1,yes,0,0,0,weapon,0, RK_CRUSHSTRIKE,Crush Strike +2013,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, RK_REFRESH,Refresh +2014,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, RK_GIANTGROWTH,Giant Growth +2015,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, RK_STONEHARDSKIN,Stone Hard Skin +2016,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, RK_VITALITYACTIVATION,Vitality Activation +2017,0,6,4,-1,0x2,3,1,1,no,0,0,0,weapon,7, RK_STORMBLAST,Storm Blast +2018,0,6,4,0,0x3,-1,1,1,yes,0,0,0,none,0, RK_FIGHTINGSPIRIT,Fighting Spirit //CHECK Is this splash needed? +2019,9,6,4,6,0x1,0,1,1,yes,0,0,0,none,0, RK_ABUNDANCE,Abundance +2020,5:6:7:8:9,6,1,-1,0,0,5,1,yes,0,0,0,weapon,0, RK_PHANTOMTHRUST,Phantom Thrust + +//**** +// WL Warlock +//**** +2201,11,6,16,0,0,0,5,1,yes,0,0,0,magic,0, WL_WHITEIMPRISON,White Imprison +2202,11,8,1,8,0x2,1:1:1:2:2,5,-2,yes,0,0,0,magic,0, WL_SOULEXPANSION,Soul Expansion +2203,0,8,4,1,0x2,13,5,-3:-4:-5:-6:-7,yes,0,0,0,magic,0, WL_FROSTMISTY,Frosty Misty +2204,0,8,4,1,0x2,13,5,-5,yes,0,0,0,magic,0, WL_JACKFROST,Jack Frost +2205,11,6,1,0,0x1,0,5,1,yes,0,0,0,magic,0, WL_MARSHOFABYSS,Marsh of Abyss +2206,0,6,4,0,0x1,0,5,1,yes,0,0,0,magic,0, WL_RECOGNIZEDSPELL,Recognized Spell +2207,7,6,1,2,0x3,1:2:2:3:3,5,1,yes,0,0,0,magic,0, WL_SIENNAEXECRATE,Sienna Execrate +2208,0,0,0,0,0,0,3,0,no,0,0,0,none,0, WL_RADIUS,Radius +2209,0,6,4,0,0x3,9:10:11:12:13,5,1,yes,0,0,0,magic,0, WL_STASIS,Stasis +2210,11,6,1,0,0,0,5,1,yes,0,0,0,magic,0, WL_DRAINLIFE,Drain Life +2211,11,8,1,3,0x2,3,5,-7,yes,0,0,0,magic,3, WL_CRIMSONROCK,Crimson Rock +2212,11,6,1,3,0,0,5,1,yes,0,0,0,magic,0, WL_HELLINFERNO,Hell Inferno +2213,11,8,2,0,0x2,15,5,-20,yes,0,0,0,magic,2, WL_COMET,Comet //CHECK AoE in official code appears to be 15 x 15, yet casting circle is much bigger then that. +2214,11,6,1,0,0,3,5,1,yes,0,0,0,magic,0, WL_CHAINLIGHTNING,Chain Lightning //CHECK Is the splash being used for the target search? +2215,11,6,1,4,0,0,5,1,no,0,0,0,magic,0, WL_CHAINLIGHTNING_ATK,Chain Lightning Attack +2216,3,8,2,2,0,0,5,-6:-7:-8:-9:-10,yes,0,0,0,magic,0, WL_EARTHSTRAIN,Earth Strain +2217,11,6,1,0,0,0,5,1,yes,0,0,0,magic,0, WL_TETRAVORTEX,Tetra Vortex +2218,11,6,1,3,0,0,5,1,no,0,0,0,magic,0, WL_TETRAVORTEX_FIRE,Tetra Vortex Fire +2219,11,6,1,1,0,0,5,1,no,0,0,0,magic,0, WL_TETRAVORTEX_WATER,Tetra Vortex Water +2220,11,6,1,4,0,0,5,1,no,0,0,0,magic,0, WL_TETRAVORTEX_WIND,Tetra Vortex Wind +2221,11,6,1,2,0,0,5,1,no,0,0,0,magic,0, WL_TETRAVORTEX_GROUND,Tetra Vortex Earth +2222,0,6,4,3,0x1,0,5,1,yes,0,0,0,magic,0, WL_SUMMONFB,Summon Fire Ball +2223,0,6,4,4,0x1,0,5,1,yes,0,0,0,magic,0, WL_SUMMONBL,Summon Lightning Ball +2224,0,6,4,1,0x1,0,5,1,yes,0,0,0,magic,0, WL_SUMMONWB,Summon Water Ball +2225,11,6,1,3,0,0,5,1,no,0,0,1,magic,0, WL_SUMMON_ATK_FIRE,Summon Attack Fire //CHECK Summon attack ID's dont appear to have a range. +2226,11,6,1,4,0,0,5,1,no,0,0,1,magic,0, WL_SUMMON_ATK_WIND,Summon Attack Wind +2227,11,6,1,1,0,0,5,1,no,0,0,1,magic,0, WL_SUMMON_ATK_WATER,Summon Attack Water +2228,11,6,1,2,0,0,5,1,no,0,0,1,magic,0, WL_SUMMON_ATK_GROUND,Summon Attack Earth +2229,0,6,4,2,0x1,0,5,1,yes,0,0,0,magic,0, WL_SUMMONSTONE,Summon Stone +2230,11,8,1,0,0,0,2,1,yes,0,0,0,magic,0, WL_RELEASE,Release //CHECK Should it be left to do multi hit or single hit? +2231,0,6,4,0,0x1,0,1,1,yes,0,0,0,magic,0, WL_READING_SB,Reading Spellbook +2232,0,0,0,0,0,0,5,0,no,0,0,0,none,0, WL_FREEZE_SP,Freeze Spell + + +//**** +// GC Guillotine Cross +//**** +2021,10,6,1,0,0x1,0,5,1,no,0,0,0,none,0, GC_VENOMIMPRESS,Venom Impress +2022,3,8,1,-1,0,0,5,-7,no,0,0,0,weapon,0, GC_CROSSIMPACT,Cross Impact +2023,3:4:5:6:7,6,1,-1,0,0,5,1,no,0,0,0,weapon,0,GC_DARKILLUSION,Dark Illusion +2024,0,0,0,0,0,0,10,0,no,0,0,0,none,0, GC_RESEARCHNEWPOISON,Research New Poison +2025,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, GC_CREATENEWPOISON,Create New Poison +2026,5,6,16,0,0x1,0,1,1,no,0,0,0,none,0, GC_ANTIDOTE,Antidote +2027,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, GC_POISONINGWEAPON,Poisoning Weapon +2028,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, GC_WEAPONBLOCKING,Weapon Blocking +2029,-2,6,4,-1,0x2,1,5,1,no,0,0,0,weapon,3, GC_COUNTERSLASH,Counter Slash +2030,-2,6,4,-1,0x1,0,5,1,no,0,0x200,0,weapon,0, GC_WEAPONCRUSH,Weapon Crush //CHECK SHould this and the above skill have INF2 0x200? +2031,1,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, GC_VENOMPRESSURE,Venom Pressure +2032,5,6,2,0,0x1,0,5,1,yes,0,0,1,none,0, GC_POISONSMOKE,Poison Smoke +2033,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, GC_CLOAKINGEXCEED,Cloaking Exceed +2034,0,6,4,-1,0x2,3,1,1,no,0,0,0,weapon,0, GC_PHANTOMMENACE,Phantom Menace +2035,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, GC_HALLUCINATIONWALK,Hallucination Walk +2036,0,6,4,-1,0x2,1:1:1:1:2,5,1,no,0,0,0,weapon,0, GC_ROLLINGCUTTER,Rolling Cutter +2037,9:10:11:12:13,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, GC_CROSSRIPPERSLASHER,Cross Ripper Slasher + +//**** +// AB Arch Bishop +//**** +2038,11,8,1,6,0x2,3,5,-3,yes,0,0,0,magic,0, AB_JUDEX,Judex +2039,0,6,4,0,0x1,0,1,1,yes,0,0,0,magic,0, AB_ANCILLA,Ancilla +2040,11,8,1,6,0,0,10,-10,yes,0,0,0,magic,0, AB_ADORAMUS,Adoramus +2041,0,6,4,6,0x3,3:7:15,3,1,yes,0,0,0,magic,0, AB_CLEMENTIA,Crementia +2042,0,6,4,6,0x3,3:7:15,3,1,yes,0,0,0,magic,0, AB_CANTO,Canto Candidus +2043,0,6,4,6,0x3,3:7:15,3,1,yes,0,0,0,magic,0, AB_CHEAL,Coluceo Heal +2044,11,6,2,6,0x1,0,5,1,yes,0,0,1,magic,0, AB_EPICLESIS,Epiclesis +2045,0,6,4,6,0x3,15,10,1,yes,0,0,0,magic,0, AB_PRAEFATIO,Praefatio +2046,0,6,4,6,0x3,15,10,1,yes,0,0,0,magic,0, AB_ORATIO,Oratio +2047,0,6,4,6,0x3,15,4,1,yes,0,0,0,magic,0, AB_LAUDAAGNUS,Lauda Agnus +2048,0,6,4,6,0x3,15,4,1,yes,0,0,0,magic,0, AB_LAUDARAMUS,Lauda Ramus +2049,0,0,0,0,0,0,10,0,no,0,0,0,none,0, AB_EUCHARISTICA,Eucharistica +2050,11,6,16,6,0x1,0,1,1,yes,0,0,0,magic,0, AB_RENOVATIO,Renovatio +2051,11,6,16,6,0x21,0,5,1,yes,0,0,0,magic,0, AB_HIGHNESSHEAL,Highness Heal //CHECK Info shows this has magic attack. +2052,11,6,1,0,0x1,0,5,1,yes,0,0xA00,0,magic,0, AB_CLEARANCE,Clearance //CHECK Also shows this as a magic attack. Why? +2053,0,6,16,0,0x1,0,5,1,yes,0,0,0,magic,0, AB_EXPIATIO,Expiatio //CHECK Does this also give the buff to party members? +2054,0,6,4,6,0x1,0,10,1,yes,0,0,0,none,0, AB_DUPLELIGHT,Duple Light //CHECK Had issues adding a skill level check to make the % go higher with the skills level. Will do later. +2055,-1,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, AB_DUPLELIGHT_MELEE,Duple Light Melee +2056,-1,6,1,0,0,0,10,1,no,0,0,0,magic,0, AB_DUPLELIGHT_MAGIC,Duple Light Magic +2057,0,6,4,6,0x3,4:5:6:7:8,5,1,yes,0,0,0,magic,0, AB_SILENTIUM,Silentium //CHECk Marked magic attack as well. Hmmmm.... + +2515,11,6,16,0,0x1,0,5,1,yes,0,0,0,magic,0, AB_SECRAMENT,Secrament + +//**** +// RA Ranger +//**** +2233,9,8,1,-1,0x2,3:3:3:3:3:4:4:4:4:5,10,-3,yes,0,0,0,weapon,0, RA_ARROWSTORM,Arrow Storm +2234,0,6,4,0,0,0,5,1,yes,0,0,0,none,0, RA_FEARBREEZE,Fear Breeze +2235,0,0,0,0,0,0,10,0,no,0,0,0,none,0, RA_RANGERMAIN,Ranger Main +2236,9,8,1,-1,0,0,10,1,yes,0,0,0,weapon,0, RA_AIMEDBOLT,Aimed Bolt +2237,9,6,2,0,0x3,3,1,1,no,0,0,0,none,0, RA_DETONATOR,Detonator +2238,3,6,2,0,0x3,2,5,1,no,0,0x80,3,misc,0, RA_ELECTRICSHOCKER,Electric Shocker +2239,3,6,2,0,0x42,3,5,1,no,0,0x80,3,misc,0, RA_CLUSTERBOMB,Cluster Bomb +2240,0,6,4,0,0,0,1,1,no,0,0,0,none,0, RA_WUGMASTERY,Warg Mastery +2241,0,6,4,0,0,0,3,1,no,0,0,0,none,0, RA_WUGRIDER,Warg Rider +2242,0,6,4,-1,0x2,1,1,0,no,0,0,0,weapon,0, RA_WUGDASH,Warg Dash +2243,9,6,1,0,0,0,5,1,no,0,0,0,weapon,0, RA_WUGSTRIKE,Warg Strike +2244,9,6,1,0,0,0,5,1,no,0,0,0,weapon,0, RA_WUGBITE,Warg Bite +2245,0,0,0,0,0,0,10,0,no,0,0,0,none,0, RA_TOOTHOFWUG,Tooth of Warg +2246,0,6,4,0,0x2,3:4:5:6:7,5,1,no,0,0,0,weapon,0, RA_SENSITIVEKEEN,Sensitive Keen +2247,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, RA_CAMOUFLAGE,Camouflage +2248,0,0,0,0,0,0,5,0,no,0,0,0,none,0, RA_RESEARCHTRAP,Research Trap +2249,3,6,2,3,0x43,2,1,1,no,0,0x80,1,misc,0, RA_MAGENTATRAP,Magenta Trap +2250,3,6,2,1,0x43,2,1,1,no,0,0x80,1,misc,0, RA_COBALTTRAP,Cobalt Trap +2251,3,6,2,2,0x43,2,1,1,no,0,0x80,1,misc,0, RA_MAIZETRAP,Maize Trap +2252,3,6,2,4,0x43,2,1,1,no,0,0x80,1,misc,0, RA_VERDURETRAP,Verdure Trap +2253,3,6,2,0,0x42,2,5,1,no,0,0x80,2,misc,0, RA_FIRINGTRAP,Firing Trap +2254,3,6,2,0,0x42,2,5,1,no,0,0x80,2,misc,0, RA_ICEBOUNDTRAP,Icebound Trap + +//**** +// NC Mechanic +2255,0,0,0,0,0,0,5,0,no,0,0,0,none,0, NC_MADOLICENCE,Mado License +2256,11,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, NC_BOOSTKNUCKLE,Boost Knuckle +2257,3,6,1,-1,0,0,3,1,no,0,0,0,weapon,0, NC_PILEBUNKER,Pile Bunker +2258,13,6,1,-1,0,0,3,1,no,0,0,0,weapon,0, NC_VULCANARM,Vulcan Arm +2259,5,6,1,3,0,2,3,1,no,0,0,5,weapon,0, NC_FLAMELAUNCHER,Flame Launcher +2260,7,6,2,1,0x2,2:3:4,3,1,no,0,0,0,weapon,0, NC_COLDSLOWER,Cold Slower +2261,7,6,2,-1,0x42,3:2:1,3,1,no,0,0,0,weapon,0, NC_ARMSCANNON,Arm Cannon +2262,0,6,4,0,0x1,0,3,1,no,0,0,0,none,0, NC_ACCELERATION,Acceleration +2263,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, NC_HOVERING,Hovering +2264,0,6,4,0,0x1,0,1,1,no,0,0,0,none,7, NC_F_SIDESLIDE,Front-Side Slide +2265,0,6,4,0,0x1,0,1,1,no,0,0,0,none,7, NC_B_SIDESLIDE,Back-Side Slide +2266,0,0,0,0,0,0,4,0,no,0,0,0,none,0, NC_MAINFRAME,Mainframe Restructure // Check me. Part of the code notes translated to "The amount of fuel have". +2267,0,6,4,-1,0x42,2:3:4,3,1,no,0,0,0,misc,5, NC_SELFDESTRUCTION,Self Destruction +2268,0,6,4,0,0x1,0,4,1,yes,0,0,0,none,0, NC_SHAPESHIFT,Shape Shift +2269,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, NC_EMERGENCYCOOL,Emergency Cool +2270,0,6,4,0,0x3,7,1,1,yes,0,0,0,none,0, NC_INFRAREDSCAN,Infrared Scan +2271,9,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, NC_ANALYZE,Analyze +2272,0,6,4,0,0x3,1:2:3,3,1,yes,0,0,0,none,0, NC_MAGNETICFIELD,Magnetic Field +2273,0,6,4,0,0x1,0,3,1,yes,0,0,0,none,0, NC_NEUTRALBARRIER,Neutral Barrier +2274,0,6,4,0,0x1,0,3,1,yes,0,0,0,none,0, NC_STEALTHFIELD,Stealth Field +2275,5,6,16,0,0x1,0,5,1,yes,0,0,0,magic,0, NC_REPAIR,Repair +2276,0,0,0,0,0,0,10,0,no,0,0,0,none,0, NC_TRAININGAXE,Axe Training +2277,0,0,0,0,0,0,5,0,no,0,0,0,none,0, NC_RESEARCHFE,Research Fire/Earth +2278,4:5:6:7:8,6,1,-1,0,0,5,1,no,0,0,0,weapon,2:3:4:5:6, NC_AXEBOOMERANG,Axe Boomerang +2279,1,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, NC_POWERSWING,Power Swing +2280,0,8,4,-1,0x2,2:2:3:3:3,5,-6,no,0,0,0,weapon,0, NC_AXETORNADO,Axe Tornado // Check me. Takes 20 * Skill LV amount of HP each use. +2281,2,6,2,0,0x1,0,5,1,yes,0,0,2,none,0, NC_SILVERSNIPER,FAW - Silver Sniper +2282,2,6,2,0,0x1,0,5,1,yes,0,0,2,none,0, NC_MAGICDECOY,FAW - Magic Decoy //CHECK FIX ME!!!! Wind and Earth stones spawning opposite decoys. +2283,2,6,1,0,0x1,0,1,1,no,0,0,0,none,0, NC_DISJOINT,FAW Removal + +//**** +// SC Shadow Chaser +2284,1,6,1,-1,0x2,1,5,1,no,0,0,0,weapon,0, SC_FATALMENACE,Fatal Menace +2285,0,6,4,0,0x1,0,10,1,no,0,0,0,none,0, SC_REPRODUCE,Reproduce +2286,0,6,4,0,0x1,0,10,1,yes,0,0,0,none,0, SC_AUTOSHADOWSPELL,Auto Shadow Spell +2287,5,6,1,0,0x1,0,5,1,no,0,0,0,none,0, SC_SHADOWFORM,Shadow Form +2288,7:7:7:9:9:9:9:11:11:11,8,1,-1,0,0,10,-3,yes,0,0,0,weapon,3, SC_TRIANGLESHOT,Triangle Shot +2289,0,6,4,0,0x3,2,5,1,no,0,0,0,none,0, SC_BODYPAINT,Body Painting +2290,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, SC_INVISIBILITY,Invisibility +2291,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, SC_DEADLYINFECT,Deadly Infect +2292,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_ENERVATION,Masquerade - Enervation +2293,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_GROOMY,Masquerade - Gloomy +2294,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_IGNORANCE,Masquerade - Ignorance +2295,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_LAZINESS,Masquerade - Laziness +2296,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_UNLUCKY,Masquerade - Unlucky +2297,3,6,1,0,0x1,0,3,1,yes,0,0,0,none,0, SC_WEAKNESS,Masquerade - Weakness +2298,3,6,1,0,0x1,0,5,1,yes,0,0,0,weapon,0, SC_STRIPACCESSARY,Strip Accessory //CHECK Is weapon attack type needed? +2299,7,6,2,0,0x1,0,3,1,yes,0,0,3,none,0, SC_MANHOLE,Man Hole +2300,7,6,2,0,0x1,0,3,1,yes,0,0,1,none,0, SC_DIMENSIONDOOR,Dimension Door +2301,7,6,2,0,0x1,0,3,1,yes,0,0,0,none,0, SC_CHAOSPANIC,Chaos Panic +2302,7,6,2,0,0x1,0,3,1,yes,0,0,0,none,0, SC_MAELSTROM,Maelstrom +2303,7,6,2,0,0x1,0,3,1,yes,0,0,0,none,0, SC_BLOODYLUST,Bloody Lust +2304,0,6,4,-1,0,0,3,1,no,0,0,0,weapon,0, SC_FEINTBOMB,Feint Bomb + +//**** +// LG Royal Guard +2307,11,8,1,-1,0,2,5,1,no,0,0,10,weapon,0, LG_CANNONSPEAR,Cannon Spear +2308,7,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, LG_BANISHINGPOINT,Banishing Point +2309,0,6,4,0,0x3,2,3,1,no,0,0,0,none,0, LG_TRAMPLE,Trample +2310,1,6,1,0,0,0,5,1,no,0,0,0,weapon,0, LG_SHIELDPRESS,Shield Press +2311,0,6,4,0,0x3,3,5,1,no,0,0,0,none,0, LG_REFLECTDAMAGE,Reflect Damage +2312,5,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, LG_PINPOINTATTACK,Pinpoint Attack +2313,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, LG_FORCEOFVANGUARD,Force of Vanguard +2314,1,6,1,-1,0,0,1,1,no,0,0,0,weapon,0, LG_RAGEBURST,Rage Burst +2315,0,6,4,0,0x2,3,3,1,yes,0,0,0,none,2, LG_SHIELDSPELL,Shield Spell +2316,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, LG_EXEEDBREAK,Exceed Break +2317,1,6,2,-1,0x2,5,5,1,yes,0,0,0,none,3:4:5:6:7, LG_OVERBRAND,Over Brand //CHECK I know the splash is needed somehow for the strange AoE it gives. +2318,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, LG_PRESTIGE,Prestige +2319,0,6,4,0,0x1,3,5,1,no,0,0,0,weapon,0, LG_BANDING,Banding //CHECK Splash isnt needed right? Banding has its own UNIT ID. +2320,0,6,4,-1,0x2,3,5,1,yes,0,0,0,weapon,0, LG_MOONSLASHER,Moon Slasher +2321,1,8,2,6,0x2,5,5,-7,yes,0,0,0,weapon,0, LG_RAYOFGENESIS,Ray of Genesis +2322,0,6,16,0,0x3,1,5,1,yes,0,0,0,none,0, LG_PIETY,Piety +2323,0,8,4,2,0x2,1:1:2:2:3,5,-5,yes,0,0,0,weapon,0, LG_EARTHDRIVE,Earth Drive +2324,3,8,1,-1,0,0,5,3,yes,0,0,0,weapon,0, LG_HESPERUSLIT,Hesperus Lit +2325,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, LG_INSPIRATION,Inspiration + +//**** +// SR Sura +2326,-2,8,1,-1,0,0,10,2,no,0,0,0,weapon,0, SR_DRAGONCOMBO,Dragon Combo //CHECK Is this 2 regular hits or sub hits? Yes its 2 sub hits. +2327,0,8,4,-1,0x2,2,5,-3,no,0,0,0,weapon,3, SR_SKYNETBLOW,Sky Net Blow //CHECK Video shows 3 hits. Its sub hits right? Data check shows no sub, one source shows 3 hits, another shows 5. +2328,0,6,4,-1,0x2,1:2:3:4:5,5,1,no,0,0,0,weapon,0, SR_EARTHSHAKER,Earth Shaker //CHECK Must add a check in battle.c to triple damage if hitting a hidden target. +2329,-2,8,4,-1,0,0,5,-2,no,0,0x200,0,weapon,0, SR_FALLENEMPIRE,Fallen Empire //CHECK Video shows 2 hits. Is it sub hits? Yes its divided between 2 hits. +2330,-2,6,1,-1,0x42,1:1:1:1:1:2:2:2:2:2,10,1,yes,0,0,0,weapon,0, SR_TIGERCANNON,Tiger Cannon //CHECK Need to fix to be enemy targeted and also combo after Fallen Empire. +2331,0,0,0,0,0,0,10,0,no,0,0,0,none,0, SR_HELLGATE,Hell Gate +2332,5,6,4,-1,0x2,3,5,1,no,0,0,0,weapon,0, SR_RAMPAGEBLASTER,Rampage Blaster +2333,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, SR_CRESCENTELBOW,Crescent Elbow //CHECK Check the autospell ID. +2334,0,6,4,0,0x3,1:1:2:2:3,5,1,no,0,0,0,none,0, SR_CURSEDCIRCLE,Cursed Circle //CHECK Code shows it takes up to 5% of your HP upon use? +2335,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, SR_LIGHTNINGWALK,Lightning Walk +2336,7:8:9:10:11,6,1,-1,0,0,5,1,no,0,0,0,weapon,2:3:4:5:6, SR_KNUCKLEARROW,Knuckle Arrow +2337,0,6,4,-1,0x2,2,1,1,yes,0,0,0,weapon,0, SR_WINDMILL,Windmill +2338,0,6,4,0,0x1,0,10,1,no,0,0,0,none,0, SR_RAISINGDRAGON,Raising Dragon +2339,0,0,0,0,0,0,5,1,no,0,0,0,none,0, SR_GENTLETOUCH,Gentle Touch +2340,0,6,4,0,0x3,2,1,1,no,0,0,0,none,0, SR_ASSIMILATEPOWER,Assimilate Power +2341,3,6,16,0,0x1,0,1,1,yes,0,0x200,0,none,0, SR_POWERVELOCITY,Power Velocity +2342,1,6,1,-1,0x20,0,5,1,no,0,0,0,weapon,3, SR_CRESCENTELBOW_AUTOSPELL,Crescent Elbow Autospell //CHECK Does this ignore defense? +2343,1:2:3:3:4:4:5:5:6:7,8,1,0,0,0,10,-7,yes,0,0,0,weapon,0, SR_GATEOFHELL,Gate of Hell //CHECK Need to fix to be enemy targeted and also combo after Fallen Empire +2344,2,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, SR_GENTLETOUCH_QUIET,Gentle Touch - Quiet +2345,2,6,16,0,0x1,0,5,1,no,0,0,0,magic,0, SR_GENTLETOUCH_CURE,Gentle Touch - Cure //CHECK Its a healing skill. Guessing it has to be magic type? Healing isnt working. +2346,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, SR_GENTLETOUCH_ENERGYGAIN,Gentle Touch - Energy Gain +2347,2,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, SR_GENTLETOUCH_CHANGE,Gentle Touch - Change +2348,2,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, SR_GENTLETOUCH_REVITALIZE,Gentle Touch - Revitalize +//More from Sura but not following ID order +2517,0,6,4,-1,0x2,3:4:5:6:7,5,1,no,0,0,0,weapon,0, SR_HOWLINGOFLION,Howling of Lion +2518,11,6,2,-1,0x2,2:2:3:3:4,5,1,no,0,0,0,weapon,0, SR_RIDEINLIGHTNING,Ride In Lightening + +//**** +// WA Wanderer +2350,0,6,4,0,0x3,7:8:9:10:11,5,1,yes,0,0,0,none,0, WA_SWING_DANCE,Swing Dance +2351,0,6,4,0,0x3,7:8:9:10:11,5,1,yes,0,0,0,none,0, WA_SYMPHONY_OF_LOVER,Symphony of Lovers +2352,0,6,4,0,0x3,7:8:9:10:11,5,1,yes,0,0,0,none,0, WA_MOONLIT_SERENADE,Moonlit Serenade + +//**** +// MI Minstrel +2381,0,6,4,0,0x3,7:8:9:10:11,5,1,yes,0,0,0,none,0, MI_RUSH_WINDMILL,Windmill Rush Attack +2382,0,6,4,0,0x3,7:8:9:10:11,5,1,yes,0,0,0,none,0, MI_ECHOSONG,Echo Song +2383,9,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, MI_HARMONIZE,Harmonize + +//**** +// WM Wanderer/Minstrel +2412,0,0,0,0,0,0,10,0,no,0,0,0,none,0, WM_LESSON,Lesson +2413,9,8,1,-1,0,0,5,-2:-2:-3:-3:-4,yes,0,0,0,magic,0, WM_METALICSOUND,Metallic Sound +2414,9,6,2,-1,0x3,1,5,1,yes,0,0x80,3,none,0, WM_REVERBERATION,Reverberation +2415,0,6,1,-1,0x6,1,5,1,no,0,0,0,weapon,0, WM_REVERBERATION_MELEE,Reverberation Melee +2416,0,6,1,0,0x6,1,5,1,no,0,0,0,magic,0, WM_REVERBERATION_MAGIC,Reverberation Magic +2417,11,6,2,0,0x3,5,1,1,no,0,0,0,none,0, WM_DOMINION_IMPULSE,Dominion Impulse +2418,9,6,2,-1,0x1,0,5,1,yes,0,0,0,none,0, WM_SEVERE_RAINSTORM,Severe Rainstorm +2419,9,6,2,0,0x3,1,5,1,yes,0,0x80,5,none,0, WM_POEMOFNETHERWORLD,Poem of The Netherworld //CHECK May need to recode too. +2420,0,6,4,0,0x2,2:3:4:5:6,5,1,yes,0,0,0,none,0, WM_VOICEOFSIREN,Voice of Siren +2421,7,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, WM_DEADHILLHERE,Valley of Death +2422,7,6,4,0,0x3,5:6:7:8:9,5,1,yes,0,0,0,none,0, WM_LULLABY_DEEPSLEEP,Deep Sleep Lullaby +2423,0,6,4,0,0x3,3:4:5:6:7,5,1,yes,0,0,0,none,0, WM_SIRCLEOFNATURE,Circle of Nature's Sound +2424,9,6,4,0,0x1,0,5,1,yes,0,0,0,magic,0, WM_RANDOMIZESPELL,Improvised Song +2425,9,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, WM_GLOOMYDAY,Gloomy Day +2426,9,6,2,0,0x2,2:3:3:4:4,5,1,yes,0,0x4000,0,weapon,0, WM_GREAT_ECHO,Great Echo +2427,0,6,4,0,0x3,5:6:7:8:9,5,1,yes,0,0x4000,0,none,0, WM_SONG_OF_MANA,Song of Mana +2428,0,6,4,0,0x3,5:6:7:8:9,5,1,yes,0,0x4000,0,none,0, WM_DANCE_WITH_WUG,Dance With A Warg +2429,9,6,1,0,0x2,2:2:3:3:4,5,1,yes,0,0x4000,0,weapon,0, WM_SOUND_OF_DESTRUCTION,Sound of Destruction //CHECK Source shows its magic attack. Need to confirm before changing. +2430,0,6,4,0,0x3,3:4:5:6:7,5,1,yes,0,0x4000,0,none,0, WM_SATURDAY_NIGHT_FEVER,Saturday Night Fever +2431,0,6,4,0,0x3,5,5,1,yes,0,0x4000,0,magic,0, WM_LERADS_DEW,Lerad's Dew +2432,0,6,4,0,0x3,5,5,1,yes,0,0x4000,0,none,0, WM_MELODYOFSINK,Melody of Sink +2433,0,6,4,0,0x3,5,5,1,yes,0,0x4000,0,none,0, WM_BEYOND_OF_WARCRY,Warcry of Beyond +2434,0,6,4,0,0x3,5,5,1,yes,0,0x4000,0,none,0, WM_UNLIMITED_HUMMING_VOICE,Unlimited Humming Voice +2516,11,6,1,-1,0x2,5,5,1,no,0,0,0,weapon,0, WM_SEVERE_RAINSTORM_MELEE,Severe Rainstorm Melee + +//**** +// SO Sorcerer +2443,0,6,4,3,0,0,5,1,yes,0,0,8:10:12:14:16,magic,0, SO_FIREWALK,Fire Walk //CHECK Video and data shows each cell only hits once. +2444,0,6,4,4,0,0,5,1,yes,0,0,8:10:12:14:16,magic,0, SO_ELECTRICWALK,Electric Walk +2445,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, SO_SPELLFIST,Spell Fist +2446,9,6,2,2,0,0,5,-3,yes,0,0,0,magic,0, SO_EARTHGRAVE,Earth Grave +2447,9,6,2,1,0,0,5,-5,yes,0,0,0,magic,0, SO_DIAMONDDUST,Diamond Dust +2448,9,6,1,5,0x2,1:1:1:1:2,5,1,yes,0,0,0,magic,0, SO_POISON_BUSTER,Poison Buster +2449,9,6,2,0,0,0,5,1,yes,0,0,0,magic,0, SO_PSYCHIC_WAVE,Psychic Wave +2450,9,6,2,5,0,0,5,1,yes,0,0,0,magic,0, SO_CLOUD_KILL,Cloud Kill +2451,9,6,16,0,0x1,0,5,1,yes,0,0,0,none,0, SO_STRIKING,Striking //CHECK Data shows a % for increased successful refine rate. Is this true? +2452,9,6,2,3,0x1,0,5,1,yes,0,0,0,magic,0, SO_WARMER,Warmer +2453,9,6,2,0,0x1,0,5,1,yes,0,0,0,magic,0, SO_VACUUM_EXTREME,Vacuum Extreme +2454,9,6,1,4,0x2,1:1:2:2:3,5,1,yes,0,0,0,magic,0, SO_VARETYR_SPEAR,Varetyr Spear +2455,9,6,2,0,0x3,1:1:2:2:3,5,1,yes,0,0,0,magic,0, SO_ARRULLO,Arrullo +2456,0,6,4,0,0x1,0,4,1,yes,0,0,0,none,0, SO_EL_CONTROL,Spirit Control +2457,0,6,4,3,0x1,0,3,1,yes,0,0,0,none,0, SO_SUMMON_AGNI,Summon Fire Spirit Agni +2458,0,6,4,1,0x1,0,3,1,yes,0,0,0,none,0, SO_SUMMON_AQUA,Summon Water Spirit Aqua +2459,0,6,4,4,0x1,0,3,1,yes,0,0,0,none,0, SO_SUMMON_VENTUS,Summon Wind Spirit Ventus +2460,0,6,4,2,0x1,0,3,1,yes,0,0,0,none,0, SO_SUMMON_TERA,Summon Earth Spirit Tera +2461,5,6,1,0,0x1,0,1,1,no,0,0,0,none,0, SO_EL_ACTION,Elemental Action +2462,0,6,4,0,0x1,0,2,1,yes,0,0,0,none,0, SO_EL_ANALYSIS,Four Spirit Analysis +2463,0,0,0,0,0,0,5,0,no,0,0,0,none,0, SO_EL_SYMPATHY,Spirit Sympathy +2464,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, SO_EL_CURE,Spirit Recovery +2465,9,6,2,3,0x1,0,3,1,yes,0,0,1,magic,0, SO_FIRE_INSIGNIA,Fire Insignia //CHECK All 4 insignia skills can be targeted and animations work +2466,9,6,2,1,0x1,0,3,1,yes,0,0,1,magic,0, SO_WATER_INSIGNIA,Water Insignia // but its effects havent been coded yet. +2467,9,6,2,4,0x1,0,3,1,yes,0,0,1,magic,0, SO_WIND_INSIGNIA,Wind Insignia +2468,9,6,2,2,0x1,0,3,1,yes,0,0,1,magic,0, SO_EARTH_INSIGNIA,Earth Insignia + +//**** +// GN Genetic +2474,0,0,0,0,0,0,5,0,no,0,0,0,none,0, GN_TRAINING_SWORD,Sword Training +2475,0,0,0,0,0,0,5,0,no,0,0,0,none,0, GN_REMODELING_CART,Cart Remodeling +2476,0,6,4,-1,0x2,2,5,1,no,0,0,0,weapon,2, GN_CART_TORNADO,Cart Tornado +2477,7:8:9:10:11,6,1,-1,0x2,1:1:2:2:3,5,1,yes,0,0,0,weapon,0, GN_CARTCANNON,Cart Cannon +2478,0,6,4,0,0x1,0,5,1,yes,0,0,0,none,0, GN_CARTBOOST,Cart Boost +2479,9,6,2,0,0,0,5,1,yes,0,0x80,5,misc,0, GN_THORNS_TRAP,Thorn Trap +2480,11,6,1,0,0x1,0,5,1,yes,0,0,3,misc,0, GN_BLOOD_SUCKER,Blood Sucker //CHECK Data says its a magic attack. Hmmmm.... +2481,11,6,1,-1,0x2,1:2:3:4:5,5,1,yes,0,0,0,weapon,0, GN_SPORE_EXPLOSION,Spore Explosion //CHECK Data says its element is set to neutral. Need to confirm. +2482,11,6,16,0,0,0,5,1,yes,0,0,1,weapon,2, GN_WALLOFTHORN,Wall of Thorns +2483,11,6,2,0,0x3,4,10,1,yes,0,0x2000,0,weapon,0, GN_CRAZYWEED,Crazy Weed +2484,0,6,2,2,0x2,3,10,1,no,0,0x2000,0,weapon,0, GN_CRAZYWEED_ATK,Crazy Weed Attack +2485,9,6,2,3,0,0,5,1,yes,0,0,0,magic,0, GN_DEMONIC_FIRE,Demonic Fire +2486,9,6,2,0,0,0,5,1,yes,0,0,0,none,0, GN_FIRE_EXPANSION,Fire Expansion //CHECK FIX ME!!!! Level 1 is reducing the damage. Should increase it by 50% +2487,9,6,2,0,0,0,1,1,no,0,0,0,none,0, GN_FIRE_EXPANSION_SMOKE_POWDER,Fire Expansion Smoke Powder +2488,9,6,2,0,0,0,1,1,no,0,0,0,none,0, GN_FIRE_EXPANSION_TEAR_GAS,Fire Expansion Tear Gas +2489,11,6,1,0,0,0,10,1:2:3:4:5:6:7:8:9:10,no,0,0,0,weapon,0, GN_FIRE_EXPANSION_ACID,Fire Expansion Acid +2490,9,6,2,0,0x3,1,5,1,yes,0,0x80,2:3:4:5:6,none,0, GN_HELLS_PLANT,Hell's Plant +2491,0,6,1,0,0xC0,0,5,1,no,0,0,0,misc,0, GN_HELLS_PLANT_ATK,Hell's Plant Attack +2492,0,6,4,0,0x3,6:7:8:9:10,5,1,yes,0,0,0,none,0, GN_MANDRAGORA,Howling of Mandragora +2493,11,6,16,0,0x1,0,1,1,yes,0,0,0,none,0, GN_SLINGITEM,Sling Item +2494,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, GN_CHANGEMATERIAL,Change Material +2495,0,6,4,0,0x1,0,2,1,no,0,0,0,none,0, GN_MIX_COOKING,Mix Cooking +2496,0,6,4,0,0x1,0,2,1,no,0,0,0,none,0, GN_MAKEBOMB,Create Bomb +2497,0,6,4,0,0x1,0,10,1,no,0,0,0,none,0, GN_S_PHARMACY,Special Pharmacy +2498,11,6,1,0,0,0,1,1,no,0,0,0,weapon,0, GN_SLINGITEM_RANGEMELEEATK,Sling Item Attack + +// Episode 13.3 +//2533,0,0,4,0,0x1,0,1,0,no,0,0,0,none,0, ALL_ODINS_RECALL,Odin's Recall +2534,0,0,4,0,0x1,0,1,0,no,0,0,0,none,0, RETURN_TO_ELDICASTES,Return To Eldicastes +2535,0,0,4,0,0x1,0,1,0,no,0,0x1,0,none,0, ALL_BUYING_STORE,Open Buying Store +2536,0,0,4,0,0x1,0,1,0,no,0,0,0,none,0, ALL_GUARDIAN_RECALL,Guardian's Recall +2537,9,6,16,0,0x1,0,2,1,yes,0,0,0,magic,0, ALL_ODINS_POWER,Odin's Power +//2538,0,0,0,0,0,0,??,0,no,0,0,0,none,0, BEER_BOTTLE_CAP,Beer Bottle Cap +//2539,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_ASSASSINCROSS,Assassin Cross of Sunset 2 +//2540,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_DISSONANCE,Dissonance 2 +//2541,0,0,0,0,0,0,??,0,no,0,0,0,none,0, NPC_UGLYDANCE,Ugly Dance 2 +//2542,0,0,0,0,0,0,??,0,no,0,0,0,none,0, ALL_TETANY,Tetany +//2543,0,0,0,0,0,0,??,0,no,0,0,0,none,0, ALL_RAY_OF_PROTECTION,Ray of Protection +//2544,0,0,0,0,0,0,??,0,no,0,0,0,none,0, MC_CARTDECORATE,Decorate Cart + +//**** +// Kagerou & Oboro +3001,0,6,4,0,0,0,1,1,no,0,0,0,none,0, KO_YAMIKUMO,Yamikumo +3002,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, KO_RIGHT,Right Hand Mastery +3003,0,0,0,0,0,0,5,0,no,0,0,0,weapon,0, KO_LEFT,Left Hand Mastery +3004,3:4:5:6:7,8,1,-1,0,0,5,-2,no,0,0,0,weapon,0, KO_JYUMONJIKIRI,Cross Strike +3005,2,6,1,-1,0,0,5,1,no,0,0,0,weapon,0, KO_SETSUDAN,Soul Sever +3006,7:8:9:10:11,6,2,0,0x2,2,5,0,no,0,0,0,weapon,0, KO_BAKURETSU,Bakuretsu Kunai +3007,0,6,4,-1,0x42,4:4:4:4:5,5,0,no,0,0,0,misc,0, KO_HAPPOKUNAI,Happo Kunai +3008,9,8,2,0,0x52,2,10,-10,no,0,0,0,misc,0, KO_MUCHANAGE,Mucha Nage +3009,9:10:11:12:13,8,2,-1,0x2,3,5,2,no,0,0,0,weapon,0, KO_HUUMARANKA,Huuma Shuriken Ranka +3010,3,6,4,0,0x43,0,5,1,no,0,0x80,0,misc,0, KO_MAKIBISHI,Makibishi +3011,0,6,4,0,0x1,0,5,0,yes,0,0,0,none,0, KO_MEIKYOUSISUI,Meikyo Shisui +3012,0,6,4,0,0x1,0,5,0,no,0,0,1,none,7, KO_ZANZOU,Zanzou +3013,5,6,1,0,0x1,0,5,0,no,0,0,0,none,0, KO_KYOUGAKU,Kyougaku +3014,5,6,1,0,0x1,0,5,0,no,0,0,0,none,0, KO_JYUSATSU,Jyusatsu +3015,0,6,4,3,0x1,0,1,1,no,0,0,0,none,0, KO_KAHU_ENTEN,Kahu Enten +3016,0,6,4,1,0x1,0,1,1,no,0,0,0,none,0, KO_HYOUHU_HUBUKI,Hyouhu Hubuki +3017,0,6,4,4,0x1,0,1,1,no,0,0,0,none,0, KO_KAZEHU_SEIRAN,Kazehu Seiran +3018,0,6,4,2,0x1,0,1,1,no,0,0,0,none,0, KO_DOHU_KOUKAI,Dohu Koukai +3019,11,6,1,0,0,0,5,0,no,0,0,0,weapon,0, KO_KAIHOU,Technique Kaihou +3020,7,6,2,0,0,0,1,3,yes,0,0,0,magic,0, KO_ZENKAI,Zenkai +3021,5:6:7:8:9,6,16,0,0x1,0,5,1,no,0,0,0,none,0, KO_GENWAKU,Genwaku +3022,0,6,4,0,0x1,0,5,0,no,0,0,0,none,0, KO_IZAYOI,Izayoi +3023,0,6,4,0,0x3,2:3:4:5:6,5,0,no,0,0,0,none,0, KG_KAGEHUMI,Kagehumi +3024,7,6,1,0,0x1,0,5,1,no,0,0,0,none,0, KG_KYOMU,Kyomu +3025,7,6,16,0,0x1,0,5,1,no,0,0,0,none,0, KG_KAGEMUSYA,Kagemusha +3026,7,6,16,0,0x1,0,5,1,no,0,0,0,none,0, OB_ZANGETSU,Zangetsu +3027,7,6,16,0,0x1,0,5,1,no,0,0,0,none,0, OB_OBOROGENSOU,Oboro Gensou +3028,1,6,4,0,0x2,3,1,1,no,0,0,0,weapon,0, OB_OBOROGENSOU_TRANSITION_ATK, +3029,7,6,1,0,0x1,0,5,0,no,0,0,0,none,0, OB_AKAITSUKI,Akaitsuki + +8001,9,6,4,0,0x1,0,5,1,no,0,0,0,magic,0, HLIF_HEAL,Healing Touch +8002,0,6,4,0,0x3,-1,5,1,no,0,0,0,none,0, HLIF_AVOID,Avoid +8003,0,0,0,0,0,1,5,0,no,0,0,0,none,0, HLIF_BRAIN,Brain Surgery +8004,0,6,4,0,0x1,0,3,0,no,0,0,0,none,0, HLIF_CHANGE,Change +8005,0,6,4,0,0x1,0,5,0,no,0,0,0,none,0, HAMI_CASTLE,Castling +8006,0,6,4,0,0x1,0,5,0,no,0,0,0,none,0, HAMI_DEFENCE,Defense +8007,0,0,0,0,0x1,0,5,0,no,0,0,0,none,0, HAMI_SKIN,Adamantium Skin +8008,0,6,4,0,0x1,0,3,0,no,0,0,0,none,0, HAMI_BLOODLUST,Bloodlust +8009,1,8,1,0,0,0,5,-1:-2:-2:-2:-3,no,0,0,0,weapon,0, HFLI_MOON,Moonlight +8010,0,6,4,0,0x1,0,5,0,no,0,0,0,none,0, HFLI_FLEET,Fleeting Move +8011,0,6,4,0,0x1,0,5,0,yes,0,0,0,misc,0, HFLI_SPEED,Speed +8012,1,6,1,0,0,0,3,0,no,0,0,0,none,0, HFLI_SBR44,S.B.R.44 +8013,9,6,1,0,0,0,5,1:2:3:4:5,no,0,0,0,magic,0, HVAN_CAPRICE,Caprice +8014,0,6,4,0,0x1,0,5,0,no,0,0,0,none,0, HVAN_CHAOTIC,Benediction of Chaos +8015,0,0,0,0,0x1,0,5,0,no,0,0,0,none,0, HVAN_INSTRUCT,Instruct +8016,4,6,4,-1,0xD2,4,3,1,no,0,0,0,misc,0, HVAN_EXPLOSION,Bio Explosion +// +8018,9,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MH_SUMMON_LEGION,Summon Legion +8019,5,6,1,5,0,0,5,1,no,0,0,0,weapon,0, MH_NEEDLE_OF_PARALYZE,Needle of Paralyze +8020,5,6,2,5,0,0,5,1,no,0,0,1,weapon,0, MH_POISON_MIST,Poison Mist +8021,1,6,1,0,0x1,0,5,1,no,0,0,0,none,0, MH_PAIN_KILLER,Pain Killer +8022,0,6,4,0,0,0x1,5,1,no,0,0,0,none,0, MH_LIGHT_OF_REGENE,Light of Regene +8023,0,6,4,0,0,0x1,5,1,no,0,0,0,none,0, MH_OVERED_BOOST,Overed Boost +8024,7,6,1,4:0:4:0:4,0,0,5,1,no,0,0,0,magic,0, MH_ERASER_CUTTER,Eraser Cutter +8025,7,6,2,4:0:4:0:4,0,0,5,1,no,0,0,0,magic,0, MH_XENO_SLASHER,Xeno Slasher +8026,5:5:7:7:9,6,16,0,0x1,0,5,1,no,0,0,0,magic,0, MH_SILENT_BREEZE,Silent Breeze +8027,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, MH_STYLE_CHANGE,Style Change +8028,1,8,1,0,0,0,5,1,no,0,0,0,weapon,0, MH_SONIC_CRAW,Sonic Claw +8029,1,6,4,0,0,0,5,1,no,0,0x200,0,weapon,0, MH_SILVERVEIN_RUSH,Silver Bain Rush +8030,1,6,4,0,0,0,5,1,no,0,0x200,0,weapon,0, MH_MIDNIGHT_FRENZY,Midnight Frenzy +8031,5:6:7:8:9,6,1,0,0,0,5,1,no,0,0,0,weapon,3, MH_STAHL_HORN,Steel Horn +8032,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MH_GOLDENE_FERSE,Golden Heel +8033,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MH_STEINWAND,Stone Wall +8034,9,6,1,6,0x2,1:1:1:1:2,5,1,no,0,0,0,magic,0, MH_HEILIGE_STANGE,Holy Pole +8035,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MH_ANGRIFFS_MODUS,Attack Mode +8036,3:4:5:6:7,6,1,0,0,0,5,1,no,0,0,0,weapon,0, MH_TINDER_BREAKER,Tinder Breaker +8037,1,6,4,0,0,0,5,1,no,0,0x200,0,weapon,0, MH_CBC,Continual Break Combo +8038,1,6,4,0,0,0,5,1,no,0,0x200,0,weapon,0, MH_EQC,Eternal Quick Combo +8039,0,6,4,3,0x2,1:1:1:2:2,5,1,no,0,0,0,weapon,0, MH_MAGMA_FLOW,Magma Flow +8040,0,6,4,0,0x1,0,5,1,no,0,0,0,none,0, MH_GRANITIC_ARMOR,Granitic Armor +8041,7,6,2,3,0x2,0,5,1,no,0,0,1,weapon,0, MH_LAVA_SLIDE,Lava Slide +8042,0,6,4,3,0x1,0,5,1,no,0,0,0,none,0, MH_PYROCLASTIC,Pyroclastic +8043,7,6,2,0,0x1,0,5,1,no,0,0,3,none,0, MH_VOLCANIC_ASH,Volcanic Ash + +// Mercenary Skill Place holders +8201,-1,6,1,-1,0,0,10,1,no,0,0,0,weapon,0, MS_BASH,Bash +8202,0,6,4,3,0x2,2,10,1,no,0,0,0,weapon,2, MS_MAGNUM,Magnum_Break +8203,-2,6,1,-1,0x2,1,10,1,no,33,0,0,weapon,1, MS_BOWLINGBASH,Bowling_Bash +8204,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, MS_PARRYING,Parry +8205,0,6,4,0,0,0,10,1,no,0,0,0,weapon,0, MS_REFLECTSHIELD,Shield_Reflect +8206,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, MS_BERSERK,Frenzy +8207,-9,8,1,-1,0,0,10,2,no,0,0,0,weapon,0, MA_DOUBLE,Double_Strafe +8208,-9,6,2,-1,0x2,2,10,1,no,0,0x2000,0,weapon,2, MA_SHOWER,Arrow_Shower +8209,3,6,2,0,0x1,0,5,1,no,0,0x80,0,misc,6:7:8:9:10, MA_SKIDTRAP,Skid_Trap +8210,3,6,2,2,0x40,0,5,1,no,0,0x80,0,misc,0, MA_LANDMINE,Land_Mine +8211,3,6,2,0,0x3,2,5,1,no,0,0x80,0,misc,0, MA_SANDMAN,Sandman +8212,3,6,2,1,0x42,1,5,1,no,0,0x80,0,weapon,0, MA_FREEZINGTRAP,Freezing_Trap +8213,2,6,32,0,0x1,0,1,1,no,0,0,0,misc,0, MA_REMOVETRAP,Remove_Trap +8214,-9,6,1,-1,0x2,0,1,1,no,0,0x1,0,weapon,6, MA_CHARGEARROW,Arrow_Repel +8215,9,8,1,-1,0,2,5,1,yes,0,0,13,weapon,0, MA_SHARPSHOOTING,Focused_Arrow_Strike +8216,-2,8,1,-1,0,0,10,3,no,0,0,0,weapon,0, ML_PIERCE,Pierce +8217,-2,6,1,-1,0x1,0,10,1,no,33,0,0,weapon,3, ML_BRANDISH,Brandish_Spear +8218,5,8,1,-1,0x20,0,5,5,no,0,0,0,weapon,0, ML_SPIRALPIERCE,Spiral_Pierce +8219,0,6,4,0,0x1,0,5,1,no,0,0,0,weapon,0, ML_DEFENDER,Defending_Aura +8220,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, ML_AUTOGUARD,Guard +8221,7:8:9:10:11,6,16,0,0x1,0,5,1,yes,0,0x600,0,none,0, ML_DEVOTION,Sacrifice +8222,0,6,4,0,0x3,-1,5,1,yes,0,0,0,magic,0, MER_MAGNIFICAT,Magnificat +8223,0,6,4,0,0x1,0,10,1,no,0,0,0,weapon,0, MER_QUICKEN,Two-Hand_Quicken +8224,0,6,4,3,0x3,3,1,1,yes,0,0,0,magic,0, MER_SIGHT,Sight +8225,1,8,1,-1,0,0,5,3,no,0,0,0,weapon,0, MER_CRASH,Crash +8226,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_REGAIN,Regain +8227,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_TENDER,Tender +8228,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_BENEDICTION,Benediction +8229,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_RECUPERATE,Recuperate +8230,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_MENTALCURE,Mental_Cure +8231,9,6,16,0,0x1,0,1,1,yes,0,0x1000,0,magic,0, MER_COMPRESS,Compress +8232,9,6,1,0,0x1,0,10,1,no,0,0,0,none,0, MER_PROVOKE,Provoke +8233,0,6,4,0,0x1,0,1,1,no,0,0,0,weapon,0, MER_AUTOBERSERK,Berserk +8234,9,6,1,0,0x1,0,10,1,yes,0,0,0,magic,0, MER_DECAGI,Decrease_AGI +8235,0,6,4,0,0x1,0,1,1,yes,0,0,0,none,0, MER_SCAPEGOAT,Scapegoat +8236,5,6,1,0,0x1,0,10,0,yes,0,0,0,magic,0, MER_LEXDIVINA,Lex_Divina +8237,9,6,1,0,0x1,0,1,1,yes,0,0,0,magic,0, MER_ESTIMATION,Sense +8238,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, MER_KYRIE,Kyrie Eleison +8239,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, MER_BLESSING,Blessing +8240,9,6,16,0,0x1,0,10,1,yes,0,0,0,magic,0, MER_INCAGI,Increase Agility + +// Elemental Spirits Skills +8401,0,6,4,3,0,0,1,1,no,0,0,0,weapon,2, EL_CIRCLE_OF_FIRE,Circle of Fire +8402,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_FIRE_CLOAK,Fire Cloak +8403,0,6,4,3,0,0,1,1,no,0,0,3,magic,2, EL_FIRE_MANTLE,Fire Mantle +8404,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_WATER_SCREEN,Water Screen +8405,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_WATER_DROP,Water Drop +8406,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_WATER_BARRIER,Water Barrier +8407,0,6,4,0,0x1,0,1,1,no,0,0,0,none,5, EL_WIND_STEP,Wind Step +8408,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_WIND_CURTAIN,Wind Curtain +8409,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_ZEPHYR,Zephyr +8410,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_SOLID_SKIN,Solid Skin +8411,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_STONE_SHIELD,Stone Shield +8412,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_POWER_OF_GAIA,Power of Gaia +8413,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_PYROTECHNIC,Pyrotechnic +8414,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_HEATER,Heater +8415,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_TROPIC,Tropic +8416,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_AQUAPLAY,Aqua Play +8417,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_COOLER,Cooler +8418,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_CHILLY_AIR,Cool Air +8419,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_GUST,Gust +8420,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_BLAST,Blast +8421,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_WILD_STORM,Wild Storm +8422,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_PETROLOGY,Petrology +8423,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_CURSED_SOIL,Cursed Soil +8424,0,6,4,0,0x1,0,1,1,no,0,0,0,none,0, EL_UPHEAVAL,Upheaval +8425,6,6,1,3,0,0,1,1,no,0,0,0,magic,0, EL_FIRE_ARROW,Fire Arrow +8426,6,6,1,3,0,1,1,1,no,0,0,0,magic,0, EL_FIRE_BOMB,Fire Bomb +8427,6,6,1,3,0,1,1,1,no,0,0,0,weapon,0, EL_FIRE_BOMB_ATK,Fire Bomb Attack +8428,6,6,1,3,0,1,1,1,no,0,0,0,magic,0, EL_FIRE_WAVE,Fire Wave +8429,6,6,1,3,0,1,1,1,no,0,0,0,weapon,0, EL_FIRE_WAVE_ATK,Fire Wave Attack +8430,9,6,1,1,0,0,1,1,no,0,0,0,magic,0, EL_ICE_NEEDLE,Ice Needle +8431,9,6,1,1,0,1,1,1,no,0,0,0,magic,0, EL_WATER_SCREW,Water Screw +8432,9,6,1,1,0,1,1,1,no,0,0,0,weapon,0, EL_WATER_SCREW_ATK,Water Screw Attack +8433,9,6,1,1,0,1,1,1,no,0,0,0,weapon,0, EL_TIDAL_WEAPON,Tidal Weapon +8434,11,6,1,4,0,0,1,1,no,0,0,0,weapon,0, EL_WIND_SLASH,Wind Slasher +8435,11,6,1,4,0,1,1,1,no,0,0,0,weapon,0, EL_HURRICANE,Hurricane Rage +8436,7,6,1,4,0,0,1,1,no,0,0,0,magic,0, EL_HURRICANE_ATK,Hurricane Rage Attack +8437,11,8,1,4,0,1,1,-3,no,0,0,0,weapon,0, EL_TYPOON_MIS,Typhoon Missile +8438,11,8,1,4,0,1,1,-3,no,0,0,0,magic,0, EL_TYPOON_MIS_ATK,Typhoon Missile Attack +8439,5,6,1,2,0,0,1,1,no,0,0,0,weapon,0, EL_STONE_HAMMER,Stone Hammer +8440,3,6,1,2,0,1,1,1,no,0,0,0,weapon,0, EL_ROCK_CRUSHER,Rock Launcher +8441,5,6,1,2,0,1,1,1,no,0,0,0,magic,0, EL_ROCK_CRUSHER_ATK,Rock Launcher Attack +8442,9,6,1,2,0,1,1,-5,no,0,0,0,weapon,0, EL_STONE_RAIN,Stone Rain + +10000,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_APPROVAL,Official Guild Approval +10001,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_KAFRACONTRACT,Kafra Contract +10002,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_GUARDRESEARCH,Guardian Research +10003,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_GUARDUP,Strengthen Guardians +10004,0,0,0,0,0,0,10,0,no,0,0x10,0,none,0, GD_EXTENSION,Guild Extension +10005,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_GLORYGUILD,Guild's Glory +10006,0,0,0,0,0,2,1,0,no,0,0x10,0,none,0, GD_LEADERSHIP,Great Leadership +10007,0,0,0,0,0,2,1,0,no,0,0x10,0,none,0, GD_GLORYWOUNDS,Glorious Wounds +10008,0,0,0,0,0,2,1,0,no,0,0x10,0,none,0, GD_SOULCOLD,Cold Heart +10009,0,0,0,0,0,2,1,0,no,0,0x10,0,none,0, GD_HAWKEYES,Sharp Gaze +10010,0,0,4,0,0x3,15,1,0,yes,0,0x10,0,none,0, GD_BATTLEORDER,Battle Orders +10011,0,0,4,0,0x3,15,3,0,yes,0,0x10,0,none,0, GD_REGENERATION,Regeneration +10012,0,0,4,0,0x3,15,1,0,yes,0,0x10,0,none,0, GD_RESTORE,Restoration +10013,0,0,4,0,0x3,0,1,0,yes,0,0x10,0,none,0, GD_EMERGENCYCALL,Urgent Call +10014,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_DEVELOPMENT,Permanent Development +//10015,0,0,0,0,0,0,1,0,no,0,0x10,0,none,0, GD_ITEMEMERGENCYCALL,Unknown Skill diff --git a/src/map/clif.c b/src/map/clif.c index 06c74a5f8..c314a6f33 100644 --- a/src/map/clif.c +++ b/src/map/clif.c @@ -1,17128 +1,17128 @@ -// Copyright (c) Athena Dev Teams - Licensed under GNU GPL -// For more information, see LICENCE in the main folder - -#include "../common/cbasetypes.h" -#include "../common/socket.h" -#include "../common/timer.h" -#include "../common/grfio.h" -#include "../common/malloc.h" -#include "../common/nullpo.h" -#include "../common/random.h" -#include "../common/showmsg.h" -#include "../common/strlib.h" -#include "../common/utils.h" -#include "../common/ers.h" - -#include "map.h" -#include "chrif.h" -#include "pc.h" -#include "status.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 "battleground.h" -#include "mob.h" -#include "party.h" -#include "unit.h" -#include "guild.h" -#include "vending.h" -#include "pet.h" -#include "homunculus.h" -#include "instance.h" -#include "mercenary.h" -#include "elemental.h" -#include "log.h" -#include "clif.h" -#include "mail.h" -#include "quest.h" - -#include -#include -#include -#include -#include - -/* for clif_clearunit_delayed */ -static struct eri *delay_clearunit_ers; - -//#define DUMP_UNKNOWN_PACKET -//#define DUMP_INVALID_PACKET - -struct Clif_Config { - int packet_db_ver; //Preferred packet version. - int connect_cmd[MAX_PACKET_VER + 1]; //Store the connect command for all versions. [Skotlex] -} clif_config; - -struct s_packet_db packet_db[MAX_PACKET_VER + 1][MAX_PACKET_DB + 1]; - -//Converts item type in case of pet eggs. -static inline int itemtype(int type) -{ - return ( type == IT_PETEGG ) ? IT_WEAPON : type; -} - - -static inline void WBUFPOS(uint8* p, unsigned short pos, short x, short y, unsigned char dir) -{ - p += pos; - p[0] = (uint8)(x>>2); - p[1] = (uint8)((x<<6) | ((y>>4)&0x3f)); - p[2] = (uint8)((y<<4) | (dir&0xf)); -} - - -// client-side: x0+=sx0*0.0625-0.5 and y0+=sy0*0.0625-0.5 -static inline void WBUFPOS2(uint8* p, unsigned short pos, short x0, short y0, short x1, short y1, unsigned char sx0, unsigned char sy0) -{ - p += pos; - p[0] = (uint8)(x0>>2); - p[1] = (uint8)((x0<<6) | ((y0>>4)&0x3f)); - p[2] = (uint8)((y0<<4) | ((x1>>6)&0x0f)); - p[3] = (uint8)((x1<<2) | ((y1>>8)&0x03)); - p[4] = (uint8)y1; - p[5] = (uint8)((sx0<<4) | (sy0&0x0f)); -} - - -static inline void WFIFOPOS(int fd, unsigned short pos, short x, short y, unsigned char dir) -{ - WBUFPOS(WFIFOP(fd,pos), 0, x, y, dir); -} - - -static inline void WFIFOPOS2(int fd, unsigned short pos, short x0, short y0, short x1, short y1, unsigned char sx0, unsigned char sy0) -{ - WBUFPOS2(WFIFOP(fd,pos), 0, x0, y0, x1, y1, sx0, sy0); -} - - -static inline void RBUFPOS(const uint8* p, unsigned short pos, short* x, short* y, unsigned char* dir) -{ - p += pos; - - if( x ) { - x[0] = ( ( p[0] & 0xff ) << 2 ) | ( p[1] >> 6 ); - } - - if( y ) { - y[0] = ( ( p[1] & 0x3f ) << 4 ) | ( p[2] >> 4 ); - } - - if( dir ) { - dir[0] = ( p[2] & 0x0f ); - } -} - - -static inline void RBUFPOS2(const uint8* p, unsigned short pos, short* x0, short* y0, short* x1, short* y1, unsigned char* sx0, unsigned char* sy0) -{ - p += pos; - - if( x0 ) { - x0[0] = ( ( p[0] & 0xff ) << 2 ) | ( p[1] >> 6 ); - } - - if( y0 ) { - y0[0] = ( ( p[1] & 0x3f ) << 4 ) | ( p[2] >> 4 ); - } - - if( x1 ) { - x1[0] = ( ( p[2] & 0x0f ) << 6 ) | ( p[3] >> 2 ); - } - - if( y1 ) { - y1[0] = ( ( p[3] & 0x03 ) << 8 ) | ( p[4] >> 0 ); - } - - if( sx0 ) { - sx0[0] = ( p[5] & 0xf0 ) >> 4; - } - - if( sy0 ) { - sy0[0] = ( p[5] & 0x0f ) >> 0; - } -} - - -static inline void RFIFOPOS(int fd, unsigned short pos, short* x, short* y, unsigned char* dir) -{ - RBUFPOS(RFIFOP(fd,pos), 0, x, y, dir); -} - - -static inline void RFIFOPOS2(int fd, unsigned short pos, short* x0, short* y0, short* x1, short* y1, unsigned char* sx0, unsigned char* sy0) -{ - RBUFPOS2(WFIFOP(fd,pos), 0, x0, y0, x1, y1, sx0, sy0); -} - - -//To idenfity disguised characters. -static inline bool disguised(struct block_list* bl) -{ - return (bool)( bl->type == BL_PC && ((TBL_PC*)bl)->disguise ); -} - - -//Guarantees that the given string does not exceeds the allowed size, as well as making sure it's null terminated. [Skotlex] -static inline unsigned int mes_len_check(char* mes, unsigned int len, unsigned int max) -{ - if( len > max ) - len = max; - - mes[len-1] = '\0'; - - return len; -} - - -static char map_ip_str[128]; -static uint32 map_ip; -static uint32 bind_ip = INADDR_ANY; -static uint16 map_port = 5121; -int map_fd; - -static int clif_parse (int fd); - -/*========================================== - * Ip setting of map-server - *------------------------------------------*/ -int clif_setip(const char* ip) -{ - char ip_str[16]; - map_ip = host2ip(ip); - if (!map_ip) { - ShowWarning("Failed to Resolve Map Server Address! (%s)\n", ip); - return 0; - } - - strncpy(map_ip_str, ip, sizeof(map_ip_str)); - ShowInfo("Map Server IP Address : '"CL_WHITE"%s"CL_RESET"' -> '"CL_WHITE"%s"CL_RESET"'.\n", ip, ip2str(map_ip, ip_str)); - return 1; -} - -void clif_setbindip(const char* ip) -{ - char ip_str[16]; - bind_ip = host2ip(ip); - if (bind_ip) { - ShowInfo("Map Server Bind IP Address : '"CL_WHITE"%s"CL_RESET"' -> '"CL_WHITE"%s"CL_RESET"'.\n", ip, ip2str(bind_ip, ip_str)); - } else { - ShowWarning("Failed to Resolve Map Server Address! (%s)\n", ip); - } -} - -/*========================================== - * Sets map port to 'port' - * is run from map.c upon loading map server configuration - *------------------------------------------*/ -void clif_setport(uint16 port) -{ - map_port = port; -} - -/*========================================== - * Returns map server IP - *------------------------------------------*/ -uint32 clif_getip(void) -{ - return map_ip; -} - -//Refreshes map_server ip, returns the new ip if the ip changed, otherwise it returns 0. -uint32 clif_refresh_ip(void) -{ - uint32 new_ip; - - new_ip = host2ip(map_ip_str); - if (new_ip && new_ip != map_ip) { - map_ip = new_ip; - ShowInfo("Updating IP resolution of [%s].\n", map_ip_str); - return map_ip; - } - return 0; -} - -/*========================================== - * Returns map port which is set by clif_setport() - *------------------------------------------*/ -uint16 clif_getport(void) -{ - return map_port; -} - -#if PACKETVER >= 20071106 -static inline unsigned char clif_bl_type(struct block_list *bl) { - switch (bl->type) { - case BL_PC: return disguised(bl)?0x1:0x0; //PC_TYPE - case BL_ITEM: return 0x2; //ITEM_TYPE - case BL_SKILL: return 0x3; //SKILL_TYPE - case BL_CHAT: return 0x4; //UNKNOWN_TYPE - case BL_MOB: return pcdb_checkid(status_get_viewdata(bl)->class_)?0x0:0x5; //NPC_MOB_TYPE - case BL_NPC: return 0x6; //NPC_EVT_TYPE - case BL_PET: return pcdb_checkid(status_get_viewdata(bl)->class_)?0x0:0x7; //NPC_PET_TYPE - case BL_HOM: return 0x8; //NPC_HOM_TYPE - case BL_MER: return 0x9; //NPC_MERSOL_TYPE - case BL_ELEM: return 0xa; //NPC_ELEMENTAL_TYPE - default: return 0x1; //NPC_TYPE - } -} -#endif - -/*========================================== - * sub process of clif_send - * Called from a map_foreachinarea (grabs all players in specific area and subjects them to this function) - * In order to send area-wise packets, such as: - * - AREA : everyone nearby your area - * - AREA_WOSC (AREA WITHOUT SAME CHAT) : Not run for people in the same chat as yours - * - AREA_WOC (AREA WITHOUT CHAT) : Not run for people inside a chat - * - AREA_WOS (AREA WITHOUT SELF) : Not run for self - * - AREA_CHAT_WOC : Everyone in the area of your chat without a chat - *------------------------------------------*/ -static int clif_send_sub(struct block_list *bl, va_list ap) -{ - struct block_list *src_bl; - struct map_session_data *sd; - unsigned char *buf; - int len, type, fd; - - nullpo_ret(bl); - nullpo_ret(sd = (struct map_session_data *)bl); - - fd = sd->fd; - if (!fd) //Don't send to disconnected clients. - return 0; - - buf = va_arg(ap,unsigned char*); - len = va_arg(ap,int); - nullpo_ret(src_bl = va_arg(ap,struct block_list*)); - type = va_arg(ap,int); - - switch(type) - { - case AREA_WOS: - if (bl == src_bl) - return 0; - break; - case AREA_WOC: - if (sd->chatID || bl == src_bl) - return 0; - break; - case AREA_WOSC: - { - if(src_bl->type == BL_PC){ - struct map_session_data *ssd = (struct map_session_data *)src_bl; - if (ssd && sd->chatID && (sd->chatID == ssd->chatID)) - return 0; - } - else if(src_bl->type == BL_NPC) { - struct npc_data *nd = (struct npc_data *)src_bl; - if (nd && sd->chatID && (sd->chatID == nd->chat_id)) - return 0; - } - } - break; - } - - if (session[fd] == NULL) - return 0; - - WFIFOHEAD(fd, len); - if (WFIFOP(fd,0) == buf) { - ShowError("WARNING: Invalid use of clif_send function\n"); - ShowError(" Packet x%4x use a WFIFO of a player instead of to use a buffer.\n", WBUFW(buf,0)); - ShowError(" Please correct your code.\n"); - // don't send to not move the pointer of the packet for next sessions in the loop - //WFIFOSET(fd,0);//## TODO is this ok? - //NO. It is not ok. There is the chance WFIFOSET actually sends the buffer data, and shifts elements around, which will corrupt the buffer. - return 0; - } - - if (packet_db[sd->packet_ver][RBUFW(buf,0)].len) { // packet must exist for the client version - memcpy(WFIFOP(fd,0), buf, len); - WFIFOSET(fd,len); - } - - return 0; -} - -/*========================================== - * Packet Delegation (called on all packets that require data to be sent to more than one client) - * functions that are sent solely to one use whose ID it posses use WFIFOSET - *------------------------------------------*/ -int clif_send(const uint8* buf, int len, struct block_list* bl, enum send_target type) -{ - int i; - struct map_session_data *sd, *tsd; - struct party_data *p = NULL; - struct guild *g = NULL; - struct battleground_data *bg = NULL; - int x0 = 0, x1 = 0, y0 = 0, y1 = 0, fd; - struct s_mapiterator* iter; - - if( type != ALL_CLIENT && type != CHAT_MAINCHAT ) - nullpo_ret(bl); - - sd = BL_CAST(BL_PC, bl); - - switch(type) { - - case ALL_CLIENT: //All player clients. - iter = mapit_getallusers(); - while( (tsd = (TBL_PC*)mapit_next(iter)) != NULL ) - { - if( packet_db[tsd->packet_ver][RBUFW(buf,0)].len ) - { // packet must exist for the client version - WFIFOHEAD(tsd->fd, len); - memcpy(WFIFOP(tsd->fd,0), buf, len); - WFIFOSET(tsd->fd,len); - } - } - mapit_free(iter); - break; - - case ALL_SAMEMAP: //All players on the same map - iter = mapit_getallusers(); - while( (tsd = (TBL_PC*)mapit_next(iter)) != NULL ) - { - if( bl->m == tsd->bl.m && packet_db[tsd->packet_ver][RBUFW(buf,0)].len ) - { // packet must exist for the client version - WFIFOHEAD(tsd->fd, len); - memcpy(WFIFOP(tsd->fd,0), buf, len); - WFIFOSET(tsd->fd,len); - } - } - mapit_free(iter); - break; - - case AREA: - case AREA_WOSC: - if (sd && bl->prev == NULL) //Otherwise source misses the packet.[Skotlex] - clif_send (buf, len, bl, SELF); - case AREA_WOC: - case AREA_WOS: - 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: - { - struct chat_data *cd; - if (sd) { - cd = (struct chat_data*)map_id2bl(sd->chatID); - } else if (bl->type == BL_CHAT) { - cd = (struct chat_data*)bl; - } else break; - if (cd == NULL) - break; - for(i = 0; i < cd->users; i++) { - if (type == CHAT_WOS && cd->usersd[i] == sd) - continue; - if (packet_db[cd->usersd[i]->packet_ver][RBUFW(buf,0)].len) { // packet must exist for the client version - if ((fd=cd->usersd[i]->fd) >0 && session[fd]) // Added check to see if session exists [PoW] - { - WFIFOHEAD(fd,len); - memcpy(WFIFOP(fd,0), buf, len); - WFIFOSET(fd,len); - } - } - } - } - break; - - case CHAT_MAINCHAT: //[LuzZza] - iter = mapit_getallusers(); - while( (tsd = (TBL_PC*)mapit_next(iter)) != NULL ) - { - if( tsd->state.mainchat && tsd->chatID == 0 && packet_db[tsd->packet_ver][RBUFW(buf,0)].len ) - { // packet must exist for the client version - WFIFOHEAD(tsd->fd, len); - memcpy(WFIFOP(tsd->fd,0), buf, len); - WFIFOSET(tsd->fd,len); - } - } - mapit_free(iter); - 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 (sd && sd->status.party_id) - p = party_search(sd->status.party_id); - - if (p) { - for(i=0;idata[i].sd) == NULL ) - continue; - - if( !(fd=sd->fd) ) - continue; - - 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_db[sd->packet_ver][RBUFW(buf,0)].len ) - { // packet must exist for the client version - WFIFOHEAD(fd,len); - memcpy(WFIFOP(fd,0), buf, len); - WFIFOSET(fd,len); - } - } - if (!enable_spy) //Skip unnecessary parsing. [Skotlex] - break; - - iter = mapit_getallusers(); - while( (tsd = (TBL_PC*)mapit_next(iter)) != NULL ) - { - if( tsd->partyspy == p->party.party_id && packet_db[tsd->packet_ver][RBUFW(buf,0)].len ) - { // packet must exist for the client version - WFIFOHEAD(tsd->fd, len); - memcpy(WFIFOP(tsd->fd,0), buf, len); - WFIFOSET(tsd->fd,len); - } - } - mapit_free(iter); - } - break; - - case DUEL: - case DUEL_WOS: - if (!sd || !sd->duel_group) break; //Invalid usage. - - iter = mapit_getallusers(); - while( (tsd = (TBL_PC*)mapit_next(iter)) != NULL ) - { - if( type == DUEL_WOS && bl->id == tsd->bl.id ) - continue; - if( sd->duel_group == tsd->duel_group && packet_db[tsd->packet_ver][RBUFW(buf,0)].len ) - { // packet must exist for the client version - WFIFOHEAD(tsd->fd, len); - memcpy(WFIFOP(tsd->fd,0), buf, len); - WFIFOSET(tsd->fd,len); - } - } - mapit_free(iter); - break; - - case SELF: - if (sd && (fd=sd->fd) && packet_db[sd->packet_ver][RBUFW(buf,0)].len) { // packet must exist for the client version - WFIFOHEAD(fd,len); - memcpy(WFIFOP(fd,0), buf, len); - WFIFOSET(fd,len); - } - break; - - // New definitions for guilds [Valaris] - Cleaned up and reorganized by [Skotlex] - 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_SAMEMAP: - case GUILD_SAMEMAP_WOS: - case GUILD: - case GUILD_WOS: - case GUILD_NOBG: - if (sd && sd->status.guild_id) - 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( !(fd=sd->fd) ) - continue; - - if( type == GUILD_NOBG && sd->bg_id ) - continue; - - if( sd->bl.id == bl->id && (type == GUILD_WOS || type == GUILD_SAMEMAP_WOS || type == GUILD_AREA_WOS) ) - continue; - - if( type != GUILD && type != GUILD_NOBG && type != GUILD_WOS && sd->bl.m != 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_db[sd->packet_ver][RBUFW(buf,0)].len ) - { // packet must exist for the client version - WFIFOHEAD(fd,len); - memcpy(WFIFOP(fd,0), buf, len); - WFIFOSET(fd,len); - } - } - } - if (!enable_spy) //Skip unnecessary parsing. [Skotlex] - break; - - iter = mapit_getallusers(); - while( (tsd = (TBL_PC*)mapit_next(iter)) != NULL ) - { - if( tsd->guildspy == g->guild_id && packet_db[tsd->packet_ver][RBUFW(buf,0)].len ) - { // packet must exist for the client version - WFIFOHEAD(tsd->fd, len); - memcpy(WFIFOP(tsd->fd,0), buf, len); - WFIFOSET(tsd->fd,len); - } - } - mapit_free(iter); - } - break; - - case BG_AREA: - case BG_AREA_WOS: - x0 = bl->x - AREA_SIZE; - y0 = bl->y - AREA_SIZE; - x1 = bl->x + AREA_SIZE; - y1 = bl->y + AREA_SIZE; - case BG_SAMEMAP: - case BG_SAMEMAP_WOS: - case BG: - case BG_WOS: - if( sd && sd->bg_id && (bg = bg_team_search(sd->bg_id)) != NULL ) - { - for( i = 0; i < MAX_BG_MEMBERS; i++ ) - { - if( (sd = bg->members[i].sd) == NULL || !(fd = sd->fd) ) - continue; - if( sd->bl.id == bl->id && (type == BG_WOS || type == BG_SAMEMAP_WOS || type == BG_AREA_WOS) ) - continue; - if( type != BG && type != BG_WOS && sd->bl.m != bl->m ) - continue; - if( (type == BG_AREA || type == BG_AREA_WOS) && (sd->bl.x < x0 || sd->bl.y < y0 || sd->bl.x > x1 || sd->bl.y > y1) ) - continue; - if( packet_db[sd->packet_ver][RBUFW(buf,0)].len ) - { // packet must exist for the client version - WFIFOHEAD(fd,len); - memcpy(WFIFOP(fd,0), buf, len); - WFIFOSET(fd,len); - } - } - } - break; - - default: - ShowError("clif_send: Unrecognized type %d\n",type); - return -1; - } - - return 0; -} - - -/// Notifies the client, that it's connection attempt was accepted. -/// 0073 .L .3B .B .B (ZC_ACCEPT_ENTER) -/// 02eb .L .3B .B .B .W (ZC_ACCEPT_ENTER2) -void clif_authok(struct map_session_data *sd) -{ -#if PACKETVER < 20080102 - const int cmd = 0x73; -#else - const int cmd = 0x2eb; -#endif - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(cmd)); - WFIFOW(fd, 0) = cmd; - WFIFOL(fd, 2) = gettick(); - WFIFOPOS(fd, 6, sd->bl.x, sd->bl.y, sd->ud.dir); - WFIFOB(fd, 9) = 5; // ignored - WFIFOB(fd,10) = 5; // ignored -#if PACKETVER >= 20080102 - WFIFOW(fd,11) = sd->user_font; // FIXME: Font is currently not saved. -#endif - WFIFOSET(fd,packet_len(cmd)); -} - - -/// Notifies the client, that it's connection attempt was refused (ZC_REFUSE_ENTER). -/// 0074 .B -/// error code: -/// 0 = client type mismatch -/// 1 = ID mismatch -/// 2 = mobile - out of available time -/// 3 = mobile - already logged in -/// 4 = mobile - waiting state -void clif_authrefuse(int fd, uint8 error_code) -{ - WFIFOHEAD(fd,packet_len(0x74)); - WFIFOW(fd,0) = 0x74; - WFIFOB(fd,2) = error_code; - WFIFOSET(fd,packet_len(0x74)); -} - - -/// Notifies the client of a ban or forced disconnect (SC_NOTIFY_BAN). -/// 0081 .B -/// error code: -/// 0 = BAN_UNFAIR -/// 1 = server closed -> MsgStringTable[4] -/// 2 = ID already logged in -> MsgStringTable[5] -/// 3 = timeout/too much lag -> MsgStringTable[241] -/// 4 = server full -> MsgStringTable[264] -/// 5 = underaged -> MsgStringTable[305] -/// 8 = Server sill recognizes last connection -> MsgStringTable[441] -/// 9 = too many connections from this ip -> MsgStringTable[529] -/// 10 = out of available time paid for -> MsgStringTable[530] -/// 11 = BAN_PAY_SUSPEND -/// 12 = BAN_PAY_CHANGE -/// 13 = BAN_PAY_WRONGIP -/// 14 = BAN_PAY_PNGAMEROOM -/// 15 = disconnected by a GM -> if( servicetype == taiwan ) MsgStringTable[579] -/// 16 = BAN_JAPAN_REFUSE1 -/// 17 = BAN_JAPAN_REFUSE2 -/// 18 = BAN_INFORMATION_REMAINED_ANOTHER_ACCOUNT -/// 100 = BAN_PC_IP_UNFAIR -/// 101 = BAN_PC_IP_COUNT_ALL -/// 102 = BAN_PC_IP_COUNT -/// 103 = BAN_GRAVITY_MEM_AGREE -/// 104 = BAN_GAME_MEM_AGREE -/// 105 = BAN_HAN_VALID -/// 106 = BAN_PC_IP_LIMIT_ACCESS -/// 107 = BAN_OVER_CHARACTER_LIST -/// 108 = BAN_IP_BLOCK -/// 109 = BAN_INVALID_PWD_CNT -/// 110 = BAN_NOT_ALLOWED_JOBCLASS -/// ? = disconnected -> MsgStringTable[3] -void clif_authfail_fd(int fd, int type) -{ - if (!fd || !session[fd] || session[fd]->func_parse != clif_parse) //clif_authfail should only be invoked on players! - return; - - WFIFOHEAD(fd, packet_len(0x81)); - WFIFOW(fd,0) = 0x81; - WFIFOB(fd,2) = type; - WFIFOSET(fd,packet_len(0x81)); - set_eof(fd); -} - - -/// Notifies the client, whether it can disconnect and change servers (ZC_RESTART_ACK). -/// 00b3 .B -/// type: -/// 1 = disconnect, char-select -/// ? = nothing -void clif_charselectok(int id, uint8 ok) -{ - struct map_session_data* sd; - int fd; - - if ((sd = map_id2sd(id)) == NULL || !sd->fd) - return; - - fd = sd->fd; - WFIFOHEAD(fd,packet_len(0xb3)); - WFIFOW(fd,0) = 0xb3; - WFIFOB(fd,2) = ok; - WFIFOSET(fd,packet_len(0xb3)); -} - -/// Makes an item appear on the ground. -/// 009e .L .W .B .W .W .B .B .W (ZC_ITEM_FALL_ENTRY) -/// 084b (ZC_ITEM_FALL_ENTRY4) -void clif_dropflooritem(struct flooritem_data* fitem) -{ - uint8 buf[17]; - int view; - - nullpo_retv(fitem); - - if (fitem->item_data.nameid <= 0) - return; - - WBUFW(buf, 0) = 0x9e; - WBUFL(buf, 2) = fitem->bl.id; - WBUFW(buf, 6) = ((view = itemdb_viewid(fitem->item_data.nameid)) > 0) ? view : 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; - - clif_send(buf, packet_len(0x9e), &fitem->bl, AREA); -} - - - -/// Makes an item disappear from the ground. -/// 00a1 .L (ZC_ITEM_DISAPPEAR) -void clif_clearflooritem(struct flooritem_data *fitem, int fd) -{ - unsigned char buf[16]; - - nullpo_retv(fitem); - - WBUFW(buf,0) = 0xa1; - WBUFL(buf,2) = fitem->bl.id; - - if (fd == 0) { - clif_send(buf, packet_len(0xa1), &fitem->bl, AREA); - } else { - WFIFOHEAD(fd,packet_len(0xa1)); - memcpy(WFIFOP(fd,0), buf, packet_len(0xa1)); - WFIFOSET(fd,packet_len(0xa1)); - } -} - - -/// Makes a unit (char, npc, mob, homun) disappear to one client (ZC_NOTIFY_VANISH). -/// 0080 .L .B -/// type: -/// 0 = out of sight -/// 1 = died -/// 2 = logged out -/// 3 = teleport -/// 4 = trickdead -void clif_clearunit_single(int id, clr_type type, int fd) -{ - WFIFOHEAD(fd, packet_len(0x80)); - WFIFOW(fd,0) = 0x80; - WFIFOL(fd,2) = id; - WFIFOB(fd,6) = type; - WFIFOSET(fd, packet_len(0x80)); -} - -/// Makes a unit (char, npc, mob, homun) disappear to all clients in area (ZC_NOTIFY_VANISH). -/// 0080 .L .B -/// type: -/// 0 = out of sight -/// 1 = died -/// 2 = logged out -/// 3 = teleport -/// 4 = trickdead -void clif_clearunit_area(struct block_list* bl, clr_type type) -{ - unsigned char buf[8]; - - nullpo_retv(bl); - - WBUFW(buf,0) = 0x80; - WBUFL(buf,2) = bl->id; - WBUFB(buf,6) = type; - - clif_send(buf, packet_len(0x80), bl, type == CLR_DEAD ? AREA : AREA_WOS); - - if(disguised(bl)) { - WBUFL(buf,2) = -bl->id; - clif_send(buf, packet_len(0x80), bl, SELF); - } -} - - -/// Used to make monsters with player-sprites disappear after dying -/// like normal monsters, because the client does not remove those -/// automatically. -static int clif_clearunit_delayed_sub(int tid, unsigned int tick, int id, intptr_t data) -{ - struct block_list *bl = (struct block_list *)data; - clif_clearunit_area(bl, (clr_type) id); - ers_free(delay_clearunit_ers,bl); - return 0; -} -void clif_clearunit_delayed(struct block_list* bl, clr_type type, unsigned int tick) -{ - struct block_list *tbl = ers_alloc(delay_clearunit_ers, struct block_list); - memcpy (tbl, bl, sizeof (struct block_list)); - add_timer(tick, clif_clearunit_delayed_sub, (int)type, (intptr_t)tbl); -} - -void clif_get_weapon_view(struct map_session_data* sd, unsigned short *rhand, unsigned short *lhand) -{ - if(sd->sc.option&(OPTION_WEDDING|OPTION_XMAS|OPTION_SUMMER)) - { - *rhand = *lhand = 0; - return; - } - -#if PACKETVER < 4 - *rhand = sd->status.weapon; - *lhand = sd->status.shield; -#else - if (sd->equip_index[EQI_HAND_R] >= 0 && - sd->inventory_data[sd->equip_index[EQI_HAND_R]]) - { - struct item_data* id = sd->inventory_data[sd->equip_index[EQI_HAND_R]]; - if (id->view_id > 0) - *rhand = id->view_id; - else - *rhand = id->nameid; - } else - *rhand = 0; - - if (sd->equip_index[EQI_HAND_L] >= 0 && - sd->equip_index[EQI_HAND_L] != sd->equip_index[EQI_HAND_R] && - sd->inventory_data[sd->equip_index[EQI_HAND_L]]) - { - struct item_data* id = sd->inventory_data[sd->equip_index[EQI_HAND_L]]; - if (id->view_id > 0) - *lhand = id->view_id; - else - *lhand = id->nameid; - } else - *lhand = 0; -#endif -} - -//To make the assignation of the level based on limits clearer/easier. [Skotlex] -static int clif_setlevel_sub(int lv) -{ - if( lv < battle_config.max_lv ) - { - ; - } - else if( lv < battle_config.aura_lv ) - { - lv = battle_config.max_lv - 1; - } - else - { - lv = battle_config.max_lv; - } - - return lv; -} - -static int clif_setlevel(struct block_list* bl) -{ - int lv = status_get_lv(bl); - if( battle_config.client_limit_unit_lv&bl->type ) - return clif_setlevel_sub(lv); - switch( bl->type ) - { - case BL_NPC: - case BL_PET: - // npcs and pets do not have level - return 0; - } - return lv; -} - -/*========================================== - * Prepares 'unit standing/spawning' packet - *------------------------------------------*/ -static int clif_set_unit_idle(struct block_list* bl, unsigned char* buffer, bool spawn) -{ - struct map_session_data* sd; - struct status_change* sc = status_get_sc(bl); - struct view_data* vd = status_get_viewdata(bl); - unsigned char *buf = WBUFP(buffer,0); -#if PACKETVER < 20091103 - bool type = !pcdb_checkid(vd->class_); -#endif - unsigned short offset = 0; -#if PACKETVER >= 20091103 - const char *name; -#endif - sd = BL_CAST(BL_PC, bl); - -#if PACKETVER < 20091103 - if(type) - WBUFW(buf,0) = spawn?0x7c:0x78; - else -#endif -#if PACKETVER < 4 - WBUFW(buf,0) = spawn?0x79:0x78; -#elif PACKETVER < 7 - WBUFW(buf,0) = spawn?0x1d9:0x1d8; -#elif PACKETVER < 20080102 - WBUFW(buf,0) = spawn?0x22b:0x22a; -#elif PACKETVER < 20091103 - WBUFW(buf,0) = spawn?0x2ed:0x2ee; -#elif PACKETVER < 20101124 - WBUFW(buf,0) = spawn?0x7f8:0x7f9; -#else - WBUFW(buf,0) = spawn?0x858:0x857; -#endif - -#if PACKETVER >= 20091103 - name = status_get_name(bl); -#if PACKETVER < 20110111 - WBUFW(buf,2) = (spawn?62:63)+strlen(name); -#else - WBUFW(buf,2) = (spawn?64:65)+strlen(name); -#endif - WBUFB(buf,4) = clif_bl_type(bl); - offset+=3; - buf = WBUFP(buffer,offset); -#elif PACKETVER >= 20071106 - if (type) { //Non-player packets - WBUFB(buf,2) = clif_bl_type(bl); - offset++; - buf = WBUFP(buffer,offset); - } -#endif - WBUFL(buf, 2) = bl->id; - WBUFW(buf, 6) = status_get_speed(bl); - WBUFW(buf, 8) = (sc)? sc->opt1 : 0; - WBUFW(buf,10) = (sc)? sc->opt2 : 0; -#if PACKETVER < 20091103 - if (type&&spawn) { //uses an older and different packet structure - WBUFW(buf,12) = (sc)? sc->option : 0; - WBUFW(buf,14) = vd->hair_style; - WBUFW(buf,16) = vd->weapon; - WBUFW(buf,18) = vd->head_bottom; - WBUFW(buf,20) = vd->class_; //Pet armor (ignored by client) - WBUFW(buf,22) = vd->shield; - } else { -#endif -#if PACKETVER >= 20091103 - WBUFL(buf,12) = (sc)? sc->option : 0; - offset+=2; - buf = WBUFP(buffer,offset); -#elif PACKETVER >= 7 - if (!type) { - WBUFL(buf,12) = (sc)? sc->option : 0; - offset+=2; - buf = WBUFP(buffer,offset); - } else - WBUFW(buf,12) = (sc)? sc->option : 0; -#else - WBUFW(buf,12) = (sc)? sc->option : 0; -#endif - WBUFW(buf,14) = vd->class_; - WBUFW(buf,16) = vd->hair_style; - WBUFW(buf,18) = vd->weapon; -#if PACKETVER < 4 - WBUFW(buf,20) = vd->head_bottom; - WBUFW(buf,22) = vd->shield; -#else - WBUFW(buf,20) = vd->shield; - WBUFW(buf,22) = vd->head_bottom; -#endif -#if PACKETVER < 20091103 - } -#endif - WBUFW(buf,24) = vd->head_top; - WBUFW(buf,26) = vd->head_mid; - - if( bl->type == BL_NPC && vd->class_ == FLAG_CLASS ) - { //The hell, why flags work like this? - WBUFW(buf,22) = status_get_emblem_id(bl); - WBUFW(buf,24) = GetWord(status_get_guild_id(bl), 1); - WBUFW(buf,26) = GetWord(status_get_guild_id(bl), 0); - } - - WBUFW(buf,28) = vd->hair_color; - WBUFW(buf,30) = vd->cloth_color; - WBUFW(buf,32) = (sd)? sd->head_dir : 0; -#if PACKETVER < 20091103 - if (type&&spawn) { //End of packet 0x7c - WBUFB(buf,34) = (sd)?sd->status.karma:0; // karma - WBUFB(buf,35) = vd->sex; - WBUFPOS(buf,36,bl->x,bl->y,unit_getdir(bl)); - WBUFB(buf,39) = 0; - WBUFB(buf,40) = 0; - return packet_len(0x7c); - } -#endif -#if PACKETVER >= 20110111 - WBUFW(buf,34) = vd->robe; - offset+= 2; - buf = WBUFP(buffer,offset); -#endif - WBUFL(buf,34) = status_get_guild_id(bl); - WBUFW(buf,38) = status_get_emblem_id(bl); - WBUFW(buf,40) = (sd)? sd->status.manner : 0; -#if PACKETVER >= 20091103 - WBUFL(buf,42) = (sc)? sc->opt3 : 0; - offset+=2; - buf = WBUFP(buffer,offset); -#elif PACKETVER >= 7 - if (!type) { - WBUFL(buf,42) = (sc)? sc->opt3 : 0; - offset+=2; - buf = WBUFP(buffer,offset); - } else - WBUFW(buf,42) = (sc)? sc->opt3 : 0; -#else - WBUFW(buf,42) = (sc)? sc->opt3 : 0; -#endif - WBUFB(buf,44) = (sd)? sd->status.karma : 0; - WBUFB(buf,45) = vd->sex; - WBUFPOS(buf,46,bl->x,bl->y,unit_getdir(bl)); - WBUFB(buf,49) = (sd)? 5 : 0; - WBUFB(buf,50) = (sd)? 5 : 0; - if (!spawn) { - WBUFB(buf,51) = vd->dead_sit; - offset++; - buf = WBUFP(buffer,offset); - } - WBUFW(buf,51) = clif_setlevel(bl); -#if PACKETVER < 20091103 - if (type) //End for non-player packet - return packet_len(WBUFW(buffer,0)); -#endif -#if PACKETVER >= 20080102 - WBUFW(buf,53) = sd?sd->user_font:0; -#endif -#if PACKETVER >= 20091103 - memcpy((char*)WBUFP(buf,55), name, NAME_LENGTH); - return WBUFW(buffer,2); -#else - return packet_len(WBUFW(buffer,0)); -#endif -} - -/*========================================== - * Prepares 'unit walking' packet - *------------------------------------------*/ -static int clif_set_unit_walking(struct block_list* bl, struct unit_data* ud, unsigned char* buffer) -{ - struct map_session_data* sd; - struct status_change* sc = status_get_sc(bl); - struct view_data* vd = status_get_viewdata(bl); - unsigned char* buf = WBUFP(buffer,0); -#if PACKETVER >= 7 - unsigned short offset = 0; -#endif -#if PACKETVER >= 20091103 - const char *name; -#endif - - sd = BL_CAST(BL_PC, bl); - -#if PACKETVER < 4 - WBUFW(buf, 0) = 0x7b; -#elif PACKETVER < 7 - WBUFW(buf, 0) = 0x1da; -#elif PACKETVER < 20080102 - WBUFW(buf, 0) = 0x22c; -#elif PACKETVER < 20091103 - WBUFW(buf, 0) = 0x2ec; -#elif PACKETVER < 20101124 - WBUFW(buf, 0) = 0x7f7; -#else - WBUFW(buf, 0) = 0x856; -#endif - -#if PACKETVER >= 20091103 - name = status_get_name(bl); -#if PACKETVER < 20110111 - WBUFW(buf, 2) = 69+strlen(name); -#else - WBUFW(buf, 2) = 71+strlen(name); -#endif - offset+=2; - buf = WBUFP(buffer,offset); -#endif -#if PACKETVER >= 20071106 - WBUFB(buf, 2) = clif_bl_type(bl); - offset++; - buf = WBUFP(buffer,offset); -#endif - WBUFL(buf, 2) = bl->id; - WBUFW(buf, 6) = status_get_speed(bl); - WBUFW(buf, 8) = (sc)? sc->opt1 : 0; - WBUFW(buf,10) = (sc)? sc->opt2 : 0; -#if PACKETVER < 7 - WBUFW(buf,12) = (sc)? sc->option : 0; -#else - WBUFL(buf,12) = (sc)? sc->option : 0; - offset+=2; //Shift the rest of elements by 2 bytes. - buf = WBUFP(buffer,offset); -#endif - WBUFW(buf,14) = vd->class_; - WBUFW(buf,16) = vd->hair_style; - WBUFW(buf,18) = vd->weapon; -#if PACKETVER < 4 - WBUFW(buf,20) = vd->head_bottom; - WBUFL(buf,22) = gettick(); - WBUFW(buf,26) = vd->shield; -#else - WBUFW(buf,20) = vd->shield; - WBUFW(buf,22) = vd->head_bottom; - WBUFL(buf,24) = gettick(); -#endif - WBUFW(buf,28) = vd->head_top; - WBUFW(buf,30) = vd->head_mid; - WBUFW(buf,32) = vd->hair_color; - WBUFW(buf,34) = vd->cloth_color; - WBUFW(buf,36) = (sd)? sd->head_dir : 0; -#if PACKETVER >= 20110111 - WBUFW(buf,38) = vd->robe; - offset+= 2; - buf = WBUFP(buffer,offset); -#endif - WBUFL(buf,38) = status_get_guild_id(bl); - WBUFW(buf,42) = status_get_emblem_id(bl); - WBUFW(buf,44) = (sd)? sd->status.manner : 0; -#if PACKETVER < 7 - WBUFW(buf,46) = (sc)? sc->opt3 : 0; -#else - WBUFL(buf,46) = (sc)? sc->opt3 : 0; - offset+=2; //Shift the rest of elements by 2 bytes. - buf = WBUFP(buffer,offset); -#endif - WBUFB(buf,48) = (sd)? sd->status.karma : 0; - WBUFB(buf,49) = vd->sex; - WBUFPOS2(buf,50,bl->x,bl->y,ud->to_x,ud->to_y,8,8); - WBUFB(buf,56) = (sd)? 5 : 0; - WBUFB(buf,57) = (sd)? 5 : 0; - WBUFW(buf,58) = clif_setlevel(bl); -#if PACKETVER >= 20080102 - WBUFW(buf,60) = sd?sd->user_font:0; -#endif -#if PACKETVER >= 20091103 - memcpy((char*)WBUFP(buf,62), name, NAME_LENGTH); - return WBUFW(buffer,2); -#else - return packet_len(WBUFW(buffer,0)); -#endif -} - -//Modifies the buffer for disguise characters and sends it to self. -//Used for spawn/walk packets, where the ID offset changes for packetver >=9 -static void clif_setdisguise(struct block_list *bl, unsigned char *buf,int len) -{ -#if PACKETVER >= 20091103 - WBUFB(buf,4)= pcdb_checkid(status_get_viewdata(bl)->class_) ? 0x0 : 0x5; //PC_TYPE : NPC_MOB_TYPE - WBUFL(buf,5)=-bl->id; -#elif PACKETVER >= 20071106 - WBUFB(buf,2)= pcdb_checkid(status_get_viewdata(bl)->class_) ? 0x0 : 0x5; //PC_TYPE : NPC_MOB_TYPE - WBUFL(buf,3)=-bl->id; -#else - WBUFL(buf,2)=-bl->id; -#endif - clif_send(buf, len, bl, SELF); -} - - -/// Changes sprite of an NPC object (ZC_NPCSPRITE_CHANGE). -/// 01b0 .L .B .L -/// type: -/// unused -void clif_class_change(struct block_list *bl,int class_,int type) -{ - unsigned char buf[16]; - - nullpo_retv(bl); - - if(!pcdb_checkid(class_)) - {// player classes yield missing sprites - WBUFW(buf,0)=0x1b0; - WBUFL(buf,2)=bl->id; - WBUFB(buf,6)=type; - WBUFL(buf,7)=class_; - clif_send(buf,packet_len(0x1b0),bl,AREA); - } -} - - -/// Notifies the client of an object's spirits. -/// 01d0 .L .W (ZC_SPIRITS) -/// 01e1 .L .W (ZC_SPIRITS2) -static void clif_spiritball_single(int fd, struct map_session_data *sd) -{ - WFIFOHEAD(fd, packet_len(0x1e1)); - WFIFOW(fd,0)=0x1e1; - WFIFOL(fd,2)=sd->bl.id; - WFIFOW(fd,6)=sd->spiritball; - WFIFOSET(fd, packet_len(0x1e1)); -} - -/*========================================== - * Kagerou/Oboro amulet spirit - *------------------------------------------*/ -static void clif_talisman_single(int fd, struct map_session_data *sd, short type) -{ - WFIFOHEAD(fd, packet_len(0x08cf)); - WFIFOW(fd,0)=0x08cf; - WFIFOL(fd,2)=sd->bl.id; - WFIFOW(fd,6)=type; - WFIFOW(fd,8)=sd->talisman[type]; - WFIFOSET(fd, packet_len(0x08cf)); -} - -/*========================================== - * Run when player changes map / refreshes - * Tells its client to display all weather settings being used by this map - *------------------------------------------*/ -static void clif_weather_check(struct map_session_data *sd) -{ - int16 m = sd->bl.m; - int fd = sd->fd; - - if (map[m].flag.snow - || map[m].flag.clouds - || map[m].flag.fog - || map[m].flag.fireworks - || map[m].flag.sakura - || map[m].flag.leaves - /** - * No longer available, keeping here just in case it's back someday. [Ind] - **/ - //|| map[m].flag.rain - || map[m].flag.clouds2) - { - if (map[m].flag.snow) - clif_specialeffect_single(&sd->bl, 162, fd); - if (map[m].flag.clouds) - clif_specialeffect_single(&sd->bl, 233, fd); - if (map[m].flag.clouds2) - clif_specialeffect_single(&sd->bl, 516, fd); - if (map[m].flag.fog) - clif_specialeffect_single(&sd->bl, 515, fd); - if (map[m].flag.fireworks) { - clif_specialeffect_single(&sd->bl, 297, fd); - clif_specialeffect_single(&sd->bl, 299, fd); - clif_specialeffect_single(&sd->bl, 301, fd); - } - if (map[m].flag.sakura) - clif_specialeffect_single(&sd->bl, 163, fd); - if (map[m].flag.leaves) - clif_specialeffect_single(&sd->bl, 333, fd); - /** - * No longer available, keeping here just in case it's back someday. [Ind] - **/ - //if (map[m].flag.rain) - // clif_specialeffect_single(&sd->bl, 161, fd); - } -} -/** - * Run when the weather on a map changes, throws all players in map id 'm' to clif_weather_check function - **/ -void clif_weather(int16 m) -{ - struct s_mapiterator* iter; - struct map_session_data *sd=NULL; - - iter = mapit_getallusers(); - for( sd = (struct map_session_data*)mapit_first(iter); mapit_exists(iter); sd = (struct map_session_data*)mapit_next(iter) ) - { - if( sd->bl.m == m ) - clif_weather_check(sd); - } - mapit_free(iter); -} -/** - * Main function to spawn a unit on the client (player/mob/pet/etc) - **/ -int clif_spawn(struct block_list *bl) -{ - unsigned char buf[128]; - struct view_data *vd; - int len; - - vd = status_get_viewdata(bl); - if( !vd || vd->class_ == INVISIBLE_CLASS ) - return 0; - - /** - * Hide NPC from maya purple card. - **/ - if(bl->type == BL_NPC && !((TBL_NPC*)bl)->chat_id && (((TBL_NPC*)bl)->sc.option&OPTION_INVISIBLE)) - return 0; - - len = clif_set_unit_idle(bl, buf,true); - clif_send(buf, len, bl, AREA_WOS); - if (disguised(bl)) - clif_setdisguise(bl, buf, len); - - if (vd->cloth_color) - clif_refreshlook(bl,bl->id,LOOK_CLOTHES_COLOR,vd->cloth_color,AREA_WOS); - - switch (bl->type) - { - case BL_PC: - { - TBL_PC *sd = ((TBL_PC*)bl); - int i; - if (sd->spiritball > 0) - clif_spiritball(&sd->bl); - if(sd->state.size==SZ_BIG) // tiny/big players [Valaris] - clif_specialeffect(bl,423,AREA); - else if(sd->state.size==SZ_MEDIUM) - clif_specialeffect(bl,421,AREA); - if( sd->bg_id && map[sd->bl.m].flag.battleground ) - clif_sendbgemblem_area(sd); - if( sd->sc.option&OPTION_MOUNTING ) { - //New Mounts are not complaint to the original method, so we gotta tell this guy that he is mounting. - clif_status_load_notick(&sd->bl,SI_ALL_RIDING,2,1,0,0); - } - for(i = 1; i < 5; i++){ - if( sd->talisman[i] > 0 ) - clif_talisman(sd, i); - } - #ifdef NEW_CARTS - if( sd->sc.data[SC_PUSH_CART] ) - clif_status_load_notick(&sd->bl, SI_ON_PUSH_CART, 2, sd->sc.data[SC_PUSH_CART]->val1, 0, 0); - #endif - #if PACKETVER <= 20120207 - if (sd->status.robe) - clif_refreshlook(bl,bl->id,LOOK_ROBE,sd->status.robe,AREA); - #endif - } - break; - case BL_MOB: - { - TBL_MOB *md = ((TBL_MOB*)bl); - if(md->special_state.size==SZ_BIG) // tiny/big mobs [Valaris] - clif_specialeffect(&md->bl,423,AREA); - else if(md->special_state.size==SZ_MEDIUM) - clif_specialeffect(&md->bl,421,AREA); - } - break; - case BL_NPC: - { - TBL_NPC *nd = ((TBL_NPC*)bl); - if( nd->size == SZ_BIG ) - clif_specialeffect(&nd->bl,423,AREA); - else if( nd->size == SZ_MEDIUM ) - clif_specialeffect(&nd->bl,421,AREA); - } - break; - case BL_PET: - if (vd->head_bottom) - clif_pet_equip_area((TBL_PET*)bl); // needed to display pet equip properly - break; - } - return 0; -} - -/// Sends information about owned homunculus to the client (ZC_PROPERTY_HOMUN). [orn] -/// 022e .24B .B .W .W .W .W .W .W .W .W .W .W .W .W .W .W .W .W .L .L .W .W -void clif_hominfo(struct map_session_data *sd, struct homun_data *hd, int flag) -{ - struct status_data *status; - unsigned char buf[128]; - int m_class; - - nullpo_retv(hd); - - status = &hd->battle_status; - m_class = hom_class2mapid(hd->homunculus.class_); - - memset(buf,0,packet_len(0x22e)); - WBUFW(buf,0)=0x22e; - memcpy(WBUFP(buf,2),hd->homunculus.name,NAME_LENGTH); - // Bit field, bit 0 : rename_flag (1 = already renamed), bit 1 : homunc vaporized (1 = true), bit 2 : homunc dead (1 = true) - WBUFB(buf,26)=(battle_config.hom_rename?0:hd->homunculus.rename_flag) | (hd->homunculus.vaporize << 1) | (hd->homunculus.hp?0:4); - WBUFW(buf,27)=hd->homunculus.level; - WBUFW(buf,29)=hd->homunculus.hunger; - WBUFW(buf,31)=(unsigned short) (hd->homunculus.intimacy / 100) ; - WBUFW(buf,33)=0; // equip id - WBUFW(buf,35)=cap_value(status->rhw.atk2+status->batk, 0, INT16_MAX); - WBUFW(buf,37)=cap_value(status->matk_max, 0, INT16_MAX); - WBUFW(buf,39)=status->hit; - if (battle_config.hom_setting&0x10) - WBUFW(buf,41)=status->luk/3 + 1; //crit is a +1 decimal value! Just display purpose.[Vicious] - else - WBUFW(buf,41)=status->cri/10; - WBUFW(buf,43)=status->def + status->vit ; - WBUFW(buf,45)=status->mdef; - WBUFW(buf,47)=status->flee; - WBUFW(buf,49)=(flag)?0:status->amotion; - if (status->max_hp > INT16_MAX) { - WBUFW(buf,51) = status->hp/(status->max_hp/100); - WBUFW(buf,53) = 100; - } else { - WBUFW(buf,51)=status->hp; - WBUFW(buf,53)=status->max_hp; - } - if (status->max_sp > INT16_MAX) { - WBUFW(buf,55) = status->sp/(status->max_sp/100); - WBUFW(buf,57) = 100; - } else { - WBUFW(buf,55)=status->sp; - WBUFW(buf,57)=status->max_sp; - } - WBUFL(buf,59)=hd->homunculus.exp; - if( ((m_class&HOM_REG) && hd->homunculus.level >= battle_config.hom_max_level) || ((m_class&HOM_S) && hd->homunculus.level >= battle_config.hom_S_max_level) ) - WBUFL(buf,63)=0; - else - WBUFL(buf,63)=hd->exp_next; - WBUFW(buf,67)=hd->homunculus.skillpts; - WBUFW(buf,69)=status_get_range(&hd->bl); - clif_send(buf,packet_len(0x22e),&sd->bl,SELF); -} - - -/// Notification about a change in homunuculus' state (ZC_CHANGESTATE_MER). -/// 0230 .B .B .L .L -/// type: -/// unused -/// state: -/// 0 = pre-init -/// 1 = intimacy -/// 2 = hunger -/// 3 = accessory? -/// ? = ignored -void clif_send_homdata(struct map_session_data *sd, int state, int param) -{ //[orn] - int fd = sd->fd; - - if ( (state == SP_INTIMATE) && (param >= 910) && (sd->hd->homunculus.class_ == sd->hd->homunculusDB->evo_class) ) - merc_hom_calc_skilltree(sd->hd, 0); - - WFIFOHEAD(fd, packet_len(0x230)); - WFIFOW(fd,0)=0x230; - WFIFOB(fd,2)=0; - WFIFOB(fd,3)=state; - WFIFOL(fd,4)=sd->hd->bl.id; - WFIFOL(fd,8)=param; - WFIFOSET(fd,packet_len(0x230)); -} - - -int clif_homskillinfoblock(struct map_session_data *sd) -{ //[orn] - struct homun_data *hd; - int fd = sd->fd; - int i,j,len=4,id; - WFIFOHEAD(fd, 4+37*MAX_HOMUNSKILL); - - hd = sd->hd; - if ( !hd ) - return 0 ; - - WFIFOW(fd,0)=0x235; - for ( i = 0; i < MAX_HOMUNSKILL; i++){ - if( (id = hd->homunculus.hskill[i].id) != 0 ){ - j = id - HM_SKILLBASE; - WFIFOW(fd,len ) = id; - WFIFOW(fd,len+2) = skill_get_inf(id); - WFIFOW(fd,len+4) = 0; - WFIFOW(fd,len+6) = hd->homunculus.hskill[j].lv; - WFIFOW(fd,len+8) = skill_get_sp(id,hd->homunculus.hskill[j].lv); - WFIFOW(fd,len+10)= skill_get_range2(&sd->hd->bl, id,hd->homunculus.hskill[j].lv); - safestrncpy((char*)WFIFOP(fd,len+12), skill_get_name(id), NAME_LENGTH); - WFIFOB(fd,len+36) = (hd->homunculus.hskill[j].lv < merc_skill_tree_get_max(id, hd->homunculus.class_))?1:0; - len+=37; - } - } - WFIFOW(fd,2)=len; - WFIFOSET(fd,len); - - return 0; -} - -void clif_homskillup(struct map_session_data *sd, uint16 skill_id) -{ //[orn] - struct homun_data *hd; - int fd, idx; - nullpo_retv(sd); - idx = skill_id - HM_SKILLBASE; - - fd=sd->fd; - hd=sd->hd; - - WFIFOHEAD(fd, packet_len(0x239)); - WFIFOW(fd,0) = 0x239; - WFIFOW(fd,2) = skill_id; - WFIFOW(fd,4) = hd->homunculus.hskill[idx].lv; - WFIFOW(fd,6) = skill_get_sp(skill_id,hd->homunculus.hskill[idx].lv); - WFIFOW(fd,8) = skill_get_range2(&hd->bl, skill_id,hd->homunculus.hskill[idx].lv); - WFIFOB(fd,10) = (hd->homunculus.hskill[idx].lv < skill_get_max(hd->homunculus.hskill[idx].id)) ? 1 : 0; - WFIFOSET(fd,packet_len(0x239)); -} - -int clif_hom_food(struct map_session_data *sd,int foodid,int fail) //[orn] -{ - int fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x22f)); - WFIFOW(fd,0)=0x22f; - WFIFOB(fd,2)=fail; - WFIFOW(fd,3)=foodid; - WFIFOSET(fd,packet_len(0x22f)); - - return 0; -} - - -/// Notifies the client, that it is walking (ZC_NOTIFY_PLAYERMOVE). -/// 0087 .L .6B -void clif_walkok(struct map_session_data *sd) -{ - int fd=sd->fd; - - WFIFOHEAD(fd, packet_len(0x87)); - WFIFOW(fd,0)=0x87; - WFIFOL(fd,2)=gettick(); - WFIFOPOS2(fd,6,sd->bl.x,sd->bl.y,sd->ud.to_x,sd->ud.to_y,8,8); - WFIFOSET(fd,packet_len(0x87)); -} - - -static void clif_move2(struct block_list *bl, struct view_data *vd, struct unit_data *ud) -{ - uint8 buf[128]; - int len; - - len = clif_set_unit_walking(bl,ud,buf); - clif_send(buf,len,bl,AREA_WOS); - if (disguised(bl)) - clif_setdisguise(bl, buf, len); - - if(vd->cloth_color) - clif_refreshlook(bl,bl->id,LOOK_CLOTHES_COLOR,vd->cloth_color,AREA_WOS); - - switch(bl->type) - { - case BL_PC: - { - TBL_PC *sd = ((TBL_PC*)bl); -// clif_movepc(sd); - if(sd->state.size==SZ_BIG) // tiny/big players [Valaris] - clif_specialeffect(&sd->bl,423,AREA); - else if(sd->state.size==SZ_MEDIUM) - clif_specialeffect(&sd->bl,421,AREA); - } - break; - case BL_MOB: - { - TBL_MOB *md = ((TBL_MOB*)bl); - if(md->special_state.size==SZ_BIG) // tiny/big mobs [Valaris] - clif_specialeffect(&md->bl,423,AREA); - else if(md->special_state.size==SZ_MEDIUM) - clif_specialeffect(&md->bl,421,AREA); - } - break; - case BL_PET: - if( vd->head_bottom ) - {// needed to display pet equip properly - clif_pet_equip_area((TBL_PET*)bl); - } - break; - } -} - - -/// Notifies clients in an area, that an other visible object is walking (ZC_NOTIFY_PLAYERMOVE). -/// 0086 .L .6B .L -/// Note: unit must not be self -void clif_move(struct unit_data *ud) -{ - unsigned char buf[16]; - struct view_data* vd; - struct block_list* bl = ud->bl; - - vd = status_get_viewdata(bl); - if (!vd || vd->class_ == INVISIBLE_CLASS) - return; //This performance check is needed to keep GM-hidden objects from being notified to bots. - - /** - * Hide NPC from maya purple card. - **/ - if(bl->type == BL_NPC && !((TBL_NPC*)bl)->chat_id && (((TBL_NPC*)bl)->sc.option&OPTION_INVISIBLE)) - return; - - if (ud->state.speed_changed) { - // Since we don't know how to update the speed of other objects, - // use the old walk packet to update the data. - ud->state.speed_changed = 0; - clif_move2(bl, vd, ud); - return; - } - - WBUFW(buf,0)=0x86; - WBUFL(buf,2)=bl->id; - WBUFPOS2(buf,6,bl->x,bl->y,ud->to_x,ud->to_y,8,8); - WBUFL(buf,12)=gettick(); - clif_send(buf, packet_len(0x86), bl, AREA_WOS); - if (disguised(bl)) - { - WBUFL(buf,2)=-bl->id; - clif_send(buf, packet_len(0x86), bl, SELF); - } -} - - -/*========================================== - * Delays the map_quit of a player after they are disconnected. [Skotlex] - *------------------------------------------*/ -static int clif_delayquit(int tid, unsigned int tick, int id, intptr_t data) -{ - struct map_session_data *sd = NULL; - - //Remove player from map server - if ((sd = map_id2sd(id)) != NULL && sd->fd == 0) //Should be a disconnected player. - map_quit(sd); - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -void clif_quitsave(int fd,struct map_session_data *sd) -{ - if (!battle_config.prevent_logout || - DIFF_TICK(gettick(), sd->canlog_tick) > battle_config.prevent_logout) - map_quit(sd); - else if (sd->fd) - { //Disassociate session from player (session is deleted after this function was called) - //And set a timer to make him quit later. - session[sd->fd]->session_data = NULL; - sd->fd = 0; - add_timer(gettick() + 10000, clif_delayquit, sd->bl.id, 0); - } -} - -/// Notifies the client of a position change to coordinates on given map (ZC_NPCACK_MAPMOVE). -/// 0091 .16B .W .W -void clif_changemap(struct map_session_data *sd, short map, int x, int y) -{ - int fd; - nullpo_retv(sd); - fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x91)); - WFIFOW(fd,0) = 0x91; - mapindex_getmapname_ext(mapindex_id2name(map), (char*)WFIFOP(fd,2)); - WFIFOW(fd,18) = x; - WFIFOW(fd,20) = y; - WFIFOSET(fd,packet_len(0x91)); -} - - -/// Notifies the client of a position change to coordinates on given map, which is on another map-server (ZC_NPCACK_SERVERMOVE). -/// 0092 .16B .W .W .L .W -void clif_changemapserver(struct map_session_data* sd, unsigned short map_index, int x, int y, uint32 ip, uint16 port) -{ - int fd; - nullpo_retv(sd); - fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x92)); - WFIFOW(fd,0) = 0x92; - mapindex_getmapname_ext(mapindex_id2name(map_index), (char*)WFIFOP(fd,2)); - WFIFOW(fd,18) = x; - WFIFOW(fd,20) = y; - WFIFOL(fd,22) = htonl(ip); - WFIFOW(fd,26) = ntows(htons(port)); // [!] LE byte order here [!] - WFIFOSET(fd,packet_len(0x92)); -} - - -void clif_blown(struct block_list *bl) -{ -//Aegis packets says fixpos, but it's unsure whether slide works better or not. -// clif_fixpos(bl); - clif_slide(bl, bl->x, bl->y); -} - - -/// Visually moves(slides) a character to x,y. If the target cell -/// isn't walkable, the char doesn't move at all. If the char is -/// sitting it will stand up (ZC_STOPMOVE). -/// 0088 .L .W .W -void clif_fixpos(struct block_list *bl) -{ - unsigned char buf[10]; - nullpo_retv(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(0x88), bl, AREA); - - if( disguised(bl) ) - { - WBUFL(buf,2) = -bl->id; - clif_send(buf, packet_len(0x88), bl, SELF); - } -} - - -/// Displays the buy/sell dialog of an NPC shop (ZC_SELECT_DEALTYPE). -/// 00c4 .L -void clif_npcbuysell(struct map_session_data* sd, int id) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd, packet_len(0xc4)); - WFIFOW(fd,0)=0xc4; - WFIFOL(fd,2)=id; - WFIFOSET(fd,packet_len(0xc4)); -} - - -/// Presents list of items, that can be bought in an NPC shop (ZC_PC_PURCHASE_ITEMLIST). -/// 00c6 .W { .L .L .B .W }* -void clif_buylist(struct map_session_data *sd, struct npc_data *nd) -{ - int fd,i,c; - - nullpo_retv(sd); - nullpo_retv(nd); - - fd = sd->fd; - WFIFOHEAD(fd, 4 + nd->u.shop.count * 11); - WFIFOW(fd,0) = 0xc6; - - c = 0; - for( i = 0; i < nd->u.shop.count; i++ ) - { - struct item_data* id = itemdb_exists(nd->u.shop.shop_item[i].nameid); - int val = nd->u.shop.shop_item[i].value; - if( id == NULL ) - continue; - WFIFOL(fd, 4+c*11) = val; - WFIFOL(fd, 8+c*11) = pc_modifybuyvalue(sd,val); - WFIFOB(fd,12+c*11) = itemtype(id->type); - WFIFOW(fd,13+c*11) = ( id->view_id > 0 ) ? id->view_id : id->nameid; - c++; - } - - WFIFOW(fd,2) = 4 + c*11; - WFIFOSET(fd,WFIFOW(fd,2)); -} - - -/// Presents list of items, that can be sold to an NPC shop (ZC_PC_SELL_ITEMLIST). -/// 00c7 .W { .W .L .L }* -void clif_selllist(struct map_session_data *sd) -{ - int fd,i,c=0,val; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd, MAX_INVENTORY * 10 + 4); - WFIFOW(fd,0)=0xc7; - for( i = 0; i < MAX_INVENTORY; i++ ) - { - if( sd->status.inventory[i].nameid > 0 && sd->inventory_data[i] ) - { - if( !itemdb_cansell(&sd->status.inventory[i], pc_get_group_level(sd)) ) - continue; - - if( sd->status.inventory[i].expire_time ) - continue; // Cannot Sell Rental Items - - val=sd->inventory_data[i]->value_sell; - if( val < 0 ) - continue; - WFIFOW(fd,4+c*10)=i+2; - WFIFOL(fd,6+c*10)=val; - WFIFOL(fd,10+c*10)=pc_modifysellvalue(sd,val); - c++; - } - } - WFIFOW(fd,2)=c*10+4; - WFIFOSET(fd,WFIFOW(fd,2)); -} - - -/// Displays an NPC dialog message (ZC_SAY_DIALOG). -/// 00b4 .W .L .?B -/// Client behavior (dialog window): -/// - disable mouse targeting -/// - open the dialog window -/// - set npcid of dialog window (0 by default) -/// - if set to clear on next mes, clear contents -/// - append this text -void clif_scriptmes(struct map_session_data *sd, int npcid, const char *mes) -{ - int fd = sd->fd; - int slen = strlen(mes) + 9; - - WFIFOHEAD(fd, slen); - WFIFOW(fd,0)=0xb4; - WFIFOW(fd,2)=slen; - WFIFOL(fd,4)=npcid; - memcpy((char*)WFIFOP(fd,8), mes, slen-8); - WFIFOSET(fd,WFIFOW(fd,2)); -} - - -/// Adds a 'next' button to an NPC dialog (ZC_WAIT_DIALOG). -/// 00b5 .L -/// Client behavior (dialog window): -/// - disable mouse targeting -/// - open the dialog window -/// - add 'next' button -/// When 'next' is pressed: -/// - 00B9 .L -/// - set to clear on next mes -/// - remove 'next' button -void clif_scriptnext(struct map_session_data *sd,int npcid) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd, packet_len(0xb5)); - WFIFOW(fd,0)=0xb5; - WFIFOL(fd,2)=npcid; - WFIFOSET(fd,packet_len(0xb5)); -} - - -/// Adds a 'close' button to an NPC dialog (ZC_CLOSE_DIALOG). -/// 00b6 .L -/// Client behavior: -/// - if dialog window is open: -/// - remove 'next' button -/// - add 'close' button -/// - else: -/// - enable mouse targeting -/// - close the dialog window -/// - close the menu window -/// When 'close' is pressed: -/// - enable mouse targeting -/// - close the dialog window -/// - close the menu window -/// - 0146 .L -void clif_scriptclose(struct map_session_data *sd, int npcid) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd, packet_len(0xb6)); - WFIFOW(fd,0)=0xb6; - WFIFOL(fd,2)=npcid; - WFIFOSET(fd,packet_len(0xb6)); -} - -/*========================================== - * - *------------------------------------------*/ -void clif_sendfakenpc(struct map_session_data *sd, int npcid) -{ - unsigned char *buf; - int fd = sd->fd; - sd->state.using_fake_npc = 1; - - WFIFOHEAD(fd, packet_len(0x78)); - buf = WFIFOP(fd,0); - memset(WBUFP(buf,0), 0, packet_len(0x78)); - WBUFW(buf,0)=0x78; -#if PACKETVER >= 20071106 - WBUFB(buf,2) = 0; // object type - buf = WFIFOP(fd,1); -#endif - WBUFL(buf,2)=npcid; - WBUFW(buf,14)=111; - WBUFPOS(buf,46,sd->bl.x,sd->bl.y,sd->ud.dir); - WBUFB(buf,49)=5; - WBUFB(buf,50)=5; - WFIFOSET(fd, packet_len(0x78)); -} - - -/// Displays an NPC dialog menu (ZC_MENU_LIST). -/// 00b7 .W .L .?B -/// Client behavior: -/// - disable mouse targeting -/// - close the menu window -/// - open the menu window -/// - add options to the menu (separated in the text by ":") -/// - set npcid of menu window -/// - if dialog window is open: -/// - remove 'next' button -/// When 'ok' is pressed: -/// - 00B8 .L .B -/// - close the menu window -/// When 'cancel' is pressed: -/// - 00B8 .L <-1>.B -/// - enable mouse targeting -/// - close a bunch of windows... -/// WARNING: the 'cancel' button closes other windows besides the dialog window and the menu window. -/// Which suggests their have intertwined behavior. (probably the mouse targeting) -/// TODO investigate behavior of other windows [FlavioJS] -void clif_scriptmenu(struct map_session_data* sd, int npcid, const char* mes) -{ - int fd = sd->fd; - int slen = strlen(mes) + 9; - struct block_list *bl = NULL; - - if (!sd->state.using_fake_npc && (npcid == fake_nd->bl.id || ((bl = map_id2bl(npcid)) && (bl->m!=sd->bl.m || - bl->xbl.x-AREA_SIZE-1 || bl->x>sd->bl.x+AREA_SIZE+1 || - bl->ybl.y-AREA_SIZE-1 || bl->y>sd->bl.y+AREA_SIZE+1)))) - clif_sendfakenpc(sd, npcid); - - WFIFOHEAD(fd, slen); - WFIFOW(fd,0)=0xb7; - WFIFOW(fd,2)=slen; - WFIFOL(fd,4)=npcid; - memcpy((char*)WFIFOP(fd,8), mes, slen-8); - WFIFOSET(fd,WFIFOW(fd,2)); -} - - -/// Displays an NPC dialog input box for numbers (ZC_OPEN_EDITDLG). -/// 0142 .L -/// Client behavior (inputnum window): -/// - if npcid exists in the client: -/// - open the inputnum window -/// - set npcid of inputnum window -/// When 'ok' is pressed: -/// - if inputnum window has text: -/// - if npcid exists in the client: -/// - 0143 .L .L -/// - close inputnum window -void clif_scriptinput(struct map_session_data *sd, int npcid) -{ - int fd; - struct block_list *bl = NULL; - - nullpo_retv(sd); - - if (!sd->state.using_fake_npc && (npcid == fake_nd->bl.id || ((bl = map_id2bl(npcid)) && (bl->m!=sd->bl.m || - bl->xbl.x-AREA_SIZE-1 || bl->x>sd->bl.x+AREA_SIZE+1 || - bl->ybl.y-AREA_SIZE-1 || bl->y>sd->bl.y+AREA_SIZE+1)))) - clif_sendfakenpc(sd, npcid); - - fd=sd->fd; - WFIFOHEAD(fd, packet_len(0x142)); - WFIFOW(fd,0)=0x142; - WFIFOL(fd,2)=npcid; - WFIFOSET(fd,packet_len(0x142)); -} - - -/// Displays an NPC dialog input box for numbers (ZC_OPEN_EDITDLGSTR). -/// 01d4 .L -/// Client behavior (inputstr window): -/// - if npcid is 0 or npcid exists in the client: -/// - open the inputstr window -/// - set npcid of inputstr window -/// When 'ok' is pressed: -/// - if inputstr window has text and isn't an insult(manner.txt): -/// - if npcid is 0 or npcid exists in the client: -/// - 01d5 .W .L .?B -/// - close inputstr window -void clif_scriptinputstr(struct map_session_data *sd, int npcid) -{ - int fd; - struct block_list *bl = NULL; - - nullpo_retv(sd); - - if (!sd->state.using_fake_npc && (npcid == fake_nd->bl.id || ((bl = map_id2bl(npcid)) && (bl->m!=sd->bl.m || - bl->xbl.x-AREA_SIZE-1 || bl->x>sd->bl.x+AREA_SIZE+1 || - bl->ybl.y-AREA_SIZE-1 || bl->y>sd->bl.y+AREA_SIZE+1)))) - clif_sendfakenpc(sd, npcid); - - fd=sd->fd; - WFIFOHEAD(fd, packet_len(0x1d4)); - WFIFOW(fd,0)=0x1d4; - WFIFOL(fd,2)=npcid; - WFIFOSET(fd,packet_len(0x1d4)); -} - - -/// Marks a position on client's minimap (ZC_COMPASS). -/// 0144 .L .L .L .L .B .L -/// npc id: -/// is ignored in the client -/// type: -/// 0 = display mark for 15 seconds -/// 1 = display mark until dead or teleported -/// 2 = remove mark -/// color: -/// 0x00RRGGBB -void clif_viewpoint(struct map_session_data *sd, int npc_id, int type, int x, int y, int id, int color) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd, packet_len(0x144)); - 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(0x144)); -} - - -/// Displays an illustration image. -/// 0145 .16B .B (ZC_SHOW_IMAGE) -/// 01b3 .64B .B (ZC_SHOW_IMAGE2) -/// type: -/// 0 = bottom left corner -/// 1 = bottom middle -/// 2 = bottom right corner -/// 3 = middle of screen, inside a movable window -/// 4 = middle of screen, movable with a close button, chrome-less -void clif_cutin(struct map_session_data* sd, const char* image, int type) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd, packet_len(0x1b3)); - WFIFOW(fd,0)=0x1b3; - strncpy((char*)WFIFOP(fd,2),image,64); - WFIFOB(fd,66)=type; - WFIFOSET(fd,packet_len(0x1b3)); -} - - -/*========================================== - * Fills in card data from the given item and into the buffer. [Skotlex] - *------------------------------------------*/ -static void clif_addcards(unsigned char* buf, struct item* item) -{ - int i=0,j; - if( item == NULL ) { //Blank data - WBUFW(buf,0) = 0; - WBUFW(buf,2) = 0; - WBUFW(buf,4) = 0; - WBUFW(buf,6) = 0; - return; - } - if( item->card[0] == CARD0_PET ) { //pet eggs - WBUFW(buf,0) = 0; - WBUFW(buf,2) = 0; - WBUFW(buf,4) = 0; - WBUFW(buf,6) = item->card[3]; //Pet renamed flag. - return; - } - if( item->card[0] == CARD0_FORGE || item->card[0] == CARD0_CREATE ) { //Forged/created items - WBUFW(buf,0) = item->card[0]; - WBUFW(buf,2) = item->card[1]; - WBUFW(buf,4) = item->card[2]; - WBUFW(buf,6) = item->card[3]; - return; - } - //Client only receives four cards.. so randomly send them a set of cards. [Skotlex] - if( MAX_SLOTS > 4 && (j = itemdb_slot(item->nameid)) > 4 ) - i = rnd()%(j-3); //eg: 6 slots, possible i values: 0->3, 1->4, 2->5 => i = rnd()%3; - - //Normal items. - if( item->card[i] > 0 && (j=itemdb_viewid(item->card[i])) > 0 ) - WBUFW(buf,0) = j; - else - WBUFW(buf,0) = item->card[i]; - - if( item->card[++i] > 0 && (j=itemdb_viewid(item->card[i])) > 0 ) - WBUFW(buf,2) = j; - else - WBUFW(buf,2) = item->card[i]; - - if( item->card[++i] > 0 && (j=itemdb_viewid(item->card[i])) > 0 ) - WBUFW(buf,4) = j; - else - WBUFW(buf,4) = item->card[i]; - - if( item->card[++i] > 0 && (j=itemdb_viewid(item->card[i])) > 0 ) - WBUFW(buf,6) = j; - else - WBUFW(buf,6) = item->card[i]; -} - - -/// Notifies the client, about a received inventory item or the result of a pick-up request. -/// 00a0 .W .W .W .B .B .B .W .W .W .W .W .B .B (ZC_ITEM_PICKUP_ACK) -/// 029a .W .W .W .B .B .B .W .W .W .W .W .B .B .L (ZC_ITEM_PICKUP_ACK2) -/// 02d4 .W .W .W .B .B .B .W .W .W .W .W .B .B .L .W (ZC_ITEM_PICKUP_ACK3) -void clif_additem(struct map_session_data *sd, int n, int amount, int fail) -{ - int fd; -#if PACKETVER < 20061218 - const int cmd = 0xa0; -#elif PACKETVER < 20071002 - const int cmd = 0x29a; -#else - const int cmd = 0x2d4; -#endif - nullpo_retv(sd); - - fd = sd->fd; - if( !session_isActive(fd) ) //Sasuke- - return; - - WFIFOHEAD(fd,packet_len(cmd)); - if( fail ) - { - WFIFOW(fd,0)=cmd; - WFIFOW(fd,2)=n+2; - WFIFOW(fd,4)=amount; - WFIFOW(fd,6)=0; - WFIFOB(fd,8)=0; - WFIFOB(fd,9)=0; - WFIFOB(fd,10)=0; - WFIFOW(fd,11)=0; - WFIFOW(fd,13)=0; - WFIFOW(fd,15)=0; - WFIFOW(fd,17)=0; - WFIFOW(fd,19)=0; - WFIFOB(fd,21)=0; - WFIFOB(fd,22)=fail; -#if PACKETVER >= 20061218 - WFIFOL(fd,23)=0; -#endif -#if PACKETVER >= 20071002 - WFIFOW(fd,27)=0; // unknown -#endif - } - else - { - if( n < 0 || n >= MAX_INVENTORY || sd->status.inventory[n].nameid <=0 || sd->inventory_data[n] == NULL ) - return; - - WFIFOW(fd,0)=cmd; - WFIFOW(fd,2)=n+2; - WFIFOW(fd,4)=amount; - if (sd->inventory_data[n]->view_id > 0) - WFIFOW(fd,6)=sd->inventory_data[n]->view_id; - else - WFIFOW(fd,6)=sd->status.inventory[n].nameid; - WFIFOB(fd,8)=sd->status.inventory[n].identify; - WFIFOB(fd,9)=sd->status.inventory[n].attribute; - WFIFOB(fd,10)=sd->status.inventory[n].refine; - clif_addcards(WFIFOP(fd,11), &sd->status.inventory[n]); - WFIFOW(fd,19)=pc_equippoint(sd,n); - WFIFOB(fd,21)=itemtype(sd->inventory_data[n]->type); - WFIFOB(fd,22)=fail; -#if PACKETVER >= 20061218 - WFIFOL(fd,23)=sd->status.inventory[n].expire_time; -#endif -#if PACKETVER >= 20071002 - WFIFOW(fd,27)=0; // unknown -#endif - } - - WFIFOSET(fd,packet_len(cmd)); -} - - -/// Notifies the client, that an inventory item was deleted or dropped (ZC_ITEM_THROW_ACK). -/// 00af .W .W -void clif_dropitem(struct map_session_data *sd,int n,int amount) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd, packet_len(0xaf)); - WFIFOW(fd,0)=0xaf; - WFIFOW(fd,2)=n+2; - WFIFOW(fd,4)=amount; - WFIFOSET(fd,packet_len(0xaf)); -} - - -/// Notifies the client, that an inventory item was deleted (ZC_DELETE_ITEM_FROM_BODY). -/// 07fa .W .W .W -/// delete type: -/// 0 = Normal -/// 1 = Item used for a skill -/// 2 = Refine failed -/// 3 = Material changed -/// 4 = Moved to storage -/// 5 = Moved to cart -/// 6 = Item sold -/// 7 = Consumed by Four Spirit Analysis (SO_EL_ANALYSIS) skill -void clif_delitem(struct map_session_data *sd,int n,int amount, short reason) -{ -#if PACKETVER < 20091117 - clif_dropitem(sd,n,amount); -#else - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - - WFIFOHEAD(fd, packet_len(0x7fa)); - WFIFOW(fd,0)=0x7fa; - WFIFOW(fd,2)=reason; - WFIFOW(fd,4)=n+2; - WFIFOW(fd,6)=amount; - WFIFOSET(fd,packet_len(0x7fa)); -#endif -} - - -// Simplifies inventory/cart/storage packets by handling the packet section relevant to items. [Skotlex] -// Equip is >= 0 for equippable items (holds the equip-point, is 0 for pet -// armor/egg) -1 for stackable items, -2 for stackable items where arrows must send in the equip-point. -void clif_item_sub(unsigned char *buf, int n, struct item *i, struct item_data *id, int equip) -{ - if (id->view_id > 0) - WBUFW(buf,n)=id->view_id; - else - WBUFW(buf,n)=i->nameid; - WBUFB(buf,n+2)=itemtype(id->type); - WBUFB(buf,n+3)=i->identify; - if (equip >= 0) { //Equippable item - WBUFW(buf,n+4)=equip; - WBUFW(buf,n+6)=i->equip; - WBUFB(buf,n+8)=i->attribute; - WBUFB(buf,n+9)=i->refine; - } else { //Stackable item. - WBUFW(buf,n+4)=i->amount; - if (equip == -2 && id->equip == EQP_AMMO) - WBUFW(buf,n+6)=EQP_AMMO; - else - WBUFW(buf,n+6)=0; - } - -} -void clif_favorite_item(struct map_session_data* sd, unsigned short index); -//Unified inventory function which sends all of the inventory (requires two packets, one for equipable items and one for stackable ones. [Skotlex] -void clif_inventorylist(struct map_session_data *sd) -{ - int i,n,ne,arrow=-1; - unsigned char *buf; - unsigned char *bufe; - -#if PACKETVER < 5 - const int s = 10; //Entry size. -#elif PACKETVER < 20080102 - const int s = 18; -#else - const int s = 22; -#endif -#if PACKETVER < 20071002 - const int se = 20; -#elif PACKETVER < 20100629 - const int se = 26; -#else - const int se = 28; -#endif - - buf = (unsigned char*)aMalloc(MAX_INVENTORY * s + 4); - bufe = (unsigned char*)aMalloc(MAX_INVENTORY * se + 4); - - for( i = 0, n = 0, ne = 0; i < MAX_INVENTORY; i++ ) - { - if( sd->status.inventory[i].nameid <=0 || sd->inventory_data[i] == NULL ) - continue; - - if( !itemdb_isstackable2(sd->inventory_data[i]) ) - { //Non-stackable (Equippable) - WBUFW(bufe,ne*se+4)=i+2; - clif_item_sub(bufe, ne*se+6, &sd->status.inventory[i], sd->inventory_data[i], pc_equippoint(sd,i)); - clif_addcards(WBUFP(bufe, ne*se+16), &sd->status.inventory[i]); -#if PACKETVER >= 20071002 - WBUFL(bufe,ne*se+24)=sd->status.inventory[i].expire_time; - WBUFW(bufe,ne*se+28)=0; //Unknown -#endif -#if PACKETVER >= 20100629 - if (sd->inventory_data[i]->equip&EQP_VISIBLE) - WBUFW(bufe,ne*se+30)= sd->inventory_data[i]->look; - else - WBUFW(bufe,ne*se+30)=0; -#endif - ne++; - } - else - { //Stackable. - WBUFW(buf,n*s+4)=i+2; - clif_item_sub(buf, n*s+6, &sd->status.inventory[i], sd->inventory_data[i], -2); - if( sd->inventory_data[i]->equip == EQP_AMMO && sd->status.inventory[i].equip ) - arrow=i; -#if PACKETVER >= 5 - clif_addcards(WBUFP(buf, n*s+14), &sd->status.inventory[i]); -#endif -#if PACKETVER >= 20080102 - WBUFL(buf,n*s+22)=sd->status.inventory[i].expire_time; -#endif - n++; - } - } - if( n ) - { -#if PACKETVER < 5 - WBUFW(buf,0)=0xa3; -#elif PACKETVER < 20080102 - WBUFW(buf,0)=0x1ee; -#else - WBUFW(buf,0)=0x2e8; -#endif - WBUFW(buf,2)=4+n*s; - clif_send(buf, WBUFW(buf,2), &sd->bl, SELF); - } - if( arrow >= 0 ) - clif_arrowequip(sd,arrow); - - if( ne ) - { -#if PACKETVER < 20071002 - WBUFW(bufe,0)=0xa4; -#else - WBUFW(bufe,0)=0x2d0; -#endif - WBUFW(bufe,2)=4+ne*se; - clif_send(bufe, WBUFW(bufe,2), &sd->bl, SELF); - } -#if PACKETVER >= 20111122 - for( i = 0; i < MAX_INVENTORY; i++ ) { - if( sd->status.inventory[i].nameid <= 0 || sd->inventory_data[i] == NULL ) - continue; - - if ( sd->status.inventory[i].favorite ) - clif_favorite_item(sd, i); - } -#endif - - if( buf ) aFree(buf); - if( bufe ) aFree(bufe); -} - -//Required when items break/get-repaired. Only sends equippable item list. -void clif_equiplist(struct map_session_data *sd) -{ - int i,n,fd = sd->fd; - unsigned char *buf; -#if PACKETVER < 20071002 - const int cmd = 20; -#elif PACKETVER < 20100629 - const int cmd = 26; -#else - const int cmd = 28; -#endif - - WFIFOHEAD(fd, MAX_INVENTORY * cmd + 4); - buf = WFIFOP(fd,0); - - for(i=0,n=0;istatus.inventory[i].nameid <=0 || sd->inventory_data[i] == NULL) - continue; - - if(itemdb_isstackable2(sd->inventory_data[i])) - continue; - //Equippable - WBUFW(buf,n*cmd+4)=i+2; - clif_item_sub(buf, n*cmd+6, &sd->status.inventory[i], sd->inventory_data[i], pc_equippoint(sd,i)); - clif_addcards(WBUFP(buf, n*cmd+16), &sd->status.inventory[i]); -#if PACKETVER >= 20071002 - WBUFL(buf,n*cmd+24)=sd->status.inventory[i].expire_time; - WBUFW(buf,n*cmd+28)=0; //Unknown -#endif -#if PACKETVER >= 20100629 - if (sd->inventory_data[i]->equip&EQP_VISIBLE) - WBUFW(buf,n*cmd+30)= sd->inventory_data[i]->look; - else - WBUFW(buf,n*cmd+30)=0; -#endif - n++; - } - if (n) { -#if PACKETVER < 20071002 - WBUFW(buf,0)=0xa4; -#else - WBUFW(buf,0)=0x2d0; -#endif - WBUFW(buf,2)=4+n*cmd; - WFIFOSET(fd,WFIFOW(fd,2)); - } -} - -void clif_storagelist(struct map_session_data* sd, struct item* items, int items_length) -{ - struct item_data *id; - int i,n,ne; - unsigned char *buf; - unsigned char *bufe; -#if PACKETVER < 5 - const int s = 10; //Entry size. -#elif PACKETVER < 20080102 - const int s = 18; -#else - const int s = 22; -#endif -#if PACKETVER < 20071002 - const int cmd = 20; -#elif PACKETVER < 20100629 - const int cmd = 26; -#else - const int cmd = 28; -#endif - - buf = (unsigned char*)aMalloc(items_length * s + 4); - bufe = (unsigned char*)aMalloc(items_length * cmd + 4); - - for( i = 0, n = 0, ne = 0; i < items_length; i++ ) - { - if( items[i].nameid <= 0 ) - continue; - id = itemdb_search(items[i].nameid); - if( !itemdb_isstackable2(id) ) - { //Equippable - WBUFW(bufe,ne*cmd+4)=i+1; - clif_item_sub(bufe, ne*cmd+6, &items[i], id, id->equip); - clif_addcards(WBUFP(bufe, ne*cmd+16), &items[i]); -#if PACKETVER >= 20071002 - WBUFL(bufe,ne*cmd+24)=items[i].expire_time; - WBUFW(bufe,ne*cmd+28)=0; //Unknown -#endif - ne++; - } - else - { //Stackable - WBUFW(buf,n*s+4)=i+1; - clif_item_sub(buf, n*s+6, &items[i], id,-1); -#if PACKETVER >= 5 - clif_addcards(WBUFP(buf,n*s+14), &items[i]); -#endif -#if PACKETVER >= 20080102 - WBUFL(buf,n*s+22)=items[i].expire_time; -#endif - n++; - } - } - if( n ) - { -#if PACKETVER < 5 - WBUFW(buf,0)=0xa5; -#elif PACKETVER < 20080102 - WBUFW(buf,0)=0x1f0; -#else - WBUFW(buf,0)=0x2ea; -#endif - WBUFW(buf,2)=4+n*s; - clif_send(buf, WBUFW(buf,2), &sd->bl, SELF); - } - if( ne ) - { -#if PACKETVER < 20071002 - WBUFW(bufe,0)=0xa6; -#else - WBUFW(bufe,0)=0x2d1; -#endif - WBUFW(bufe,2)=4+ne*cmd; - clif_send(bufe, WBUFW(bufe,2), &sd->bl, SELF); - } - - if( buf ) aFree(buf); - if( bufe ) aFree(bufe); -} - -void clif_cartlist(struct map_session_data *sd) -{ - struct item_data *id; - int i,n,ne; - unsigned char *buf; - unsigned char *bufe; -#if PACKETVER < 5 - const int s = 10; //Entry size. -#elif PACKETVER < 20080102 - const int s = 18; -#else - const int s = 22; -#endif -#if PACKETVER < 20071002 - const int cmd = 20; -#elif PACKETVER < 20100629 - const int cmd = 26; -#else - const int cmd = 28; -#endif - - buf = (unsigned char*)aMalloc(MAX_CART * s + 4); - bufe = (unsigned char*)aMalloc(MAX_CART * cmd + 4); - - for( i = 0, n = 0, ne = 0; i < MAX_CART; i++ ) - { - if( sd->status.cart[i].nameid <= 0 ) - continue; - id = itemdb_search(sd->status.cart[i].nameid); - if( !itemdb_isstackable2(id) ) - { //Equippable - WBUFW(bufe,ne*cmd+4)=i+2; - clif_item_sub(bufe, ne*cmd+6, &sd->status.cart[i], id, id->equip); - clif_addcards(WBUFP(bufe, ne*cmd+16), &sd->status.cart[i]); -#if PACKETVER >= 20071002 - WBUFL(bufe,ne*cmd+24)=sd->status.cart[i].expire_time; - WBUFW(bufe,ne*cmd+28)=0; //Unknown -#endif - ne++; - } - else - { //Stackable - WBUFW(buf,n*s+4)=i+2; - clif_item_sub(buf, n*s+6, &sd->status.cart[i], id,-1); -#if PACKETVER >= 5 - clif_addcards(WBUFP(buf,n*s+14), &sd->status.cart[i]); -#endif -#if PACKETVER >= 20080102 - WBUFL(buf,n*s+22)=sd->status.cart[i].expire_time; -#endif - n++; - } - } - if( n ) - { -#if PACKETVER < 5 - WBUFW(buf,0)=0x123; -#elif PACKETVER < 20080102 - WBUFW(buf,0)=0x1ef; -#else - WBUFW(buf,0)=0x2e9; -#endif - WBUFW(buf,2)=4+n*s; - clif_send(buf, WBUFW(buf,2), &sd->bl, SELF); - } - if( ne ) - { -#if PACKETVER < 20071002 - WBUFW(bufe,0)=0x122; -#else - WBUFW(bufe,0)=0x2d2; -#endif - WBUFW(bufe,2)=4+ne*cmd; - clif_send(bufe, WBUFW(bufe,2), &sd->bl, SELF); - } - - if( buf ) aFree(buf); - if( bufe ) aFree(bufe); -} - - -/// Removes cart (ZC_CARTOFF). -/// 012b -/// Client behaviour: -/// Closes the cart storage and removes all it's items from memory. -/// The Num & Weight values of the cart are left untouched and the cart is NOT removed. -void clif_clearcart(int fd) -{ - WFIFOHEAD(fd, packet_len(0x12b)); - WFIFOW(fd,0) = 0x12b; - WFIFOSET(fd, packet_len(0x12b)); - -} - - -/// Guild XY locators (ZC_NOTIFY_POSITION_TO_GUILDM) [Valaris] -/// 01eb .L .W .W -void clif_guild_xy(struct map_session_data *sd) -{ - unsigned char buf[10]; - - nullpo_retv(sd); - - WBUFW(buf,0)=0x1eb; - WBUFL(buf,2)=sd->status.account_id; - WBUFW(buf,6)=sd->bl.x; - WBUFW(buf,8)=sd->bl.y; - clif_send(buf,packet_len(0x1eb),&sd->bl,GUILD_SAMEMAP_WOS); -} - -/*========================================== - * Sends x/y dot to a single fd. [Skotlex] - *------------------------------------------*/ -void clif_guild_xy_single(int fd, struct map_session_data *sd) -{ - if( sd->bg_id ) - return; - - WFIFOHEAD(fd,packet_len(0x1eb)); - WFIFOW(fd,0)=0x1eb; - WFIFOL(fd,2)=sd->status.account_id; - WFIFOW(fd,6)=sd->bl.x; - WFIFOW(fd,8)=sd->bl.y; - WFIFOSET(fd,packet_len(0x1eb)); -} - -// Guild XY locators [Valaris] -void clif_guild_xy_remove(struct map_session_data *sd) -{ - unsigned char buf[10]; - - nullpo_retv(sd); - - WBUFW(buf,0)=0x1eb; - WBUFL(buf,2)=sd->status.account_id; - WBUFW(buf,6)=-1; - WBUFW(buf,8)=-1; - clif_send(buf,packet_len(0x1eb),&sd->bl,GUILD_SAMEMAP_WOS); -} - -/*========================================== - * - *------------------------------------------*/ -static int clif_hpmeter_sub(struct block_list *bl, va_list ap) -{ - struct map_session_data *sd, *tsd; -#if PACKETVER < 20100126 - const int cmd = 0x106; -#else - const int cmd = 0x80e; -#endif - - sd = va_arg(ap, struct map_session_data *); - tsd = (TBL_PC *)bl; - - nullpo_ret(sd); - nullpo_ret(tsd); - - if( !tsd->fd || tsd == sd ) - return 0; - - if( !pc_has_permission(tsd, PC_PERM_VIEW_HPMETER) ) - return 0; - WFIFOHEAD(tsd->fd,packet_len(cmd)); - WFIFOW(tsd->fd,0) = cmd; - WFIFOL(tsd->fd,2) = sd->status.account_id; -#if PACKETVER < 20100126 - if( sd->battle_status.max_hp > INT16_MAX ) - { //To correctly display the %hp bar. [Skotlex] - WFIFOW(tsd->fd,6) = sd->battle_status.hp/(sd->battle_status.max_hp/100); - WFIFOW(tsd->fd,8) = 100; - } else { - WFIFOW(tsd->fd,6) = sd->battle_status.hp; - WFIFOW(tsd->fd,8) = sd->battle_status.max_hp; - } -#else - WFIFOL(tsd->fd,6) = sd->battle_status.hp; - WFIFOL(tsd->fd,10) = sd->battle_status.max_hp; -#endif - WFIFOSET(tsd->fd,packet_len(cmd)); - return 0; -} - -/*========================================== - * Server tells all players that are allowed to view HP bars - * and are nearby 'sd' that 'sd' hp bar was updated. - *------------------------------------------*/ -static int clif_hpmeter(struct map_session_data *sd) -{ - nullpo_ret(sd); - map_foreachinarea(clif_hpmeter_sub, sd->bl.m, sd->bl.x-AREA_SIZE, sd->bl.y-AREA_SIZE, sd->bl.x+AREA_SIZE, sd->bl.y+AREA_SIZE, BL_PC, sd); - return 0; -} - -/// Notifies client of a character parameter change. -/// 00b0 .W .L (ZC_PAR_CHANGE) -/// 00b1 .W .L (ZC_LONGPAR_CHANGE) -/// 00be .W .B (ZC_STATUS_CHANGE) -/// 0121 .W .W .L .L (ZC_NOTIFY_CARTITEM_COUNTINFO) -/// 013a .W (ZC_ATTACK_RANGE) -/// 0141 .L .L .L (ZC_COUPLESTATUS) -/// TODO: Extract individual packets. -/// FIXME: Packet lengths from packet_len(cmd) -void clif_updatestatus(struct map_session_data *sd,int type) -{ - int fd,len=8; - - nullpo_retv(sd); - - fd=sd->fd; - - if ( !session_isActive(fd) ) // Invalid pointer fix, by sasuke [Kevin] - return; - - WFIFOHEAD(fd, 14); - WFIFOW(fd,0)=0xb0; - WFIFOW(fd,2)=type; - switch(type){ - // 00b0 - case SP_WEIGHT: - pc_updateweightstatus(sd); - WFIFOHEAD(fd,14); - WFIFOW(fd,0)=0xb0; //Need to re-set as pc_updateweightstatus can alter the buffer. [Skotlex] - 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->battle_status.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_KARMA: // Adding this back, I wonder if the client intercepts this - [Lance] - WFIFOL(fd,4)=sd->status.karma; - break; - case SP_MANNER: - WFIFOL(fd,4)=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->battle_status.hit; - break; - case SP_FLEE1: - WFIFOL(fd,4)=sd->battle_status.flee; - break; - case SP_FLEE2: - WFIFOL(fd,4)=sd->battle_status.flee2/10; - break; - case SP_MAXHP: - WFIFOL(fd,4)=sd->battle_status.max_hp; - break; - case SP_MAXSP: - WFIFOL(fd,4)=sd->battle_status.max_sp; - break; - case SP_HP: - WFIFOL(fd,4)=sd->battle_status.hp; - // TODO: Won't these overwrite the current packet? - clif_hpmeter(sd); - if( !battle_config.party_hp_mode && sd->status.party_id ) - clif_party_hp(sd); - if( sd->bg_id ) - clif_bg_hp(sd); - break; - case SP_SP: - WFIFOL(fd,4)=sd->battle_status.sp; - break; - case SP_ASPD: - WFIFOL(fd,4)=sd->battle_status.amotion; - break; - case SP_ATK1: - WFIFOL(fd,4)=pc_leftside_atk(sd); - break; - case SP_DEF1: - WFIFOL(fd,4)=pc_leftside_def(sd); - break; - case SP_MDEF1: - WFIFOL(fd,4)=pc_leftside_mdef(sd); - break; - case SP_ATK2: - WFIFOL(fd,4)=pc_rightside_atk(sd); - break; - case SP_DEF2: - WFIFOL(fd,4)=pc_rightside_def(sd); - break; - case SP_MDEF2: { - //negative check (in case you have something like Berserk active) - int mdef2 = pc_rightside_mdef(sd); - - WFIFOL(fd,4)= -#ifndef RENEWAL - ( mdef2 < 0 ) ? 0 : -#endif - mdef2; - - } - break; - case SP_CRITICAL: - WFIFOL(fd,4)=sd->battle_status.cri/10; - break; - case SP_MATK1: - WFIFOL(fd,4)=pc_rightside_matk(sd); - break; - case SP_MATK2: - WFIFOL(fd,4)=pc_leftside_matk(sd); - break; - - - case SP_ZENY: - WFIFOW(fd,0)=0xb1; - 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; - - /** - * SP_U are used to update the amount of points necessary to increase that stat - **/ - 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,1); - len=5; - break; - - /** - * Tells the client how far it is allowed to attack (weapon range) - **/ - case SP_ATTACKRANGE: - WFIFOW(fd,0)=0x13a; - WFIFOW(fd,2)=sd->battle_status.rhw.range; - len=4; - break; - - case SP_STR: - WFIFOW(fd,0)=0x141; - WFIFOL(fd,2)=type; - WFIFOL(fd,6)=sd->status.str; - WFIFOL(fd,10)=sd->battle_status.str - sd->status.str; - len=14; - break; - case SP_AGI: - WFIFOW(fd,0)=0x141; - WFIFOL(fd,2)=type; - WFIFOL(fd,6)=sd->status.agi; - WFIFOL(fd,10)=sd->battle_status.agi - sd->status.agi; - len=14; - break; - case SP_VIT: - WFIFOW(fd,0)=0x141; - WFIFOL(fd,2)=type; - WFIFOL(fd,6)=sd->status.vit; - WFIFOL(fd,10)=sd->battle_status.vit - sd->status.vit; - len=14; - break; - case SP_INT: - WFIFOW(fd,0)=0x141; - WFIFOL(fd,2)=type; - WFIFOL(fd,6)=sd->status.int_; - WFIFOL(fd,10)=sd->battle_status.int_ - sd->status.int_; - len=14; - break; - case SP_DEX: - WFIFOW(fd,0)=0x141; - WFIFOL(fd,2)=type; - WFIFOL(fd,6)=sd->status.dex; - WFIFOL(fd,10)=sd->battle_status.dex - sd->status.dex; - len=14; - break; - case SP_LUK: - WFIFOW(fd,0)=0x141; - WFIFOL(fd,2)=type; - WFIFOL(fd,6)=sd->status.luk; - WFIFOL(fd,10)=sd->battle_status.luk - sd->status.luk; - len=14; - break; - - case SP_CARTINFO: - WFIFOW(fd,0)=0x121; - WFIFOW(fd,2)=sd->cart_num; - WFIFOW(fd,4)=MAX_CART; - WFIFOL(fd,6)=sd->cart_weight; - WFIFOL(fd,10)=sd->cart_weight_max; - len=14; - break; - - default: - ShowError("clif_updatestatus : unrecognized type %d\n",type); - return; - } - WFIFOSET(fd,len); -} - - -/// Notifies client of a parameter change of an another player (ZC_PAR_CHANGE_USER). -/// 01ab .L .W .L -void clif_changestatus(struct map_session_data* sd,int type,int val) -{ - unsigned char buf[12]; - - nullpo_retv(sd); - - WBUFW(buf,0)=0x1ab; - WBUFL(buf,2)=sd->bl.id; - WBUFW(buf,6)=type; - - switch(type) - { - case SP_MANNER: - WBUFL(buf,8)=val; - break; - default: - ShowError("clif_changestatus : unrecognized type %d.\n",type); - return; - } - - clif_send(buf,packet_len(0x1ab),&sd->bl,AREA_WOS); -} - - -/// Updates sprite/style properties of an object. -/// 00c3 .L .B .B (ZC_SPRITE_CHANGE) -/// 01d7 .L .B .L (ZC_SPRITE_CHANGE2) -void clif_changelook(struct block_list *bl,int type,int val) -{ - unsigned char buf[16]; - struct map_session_data* sd = NULL; - struct status_change* sc; - struct view_data* vd; - enum send_target target = AREA; - nullpo_retv(bl); - - sd = BL_CAST(BL_PC, bl); - sc = status_get_sc(bl); - vd = status_get_viewdata(bl); - //nullpo_ret(vd); - if( vd ) //temp hack to let Warp Portal change appearance - switch(type) - { - case LOOK_WEAPON: - if (sd) - { - clif_get_weapon_view(sd, &vd->weapon, &vd->shield); - val = vd->weapon; - } - else vd->weapon = val; - break; - case LOOK_SHIELD: - if (sd) - { - clif_get_weapon_view(sd, &vd->weapon, &vd->shield); - val = vd->shield; - } - else vd->shield = val; - break; - case LOOK_BASE: - vd->class_ = val; - if (vd->class_ == JOB_WEDDING || vd->class_ == JOB_XMAS || vd->class_ == JOB_SUMMER) - vd->weapon = vd->shield = 0; - if (vd->cloth_color && ( - (vd->class_ == JOB_WEDDING && battle_config.wedding_ignorepalette) || - (vd->class_ == JOB_XMAS && battle_config.xmas_ignorepalette) || - (vd->class_ == JOB_SUMMER && battle_config.summer_ignorepalette) - )) - clif_changelook(bl,LOOK_CLOTHES_COLOR,0); - break; - case LOOK_HAIR: - vd->hair_style = val; - break; - case LOOK_HEAD_BOTTOM: - vd->head_bottom = val; - break; - case LOOK_HEAD_TOP: - vd->head_top = val; - break; - case LOOK_HEAD_MID: - vd->head_mid = val; - break; - case LOOK_HAIR_COLOR: - vd->hair_color = val; - break; - case LOOK_CLOTHES_COLOR: - if (val && ( - (vd->class_ == JOB_WEDDING && battle_config.wedding_ignorepalette) || - (vd->class_ == JOB_XMAS && battle_config.xmas_ignorepalette) || - (vd->class_ == JOB_SUMMER && battle_config.summer_ignorepalette) - )) - val = 0; - vd->cloth_color = val; - break; - case LOOK_SHOES: -#if PACKETVER > 3 - if (sd) { - int n; - if((n = sd->equip_index[2]) >= 0 && sd->inventory_data[n]) { - if(sd->inventory_data[n]->view_id > 0) - val = sd->inventory_data[n]->view_id; - else - val = sd->status.inventory[n].nameid; - } else - val = 0; - } -#endif - //Shoes? No packet uses this.... - break; - case LOOK_BODY: - case LOOK_FLOOR: - // unknown purpose - break; - case LOOK_ROBE: -#if PACKETVER < 20110111 - return; -#else - vd->robe = val; -#endif - break; - } - - // prevent leaking the presence of GM-hidden objects - if( sc && sc->option&OPTION_INVISIBLE ) - target = SELF; - -#if PACKETVER < 4 - WBUFW(buf,0)=0xc3; - WBUFL(buf,2)=bl->id; - WBUFB(buf,6)=type; - WBUFB(buf,7)=val; - clif_send(buf,packet_len(0xc3),bl,target); -#else - WBUFW(buf,0)=0x1d7; - WBUFL(buf,2)=bl->id; - if(type == LOOK_WEAPON || type == LOOK_SHIELD) { - WBUFB(buf,6)=LOOK_WEAPON; - WBUFW(buf,7)=vd->weapon; - WBUFW(buf,9)=vd->shield; - } else { - WBUFB(buf,6)=type; - WBUFL(buf,7)=val; - } - clif_send(buf,packet_len(0x1d7),bl,target); -#endif -} - -//Sends a change-base-look packet required for traps as they are triggered. -void clif_changetraplook(struct block_list *bl,int val) -{ - unsigned char buf[32]; -#if PACKETVER < 4 - WBUFW(buf,0)=0xc3; - WBUFL(buf,2)=bl->id; - WBUFB(buf,6)=LOOK_BASE; - WBUFB(buf,7)=val; - clif_send(buf,packet_len(0xc3),bl,AREA); -#else - WBUFW(buf,0)=0x1d7; - WBUFL(buf,2)=bl->id; - WBUFB(buf,6)=LOOK_BASE; - WBUFW(buf,7)=val; - WBUFW(buf,9)=0; - clif_send(buf,packet_len(0x1d7),bl,AREA); -#endif -} - -//For the stupid cloth-dye bug. Resends the given view data to the area specified by bl. -void clif_refreshlook(struct block_list *bl,int id,int type,int val,enum send_target target) -{ - unsigned char buf[32]; -#if PACKETVER < 4 - WBUFW(buf,0)=0xc3; - WBUFL(buf,2)=id; - WBUFB(buf,6)=type; - WBUFB(buf,7)=val; - clif_send(buf,packet_len(0xc3),bl,target); -#else - WBUFW(buf,0)=0x1d7; - WBUFL(buf,2)=id; - WBUFB(buf,6)=type; - WBUFW(buf,7)=val; - WBUFW(buf,9)=0; - clif_send(buf,packet_len(0x1d7),bl,target); -#endif -} - - -/// Character status (ZC_STATUS). -/// 00bd .W .B .B .B .B .B .B -/// .B .B .B .B .B .B .W .W -/// .W .W .W .W .W .W .W -/// .W .W .W .W .W -void clif_initialstatus(struct map_session_data *sd) -{ - int fd, mdef2; - unsigned char *buf; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0xbd)); - buf=WFIFOP(fd,0); - - WBUFW(buf,0)=0xbd; - WBUFW(buf,2)=min(sd->status.status_point, INT16_MAX); - WBUFB(buf,4)=min(sd->status.str, UINT8_MAX); - WBUFB(buf,5)=pc_need_status_point(sd,SP_STR,1); - WBUFB(buf,6)=min(sd->status.agi, UINT8_MAX); - WBUFB(buf,7)=pc_need_status_point(sd,SP_AGI,1); - WBUFB(buf,8)=min(sd->status.vit, UINT8_MAX); - WBUFB(buf,9)=pc_need_status_point(sd,SP_VIT,1); - WBUFB(buf,10)=min(sd->status.int_, UINT8_MAX); - WBUFB(buf,11)=pc_need_status_point(sd,SP_INT,1); - WBUFB(buf,12)=min(sd->status.dex, UINT8_MAX); - WBUFB(buf,13)=pc_need_status_point(sd,SP_DEX,1); - WBUFB(buf,14)=min(sd->status.luk, UINT8_MAX); - WBUFB(buf,15)=pc_need_status_point(sd,SP_LUK,1); - - WBUFW(buf,16) = pc_leftside_atk(sd); - WBUFW(buf,18) = pc_rightside_atk(sd); - WBUFW(buf,20) = pc_rightside_matk(sd); - WBUFW(buf,22) = pc_leftside_matk(sd); - WBUFW(buf,24) = pc_leftside_def(sd); - WBUFW(buf,26) = pc_rightside_def(sd); - WBUFW(buf,28) = pc_leftside_mdef(sd); - mdef2 = pc_rightside_mdef(sd); - WBUFW(buf,30) = -#ifndef RENEWAL - ( mdef2 < 0 ) ? 0 : //Negative check for Frenzy'ed characters. -#endif - mdef2; - WBUFW(buf,32) = sd->battle_status.hit; - WBUFW(buf,34) = sd->battle_status.flee; - WBUFW(buf,36) = sd->battle_status.flee2/10; - WBUFW(buf,38) = sd->battle_status.cri/10; - WBUFW(buf,40) = sd->battle_status.amotion; // aspd - WBUFW(buf,42) = 0; // always 0 (plusASPD) - - WFIFOSET(fd,packet_len(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); -} - - -/// Marks an ammunition item in inventory as equipped (ZC_EQUIP_ARROW). -/// 013c .W -void clif_arrowequip(struct map_session_data *sd,int val) -{ - int fd; - - nullpo_retv(sd); - - pc_stop_attack(sd); // [Valaris] - - fd=sd->fd; - WFIFOHEAD(fd, packet_len(0x013c)); - WFIFOW(fd,0)=0x013c; - WFIFOW(fd,2)=val+2; //Item ID of the arrow - WFIFOSET(fd,packet_len(0x013c)); -} - - -/// Ammunition action message (ZC_ACTION_FAILURE). -/// 013b .W -/// type: -/// 0 = MsgStringTable[242]="Please equip the proper ammunition first." -/// 1 = MsgStringTable[243]="You can't Attack or use Skills because your Weight Limit has been exceeded." -/// 2 = MsgStringTable[244]="You can't use Skills because Weight Limit has been exceeded." -/// 3 = assassin, baby_assassin, assassin_cross => MsgStringTable[1040]="You have equipped throwing daggers." -/// gunslinger => MsgStringTable[1175]="Bullets have been equipped." -/// NOT ninja => MsgStringTable[245]="Ammunition has been equipped." -void clif_arrow_fail(struct map_session_data *sd,int type) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd, packet_len(0x013b)); - WFIFOW(fd,0)=0x013b; - WFIFOW(fd,2)=type; - WFIFOSET(fd,packet_len(0x013b)); -} - - -/// Presents a list of items, that can be processed by Arrow Crafting (ZC_MAKINGARROW_LIST). -/// 01ad .W { .W }* -void clif_arrow_create_list(struct map_session_data *sd) -{ - int i, c, j; - int fd; - - nullpo_retv(sd); - - fd = sd->fd; - WFIFOHEAD(fd, MAX_SKILL_ARROW_DB*2+4); - WFIFOW(fd,0) = 0x1ad; - - for (i = 0, c = 0; i < MAX_SKILL_ARROW_DB; i++) { - if (skill_arrow_db[i].nameid > 0 && - (j = pc_search_inventory(sd, skill_arrow_db[i].nameid)) >= 0 && - !sd->status.inventory[j].equip && sd->status.inventory[j].identify) - { - if ((j = itemdb_viewid(skill_arrow_db[i].nameid)) > 0) - WFIFOW(fd,c*2+4) = j; - 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->menuskill_id = AC_MAKINGARROW; - sd->menuskill_val = c; - } -} - - -/// Notifies the client, about the result of an status change request (ZC_STATUS_CHANGE_ACK). -/// 00bc .W .B .B -/// status id: -/// SP_STR ~ SP_LUK -/// result: -/// 0 = failure -/// 1 = success -void clif_statusupack(struct map_session_data *sd,int type,int ok,int val) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0xbc)); - WFIFOW(fd,0)=0xbc; - WFIFOW(fd,2)=type; - WFIFOB(fd,4)=ok; - WFIFOB(fd,5)=cap_value(val,0,UINT8_MAX); - WFIFOSET(fd,packet_len(0xbc)); -} - - -/// Notifies the client about the result of a request to equip an item (ZC_REQ_WEAR_EQUIP_ACK). -/// 00aa .W .W .B -/// 00aa .W .W .W .B (PACKETVER >= 20100629) -/// result: -/// 0 = failure -/// 1 = success -/// 2 = failure due to low level -void clif_equipitemack(struct map_session_data *sd,int n,int pos,int ok) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0xaa)); - WFIFOW(fd,0)=0xaa; - WFIFOW(fd,2)=n+2; - WFIFOW(fd,4)=pos; -#if PACKETVER < 20100629 - WFIFOB(fd,6)=ok; -#else - if (ok && sd->inventory_data[n]->equip&EQP_VISIBLE) - WFIFOW(fd,6)=sd->inventory_data[n]->look; - else - WFIFOW(fd,6)=0; - WFIFOB(fd,8)=ok; -#endif - WFIFOSET(fd,packet_len(0xaa)); -} - - -/// Notifies the client about the result of a request to take off an item (ZC_REQ_TAKEOFF_EQUIP_ACK). -/// 00ac .W .W .B -/// result: -/// 0 = failure -/// 1 = success -void clif_unequipitemack(struct map_session_data *sd,int n,int pos,int ok) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0xac)); - WFIFOW(fd,0)=0xac; - WFIFOW(fd,2)=n+2; - WFIFOW(fd,4)=pos; - WFIFOB(fd,6)=ok; - WFIFOSET(fd,packet_len(0xac)); -} - - -/// Notifies clients in the area about an special/visual effect (ZC_NOTIFY_EFFECT). -/// 019b .L .L -/// effect id: -/// 0 = base level up -/// 1 = job level up -/// 2 = refine failure -/// 3 = refine success -/// 4 = game over -/// 5 = pharmacy success -/// 6 = pharmacy failure -/// 7 = base level up (super novice) -/// 8 = job level up (super novice) -/// 9 = base level up (taekwon) -void clif_misceffect(struct block_list* bl,int type) -{ - unsigned char buf[32]; - - nullpo_retv(bl); - - WBUFW(buf,0) = 0x19b; - WBUFL(buf,2) = bl->id; - WBUFL(buf,6) = type; - - clif_send(buf,packet_len(0x19b),bl,AREA); -} - - -/// Notifies clients in the area of a state change. -/// 0119 .L .W .W .W .B (ZC_STATE_CHANGE) -/// 0229 .L .W .W .L .B (ZC_STATE_CHANGE3) -void clif_changeoption(struct block_list* bl) -{ - unsigned char buf[32]; - struct status_change *sc; - struct map_session_data* sd; - - nullpo_retv(bl); - sc = status_get_sc(bl); - if (!sc) return; //How can an option change if there's no sc? - sd = BL_CAST(BL_PC, bl); - -#if PACKETVER >= 7 - WBUFW(buf,0) = 0x229; - WBUFL(buf,2) = bl->id; - WBUFW(buf,6) = sc->opt1; - WBUFW(buf,8) = sc->opt2; - WBUFL(buf,10) = sc->option; - WBUFB(buf,14) = (sd)? sd->status.karma : 0; - if(disguised(bl)) { - clif_send(buf,packet_len(0x229),bl,AREA_WOS); - WBUFL(buf,2) = -bl->id; - clif_send(buf,packet_len(0x229),bl,SELF); - WBUFL(buf,2) = bl->id; - WBUFL(buf,10) = OPTION_INVISIBLE; - clif_send(buf,packet_len(0x229),bl,SELF); - } else - clif_send(buf,packet_len(0x229),bl,AREA); -#else - WBUFW(buf,0) = 0x119; - WBUFL(buf,2) = bl->id; - WBUFW(buf,6) = sc->opt1; - WBUFW(buf,8) = sc->opt2; - WBUFW(buf,10) = sc->option; - WBUFB(buf,12) = (sd)? sd->status.karma : 0; - if(disguised(bl)) { - clif_send(buf,packet_len(0x119),bl,AREA_WOS); - WBUFL(buf,2) = -bl->id; - clif_send(buf,packet_len(0x119),bl,SELF); - WBUFL(buf,2) = bl->id; - WBUFW(buf,10) = OPTION_INVISIBLE; - clif_send(buf,packet_len(0x119),bl,SELF); - } else - clif_send(buf,packet_len(0x119),bl,AREA); -#endif -} - - -/// Displays status change effects on NPCs/monsters (ZC_NPC_SHOWEFST_UPDATE). -/// 028a .L .L .L .L -void clif_changeoption2(struct block_list* bl) -{ - unsigned char buf[20]; - struct status_change *sc; - - sc = status_get_sc(bl); - if (!sc) return; //How can an option change if there's no sc? - - WBUFW(buf,0) = 0x28a; - WBUFL(buf,2) = bl->id; - WBUFL(buf,6) = sc->option; - WBUFL(buf,10) = clif_setlevel(bl); - WBUFL(buf,14) = sc->opt3; - if(disguised(bl)) { - clif_send(buf,packet_len(0x28a),bl,AREA_WOS); - WBUFL(buf,2) = -bl->id; - clif_send(buf,packet_len(0x28a),bl,SELF); - WBUFL(buf,2) = bl->id; - WBUFL(buf,6) = OPTION_INVISIBLE; - clif_send(buf,packet_len(0x28a),bl,SELF); - } else - clif_send(buf,packet_len(0x28a),bl,AREA); -} - - -/// Notifies the client about the result of an item use request. -/// 00a8 .W .W .B (ZC_USE_ITEM_ACK) -/// 01c8 .W .W .L .W .B (ZC_USE_ITEM_ACK2) -void clif_useitemack(struct map_session_data *sd,int index,int amount,bool ok) -{ - nullpo_retv(sd); - - if(!ok) { - int fd=sd->fd; - WFIFOHEAD(fd,packet_len(0xa8)); - WFIFOW(fd,0)=0xa8; - WFIFOW(fd,2)=index+2; - WFIFOW(fd,4)=amount; - WFIFOB(fd,6)=ok; - WFIFOSET(fd,packet_len(0xa8)); - } - else { -#if PACKETVER < 3 - int fd=sd->fd; - WFIFOHEAD(fd,packet_len(0xa8)); - WFIFOW(fd,0)=0xa8; - WFIFOW(fd,2)=index+2; - WFIFOW(fd,4)=amount; - WFIFOB(fd,6)=ok; - WFIFOSET(fd,packet_len(0xa8)); -#else - unsigned 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(0x1c8),&sd->bl,AREA); -#endif - } -} - - -/// Inform client whether chatroom creation was successful or not (ZC_ACK_CREATE_CHATROOM). -/// 00d6 .B -/// flag: -/// 0 = Room has been successfully created (opens chat room) -/// 1 = Room limit exceeded -/// 2 = Same room already exists -void clif_createchat(struct map_session_data* sd, int flag) -{ - int fd; - - nullpo_retv(sd); - - fd = sd->fd; - WFIFOHEAD(fd,packet_len(0xd6)); - WFIFOW(fd,0) = 0xd6; - WFIFOB(fd,2) = flag; - WFIFOSET(fd,packet_len(0xd6)); -} - - -/// Display a chat above the owner (ZC_ROOM_NEWENTRY). -/// 00d7 .W .L .L .W .W .B .?B -/// type: -/// 0 = private (password protected) -/// 1 = public -/// 2 = arena (npc waiting room) -/// 3 = PK zone (non-clickable) -void clif_dispchat(struct chat_data* cd, int fd) -{ - unsigned char buf[128]; - uint8 type; - - if( cd == NULL || cd->owner == NULL ) - return; - - type = (cd->owner->type == BL_PC ) ? (cd->pub) ? 1 : 0 - : (cd->owner->type == BL_NPC) ? (cd->limit) ? 2 : 3 - : 1; - - WBUFW(buf, 0) = 0xd7; - WBUFW(buf, 2) = 17 + strlen(cd->title); - WBUFL(buf, 4) = cd->owner->id; - WBUFL(buf, 8) = cd->bl.id; - WBUFW(buf,12) = cd->limit; - WBUFW(buf,14) = (cd->owner->type == BL_NPC) ? cd->users+1 : cd->users; - WBUFB(buf,16) = type; - memcpy((char*)WBUFP(buf,17), cd->title, strlen(cd->title)); // not zero-terminated - - if( fd ) { - WFIFOHEAD(fd,WBUFW(buf,2)); - memcpy(WFIFOP(fd,0),buf,WBUFW(buf,2)); - WFIFOSET(fd,WBUFW(buf,2)); - } else { - clif_send(buf,WBUFW(buf,2),cd->owner,AREA_WOSC); - } -} - - -/// Chatroom properties adjustment (ZC_CHANGE_CHATROOM). -/// 00df <packet len>.W <owner id>.L <chat id>.L <limit>.W <users>.W <type>.B <title>.?B -/// type: -/// 0 = private (password protected) -/// 1 = public -/// 2 = arena (npc waiting room) -/// 3 = PK zone (non-clickable) -void clif_changechatstatus(struct chat_data* cd) -{ - unsigned char buf[128]; - uint8 type; - - if( cd == NULL || cd->usersd[0] == NULL ) - return; - - type = (cd->owner->type == BL_PC ) ? (cd->pub) ? 1 : 0 - : (cd->owner->type == BL_NPC) ? (cd->limit) ? 2 : 3 - : 1; - - WBUFW(buf, 0) = 0xdf; - WBUFW(buf, 2) = 17 + strlen(cd->title); - WBUFL(buf, 4) = cd->owner->id; - WBUFL(buf, 8) = cd->bl.id; - WBUFW(buf,12) = cd->limit; - WBUFW(buf,14) = (cd->owner->type == BL_NPC) ? cd->users+1 : cd->users; - WBUFB(buf,16) = type; - memcpy((char*)WBUFP(buf,17), cd->title, strlen(cd->title)); // not zero-terminated - - clif_send(buf,WBUFW(buf,2),cd->owner,CHAT); -} - - -/// Removes the chatroom (ZC_DESTROY_ROOM). -/// 00d8 <chat id>.L -void clif_clearchat(struct chat_data *cd,int fd) -{ - unsigned char buf[32]; - - nullpo_retv(cd); - - WBUFW(buf,0) = 0xd8; - WBUFL(buf,2) = cd->bl.id; - if( fd ) { - WFIFOHEAD(fd,packet_len(0xd8)); - memcpy(WFIFOP(fd,0),buf,packet_len(0xd8)); - WFIFOSET(fd,packet_len(0xd8)); - } else { - clif_send(buf,packet_len(0xd8),cd->owner,AREA_WOSC); - } -} - - -/// Displays messages regarding join chat failures (ZC_REFUSE_ENTER_ROOM). -/// 00da <result>.B -/// result: -/// 0 = room full -/// 1 = wrong password -/// 2 = kicked -/// 3 = success (no message) -/// 4 = no enough zeny -/// 5 = too low level -/// 6 = too high level -/// 7 = unsuitable job class -void clif_joinchatfail(struct map_session_data *sd,int flag) -{ - int fd; - - nullpo_retv(sd); - - fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0xda)); - WFIFOW(fd,0) = 0xda; - WFIFOB(fd,2) = flag; - WFIFOSET(fd,packet_len(0xda)); -} - - -/// Notifies the client about entering a chatroom (ZC_ENTER_ROOM). -/// 00db <packet len>.W <chat id>.L { <role>.L <name>.24B }* -/// role: -/// 0 = owner (menu) -/// 1 = normal -void clif_joinchatok(struct map_session_data *sd,struct chat_data* cd) -{ - int fd; - int i,t; - - nullpo_retv(sd); - nullpo_retv(cd); - - fd = sd->fd; - if (!session_isActive(fd)) - return; - t = (int)(cd->owner->type == BL_NPC); - WFIFOHEAD(fd, 8 + (28*(cd->users+t))); - WFIFOW(fd, 0) = 0xdb; - WFIFOW(fd, 2) = 8 + (28*(cd->users+t)); - WFIFOL(fd, 4) = cd->bl.id; - - if(cd->owner->type == BL_NPC){ - WFIFOL(fd, 30) = 1; - WFIFOL(fd, 8) = 0; - memcpy(WFIFOP(fd, 12), ((struct npc_data *)cd->owner)->name, NAME_LENGTH); - for (i = 0; i < cd->users; i++) { - WFIFOL(fd, 8+(i+1)*28) = 1; - memcpy(WFIFOP(fd, 8+(i+t)*28+4), cd->usersd[i]->status.name, NAME_LENGTH); - } - } else - for (i = 0; i < cd->users; i++) { - WFIFOL(fd, 8+i*28) = (i != 0 || cd->owner->type == BL_NPC); - memcpy(WFIFOP(fd, 8+(i+t)*28+4), cd->usersd[i]->status.name, NAME_LENGTH); - } - WFIFOSET(fd, WFIFOW(fd, 2)); -} - - -/// Notifies clients in a chat about a new member (ZC_MEMBER_NEWENTRY). -/// 00dc <users>.W <name>.24B -void clif_addchat(struct chat_data* cd,struct map_session_data *sd) -{ - unsigned char buf[32]; - - nullpo_retv(sd); - nullpo_retv(cd); - - WBUFW(buf, 0) = 0xdc; - WBUFW(buf, 2) = cd->users; - memcpy(WBUFP(buf, 4),sd->status.name,NAME_LENGTH); - clif_send(buf,packet_len(0xdc),&sd->bl,CHAT_WOS); -} - - -/// Announce the new owner (ZC_ROLE_CHANGE). -/// 00e1 <role>.L <nick>.24B -/// role: -/// 0 = owner (menu) -/// 1 = normal -void clif_changechatowner(struct chat_data* cd, struct map_session_data* sd) -{ - unsigned char buf[64]; - - nullpo_retv(sd); - nullpo_retv(cd); - - WBUFW(buf, 0) = 0xe1; - WBUFL(buf, 2) = 1; - memcpy(WBUFP(buf,6),cd->usersd[0]->status.name,NAME_LENGTH); - - WBUFW(buf,30) = 0xe1; - WBUFL(buf,32) = 0; - memcpy(WBUFP(buf,36),sd->status.name,NAME_LENGTH); - - clif_send(buf,packet_len(0xe1)*2,&sd->bl,CHAT); -} - - -/// Notify about user leaving the chatroom (ZC_MEMBER_EXIT). -/// 00dd <users>.W <nick>.24B <flag>.B -/// flag: -/// 0 = left -/// 1 = kicked -void clif_leavechat(struct chat_data* cd, struct map_session_data* sd, bool flag) -{ - unsigned char buf[32]; - - nullpo_retv(sd); - nullpo_retv(cd); - - WBUFW(buf, 0) = 0xdd; - WBUFW(buf, 2) = cd->users-1; - memcpy(WBUFP(buf,4),sd->status.name,NAME_LENGTH); - WBUFB(buf,28) = flag; - - clif_send(buf,packet_len(0xdd),&sd->bl,CHAT); -} - - -/// Opens a trade request window from char 'name'. -/// 00e5 <nick>.24B (ZC_REQ_EXCHANGE_ITEM) -/// 01f4 <nick>.24B <charid>.L <baselvl>.W (ZC_REQ_EXCHANGE_ITEM2) -void clif_traderequest(struct map_session_data* sd, const char* name) -{ - int fd = sd->fd; - -#if PACKETVER < 6 - WFIFOHEAD(fd,packet_len(0xe5)); - WFIFOW(fd,0) = 0xe5; - safestrncpy((char*)WFIFOP(fd,2), name, NAME_LENGTH); - WFIFOSET(fd,packet_len(0xe5)); -#else - struct map_session_data* tsd = map_id2sd(sd->trade_partner); - if( !tsd ) return; - - WFIFOHEAD(fd,packet_len(0x1f4)); - WFIFOW(fd,0) = 0x1f4; - safestrncpy((char*)WFIFOP(fd,2), name, NAME_LENGTH); - WFIFOL(fd,26) = tsd->status.char_id; - WFIFOW(fd,30) = tsd->status.base_level; - WFIFOSET(fd,packet_len(0x1f4)); -#endif -} - - -/// Reply to a trade-request. -/// 00e7 <result>.B (ZC_ACK_EXCHANGE_ITEM) -/// 01f5 <result>.B <charid>.L <baselvl>.W (ZC_ACK_EXCHANGE_ITEM2) -/// result: -/// 0 = Char is too far -/// 1 = Character does not exist -/// 2 = Trade failed -/// 3 = Accept -/// 4 = Cancel -/// 5 = Busy -void clif_tradestart(struct map_session_data* sd, uint8 type) -{ - int fd = sd->fd; - struct map_session_data* tsd = map_id2sd(sd->trade_partner); - if( PACKETVER < 6 || !tsd ) { - WFIFOHEAD(fd,packet_len(0xe7)); - WFIFOW(fd,0) = 0xe7; - WFIFOB(fd,2) = type; - WFIFOSET(fd,packet_len(0xe7)); - } else { - WFIFOHEAD(fd,packet_len(0x1f5)); - WFIFOW(fd,0) = 0x1f5; - WFIFOB(fd,2) = type; - WFIFOL(fd,3) = tsd->status.char_id; - WFIFOW(fd,7) = tsd->status.base_level; - WFIFOSET(fd,packet_len(0x1f5)); - } -} - - -/// Notifies the client about an item from other player in current trade. -/// 00e9 <amount>.L <nameid>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W (ZC_ADD_EXCHANGE_ITEM) -/// 080f <nameid>.W <item type>.B <amount>.L <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W (ZC_ADD_EXCHANGE_ITEM2) -void clif_tradeadditem(struct map_session_data* sd, struct map_session_data* tsd, int index, int amount) -{ - int fd; - unsigned char *buf; -#if PACKETVER < 20100223 - const int cmd = 0xe9; -#else - const int cmd = 0x80f; -#endif - nullpo_retv(sd); - nullpo_retv(tsd); - - fd = tsd->fd; - buf = WFIFOP(fd,0); - WFIFOHEAD(fd,packet_len(cmd)); - WBUFW(buf,0) = cmd; - if( index == 0 ) - { -#if PACKETVER < 20100223 - WBUFL(buf,2) = amount; //amount - WBUFW(buf,6) = 0; // type id -#else - WBUFW(buf,2) = 0; // type id - WBUFB(buf,4) = 0; // item type - WBUFL(buf,5) = amount; // amount - buf = WBUFP(buf,1); //Advance 1B -#endif - WBUFB(buf,8) = 0; //identify flag - WBUFB(buf,9) = 0; // attribute - WBUFB(buf,10)= 0; //refine - WBUFW(buf,11)= 0; //card (4w) - WBUFW(buf,13)= 0; //card (4w) - WBUFW(buf,15)= 0; //card (4w) - WBUFW(buf,17)= 0; //card (4w) - } - else - { - index -= 2; //index fix -#if PACKETVER < 20100223 - WBUFL(buf,2) = amount; //amount - if(sd->inventory_data[index] && sd->inventory_data[index]->view_id > 0) - WBUFW(buf,6) = sd->inventory_data[index]->view_id; - else - WBUFW(buf,6) = sd->status.inventory[index].nameid; // type id -#else - if(sd->inventory_data[index] && sd->inventory_data[index]->view_id > 0) - WBUFW(buf,2) = sd->inventory_data[index]->view_id; - else - WBUFW(buf,2) = sd->status.inventory[index].nameid; // type id - WBUFB(buf,4) = sd->inventory_data[index]->type; // item type - WBUFL(buf,5) = amount; // amount - buf = WBUFP(buf,1); //Advance 1B -#endif - WBUFB(buf,8) = sd->status.inventory[index].identify; //identify flag - WBUFB(buf,9) = sd->status.inventory[index].attribute; // attribute - WBUFB(buf,10)= sd->status.inventory[index].refine; //refine - clif_addcards(WBUFP(buf, 11), &sd->status.inventory[index]); - } - WFIFOSET(fd,packet_len(cmd)); -} - - -/// Notifies the client about the result of request to add an item to the current trade (ZC_ACK_ADD_EXCHANGE_ITEM). -/// 00ea <index>.W <result>.B -/// result: -/// 0 = success -/// 1 = overweight -/// 2 = trade canceled -void clif_tradeitemok(struct map_session_data* sd, int index, int fail) -{ - int fd; - nullpo_retv(sd); - - fd = sd->fd; - WFIFOHEAD(fd,packet_len(0xea)); - WFIFOW(fd,0) = 0xea; - WFIFOW(fd,2) = index; - WFIFOB(fd,4) = fail; - WFIFOSET(fd,packet_len(0xea)); -} - - -/// Notifies the client about finishing one side of the current trade (ZC_CONCLUDE_EXCHANGE_ITEM). -/// 00ec <who>.B -/// who: -/// 0 = self -/// 1 = other player -void clif_tradedeal_lock(struct map_session_data* sd, int fail) -{ - int fd; - nullpo_retv(sd); - - fd = sd->fd; - WFIFOHEAD(fd,packet_len(0xec)); - WFIFOW(fd,0) = 0xec; - WFIFOB(fd,2) = fail; - WFIFOSET(fd,packet_len(0xec)); -} - - -/// Notifies the client about the trade being canceled (ZC_CANCEL_EXCHANGE_ITEM). -/// 00ee -void clif_tradecancelled(struct map_session_data* sd) -{ - int fd; - nullpo_retv(sd); - - fd = sd->fd; - WFIFOHEAD(fd,packet_len(0xee)); - WFIFOW(fd,0) = 0xee; - WFIFOSET(fd,packet_len(0xee)); -} - - -/// Result of a trade (ZC_EXEC_EXCHANGE_ITEM). -/// 00f0 <result>.B -/// result: -/// 0 = success -/// 1 = failure -void clif_tradecompleted(struct map_session_data* sd, int fail) -{ - int fd; - nullpo_retv(sd); - - fd = sd->fd; - WFIFOHEAD(fd,packet_len(0xf0)); - WFIFOW(fd,0) = 0xf0; - WFIFOB(fd,2) = fail; - WFIFOSET(fd,packet_len(0xf0)); -} - - -/// Resets the trade window on the send side (ZC_EXCHANGEITEM_UNDO). -/// 00f1 -/// NOTE: Unknown purpose. Items are not removed until the window is -/// refreshed (ex. by putting another item in there). -void clif_tradeundo(struct map_session_data* sd) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0xf1)); - WFIFOW(fd,0) = 0xf1; - WFIFOSET(fd,packet_len(0xf1)); -} - - -/// Updates storage total amount (ZC_NOTIFY_STOREITEM_COUNTINFO). -/// 00f2 <current count>.W <max count>.W -void clif_updatestorageamount(struct map_session_data* sd, int amount, int max_amount) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0xf2)); - WFIFOW(fd,0) = 0xf2; - WFIFOW(fd,2) = amount; - WFIFOW(fd,4) = max_amount; - WFIFOSET(fd,packet_len(0xf2)); -} - - -/// Notifies the client of an item being added to the storage. -/// 00f4 <index>.W <amount>.L <nameid>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W (ZC_ADD_ITEM_TO_STORE) -/// 01c4 <index>.W <amount>.L <nameid>.W <type>.B <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W (ZC_ADD_ITEM_TO_STORE2) -void clif_storageitemadded(struct map_session_data* sd, struct item* i, int index, int amount) -{ - int view,fd; - - nullpo_retv(sd); - nullpo_retv(i); - fd=sd->fd; - view = itemdb_viewid(i->nameid); - -#if PACKETVER < 5 - WFIFOHEAD(fd,packet_len(0xf4)); - WFIFOW(fd, 0) = 0xf4; // Storage item added - WFIFOW(fd, 2) = index+1; // index - WFIFOL(fd, 4) = amount; // amount - WFIFOW(fd, 8) = ( view > 0 ) ? view : i->nameid; // id - WFIFOB(fd,10) = i->identify; //identify flag - WFIFOB(fd,11) = i->attribute; // attribute - WFIFOB(fd,12) = i->refine; //refine - clif_addcards(WFIFOP(fd,13), i); - WFIFOSET(fd,packet_len(0xf4)); -#else - WFIFOHEAD(fd,packet_len(0x1c4)); - WFIFOW(fd, 0) = 0x1c4; // Storage item added - WFIFOW(fd, 2) = index+1; // index - WFIFOL(fd, 4) = amount; // amount - WFIFOW(fd, 8) = ( view > 0 ) ? view : i->nameid; // id - WFIFOB(fd,10) = itemdb_type(i->nameid); //type - WFIFOB(fd,11) = i->identify; //identify flag - WFIFOB(fd,12) = i->attribute; // attribute - WFIFOB(fd,13) = i->refine; //refine - clif_addcards(WFIFOP(fd,14), i); - WFIFOSET(fd,packet_len(0x1c4)); -#endif -} - - -/// Notifies the client of an item being deleted from the storage (ZC_DELETE_ITEM_FROM_STORE). -/// 00f6 <index>.W <amount>.L -void clif_storageitemremoved(struct map_session_data* sd, int index, int amount) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0xf6)); - WFIFOW(fd,0)=0xf6; // Storage item removed - WFIFOW(fd,2)=index+1; - WFIFOL(fd,4)=amount; - WFIFOSET(fd,packet_len(0xf6)); -} - - -/// Closes storage (ZC_CLOSE_STORE). -/// 00f8 -void clif_storageclose(struct map_session_data* sd) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0xf8)); - WFIFOW(fd,0) = 0xf8; // Storage Closed - WFIFOSET(fd,packet_len(0xf8)); -} - -/*========================================== - * Server tells 'sd' player client the abouts of 'dstsd' player - *------------------------------------------*/ -static void clif_getareachar_pc(struct map_session_data* sd,struct map_session_data* dstsd) -{ - struct block_list *d_bl; - int i; - - if( dstsd->chatID ) { - struct chat_data *cd = NULL; - if( (cd = (struct chat_data*)map_id2bl(dstsd->chatID)) && cd->usersd[0]==dstsd) - clif_dispchat(cd,sd->fd); - } else if( dstsd->state.vending ) - clif_showvendingboard(&dstsd->bl,dstsd->message,sd->fd); - else if( dstsd->state.buyingstore ) - clif_buyingstore_entry_single(sd, dstsd); - - if(dstsd->spiritball > 0) - clif_spiritball_single(sd->fd, dstsd); - for(i = 1; i < 5; i++){ - if( dstsd->talisman[i] > 0 ) - clif_talisman_single(sd->fd, dstsd, i); - } - if( dstsd->sc.option&OPTION_MOUNTING ) { - //New Mounts are not complaint to the original method, so we gotta tell this guy that I'm mounting. - clif_status_load_single(sd->fd,dstsd->bl.id,SI_ALL_RIDING,2,1,0,0); - } -#ifdef NEW_CARTS - if( dstsd->sc.data[SC_PUSH_CART] ) - clif_status_load_single(sd->fd, dstsd->bl.id, SI_ON_PUSH_CART, 2, dstsd->sc.data[SC_PUSH_CART]->val1, 0, 0); -#endif - if( (sd->status.party_id && dstsd->status.party_id == sd->status.party_id) || //Party-mate, or hpdisp setting. - (sd->bg_id && sd->bg_id == dstsd->bg_id) || //BattleGround - pc_has_permission(sd, PC_PERM_VIEW_HPMETER) - ) - clif_hpmeter_single(sd->fd, dstsd->bl.id, dstsd->battle_status.hp, dstsd->battle_status.max_hp); - - // display link (sd - dstsd) to sd - ARR_FIND( 0, 5, i, sd->devotion[i] == dstsd->bl.id ); - if( i < 5 ) clif_devotion(&sd->bl, sd); - // display links (dstsd - devotees) to sd - ARR_FIND( 0, 5, i, dstsd->devotion[i] > 0 ); - if( i < 5 ) clif_devotion(&dstsd->bl, sd); - // display link (dstsd - crusader) to sd - if( dstsd->sc.data[SC_DEVOTION] && (d_bl = map_id2bl(dstsd->sc.data[SC_DEVOTION]->val1)) != NULL ) - clif_devotion(d_bl, sd); -} - -void clif_getareachar_unit(struct map_session_data* sd,struct block_list *bl) -{ - uint8 buf[128]; - struct unit_data *ud; - struct view_data *vd; - int len; - - vd = status_get_viewdata(bl); - if (!vd || vd->class_ == INVISIBLE_CLASS) - return; - - /** - * Hide NPC from maya purple card. - **/ - if(bl->type == BL_NPC && !((TBL_NPC*)bl)->chat_id && (((TBL_NPC*)bl)->sc.option&OPTION_INVISIBLE)) - return; - - ud = unit_bl2ud(bl); - len = ( ud && ud->walktimer != INVALID_TIMER ) ? clif_set_unit_walking(bl,ud,buf) : clif_set_unit_idle(bl,buf,false); - clif_send(buf,len,&sd->bl,SELF); - - if (vd->cloth_color) - clif_refreshlook(&sd->bl,bl->id,LOOK_CLOTHES_COLOR,vd->cloth_color,SELF); - - switch (bl->type) - { - case BL_PC: - { - TBL_PC* tsd = (TBL_PC*)bl; - clif_getareachar_pc(sd, tsd); - if(tsd->state.size==SZ_BIG) // tiny/big players [Valaris] - clif_specialeffect_single(bl,423,sd->fd); - else if(tsd->state.size==SZ_MEDIUM) - clif_specialeffect_single(bl,421,sd->fd); - if( tsd->bg_id && map[tsd->bl.m].flag.battleground ) - clif_sendbgemblem_single(sd->fd,tsd); - if( tsd->sc.data[SC_CAMOUFLAGE] ) - clif_status_load(bl,SI_CAMOUFLAGE,1); - } - break; - case BL_MER: // Devotion Effects - if( ((TBL_MER*)bl)->devotion_flag ) - clif_devotion(bl, sd); - break; - case BL_NPC: - { - TBL_NPC* nd = (TBL_NPC*)bl; - if( nd->chat_id ) - clif_dispchat((struct chat_data*)map_id2bl(nd->chat_id),sd->fd); - if( nd->size == SZ_BIG ) - clif_specialeffect_single(bl,423,sd->fd); - else if( nd->size == SZ_MEDIUM ) - clif_specialeffect_single(bl,421,sd->fd); - } - break; - case BL_MOB: - { - TBL_MOB* md = (TBL_MOB*)bl; - if(md->special_state.size==SZ_BIG) // tiny/big mobs [Valaris] - clif_specialeffect_single(bl,423,sd->fd); - else if(md->special_state.size==SZ_MEDIUM) - clif_specialeffect_single(bl,421,sd->fd); -#if PACKETVER >= 20120404 - if( !(md->status.mode&MD_BOSS) ){ - int i; - for(i = 0; i < DAMAGELOG_SIZE; i++)// must show hp bar to all char who already hit the mob. - if( md->dmglog[i].id == sd->status.char_id ) - clif_monster_hp_bar(md, sd->fd); - } -#endif - } - break; - case BL_PET: - if (vd->head_bottom) - clif_pet_equip(sd, (TBL_PET*)bl); // needed to display pet equip properly - break; - } -} - -//Modifies the type of damage according to status changes [Skotlex] -//Aegis data specifies that: 4 endure against single hit sources, 9 against multi-hit. -static inline int clif_calc_delay(int type, int div, int damage, int delay) -{ - return ( delay == 0 && damage > 0 ) ? ( div > 1 ? 9 : 4 ) : type; -} - -/*========================================== - * Estimates walk delay based on the damage criteria. [Skotlex] - *------------------------------------------*/ -static int clif_calc_walkdelay(struct block_list *bl,int delay, int type, int damage, int div_) -{ - if (type == 4 || type == 9 || damage <=0) - return 0; - - if (bl->type == BL_PC) { - if (battle_config.pc_walk_delay_rate != 100) - delay = delay*battle_config.pc_walk_delay_rate/100; - } else - if (battle_config.walk_delay_rate != 100) - delay = delay*battle_config.walk_delay_rate/100; - - if (div_ > 1) //Multi-hit skills mean higher delays. - delay += battle_config.multihit_delay*(div_-1); - - return delay>0?delay:1; //Return 1 to specify there should be no noticeable delay, but you should stop walking. -} - - -/// Sends a 'damage' packet (src performs action on dst) -/// 008a <src ID>.L <dst ID>.L <server tick>.L <src speed>.L <dst speed>.L <damage>.W <div>.W <type>.B <damage2>.W (ZC_NOTIFY_ACT) -/// 02e1 <src ID>.L <dst ID>.L <server tick>.L <src speed>.L <dst speed>.L <damage>.L <div>.W <type>.B <damage2>.L (ZC_NOTIFY_ACT2) -/// type: -/// 0 = damage [ damage: total damage, div: amount of hits, damage2: assassin dual-wield damage ] -/// 1 = pick up item -/// 2 = sit down -/// 3 = stand up -/// 4 = damage (endure) -/// 5 = (splash?) -/// 6 = (skill?) -/// 7 = (repeat damage?) -/// 8 = multi-hit damage -/// 9 = multi-hit damage (endure) -/// 10 = critical hit -/// 11 = lucky dodge -/// 12 = (touch skill?) -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[33]; - struct status_change *sc; -#if PACKETVER < 20071113 - const int cmd = 0x8a; -#else - const int cmd = 0x2e1; -#endif - - nullpo_ret(src); - nullpo_ret(dst); - - type = clif_calc_delay(type,div,damage+damage2,ddelay); - sc = status_get_sc(dst); - if(sc && sc->count) { - if(sc->data[SC_HALLUCINATION]) { - if(damage) damage = damage*(sc->data[SC_HALLUCINATION]->val2) + rnd()%100; - if(damage2) damage2 = damage2*(sc->data[SC_HALLUCINATION]->val2) + rnd()%100; - } - } - - WBUFW(buf,0)=cmd; - WBUFL(buf,2)=src->id; - WBUFL(buf,6)=dst->id; - WBUFL(buf,10)=tick; - WBUFL(buf,14)=sdelay; - WBUFL(buf,18)=ddelay; -#if PACKETVER < 20071113 - if (battle_config.hide_woe_damage && map_flag_gvg(src->m)) { - WBUFW(buf,22)=damage?div:0; - WBUFW(buf,27)=damage2?div:0; - } else { - WBUFW(buf,22)=min(damage, INT16_MAX); - WBUFW(buf,27)=damage2; - } - WBUFW(buf,24)=div; - WBUFB(buf,26)=type; -#else - if (battle_config.hide_woe_damage && map_flag_gvg(src->m)) { - WBUFL(buf,22)=damage?div:0; - WBUFL(buf,29)=damage2?div:0; - } else { - WBUFL(buf,22)=damage; - WBUFL(buf,29)=damage2; - } - WBUFW(buf,26)=div; - WBUFB(buf,28)=type; -#endif - if(disguised(dst)) { - clif_send(buf,packet_len(cmd),dst,AREA_WOS); - WBUFL(buf,6) = -dst->id; - clif_send(buf,packet_len(cmd),dst,SELF); - } else - clif_send(buf,packet_len(cmd),dst,AREA); - - if(disguised(src)) { - WBUFL(buf,2) = -src->id; - if (disguised(dst)) - WBUFL(buf,6) = dst->id; -#if PACKETVER < 20071113 - if(damage > 0) WBUFW(buf,22) = -1; - if(damage2 > 0) WBUFW(buf,27) = -1; -#else - if(damage > 0) WBUFL(buf,22) = -1; - if(damage2 > 0) WBUFL(buf,29) = -1; -#endif - clif_send(buf,packet_len(cmd),src,SELF); - } - - if(src == dst) { - unit_setdir(src,unit_getdir(src)); - } - //Return adjusted can't walk delay for further processing. - return clif_calc_walkdelay(dst,ddelay,type,damage+damage2,div); -} - -/*========================================== - * src picks up dst - *------------------------------------------*/ -void clif_takeitem(struct block_list* src, struct block_list* dst) -{ - //clif_damage(src,dst,0,0,0,0,0,1,0); - unsigned char buf[32]; - - nullpo_retv(src); - nullpo_retv(dst); - - WBUFW(buf, 0) = 0x8a; - WBUFL(buf, 2) = src->id; - WBUFL(buf, 6) = dst->id; - WBUFB(buf,26) = 1; - clif_send(buf, packet_len(0x8a), src, AREA); - -} - -/*========================================== - * inform clients in area that `bl` is sitting - *------------------------------------------*/ -void clif_sitting(struct block_list* bl) -{ - unsigned char buf[32]; - nullpo_retv(bl); - - WBUFW(buf, 0) = 0x8a; - WBUFL(buf, 2) = bl->id; - WBUFB(buf,26) = 2; - clif_send(buf, packet_len(0x8a), bl, AREA); - - if(disguised(bl)) { - WBUFL(buf, 2) = - bl->id; - clif_send(buf, packet_len(0x8a), bl, SELF); - } -} - -/*========================================== - * inform clients in area that `bl` is standing - *------------------------------------------*/ -void clif_standing(struct block_list* bl) -{ - unsigned char buf[32]; - nullpo_retv(bl); - - WBUFW(buf, 0) = 0x8a; - WBUFL(buf, 2) = bl->id; - WBUFB(buf,26) = 3; - clif_send(buf, packet_len(0x8a), bl, AREA); - - if(disguised(bl)) { - WBUFL(buf, 2) = - bl->id; - clif_send(buf, packet_len(0x8a), bl, SELF); - } -} - - -/// Inform client(s) about a map-cell change (ZC_UPDATE_MAPINFO). -/// 0192 <x>.W <y>.W <type>.W <map name>.16B -void clif_changemapcell(int fd, int16 m, int x, int y, int type, enum send_target target) -{ - unsigned char buf[32]; - - WBUFW(buf,0) = 0x192; - WBUFW(buf,2) = x; - WBUFW(buf,4) = y; - WBUFW(buf,6) = type; - mapindex_getmapname_ext(map[m].name,(char*)WBUFP(buf,8)); - - if( fd ) - { - WFIFOHEAD(fd,packet_len(0x192)); - memcpy(WFIFOP(fd,0), buf, packet_len(0x192)); - WFIFOSET(fd,packet_len(0x192)); - } - else - { - struct block_list dummy_bl; - dummy_bl.type = BL_NUL; - dummy_bl.x = x; - dummy_bl.y = y; - dummy_bl.m = m; - clif_send(buf,packet_len(0x192),&dummy_bl,target); - } -} - - -/// Notifies the client about an item on floor (ZC_ITEM_ENTRY). -/// 009d <id>.L <name id>.W <identified>.B <x>.W <y>.W <amount>.W <subX>.B <subY>.B -void clif_getareachar_item(struct map_session_data* sd,struct flooritem_data* fitem) -{ - int view,fd; - fd=sd->fd; - - WFIFOHEAD(fd,packet_len(0x9d)); - 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(0x9d)); -} - - -/// Notifies the client of a skill unit. -/// 011f <id>.L <creator id>.L <x>.W <y>.W <unit id>.B <visible>.B (ZC_SKILL_ENTRY) -/// 01c9 <id>.L <creator id>.L <x>.W <y>.W <unit id>.B <visible>.B <has msg>.B <msg>.80B (ZC_SKILL_ENTRY2) -static void clif_getareachar_skillunit(struct map_session_data *sd, struct skill_unit *unit) -{ - int fd = sd->fd; - - if( unit->group->state.guildaura ) - return; - -#if PACKETVER >= 3 - if(unit->group->unit_id==UNT_GRAFFITI) { // Graffiti [Valaris] - WFIFOHEAD(fd,packet_len(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; - WFIFOB(fd,16)=1; - safestrncpy((char*)WFIFOP(fd,17),unit->group->valstr,MESSAGE_SIZE); - WFIFOSET(fd,packet_len(0x1c9)); - return; - } -#endif - WFIFOHEAD(fd,packet_len(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; - if (battle_config.traps_setting&1 && skill_get_inf2(unit->group->skill_id)&INF2_TRAP) - WFIFOB(fd,14)=UNT_DUMMYSKILL; //Use invisible unit id for traps. - else if (skill_get_unit_flag(unit->group->skill_id) & UF_RANGEDSINGLEUNIT && !(unit->val2 & UF_RANGEDSINGLEUNIT)) - WFIFOB(fd,14)=UNT_DUMMYSKILL; //Use invisible unit id for traps. - else - WFIFOB(fd,14)=unit->group->unit_id; - WFIFOB(fd,15)=1; // ignored by client (always gets set to 1) - WFIFOSET(fd,packet_len(0x11f)); - - if(unit->group->skill_id == WZ_ICEWALL) - clif_changemapcell(fd,unit->bl.m,unit->bl.x,unit->bl.y,5,SELF); -} - - -/*========================================== - * Server tells client to remove unit of id 'unit->bl.id' - *------------------------------------------*/ -static void clif_clearchar_skillunit(struct skill_unit *unit, int fd) -{ - nullpo_retv(unit); - - WFIFOHEAD(fd,packet_len(0x120)); - WFIFOW(fd, 0)=0x120; - WFIFOL(fd, 2)=unit->bl.id; - WFIFOSET(fd,packet_len(0x120)); - - if(unit->group && unit->group->skill_id == WZ_ICEWALL) - clif_changemapcell(fd,unit->bl.m,unit->bl.x,unit->bl.y,unit->val2,SELF); -} - - -/// Removes a skill unit (ZC_SKILL_DISAPPEAR). -/// 0120 <id>.L -void clif_skill_delunit(struct skill_unit *unit) -{ - unsigned char buf[16]; - - nullpo_retv(unit); - - WBUFW(buf, 0)=0x120; - WBUFL(buf, 2)=unit->bl.id; - clif_send(buf,packet_len(0x120),&unit->bl,AREA); -} - - -/// Sent when an object gets ankle-snared (ZC_SKILL_UPDATE). -/// 01ac <id>.L -/// Only affects units with class [139,153] client-side. -void clif_skillunit_update(struct block_list* bl) -{ - unsigned char buf[6]; - nullpo_retv(bl); - - WBUFW(buf,0) = 0x1ac; - WBUFL(buf,2) = bl->id; - - clif_send(buf,packet_len(0x1ac),bl,AREA); -} - - -/*========================================== - * - *------------------------------------------*/ -static int clif_getareachar(struct block_list* bl,va_list ap) -{ - struct map_session_data *sd; - - nullpo_ret(bl); - - sd=va_arg(ap,struct map_session_data*); - - if (sd == NULL || !sd->fd) - return 0; - - switch(bl->type){ - case BL_ITEM: - clif_getareachar_item(sd,(struct flooritem_data*) bl); - break; - case BL_SKILL: - clif_getareachar_skillunit(sd,(TBL_SKILL*)bl); - break; - default: - if(&sd->bl == bl) - break; - clif_getareachar_unit(sd,bl); - break; - } - return 0; -} - -/*========================================== - * tbl has gone out of view-size of bl - *------------------------------------------*/ -int clif_outsight(struct block_list *bl,va_list ap) -{ - struct block_list *tbl; - struct view_data *vd; - TBL_PC *sd, *tsd; - tbl=va_arg(ap,struct block_list*); - if(bl == tbl) return 0; - sd = BL_CAST(BL_PC, bl); - tsd = BL_CAST(BL_PC, tbl); - - if (tsd && tsd->fd) - { //tsd has lost sight of the bl object. - switch(bl->type){ - case BL_PC: - if (sd->vd.class_ != INVISIBLE_CLASS) - clif_clearunit_single(bl->id,CLR_OUTSIGHT,tsd->fd); - if(sd->chatID){ - struct chat_data *cd; - cd=(struct chat_data*)map_id2bl(sd->chatID); - if(cd->usersd[0]==sd) - clif_dispchat(cd,tsd->fd); - } - if( sd->state.vending ) - clif_closevendingboard(bl,tsd->fd); - if( sd->state.buyingstore ) - clif_buyingstore_disappear_entry_single(tsd, sd); - break; - case BL_ITEM: - clif_clearflooritem((struct flooritem_data*)bl,tsd->fd); - break; - case BL_SKILL: - clif_clearchar_skillunit((struct skill_unit *)bl,tsd->fd); - break; - case BL_NPC: - if( !(((TBL_NPC*)bl)->sc.option&OPTION_INVISIBLE) ) - clif_clearunit_single(bl->id,CLR_OUTSIGHT,tsd->fd); - break; - default: - if ((vd=status_get_viewdata(bl)) && vd->class_ != INVISIBLE_CLASS) - clif_clearunit_single(bl->id,CLR_OUTSIGHT,tsd->fd); - break; - } - } - if (sd && sd->fd) - { //sd is watching tbl go out of view. - if (((vd=status_get_viewdata(tbl)) && vd->class_ != INVISIBLE_CLASS) && - !(tbl->type == BL_NPC && (((TBL_NPC*)tbl)->sc.option&OPTION_INVISIBLE))) - clif_clearunit_single(tbl->id,CLR_OUTSIGHT,sd->fd); - } - return 0; -} - -/*========================================== - * tbl has come into view of bl - *------------------------------------------*/ -int clif_insight(struct block_list *bl,va_list ap) -{ - struct block_list *tbl; - TBL_PC *sd, *tsd; - tbl=va_arg(ap,struct block_list*); - - if (bl == tbl) return 0; - - sd = BL_CAST(BL_PC, bl); - tsd = BL_CAST(BL_PC, tbl); - - if (tsd && tsd->fd) - { //Tell tsd that bl entered into his view - switch(bl->type){ - case BL_ITEM: - clif_getareachar_item(tsd,(struct flooritem_data*)bl); - break; - case BL_SKILL: - clif_getareachar_skillunit(tsd,(TBL_SKILL*)bl); - break; - default: - clif_getareachar_unit(tsd,bl); - break; - } - } - if (sd && sd->fd) - { //Tell sd that tbl walked into his view - clif_getareachar_unit(sd,tbl); - } - return 0; -} - - -/// Updates whole skill tree (ZC_SKILLINFO_LIST). -/// 010f <packet len>.W { <skill id>.W <type>.L <level>.W <sp cost>.W <attack range>.W <skill name>.24B <upgradable>.B }* -void clif_skillinfoblock(struct map_session_data *sd) -{ - int fd; - int i,len,id; - - nullpo_retv(sd); - - fd=sd->fd; - if (!fd) return; - - WFIFOHEAD(fd, MAX_SKILL * 37 + 4); - WFIFOW(fd,0) = 0x10f; - for ( i = 0, len = 4; i < MAX_SKILL; i++) - { - if( (id = sd->status.skill[i].id) != 0 ) - { - // workaround for bugreport:5348 - if (len + 37 > 8192) - break; - - WFIFOW(fd,len) = id; - WFIFOL(fd,len+2) = skill_get_inf(id); - WFIFOW(fd,len+6) = sd->status.skill[i].lv; - WFIFOW(fd,len+8) = skill_get_sp(id,sd->status.skill[i].lv); - WFIFOW(fd,len+10)= skill_get_range2(&sd->bl, id,sd->status.skill[i].lv); - safestrncpy((char*)WFIFOP(fd,len+12), skill_get_name(id), NAME_LENGTH); - if(sd->status.skill[i].flag == SKILL_FLAG_PERMANENT) - WFIFOB(fd,len+36) = (sd->status.skill[i].lv < skill_tree_get_max(id, sd->status.class_))? 1:0; - else - WFIFOB(fd,len+36) = 0; - len += 37; - } - } - WFIFOW(fd,2)=len; - WFIFOSET(fd,len); - - // workaround for bugreport:5348; send the remaining skills one by one to bypass packet size limit - for ( ; i < MAX_SKILL; i++) - { - if( (id = sd->status.skill[i].id) != 0 ) - { - clif_addskill(sd, id); - clif_skillinfo(sd, id, 0); - } - } -} -/** - * Server tells client 'sd' to add skill of id 'id' to it's skill tree (e.g. with Ice Falcion item) - **/ - -/// Adds new skill to the skill tree (ZC_ADD_SKILL). -/// 0111 <skill id>.W <type>.L <level>.W <sp cost>.W <attack range>.W <skill name>.24B <upgradable>.B -void clif_addskill(struct map_session_data *sd, int id) -{ - int fd; - - nullpo_retv(sd); - - fd = sd->fd; - if (!fd) return; - - if( sd->status.skill[id].id <= 0 ) - return; - - WFIFOHEAD(fd, packet_len(0x111)); - WFIFOW(fd,0) = 0x111; - WFIFOW(fd,2) = id; - WFIFOL(fd,4) = skill_get_inf(id); - WFIFOW(fd,8) = sd->status.skill[id].lv; - WFIFOW(fd,10) = skill_get_sp(id,sd->status.skill[id].lv); - WFIFOW(fd,12)= skill_get_range2(&sd->bl, id,sd->status.skill[id].lv); - safestrncpy((char*)WFIFOP(fd,14), skill_get_name(id), NAME_LENGTH); - if( sd->status.skill[id].flag == SKILL_FLAG_PERMANENT ) - WFIFOB(fd,38) = (sd->status.skill[id].lv < skill_tree_get_max(id, sd->status.class_))? 1:0; - else - WFIFOB(fd,38) = 0; - WFIFOSET(fd,packet_len(0x111)); -} - - -/// Deletes a skill from the skill tree (ZC_SKILLINFO_DELETE). -/// 0441 <skill id>.W -void clif_deleteskill(struct map_session_data *sd, int id) -{ -#if PACKETVER >= 20081217 - int fd; - - nullpo_retv(sd); - fd = sd->fd; - if( !fd ) return; - - WFIFOHEAD(fd,packet_len(0x441)); - WFIFOW(fd,0) = 0x441; - WFIFOW(fd,2) = id; - WFIFOSET(fd,packet_len(0x441)); -#endif - clif_skillinfoblock(sd); -} - - -/// Updates a skill in the skill tree (ZC_SKILLINFO_UPDATE). -/// 010e <skill id>.W <level>.W <sp cost>.W <attack range>.W <upgradable>.B -void clif_skillup(struct map_session_data *sd,uint16 skill_id) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x10e)); - WFIFOW(fd,0) = 0x10e; - WFIFOW(fd,2) = skill_id; - WFIFOW(fd,4) = sd->status.skill[skill_id].lv; - WFIFOW(fd,6) = skill_get_sp(skill_id,sd->status.skill[skill_id].lv); - WFIFOW(fd,8) = skill_get_range2(&sd->bl,skill_id,sd->status.skill[skill_id].lv); - WFIFOB(fd,10) = (sd->status.skill[skill_id].lv < skill_tree_get_max(sd->status.skill[skill_id].id, sd->status.class_)) ? 1 : 0; - WFIFOSET(fd,packet_len(0x10e)); -} - - -/// Updates a skill in the skill tree (ZC_SKILLINFO_UPDATE2). -/// 07e1 <skill id>.W <type>.L <level>.W <sp cost>.W <attack range>.W <upgradable>.B -void clif_skillinfo(struct map_session_data *sd,int skill, int inf) -{ - const int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x7e1)); - WFIFOW(fd,0) = 0x7e1; - WFIFOW(fd,2) = skill; - WFIFOL(fd,4) = inf?inf:skill_get_inf(skill); - WFIFOW(fd,8) = sd->status.skill[skill].lv; - WFIFOW(fd,10) = skill_get_sp(skill,sd->status.skill[skill].lv); - WFIFOW(fd,12) = skill_get_range2(&sd->bl,skill,sd->status.skill[skill].lv); - if( sd->status.skill[skill].flag == SKILL_FLAG_PERMANENT ) - WFIFOB(fd,14) = (sd->status.skill[skill].lv < skill_tree_get_max(skill, sd->status.class_))? 1:0; - else - WFIFOB(fd,14) = 0; - WFIFOSET(fd,packet_len(0x7e1)); -} - - -/// Notifies clients in area, that an object is about to use a skill. -/// 013e <src id>.L <dst id>.L <x>.W <y>.W <skill id>.W <property>.L <delaytime>.L (ZC_USESKILL_ACK) -/// 07fb <src id>.L <dst id>.L <x>.W <y>.W <skill id>.W <property>.L <delaytime>.L <is disposable>.B (ZC_USESKILL_ACK2) -/// property: -/// 0 = Yellow cast aura -/// 1 = Water elemental cast aura -/// 2 = Earth elemental cast aura -/// 3 = Fire elemental cast aura -/// 4 = Wind elemental cast aura -/// 5 = Poison elemental cast aura -/// 6 = Holy elemental cast aura -/// ? = like 0 -/// is disposable: -/// 0 = yellow chat text "[src name] will use skill [skill name]." -/// 1 = no text -void clif_skillcasting(struct block_list* bl, int src_id, int dst_id, int dst_x, int dst_y, uint16 skill_id, int property, int casttime) -{ -#if PACKETVER < 20091124 - const int cmd = 0x13e; -#else - const int cmd = 0x7fb; -#endif - unsigned char buf[32]; - - WBUFW(buf,0) = cmd; - WBUFL(buf,2) = src_id; - WBUFL(buf,6) = dst_id; - WBUFW(buf,10) = dst_x; - WBUFW(buf,12) = dst_y; - WBUFW(buf,14) = skill_id; - WBUFL(buf,16) = property<0?0:property; //Avoid sending negatives as element [Skotlex] - WBUFL(buf,20) = casttime; -#if PACKETVER >= 20091124 - WBUFB(buf,24) = 0; // isDisposable -#endif - - if (disguised(bl)) { - clif_send(buf,packet_len(cmd), bl, AREA_WOS); - WBUFL(buf,2) = -src_id; - clif_send(buf,packet_len(cmd), bl, SELF); - } else - clif_send(buf,packet_len(cmd), bl, AREA); -} - - -/// Notifies clients in area, that an object canceled casting (ZC_DISPEL). -/// 01b9 <id>.L -void clif_skillcastcancel(struct block_list* bl) -{ - unsigned char buf[16]; - - nullpo_retv(bl); - - WBUFW(buf,0) = 0x1b9; - WBUFL(buf,2) = bl->id; - clif_send(buf,packet_len(0x1b9), bl, AREA); -} - - -/// Notifies the client about the result of a skill use request (ZC_ACK_TOUSESKILL). -/// 0110 <skill id>.W <num>.L <result>.B <cause>.B -/// num (only used when skill id = NV_BASIC and cause = 0): -/// 0 = "skill failed" MsgStringTable[159] -/// 1 = "no emotions" MsgStringTable[160] -/// 2 = "no sit" MsgStringTable[161] -/// 3 = "no chat" MsgStringTable[162] -/// 4 = "no party" MsgStringTable[163] -/// 5 = "no shout" MsgStringTable[164] -/// 6 = "no PKing" MsgStringTable[165] -/// 7 = "no alligning" MsgStringTable[383] -/// ? = ignored -/// cause: -/// 0 = "not enough skill level" MsgStringTable[214] (AL_WARP) -/// "steal failed" MsgStringTable[205] (TF_STEAL) -/// "envenom failed" MsgStringTable[207] (TF_POISON) -/// "skill failed" MsgStringTable[204] (otherwise) -/// ... = @see enum useskill_fail_cause -/// ? = ignored -/// -/// if(result!=0) doesn't display any of the previous messages -/// Note: when this packet is received an unknown flag is always set to 0, -/// suggesting this is an ACK packet for the UseSkill packets and should be sent on success too [FlavioJS] -void clif_skill_fail(struct map_session_data *sd,uint16 skill_id,enum useskill_fail_cause cause,int btype) -{ - int fd; - - if (!sd) { //Since this is the most common nullpo.... - ShowDebug("clif_skill_fail: Error, received NULL sd for skill %d\n", skill_id); - return; - } - - fd=sd->fd; - if (!fd) return; - - if(battle_config.display_skill_fail&1) - return; //Disable all skill failed messages - - if(cause==USESKILL_FAIL_SKILLINTERVAL && !sd->state.showdelay) - return; //Disable delay failed messages - - if(skill_id == RG_SNATCHER && battle_config.display_skill_fail&4) - return; - - if(skill_id == TF_POISON && battle_config.display_skill_fail&8) - return; - - WFIFOHEAD(fd,packet_len(0x110)); - WFIFOW(fd,0) = 0x110; - WFIFOW(fd,2) = skill_id; - WFIFOL(fd,4) = btype; - WFIFOB(fd,8) = 0;// success - WFIFOB(fd,9) = cause; - WFIFOSET(fd,packet_len(0x110)); -} - - -/// Skill cooldown display icon (ZC_SKILL_POSTDELAY). -/// 043d <skill ID>.W <tick>.L -void clif_skill_cooldown(struct map_session_data *sd, uint16 skill_id, unsigned int tick) -{ -#if PACKETVER>=20081112 - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x43d)); - WFIFOW(fd,0) = 0x43d; - WFIFOW(fd,2) = skill_id; - WFIFOL(fd,4) = tick; - WFIFOSET(fd,packet_len(0x43d)); -#endif -} - - -/// Skill attack effect and damage. -/// 0114 <skill id>.W <src id>.L <dst id>.L <tick>.L <src delay>.L <dst delay>.L <damage>.W <level>.W <div>.W <type>.B (ZC_NOTIFY_SKILL) -/// 01de <skill id>.W <src id>.L <dst id>.L <tick>.L <src delay>.L <dst delay>.L <damage>.L <level>.W <div>.W <type>.B (ZC_NOTIFY_SKILL2) -int clif_skill_damage(struct block_list *src,struct block_list *dst,unsigned int tick,int sdelay,int ddelay,int damage,int div,uint16 skill_id,uint16 skill_lv,int type) -{ - unsigned char buf[64]; - struct status_change *sc; - - nullpo_ret(src); - nullpo_ret(dst); - - type = clif_calc_delay(type,div,damage,ddelay); - sc = status_get_sc(dst); - if(sc && sc->count) { - if(sc->data[SC_HALLUCINATION] && damage) - damage = damage*(sc->data[SC_HALLUCINATION]->val2) + rnd()%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; - if (battle_config.hide_woe_damage && map_flag_gvg(src->m)) { - WBUFW(buf,24)=damage?div:0; - } else { - WBUFW(buf,24)=damage; - } - WBUFW(buf,26)=skill_lv; - WBUFW(buf,28)=div; - WBUFB(buf,30)=type; - if (disguised(dst)) { - clif_send(buf,packet_len(0x114),dst,AREA_WOS); - WBUFL(buf,8)=-dst->id; - clif_send(buf,packet_len(0x114),dst,SELF); - } else - clif_send(buf,packet_len(0x114),dst,AREA); - - if(disguised(src)) { - WBUFL(buf,4)=-src->id; - if (disguised(dst)) - WBUFL(buf,8)=dst->id; - if(damage > 0) - WBUFW(buf,24)=-1; - clif_send(buf,packet_len(0x114),src,SELF); - } -#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; - if (battle_config.hide_woe_damage && map_flag_gvg(src->m)) { - WBUFL(buf,24)=damage?div:0; - } else { - WBUFL(buf,24)=damage; - } - WBUFW(buf,28)=skill_lv; - WBUFW(buf,30)=div; - WBUFB(buf,32)=type; - if (disguised(dst)) { - clif_send(buf,packet_len(0x1de),dst,AREA_WOS); - WBUFL(buf,8)=-dst->id; - clif_send(buf,packet_len(0x1de),dst,SELF); - } else - clif_send(buf,packet_len(0x1de),dst,AREA); - - if(disguised(src)) { - WBUFL(buf,4)=-src->id; - if (disguised(dst)) - WBUFL(buf,8)=dst->id; - if(damage > 0) - WBUFL(buf,24)=-1; - clif_send(buf,packet_len(0x1de),src,SELF); - } -#endif - - //Because the damage delay must be synced with the client, here is where the can-walk tick must be updated. [Skotlex] - return clif_calc_walkdelay(dst,ddelay,type,damage,div); -} - - -/// Ground skill attack effect and damage (ZC_NOTIFY_SKILL_POSITION). -/// 0115 <skill id>.W <src id>.L <dst id>.L <tick>.L <src delay>.L <dst delay>.L <x>.W <y>.W <damage>.W <level>.W <div>.W <type>.B -/* -int clif_skill_damage2(struct block_list *src,struct block_list *dst,unsigned int tick,int sdelay,int ddelay,int damage,int div,uint16 skill_id,uint16 skill_lv,int type) -{ - unsigned char buf[64]; - struct status_change *sc; - - nullpo_ret(src); - nullpo_ret(dst); - - type = (type>0)?type:skill_get_hit(skill_id); - type = clif_calc_delay(type,div,damage,ddelay); - sc = status_get_sc(dst); - - if(sc && sc->count) { - if(sc->data[SC_HALLUCINATION] && damage) - damage = damage*(sc->data[SC_HALLUCINATION]->val2) + rnd()%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; - if (battle_config.hide_woe_damage && map_flag_gvg(src->m)) { - WBUFW(buf,28)=damage?div:0; - } else { - WBUFW(buf,28)=damage; - } - WBUFW(buf,30)=skill_lv; - WBUFW(buf,32)=div; - WBUFB(buf,34)=type; - clif_send(buf,packet_len(0x115),src,AREA); - if(disguised(src)) { - WBUFL(buf,4)=-src->id; - if(damage > 0) - WBUFW(buf,28)=-1; - clif_send(buf,packet_len(0x115),src,SELF); - } - if (disguised(dst)) { - WBUFL(buf,8)=-dst->id; - if (disguised(src)) - WBUFL(buf,4)=src->id; - else if(damage > 0) - WBUFW(buf,28)=-1; - clif_send(buf,packet_len(0x115),dst,SELF); - } - - //Because the damage delay must be synced with the client, here is where the can-walk tick must be updated. [Skotlex] - return clif_calc_walkdelay(dst,ddelay,type,damage,div); -} -*/ - - -/// Non-damaging skill effect (ZC_USE_SKILL). -/// 011a <skill id>.W <skill lv>.W <dst id>.L <src id>.L <result>.B -int clif_skill_nodamage(struct block_list *src,struct block_list *dst,uint16 skill_id,int heal,int fail) -{ - unsigned char buf[32]; - - nullpo_ret(dst); - - WBUFW(buf,0)=0x11a; - WBUFW(buf,2)=skill_id; - WBUFW(buf,4)=min(heal, INT16_MAX); - WBUFL(buf,6)=dst->id; - WBUFL(buf,10)=src?src->id:0; - WBUFB(buf,14)=fail; - - if (disguised(dst)) { - clif_send(buf,packet_len(0x11a),dst,AREA_WOS); - WBUFL(buf,6)=-dst->id; - clif_send(buf,packet_len(0x11a),dst,SELF); - } else - clif_send(buf,packet_len(0x11a),dst,AREA); - - if(src && disguised(src)) { - WBUFL(buf,10)=-src->id; - if (disguised(dst)) - WBUFL(buf,6)=dst->id; - clif_send(buf,packet_len(0x11a),src,SELF); - } - - return fail; -} - - -/// Non-damaging ground skill effect (ZC_NOTIFY_GROUNDSKILL). -/// 0117 <skill id>.W <src id>.L <level>.W <x>.W <y>.W <tick>.L -void clif_skill_poseffect(struct block_list *src,uint16 skill_id,int val,int x,int y,int tick) -{ - unsigned char buf[32]; - - nullpo_retv(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; - if(disguised(src)) { - clif_send(buf,packet_len(0x117),src,AREA_WOS); - WBUFL(buf,4)=-src->id; - clif_send(buf,packet_len(0x117),src,SELF); - } else - clif_send(buf,packet_len(0x117),src,AREA); -} - - -/*========================================== - * Tells all client's nearby 'unit' sight range that it spawned - *------------------------------------------*/ -//FIXME: this is just an AREA version of clif_getareachar_skillunit() -void clif_skill_setunit(struct skill_unit *unit) -{ - unsigned char buf[128]; - - nullpo_retv(unit); - - if( unit->group->state.guildaura ) - return; - -#if PACKETVER >= 3 - if(unit->group->unit_id==UNT_GRAFFITI) { // Graffiti [Valaris] - 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; - WBUFB(buf,16)=1; - safestrncpy((char*)WBUFP(buf,17),unit->group->valstr,MESSAGE_SIZE); - clif_send(buf,packet_len(0x1c9),&unit->bl,AREA); - return; - } -#endif - 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; - if (unit->group->state.song_dance&0x1 && unit->val2&UF_ENSEMBLE) - WBUFB(buf,14)=unit->val2&UF_SONG?UNT_DISSONANCE:UNT_UGLYDANCE; - else if (skill_get_unit_flag(unit->group->skill_id) & UF_RANGEDSINGLEUNIT && !(unit->val2 & UF_RANGEDSINGLEUNIT)) - WBUFB(buf, 14) = UNT_DUMMYSKILL; // Only display the unit at center. - else - WBUFB(buf,14)=unit->group->unit_id; - WBUFB(buf,15)=1; // ignored by client (always gets set to 1) - clif_send(buf,packet_len(0x11f),&unit->bl,AREA); -} - - -/// Presents a list of available warp destinations (ZC_WARPLIST). -/// 011c <skill id>.W { <map name>.16B }*4 -void clif_skill_warppoint(struct map_session_data* sd, uint16 skill_id, uint16 skill_lv, unsigned short map1, unsigned short map2, unsigned short map3, unsigned short map4) -{ - int fd; - nullpo_retv(sd); - fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x11c)); - WFIFOW(fd,0) = 0x11c; - WFIFOW(fd,2) = skill_id; - memset(WFIFOP(fd,4), 0x00, 4*MAP_NAME_LENGTH_EXT); - if (map1 == (unsigned short)-1) strcpy((char*)WFIFOP(fd,4), "Random"); - else // normal map name - if (map1 > 0) mapindex_getmapname_ext(mapindex_id2name(map1), (char*)WFIFOP(fd,4)); - if (map2 > 0) mapindex_getmapname_ext(mapindex_id2name(map2), (char*)WFIFOP(fd,20)); - if (map3 > 0) mapindex_getmapname_ext(mapindex_id2name(map3), (char*)WFIFOP(fd,36)); - if (map4 > 0) mapindex_getmapname_ext(mapindex_id2name(map4), (char*)WFIFOP(fd,52)); - WFIFOSET(fd,packet_len(0x11c)); - - sd->menuskill_id = skill_id; - if (skill_id == AL_WARP) - sd->menuskill_val = (sd->ud.skillx<<16)|sd->ud.skilly; //Store warp position here. - else - sd->menuskill_val = skill_lv; -} - - -/// Memo message (ZC_ACK_REMEMBER_WARPPOINT). -/// 011e <type>.B -/// type: -/// 0 = "Saved location as a Memo Point for Warp skill." in color 0xFFFF00 (cyan) -/// 1 = "Skill Level is not high enough." in color 0x0000FF (red) -/// 2 = "You haven't learned Warp." in color 0x0000FF (red) -/// -/// @param sd Who receives the message -/// @param type What message -void clif_skill_memomessage(struct map_session_data* sd, int type) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x11e)); - WFIFOW(fd,0)=0x11e; - WFIFOB(fd,2)=type; - WFIFOSET(fd,packet_len(0x11e)); -} - - -/// Teleport message (ZC_NOTIFY_MAPINFO). -/// 0189 <type>.W -/// type: -/// 0 = "Unable to Teleport in this area" in color 0xFFFF00 (cyan) -/// 1 = "Saved point cannot be memorized." in color 0x0000FF (red) -/// -/// @param sd Who receives the message -/// @param type What message -void clif_skill_teleportmessage(struct map_session_data *sd, int type) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x189)); - WFIFOW(fd,0)=0x189; - WFIFOW(fd,2)=type; - WFIFOSET(fd,packet_len(0x189)); -} - - -/// Displays Sense (WZ_ESTIMATION) information window (ZC_MONSTER_INFO). -/// 018c <class>.W <level>.W <size>.W <hp>.L <def>.W <race>.W <mdef>.W <element>.W -/// <water%>.B <earth%>.B <fire%>.B <wind%>.B <poison%>.B <holy%>.B <shadow%>.B <ghost%>.B <undead%>.B -void clif_skill_estimation(struct map_session_data *sd,struct block_list *dst) -{ - struct status_data *status; - unsigned char buf[64]; - int i;//, fix; - - nullpo_retv(sd); - nullpo_retv(dst); - - if( dst->type != BL_MOB ) - return; - - status = status_get_status_data(dst); - - WBUFW(buf, 0)=0x18c; - WBUFW(buf, 2)=status_get_class(dst); - WBUFW(buf, 4)=status_get_lv(dst); - WBUFW(buf, 6)=status->size; - WBUFL(buf, 8)=status->hp; - WBUFW(buf,12)= (battle_config.estimation_type&1?status->def:0) - +(battle_config.estimation_type&2?status->def2:0); - WBUFW(buf,14)=status->race; - WBUFW(buf,16)= (battle_config.estimation_type&1?status->mdef:0) - +(battle_config.estimation_type&2?status->mdef2:0); - WBUFW(buf,18)= status->def_ele; - for(i=0;i<9;i++) - WBUFB(buf,20+i)= (unsigned char)battle_attr_ratio(i+1,status->def_ele, status->ele_lv); -// The following caps negative attributes to 0 since the client displays them as 255-fix. [Skotlex] -// WBUFB(buf,20+i)= (unsigned char)((fix=battle_attr_ratio(i+1,status->def_ele, status->ele_lv))<0?0:fix); - - clif_send(buf,packet_len(0x18c),&sd->bl,sd->status.party_id>0?PARTY_SAMEMAP:SELF); -} - - -/// Presents a textual list of producable items (ZC_MAKABLEITEMLIST). -/// 018d <packet len>.W { <name id>.W { <material id>.W }*3 }* -/// material id: -/// unused by the client -void clif_skill_produce_mix_list(struct map_session_data *sd, int skill_id , int trigger) -{ - int i,c,view,fd; - nullpo_retv(sd); - - if(sd->menuskill_id == skill_id) - return; //Avoid resending the menu twice or more times... - if( skill_id == GC_CREATENEWPOISON ) - skill_id = GC_RESEARCHNEWPOISON; - - fd=sd->fd; - WFIFOHEAD(fd, MAX_SKILL_PRODUCE_DB * 8 + 8); - 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, 1) && - ( skill_id > 0 && skill_produce_db[i].req_skill == skill_id || skill_id < 0 ) - ){ - 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)= 0; - WFIFOW(fd,c*8+ 8)= 0; - WFIFOW(fd,c*8+10)= 0; - c++; - } - } - WFIFOW(fd, 2)=c*8+8; - WFIFOSET(fd,WFIFOW(fd,2)); - if(c > 0) { - sd->menuskill_id = skill_id; - sd->menuskill_val = trigger; - return; - } -} - - -/// Present a list of producable items (ZC_MAKINGITEM_LIST). -/// 025a <packet len>.W <mk type>.W { <name id>.W }* -/// mk type: -/// 1 = cooking -/// 2 = arrow -/// 3 = elemental -/// 4 = GN_MIX_COOKING -/// 5 = GN_MAKEBOMB -/// 6 = GN_S_PHARMACY -void clif_cooking_list(struct map_session_data *sd, int trigger, uint16 skill_id, int qty, int list_type) -{ - int fd; - int i, c; - int view; - - nullpo_retv(sd); - fd = sd->fd; - - WFIFOHEAD(fd, 6 + 2 * MAX_SKILL_PRODUCE_DB); - WFIFOW(fd,0) = 0x25a; - WFIFOW(fd,4) = list_type; // list type - - c = 0; - for( i = 0; i < MAX_SKILL_PRODUCE_DB; i++ ) { - if( !skill_can_produce_mix(sd,skill_produce_db[i].nameid,trigger, qty) ) - continue; - - if( (view = itemdb_viewid(skill_produce_db[i].nameid)) > 0 ) - WFIFOW(fd, 6 + 2 * c) = view; - else - WFIFOW(fd, 6 + 2 * c) = skill_produce_db[i].nameid; - - c++; - } - - if( skill_id == AM_PHARMACY ) { // Only send it while Cooking else check for c. - WFIFOW(fd,2) = 6 + 2 * c; - WFIFOSET(fd,WFIFOW(fd,2)); - } - - if( c > 0 ) { - sd->menuskill_id = skill_id; - sd->menuskill_val = trigger; - if( skill_id != AM_PHARMACY ) { - sd->menuskill_val2 = qty; // amount. - WFIFOW(fd,2) = 6 + 2 * c; - WFIFOSET(fd,WFIFOW(fd,2)); - } - } else { - clif_menuskill_clear(sd); - if( skill_id != AM_PHARMACY ) { // AM_PHARMACY is used to Cooking. - // It fails. -#if PACKETVER >= 20090922 - clif_msg_skill(sd,skill_id,0x625); -#else - WFIFOW(fd,2) = 6 + 2 * c; - WFIFOSET(fd,WFIFOW(fd,2)); -#endif - } - } -} - - -/// Notifies clients of a status change. -/// 0196 <index>.W <id>.L <state>.B (ZC_MSG_STATE_CHANGE) [used for ending status changes and starting them on non-pc units (when needed)] -/// 043f <index>.W <id>.L <state>.B <remain msec>.L { <val>.L }*3 (ZC_MSG_STATE_CHANGE2) [used exclusively for starting statuses on pcs] -void clif_status_change(struct block_list *bl,int type,int flag,int tick,int val1, int val2, int val3) -{ - unsigned char buf[32]; - struct map_session_data *sd; - - if (type == SI_BLANK) //It shows nothing on the client... - return; - - nullpo_retv(bl); - - sd = BL_CAST(BL_PC, bl); - - if (!(status_type2relevant_bl_types(type)&bl->type)) // only send status changes that actually matter to the client - return; - -#if PACKETVER >= 20090121 - if(flag && battle_config.display_status_timers && sd) - WBUFW(buf,0)=0x43f; - else -#endif - WBUFW(buf,0)=0x196; - WBUFW(buf,2)=type; - WBUFL(buf,4)=bl->id; - WBUFB(buf,8)=flag; -#if PACKETVER >= 20090121 - if(flag && battle_config.display_status_timers && sd) - { - if (tick <= 0) - tick = 9999; // this is indeed what official servers do - - WBUFL(buf,9) = tick; - WBUFL(buf,13) = val1; - WBUFL(buf,17) = val2; - WBUFL(buf,21) = val3; - } -#endif - clif_send(buf,packet_len(WBUFW(buf,0)),bl, (sd && sd->status.option&OPTION_INVISIBLE) ? SELF : AREA); -} - -/// Send message (modified by [Yor]) (ZC_NOTIFY_PLAYERCHAT). -/// 008e <packet len>.W <message>.?B -void clif_displaymessage(const int fd, const char* mes) -{ - nullpo_retv(mes); - - //Scrapped, as these are shared by disconnected players =X [Skotlex] - if (fd == 0) - ; - else { - char *message, *line; - - message = aStrdup(mes); - line = strtok(message, "\n"); - while(line != NULL) { - // Limit message to 255+1 characters (otherwise it causes a buffer overflow in the client) - int len = strnlen(line, 255); - - if (len > 0) { // don't send a void message (it's not displaying on the client chat). @help can send void line. - WFIFOHEAD(fd, 5 + len); - WFIFOW(fd,0) = 0x8e; - WFIFOW(fd,2) = 5 + len; // 4 + len + NULL teminate - safestrncpy((char *)WFIFOP(fd,4), line, len + 1); - WFIFOSET(fd, 5 + len); - } - line = strtok(NULL, "\n"); - } - aFree(message); - } -} - -/// Send broadcast message in yellow or blue without font formatting (ZC_BROADCAST). -/// 009a <packet len>.W <message>.?B -void clif_broadcast(struct block_list* bl, const char* mes, int len, int type, enum send_target target) -{ - int lp = type ? 4 : 0; - unsigned char *buf = (unsigned char*)aMalloc((4 + lp + len)*sizeof(unsigned char)); - - WBUFW(buf,0) = 0x9a; - WBUFW(buf,2) = 4 + lp + len; - if (type == 0x10) // bc_blue - WBUFL(buf,4) = 0x65756c62; //If there's "blue" at the beginning of the message, game client will display it in blue instead of yellow. - else if (type == 0x20) // bc_woe - WBUFL(buf,4) = 0x73737373; //If there's "ssss", game client will recognize message as 'WoE broadcast'. - memcpy(WBUFP(buf, 4 + lp), mes, len); - clif_send(buf, WBUFW(buf,2), bl, target); - - if (buf) - aFree(buf); -} - -/*========================================== - * Displays a message on a 'bl' to all it's nearby clients - * Used by npc_globalmessage - *------------------------------------------*/ -void clif_GlobalMessage(struct block_list* bl, const char* message) { - char buf[100]; - int len; - nullpo_retv(bl); - - if(!message) - return; - - len = strlen(message)+1; - - if( len > sizeof(buf)-8 ) { - ShowWarning("clif_GlobalMessage: Truncating too long message '%s' (len=%d).\n", message, len); - len = sizeof(buf)-8; - } - - WBUFW(buf,0)=0x8d; - WBUFW(buf,2)=len+8; - WBUFL(buf,4)=bl->id; - safestrncpy((char *) WBUFP(buf,8),message,len); - clif_send((unsigned char *) buf,WBUFW(buf,2),bl,ALL_CLIENT); - -} - -/*========================================== - * Send main chat message [LuzZza] - *------------------------------------------*/ -void clif_MainChatMessage(const char* message) { - uint8 buf[200]; - int len; - - if(!message) - return; - - len = strlen(message)+1; - if (len+8 > sizeof(buf)) { - ShowDebug("clif_MainChatMessage: Received message too long (len %d): %s\n", len, message); - len = sizeof(buf)-8; - } - WBUFW(buf,0)=0x8d; - WBUFW(buf,2)=len+8; - WBUFL(buf,4)=0; - safestrncpy((char *) WBUFP(buf,8),message,len); - clif_send(buf,WBUFW(buf,2),NULL,CHAT_MAINCHAT); -} - -/// Send broadcast message with font formatting (ZC_BROADCAST2). -/// 01c3 <packet len>.W <fontColor>.L <fontType>.W <fontSize>.W <fontAlign>.W <fontY>.W <message>.?B -void clif_broadcast2(struct block_list* bl, const char* mes, int len, unsigned long fontColor, short fontType, short fontSize, short fontAlign, short fontY, enum send_target target) -{ - unsigned char *buf = (unsigned char*)aMalloc((16 + len)*sizeof(unsigned char)); - - WBUFW(buf,0) = 0x1c3; - WBUFW(buf,2) = len + 16; - WBUFL(buf,4) = fontColor; - WBUFW(buf,8) = fontType; - WBUFW(buf,10) = fontSize; - WBUFW(buf,12) = fontAlign; - WBUFW(buf,14) = fontY; - memcpy(WBUFP(buf,16), mes, len); - clif_send(buf, WBUFW(buf,2), bl, target); - - if (buf) - aFree(buf); -} - - -/// Displays heal effect (ZC_RECOVERY). -/// 013d <var id>.W <amount>.W -/// var id: -/// 5 = HP (SP_HP) -/// 7 = SP (SP_SP) -/// ? = ignored -void clif_heal(int fd,int type,int val) -{ - WFIFOHEAD(fd,packet_len(0x13d)); - WFIFOW(fd,0)=0x13d; - WFIFOW(fd,2)=type; - WFIFOW(fd,4)=cap_value(val,0,INT16_MAX); - WFIFOSET(fd,packet_len(0x13d)); -} - - -/// Displays resurrection effect (ZC_RESURRECTION). -/// 0148 <id>.L <type>.W -/// type: -/// ignored -void clif_resurrection(struct block_list *bl,int type) -{ - unsigned char buf[16]; - - nullpo_retv(bl); - - WBUFW(buf,0)=0x148; - WBUFL(buf,2)=bl->id; - WBUFW(buf,6)=0; - - clif_send(buf,packet_len(0x148),bl,type==1 ? AREA : AREA_WOS); - if (disguised(bl)) - clif_spawn(bl); -} - - -/// Sets the map property (ZC_NOTIFY_MAPPROPERTY). -/// 0199 <type>.W -void clif_map_property(struct map_session_data* sd, enum map_property property) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x199)); - WFIFOW(fd,0)=0x199; - WFIFOW(fd,2)=property; - WFIFOSET(fd,packet_len(0x199)); -} - - -/// Set the map type (ZC_NOTIFY_MAPPROPERTY2). -/// 01d6 <type>.W -void clif_map_type(struct map_session_data* sd, enum map_type type) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x1D6)); - WFIFOW(fd,0)=0x1D6; - WFIFOW(fd,2)=type; - WFIFOSET(fd,packet_len(0x1D6)); -} - - -/// Updates PvP ranking (ZC_NOTIFY_RANKING). -/// 019a <id>.L <ranking>.L <total>.L -void clif_pvpset(struct map_session_data *sd,int pvprank,int pvpnum,int type) -{ - if(type == 2) { - int fd = sd->fd; - WFIFOHEAD(fd,packet_len(0x19a)); - WFIFOW(fd,0) = 0x19a; - WFIFOL(fd,2) = sd->bl.id; - WFIFOL(fd,6) = pvprank; - WFIFOL(fd,10) = pvpnum; - WFIFOSET(fd,packet_len(0x19a)); - } else { - unsigned char buf[32]; - WBUFW(buf,0) = 0x19a; - WBUFL(buf,2) = sd->bl.id; - if(sd->sc.option&(OPTION_HIDE|OPTION_CLOAK)) - WBUFL(buf,6) = UINT32_MAX; //On client displays as -- - else - WBUFL(buf,6) = pvprank; - WBUFL(buf,10) = pvpnum; - if(sd->sc.option&OPTION_INVISIBLE || sd->disguise) //Causes crashes when a 'mob' with pvp info dies. - clif_send(buf,packet_len(0x19a),&sd->bl,SELF); - else if(!type) - clif_send(buf,packet_len(0x19a),&sd->bl,AREA); - else - clif_send(buf,packet_len(0x19a),&sd->bl,ALL_SAMEMAP); - } -} - - -/*========================================== - * - *------------------------------------------*/ -void clif_map_property_mapall(int map, enum map_property property) -{ - struct block_list bl; - unsigned char buf[16]; - - bl.id = 0; - bl.type = BL_NUL; - bl.m = map; - WBUFW(buf,0)=0x199; - WBUFW(buf,2)=property; - clif_send(buf,packet_len(0x199),&bl,ALL_SAMEMAP); -} - - -/// Notifies the client about the result of a refine attempt (ZC_ACK_ITEMREFINING). -/// 0188 <result>.W <index>.W <refine>.W -/// result: -/// 0 = success -/// 1 = failure -/// 2 = downgrade -void clif_refine(int fd, int fail, int index, int val) -{ - WFIFOHEAD(fd,packet_len(0x188)); - WFIFOW(fd,0)=0x188; - WFIFOW(fd,2)=fail; - WFIFOW(fd,4)=index+2; - WFIFOW(fd,6)=val; - WFIFOSET(fd,packet_len(0x188)); -} - - -/// Notifies the client about the result of a weapon refine attempt (ZC_ACK_WEAPONREFINE). -/// 0223 <result>.L <nameid>.W -/// result: -/// 0 = "weapon upgraded: %s" MsgStringTable[911] in rgb(0,255,255) -/// 1 = "weapon upgraded: %s" MsgStringTable[912] in rgb(0,205,205) -/// 2 = "cannot upgrade %s until you level up the upgrade weapon skill" MsgStringTable[913] in rgb(255,200,200) -/// 3 = "you lack the item %s to upgrade the weapon" MsgStringTable[914] in rgb(255,200,200) -void clif_upgrademessage(int fd, int result, int item_id) -{ - WFIFOHEAD(fd,packet_len(0x223)); - WFIFOW(fd,0)=0x223; - WFIFOL(fd,2)=result; - WFIFOW(fd,6)=item_id; - WFIFOSET(fd,packet_len(0x223)); -} - - -/// Whisper is transmitted to the destination player (ZC_WHISPER). -/// 0097 <packet len>.W <nick>.24B <message>.?B -/// 0097 <packet len>.W <nick>.24B <isAdmin>.L <message>.?B (PACKETVER >= 20091104) -void clif_wis_message(int fd, const char* nick, const char* mes, int mes_len) -{ -#if PACKETVER < 20091104 - WFIFOHEAD(fd, mes_len + NAME_LENGTH + 4); - WFIFOW(fd,0) = 0x97; - WFIFOW(fd,2) = mes_len + NAME_LENGTH + 4; - safestrncpy((char*)WFIFOP(fd,4), nick, NAME_LENGTH); - safestrncpy((char*)WFIFOP(fd,28), mes, mes_len); - WFIFOSET(fd,WFIFOW(fd,2)); -#else - WFIFOHEAD(fd, mes_len + NAME_LENGTH + 8); - WFIFOW(fd,0) = 0x97; - WFIFOW(fd,2) = mes_len + NAME_LENGTH + 8; - safestrncpy((char*)WFIFOP(fd,4), nick, NAME_LENGTH); - WFIFOL(fd,28) = 0; // isAdmin; if nonzero, also displays text above char - // TODO: WFIFOL(fd,28) = pc_get_group_level(ssd); - safestrncpy((char*)WFIFOP(fd,32), mes, mes_len); - WFIFOSET(fd,WFIFOW(fd,2)); -#endif -} - - -/// Inform the player about the result of his whisper action (ZC_ACK_WHISPER). -/// 0098 <result>.B -/// result: -/// 0 = success to send wisper -/// 1 = target character is not loged in -/// 2 = ignored by target -/// 3 = everyone ignored by target -void clif_wis_end(int fd, int flag) -{ - WFIFOHEAD(fd,packet_len(0x98)); - WFIFOW(fd,0) = 0x98; - WFIFOW(fd,2) = flag; - WFIFOSET(fd,packet_len(0x98)); -} - - -/// Returns character name requested by char_id (ZC_ACK_REQNAME_BYGID). -/// 0194 <char id>.L <name>.24B -void clif_solved_charname(int fd, int charid, const char* name) -{ - WFIFOHEAD(fd,packet_len(0x194)); - WFIFOW(fd,0)=0x194; - WFIFOL(fd,2)=charid; - safestrncpy((char*)WFIFOP(fd,6), name, NAME_LENGTH); - WFIFOSET(fd,packet_len(0x194)); -} - - -/// Presents a list of items that can be carded/composed (ZC_ITEMCOMPOSITION_LIST). -/// 017b <packet len>.W { <name id>.W }* -void clif_use_card(struct map_session_data *sd,int idx) -{ - int i,c,ep; - int fd=sd->fd; - - nullpo_retv(sd); - if (idx < 0 || idx >= MAX_INVENTORY) //Crash-fix from bad packets. - return; - - if (!sd->inventory_data[idx] || sd->inventory_data[idx]->type != IT_CARD) - return; //Avoid parsing invalid item indexes (no card/no item) - - ep=sd->inventory_data[idx]->equip; - WFIFOHEAD(fd,MAX_INVENTORY * 2 + 4); - WFIFOW(fd,0)=0x17b; - - for(i=c=0;i<MAX_INVENTORY;i++){ - int j; - - if(sd->inventory_data[i] == NULL) - continue; - if(sd->inventory_data[i]->type!=IT_WEAPON && sd->inventory_data[i]->type!=IT_ARMOR) - continue; - if(itemdb_isspecial(sd->status.inventory[i].card[0])) //Can't slot it - continue; - - if(sd->status.inventory[i].identify==0 ) //Not identified - continue; - - if((sd->inventory_data[i]->equip&ep)==0) //Not equippable on this part. - continue; - - if(sd->inventory_data[i]->type==IT_WEAPON && ep==EQP_SHIELD) //Shield card won't go on left weapon. - continue; - - ARR_FIND( 0, sd->inventory_data[i]->slot, j, sd->status.inventory[i].card[j] == 0 ); - if( j == sd->inventory_data[i]->slot ) // No room - continue; - - WFIFOW(fd,4+c*2)=i+2; - c++; - } - WFIFOW(fd,2)=4+c*2; - WFIFOSET(fd,WFIFOW(fd,2)); -} - - -/// Notifies the client about the result of item carding/composition (ZC_ACK_ITEMCOMPOSITION). -/// 017d <equip index>.W <card index>.W <result>.B -/// result: -/// 0 = success -/// 1 = failure -void clif_insert_card(struct map_session_data *sd,int idx_equip,int idx_card,int flag) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x17d)); - WFIFOW(fd,0)=0x17d; - WFIFOW(fd,2)=idx_equip+2; - WFIFOW(fd,4)=idx_card+2; - WFIFOB(fd,6)=flag; - WFIFOSET(fd,packet_len(0x17d)); -} - - -/// Presents a list of items that can be identified (ZC_ITEMIDENTIFY_LIST). -/// 0177 <packet len>.W { <name id>.W }* -void clif_item_identify_list(struct map_session_data *sd) -{ - int i,c; - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - - WFIFOHEAD(fd,MAX_INVENTORY * 2 + 4); - WFIFOW(fd,0)=0x177; - for(i=c=0;i<MAX_INVENTORY;i++){ - if(sd->status.inventory[i].nameid > 0 && !sd->status.inventory[i].identify){ - WFIFOW(fd,c*2+4)=i+2; - c++; - } - } - if(c > 0) { - WFIFOW(fd,2)=c*2+4; - WFIFOSET(fd,WFIFOW(fd,2)); - sd->menuskill_id = MC_IDENTIFY; - sd->menuskill_val = c; - } -} - - -/// Notifies the client about the result of a item identify request (ZC_ACK_ITEMIDENTIFY). -/// 0179 <index>.W <result>.B -void clif_item_identified(struct map_session_data *sd,int idx,int flag) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x179)); - WFIFOW(fd, 0)=0x179; - WFIFOW(fd, 2)=idx+2; - WFIFOB(fd, 4)=flag; - WFIFOSET(fd,packet_len(0x179)); -} - - -/// Presents a list of items that can be repaired (ZC_REPAIRITEMLIST). -/// 01fc <packet len>.W { <index>.W <name id>.W <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W }* -void clif_item_repair_list(struct map_session_data *sd,struct map_session_data *dstsd, int lv) -{ - int i,c; - int fd; - int nameid; - - nullpo_retv(sd); - nullpo_retv(dstsd); - - fd=sd->fd; - - WFIFOHEAD(fd, MAX_INVENTORY * 13 + 4); - WFIFOW(fd,0)=0x1fc; - for(i=c=0;i<MAX_INVENTORY;i++){ - if((nameid=dstsd->status.inventory[i].nameid) > 0 && dstsd->status.inventory[i].attribute!=0){// && skill_can_repair(sd,nameid)){ - WFIFOW(fd,c*13+4) = i; - WFIFOW(fd,c*13+6) = nameid; - WFIFOB(fd,c*13+8) = dstsd->status.inventory[i].refine; - clif_addcards(WFIFOP(fd,c*13+9), &dstsd->status.inventory[i]); - c++; - } - } - if(c > 0) { - WFIFOW(fd,2)=c*13+4; - WFIFOSET(fd,WFIFOW(fd,2)); - sd->menuskill_id = BS_REPAIRWEAPON; - sd->menuskill_val = dstsd->bl.id; - sd->menuskill_val2 = lv; - }else - clif_skill_fail(sd,sd->ud.skill_id,USESKILL_FAIL_LEVEL,0); -} - - -/// Notifies the client about the result of a item repair request (ZC_ACK_ITEMREPAIR). -/// 01fe <index>.W <result>.B -/// index: -/// ignored (inventory index) -/// result: -/// 0 = Item repair success. -/// 1 = Item repair failure. -void clif_item_repaireffect(struct map_session_data *sd,int idx,int flag) -{ - int fd; - - nullpo_retv(sd); - - fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x1fe)); - WFIFOW(fd, 0)=0x1fe; - WFIFOW(fd, 2)=idx+2; - WFIFOB(fd, 4)=flag; - WFIFOSET(fd,packet_len(0x1fe)); - -} - - -/// Displays a message, that an equipment got damaged (ZC_EQUIPITEM_DAMAGED). -/// 02bb <equip location>.W <account id>.L -void clif_item_damaged(struct map_session_data* sd, unsigned short position) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x2bb)); - WFIFOW(fd,0) = 0x2bb; - WFIFOW(fd,2) = position; - WFIFOL(fd,4) = sd->bl.id; // TODO: the packet seems to be sent to other people as well, probably party and/or guild. - WFIFOSET(fd,packet_len(0x2bb)); -} - - -/// Presents a list of weapon items that can be refined [Taken from jAthena] (ZC_NOTIFY_WEAPONITEMLIST). -/// 0221 <packet len>.W { <index>.W <name id>.W <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W }* -void clif_item_refine_list(struct map_session_data *sd) -{ - int i,c; - int fd; - uint16 skill_lv; - int wlv; - int refine_item[5]; - - nullpo_retv(sd); - - skill_lv = pc_checkskill(sd,WS_WEAPONREFINE); - - fd=sd->fd; - - refine_item[0] = -1; - refine_item[1] = pc_search_inventory(sd,1010); - refine_item[2] = pc_search_inventory(sd,1011); - refine_item[3] = refine_item[4] = pc_search_inventory(sd,984); - - WFIFOHEAD(fd, MAX_INVENTORY * 13 + 4); - WFIFOW(fd,0)=0x221; - for(i=c=0;i<MAX_INVENTORY;i++){ - if(sd->status.inventory[i].nameid > 0 && sd->status.inventory[i].refine < skill_lv && - sd->status.inventory[i].identify && (wlv=itemdb_wlv(sd->status.inventory[i].nameid)) >=1 && - refine_item[wlv]!=-1 && !(sd->status.inventory[i].equip&EQP_ARMS)){ - WFIFOW(fd,c*13+ 4)=i+2; - WFIFOW(fd,c*13+ 6)=sd->status.inventory[i].nameid; - WFIFOB(fd,c*13+ 8)=sd->status.inventory[i].refine; - clif_addcards(WFIFOP(fd,c*13+9), &sd->status.inventory[i]); - c++; - } - } - WFIFOW(fd,2)=c*13+4; - WFIFOSET(fd,WFIFOW(fd,2)); - if (c > 0) { - sd->menuskill_id = WS_WEAPONREFINE; - sd->menuskill_val = skill_lv; - } -} - - -/// Notification of an auto-casted skill (ZC_AUTORUN_SKILL). -/// 0147 <skill id>.W <type>.L <level>.W <sp cost>.W <atk range>.W <skill name>.24B <upgradable>.B -void clif_item_skill(struct map_session_data *sd,uint16 skill_id,uint16 skill_lv) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x147)); - WFIFOW(fd, 0)=0x147; - WFIFOW(fd, 2)=skill_id; - WFIFOW(fd, 4)=skill_get_inf(skill_id); - WFIFOW(fd, 6)=0; - WFIFOW(fd, 8)=skill_lv; - WFIFOW(fd,10)=skill_get_sp(skill_id,skill_lv); - WFIFOW(fd,12)=skill_get_range2(&sd->bl, skill_id,skill_lv); - safestrncpy((char*)WFIFOP(fd,14),skill_get_name(skill_id),NAME_LENGTH); - WFIFOB(fd,38)=0; - WFIFOSET(fd,packet_len(0x147)); -} - - -/// Adds an item to character's cart. -/// 0124 <index>.W <amount>.L <name id>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W (ZC_ADD_ITEM_TO_CART) -/// 01c5 <index>.W <amount>.L <name id>.W <type>.B <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W (ZC_ADD_ITEM_TO_CART2) -void clif_cart_additem(struct map_session_data *sd,int n,int amount,int fail) -{ - int view,fd; - unsigned char *buf; - - nullpo_retv(sd); - - fd=sd->fd; - if(n<0 || n>=MAX_CART || sd->status.cart[n].nameid<=0) - return; - -#if PACKETVER < 5 - WFIFOHEAD(fd,packet_len(0x124)); - buf=WFIFOP(fd,0); - 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; - WBUFB(buf,11)=sd->status.cart[n].attribute; - WBUFB(buf,12)=sd->status.cart[n].refine; - clif_addcards(WBUFP(buf,13), &sd->status.cart[n]); - WFIFOSET(fd,packet_len(0x124)); -#else - WFIFOHEAD(fd,packet_len(0x1c5)); - buf=WFIFOP(fd,0); - WBUFW(buf,0)=0x1c5; - 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)=itemdb_type(sd->status.cart[n].nameid); - WBUFB(buf,11)=sd->status.cart[n].identify; - WBUFB(buf,12)=sd->status.cart[n].attribute; - WBUFB(buf,13)=sd->status.cart[n].refine; - clif_addcards(WBUFP(buf,14), &sd->status.cart[n]); - WFIFOSET(fd,packet_len(0x1c5)); -#endif -} - - -/// Deletes an item from character's cart (ZC_DELETE_ITEM_FROM_CART). -/// 0125 <index>.W <amount>.L -void clif_cart_delitem(struct map_session_data *sd,int n,int amount) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - - WFIFOHEAD(fd,packet_len(0x125)); - WFIFOW(fd,0)=0x125; - WFIFOW(fd,2)=n+2; - WFIFOL(fd,4)=amount; - WFIFOSET(fd,packet_len(0x125)); -} - - -/// Opens the shop creation menu (ZC_OPENSTORE). -/// 012d <num>.W -/// num: -/// number of allowed item slots -void clif_openvendingreq(struct map_session_data* sd, int num) -{ - int fd; - - nullpo_retv(sd); - - fd = sd->fd; - WFIFOHEAD(fd,packet_len(0x12d)); - WFIFOW(fd,0) = 0x12d; - WFIFOW(fd,2) = num; - WFIFOSET(fd,packet_len(0x12d)); -} - - -/// Displays a vending board to target/area (ZC_STORE_ENTRY). -/// 0131 <owner id>.L <message>.80B -void clif_showvendingboard(struct block_list* bl, const char* message, int fd) -{ - unsigned char buf[128]; - - nullpo_retv(bl); - - WBUFW(buf,0) = 0x131; - WBUFL(buf,2) = bl->id; - safestrncpy((char*)WBUFP(buf,6), message, 80); - - if( fd ) { - WFIFOHEAD(fd,packet_len(0x131)); - memcpy(WFIFOP(fd,0),buf,packet_len(0x131)); - WFIFOSET(fd,packet_len(0x131)); - } else { - clif_send(buf,packet_len(0x131),bl,AREA_WOS); - } -} - - -/// Removes a vending board from screen (ZC_DISAPPEAR_ENTRY). -/// 0132 <owner id>.L -void clif_closevendingboard(struct block_list* bl, int fd) -{ - unsigned char buf[16]; - - nullpo_retv(bl); - - WBUFW(buf,0) = 0x132; - WBUFL(buf,2) = bl->id; - if( fd ) { - WFIFOHEAD(fd,packet_len(0x132)); - memcpy(WFIFOP(fd,0),buf,packet_len(0x132)); - WFIFOSET(fd,packet_len(0x132)); - } else { - clif_send(buf,packet_len(0x132),bl,AREA_WOS); - } -} - - -/// Sends a list of items in a shop. -/// R 0133 <packet len>.W <owner id>.L { <price>.L <amount>.W <index>.W <type>.B <name id>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W }* (ZC_PC_PURCHASE_ITEMLIST_FROMMC) -/// R 0800 <packet len>.W <owner id>.L <unique id>.L { <price>.L <amount>.W <index>.W <type>.B <name id>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W }* (ZC_PC_PURCHASE_ITEMLIST_FROMMC2) -void clif_vendinglist(struct map_session_data* sd, int id, struct s_vending* vending) -{ - int i,fd; - int count; - struct map_session_data* vsd; -#if PACKETVER < 20100105 - const int cmd = 0x133; - const int offset = 8; -#else - const int cmd = 0x800; - const int offset = 12; -#endif - - nullpo_retv(sd); - nullpo_retv(vending); - nullpo_retv(vsd=map_id2sd(id)); - - fd = sd->fd; - count = vsd->vend_num; - - WFIFOHEAD(fd, offset+count*22); - WFIFOW(fd,0) = cmd; - WFIFOW(fd,2) = offset+count*22; - WFIFOL(fd,4) = id; -#if PACKETVER >= 20100105 - WFIFOL(fd,8) = vsd->vender_id; -#endif - - for( i = 0; i < count; i++ ) - { - int index = vending[i].index; - struct item_data* data = itemdb_search(vsd->status.cart[index].nameid); - WFIFOL(fd,offset+ 0+i*22) = vending[i].value; - WFIFOW(fd,offset+ 4+i*22) = vending[i].amount; - WFIFOW(fd,offset+ 6+i*22) = vending[i].index + 2; - WFIFOB(fd,offset+ 8+i*22) = itemtype(data->type); - WFIFOW(fd,offset+ 9+i*22) = ( data->view_id > 0 ) ? data->view_id : vsd->status.cart[index].nameid; - WFIFOB(fd,offset+11+i*22) = vsd->status.cart[index].identify; - WFIFOB(fd,offset+12+i*22) = vsd->status.cart[index].attribute; - WFIFOB(fd,offset+13+i*22) = vsd->status.cart[index].refine; - clif_addcards(WFIFOP(fd,offset+14+i*22), &vsd->status.cart[index]); - } - WFIFOSET(fd,WFIFOW(fd,2)); -} - - -/// Shop purchase failure (ZC_PC_PURCHASE_RESULT_FROMMC). -/// 0135 <index>.W <amount>.W <result>.B -/// result: -/// 0 = success -/// 1 = not enough zeny -/// 2 = overweight -/// 4 = out of stock -/// 5 = "cannot use an npc shop while in a trade" -/// 6 = Because the store information was incorrect the item was not purchased. -/// 7 = No sales information. -void clif_buyvending(struct map_session_data* sd, int index, int amount, int fail) -{ - int fd; - - nullpo_retv(sd); - - fd = sd->fd; - WFIFOHEAD(fd,packet_len(0x135)); - WFIFOW(fd,0) = 0x135; - WFIFOW(fd,2) = index+2; - WFIFOW(fd,4) = amount; - WFIFOB(fd,6) = fail; - WFIFOSET(fd,packet_len(0x135)); -} - - -/// Shop creation success (ZC_PC_PURCHASE_MYITEMLIST). -/// 0136 <packet len>.W <owner id>.L { <price>.L <index>.W <amount>.W <type>.B <name id>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W }* -void clif_openvending(struct map_session_data* sd, int id, struct s_vending* vending) -{ - int i,fd; - int count; - - nullpo_retv(sd); - - fd = sd->fd; - count = sd->vend_num; - - WFIFOHEAD(fd, 8+count*22); - WFIFOW(fd,0) = 0x136; - WFIFOW(fd,2) = 8+count*22; - WFIFOL(fd,4) = id; - for( i = 0; i < count; i++ ) - { - int index = vending[i].index; - struct item_data* data = itemdb_search(sd->status.cart[index].nameid); - WFIFOL(fd, 8+i*22) = vending[i].value; - WFIFOW(fd,12+i*22) = vending[i].index + 2; - WFIFOW(fd,14+i*22) = vending[i].amount; - WFIFOB(fd,16+i*22) = itemtype(data->type); - WFIFOW(fd,17+i*22) = ( data->view_id > 0 ) ? data->view_id : sd->status.cart[index].nameid; - WFIFOB(fd,19+i*22) = sd->status.cart[index].identify; - WFIFOB(fd,20+i*22) = sd->status.cart[index].attribute; - WFIFOB(fd,21+i*22) = sd->status.cart[index].refine; - clif_addcards(WFIFOP(fd,22+i*22), &sd->status.cart[index]); - } - WFIFOSET(fd,WFIFOW(fd,2)); -} - - -/// Inform merchant that someone has bought an item (ZC_DELETEITEM_FROM_MCSTORE). -/// 0137 <index>.W <amount>.W -void clif_vendingreport(struct map_session_data* sd, int index, int amount) -{ - int fd; - - nullpo_retv(sd); - - fd = sd->fd; - WFIFOHEAD(fd,packet_len(0x137)); - WFIFOW(fd,0) = 0x137; - WFIFOW(fd,2) = index+2; - WFIFOW(fd,4) = amount; - WFIFOSET(fd,packet_len(0x137)); -} - - -/// Result of organizing a party (ZC_ACK_MAKE_GROUP). -/// 00fa <result>.B -/// result: -/// 0 = opens party window and shows MsgStringTable[77]="party successfully organized" -/// 1 = MsgStringTable[78]="party name already exists" -/// 2 = MsgStringTable[79]="already in a party" -/// 3 = cannot organize parties on this map -/// ? = nothing -void clif_party_created(struct map_session_data *sd,int result) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0xfa)); - WFIFOW(fd,0)=0xfa; - WFIFOB(fd,2)=result; - WFIFOSET(fd,packet_len(0xfa)); -} - - -/// Adds new member to a party. -/// 0104 <account id>.L <role>.L <x>.W <y>.W <state>.B <party name>.24B <char name>.24B <map name>.16B (ZC_ADD_MEMBER_TO_GROUP) -/// 01e9 <account id>.L <role>.L <x>.W <y>.W <state>.B <party name>.24B <char name>.24B <map name>.16B <item pickup rule>.B <item share rule>.B (ZC_ADD_MEMBER_TO_GROUP2) -/// role: -/// 0 = leader -/// 1 = normal -/// state: -/// 0 = connected -/// 1 = disconnected -void clif_party_member_info(struct party_data *p, struct map_session_data *sd) -{ - unsigned char buf[81]; - int i; - - if (!sd) { //Pick any party member (this call is used when changing item share rules) - ARR_FIND( 0, MAX_PARTY, i, p->data[i].sd != 0 ); - } else { - ARR_FIND( 0, MAX_PARTY, i, p->data[i].sd == sd ); - } - if (i >= MAX_PARTY) return; //Should never happen... - sd = p->data[i].sd; - - WBUFW(buf, 0) = 0x1e9; - WBUFL(buf, 2) = sd->status.account_id; - WBUFL(buf, 6) = (p->party.member[i].leader)?0:1; - WBUFW(buf,10) = sd->bl.x; - WBUFW(buf,12) = sd->bl.y; - WBUFB(buf,14) = (p->party.member[i].online)?0:1; - memcpy(WBUFP(buf,15), p->party.name, NAME_LENGTH); - memcpy(WBUFP(buf,39), sd->status.name, NAME_LENGTH); - mapindex_getmapname_ext(map[sd->bl.m].name, (char*)WBUFP(buf,63)); - WBUFB(buf,79) = (p->party.item&1)?1:0; - WBUFB(buf,80) = (p->party.item&2)?1:0; - clif_send(buf,packet_len(0x1e9),&sd->bl,PARTY); -} - - -/// Sends party information (ZC_GROUP_LIST). -/// 00fb <packet len>.W <party name>.24B { <account id>.L <nick>.24B <map name>.16B <role>.B <state>.B }* -/// role: -/// 0 = leader -/// 1 = normal -/// state: -/// 0 = connected -/// 1 = disconnected -void clif_party_info(struct party_data* p, struct map_session_data *sd) -{ - unsigned char buf[2+2+NAME_LENGTH+(4+NAME_LENGTH+MAP_NAME_LENGTH_EXT+1+1)*MAX_PARTY]; - struct map_session_data* party_sd = NULL; - int i, c; - - nullpo_retv(p); - - WBUFW(buf,0) = 0xfb; - memcpy(WBUFP(buf,4), p->party.name, NAME_LENGTH); - for(i = 0, c = 0; i < MAX_PARTY; i++) - { - struct party_member* m = &p->party.member[i]; - if(!m->account_id) continue; - - if(party_sd == NULL) party_sd = p->data[i].sd; - - WBUFL(buf,28+c*46) = m->account_id; - memcpy(WBUFP(buf,28+c*46+4), m->name, NAME_LENGTH); - mapindex_getmapname_ext(mapindex_id2name(m->map), (char*)WBUFP(buf,28+c*46+28)); - 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(sd) { // send only to self - clif_send(buf, WBUFW(buf,2), &sd->bl, SELF); - } else if (party_sd) { // send to whole party - clif_send(buf, WBUFW(buf,2), &party_sd->bl, PARTY); - } -} - - -/// The player's 'party invite' state, sent during login (ZC_PARTY_CONFIG). -/// 02c9 <flag>.B -/// flag: -/// 0 = allow party invites -/// 1 = auto-deny party invites -void clif_partyinvitationstate(struct map_session_data* sd) -{ - int fd; - nullpo_retv(sd); - fd = sd->fd; - - WFIFOHEAD(fd, packet_len(0x2c9)); - WFIFOW(fd, 0) = 0x2c9; - WFIFOB(fd, 2) = 0; // not implemented - WFIFOSET(fd, packet_len(0x2c9)); -} - - -/// Party invitation request. -/// 00fe <party id>.L <party name>.24B (ZC_REQ_JOIN_GROUP) -/// 02c6 <party id>.L <party name>.24B (ZC_PARTY_JOIN_REQ) -void clif_party_invite(struct map_session_data *sd,struct map_session_data *tsd) -{ -#if PACKETVER < 20070821 - const int cmd = 0xfe; -#else - const int cmd = 0x2c6; -#endif - int fd; - struct party_data *p; - - nullpo_retv(sd); - nullpo_retv(tsd); - - fd=tsd->fd; - - if( (p=party_search(sd->status.party_id))==NULL ) - return; - - WFIFOHEAD(fd,packet_len(cmd)); - WFIFOW(fd,0)=cmd; - WFIFOL(fd,2)=sd->status.party_id; - memcpy(WFIFOP(fd,6),p->party.name,NAME_LENGTH); - WFIFOSET(fd,packet_len(cmd)); -} - - -/// Party invite result. -/// 00fd <nick>.24S <result>.B (ZC_ACK_REQ_JOIN_GROUP) -/// 02c5 <nick>.24S <result>.L (ZC_PARTY_JOIN_REQ_ACK) -/// result=0 : char is already in a party -> MsgStringTable[80] -/// result=1 : party invite was rejected -> MsgStringTable[81] -/// result=2 : party invite was accepted -> MsgStringTable[82] -/// result=3 : party is full -> MsgStringTable[83] -/// result=4 : char of the same account already joined the party -> MsgStringTable[608] -/// result=5 : char blocked party invite -> MsgStringTable[1324] (since 20070904) -/// result=7 : char is not online or doesn't exist -> MsgStringTable[71] (since 20070904) -/// result=8 : (%s) TODO instance related? -> MsgStringTable[1388] (since 20080527) -/// return=9 : TODO map prohibits party joining? -> MsgStringTable[1871] (since 20110205) -void clif_party_inviteack(struct map_session_data* sd, const char* nick, int result) -{ - int fd; - nullpo_retv(sd); - fd=sd->fd; - -#if PACKETVER < 20070904 - if( result == 7 ) { - clif_displaymessage(fd, msg_txt(3)); - return; - } -#endif - -#if PACKETVER < 20070821 - WFIFOHEAD(fd,packet_len(0xfd)); - WFIFOW(fd,0) = 0xfd; - safestrncpy((char*)WFIFOP(fd,2),nick,NAME_LENGTH); - WFIFOB(fd,26) = result; - WFIFOSET(fd,packet_len(0xfd)); -#else - WFIFOHEAD(fd,packet_len(0x2c5)); - WFIFOW(fd,0) = 0x2c5; - safestrncpy((char*)WFIFOP(fd,2),nick,NAME_LENGTH); - WFIFOL(fd,26) = result; - WFIFOSET(fd,packet_len(0x2c5)); -#endif -} - - -/// Updates party settings. -/// 0101 <exp option>.L (ZC_GROUPINFO_CHANGE) -/// 07d8 <exp option>.L <item pick rule>.B <item share rule>.B (ZC_REQ_GROUPINFO_CHANGE_V2) -/// exp option: -/// 0 = exp sharing disabled -/// 1 = exp sharing enabled -/// 2 = cannot change exp sharing -/// -/// flag: -/// 0 = send to party -/// 1 = send to sd -void clif_party_option(struct party_data *p,struct map_session_data *sd,int flag) -{ - unsigned char buf[16]; -#if PACKETVER < 20090603 - const int cmd = 0x101; -#else - const int cmd = 0x7d8; -#endif - - nullpo_retv(p); - - if(!sd && flag==0){ - int i; - for(i=0;i<MAX_PARTY && !p->data[i].sd;i++); - if (i < MAX_PARTY) - sd = p->data[i].sd; - } - if(!sd) return; - WBUFW(buf,0)=cmd; - WBUFL(buf,2)=((flag&0x01)?2:p->party.exp); -#if PACKETVER >= 20090603 - WBUFB(buf,6)=(p->party.item&1)?1:0; - WBUFB(buf,7)=(p->party.item&2)?1:0; -#endif - if(flag==0) - clif_send(buf,packet_len(cmd),&sd->bl,PARTY); - else - clif_send(buf,packet_len(cmd),&sd->bl,SELF); -} - - -/// 0105 <account id>.L <char name>.24B <result>.B (ZC_DELETE_MEMBER_FROM_GROUP). -/// result: -/// 0 = leave -/// 1 = expel -/// 2 = cannot leave party on this map -/// 3 = cannot expel from party on this map -void clif_party_withdraw(struct party_data* p, struct map_session_data* sd, int account_id, const char* name, int flag) -{ - unsigned char buf[64]; - int i; - - nullpo_retv(p); - - if(!sd && (flag&0xf0)==0) - { - for(i=0;i<MAX_PARTY && !p->data[i].sd;i++); - if (i < MAX_PARTY) - sd = p->data[i].sd; - } - - if(!sd) return; - - WBUFW(buf,0)=0x105; - WBUFL(buf,2)=account_id; - memcpy(WBUFP(buf,6),name,NAME_LENGTH); - WBUFB(buf,30)=flag&0x0f; - if((flag&0xf0)==0) - clif_send(buf,packet_len(0x105),&sd->bl,PARTY); - else - clif_send(buf,packet_len(0x105),&sd->bl,SELF); -} - - -/// Party chat message (ZC_NOTIFY_CHAT_PARTY). -/// 0109 <packet len>.W <account id>.L <message>.?B -void clif_party_message(struct party_data* p, int account_id, const char* mes, int len) -{ - struct map_session_data *sd; - int i; - - nullpo_retv(p); - - for(i=0; i < MAX_PARTY && !p->data[i].sd;i++); - if(i < MAX_PARTY){ - unsigned char buf[1024]; - - if( len > sizeof(buf)-8 ) - { - ShowWarning("clif_party_message: Truncated message '%s' (len=%d, max=%d, party_id=%d).\n", mes, len, sizeof(buf)-8, p->party.party_id); - len = sizeof(buf)-8; - } - - sd = p->data[i].sd; - WBUFW(buf,0)=0x109; - WBUFW(buf,2)=len+8; - WBUFL(buf,4)=account_id; - safestrncpy((char *)WBUFP(buf,8), mes, len); - clif_send(buf,len+8,&sd->bl,PARTY); - } -} - - -/// Updates the position of a party member on the minimap (ZC_NOTIFY_POSITION_TO_GROUPM). -/// 0107 <account id>.L <x>.W <y>.W -void clif_party_xy(struct map_session_data *sd) -{ - unsigned char buf[16]; - - nullpo_retv(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(0x107),&sd->bl,PARTY_SAMEMAP_WOS); -} - - -/*========================================== - * Sends x/y dot to a single fd. [Skotlex] - *------------------------------------------*/ -void clif_party_xy_single(int fd, struct map_session_data *sd) -{ - WFIFOHEAD(fd,packet_len(0x107)); - WFIFOW(fd,0)=0x107; - WFIFOL(fd,2)=sd->status.account_id; - WFIFOW(fd,6)=sd->bl.x; - WFIFOW(fd,8)=sd->bl.y; - WFIFOSET(fd,packet_len(0x107)); -} - - -/// Updates HP bar of a party member. -/// 0106 <account id>.L <hp>.W <max hp>.W (ZC_NOTIFY_HP_TO_GROUPM) -/// 080e <account id>.L <hp>.L <max hp>.L (ZC_NOTIFY_HP_TO_GROUPM_R2) -void clif_party_hp(struct map_session_data *sd) -{ - unsigned char buf[16]; -#if PACKETVER < 20100126 - const int cmd = 0x106; -#else - const int cmd = 0x80e; -#endif - - nullpo_retv(sd); - - WBUFW(buf,0)=cmd; - WBUFL(buf,2)=sd->status.account_id; -#if PACKETVER < 20100126 - if (sd->battle_status.max_hp > INT16_MAX) { //To correctly display the %hp bar. [Skotlex] - WBUFW(buf,6) = sd->battle_status.hp/(sd->battle_status.max_hp/100); - WBUFW(buf,8) = 100; - } else { - WBUFW(buf,6) = sd->battle_status.hp; - WBUFW(buf,8) = sd->battle_status.max_hp; - } -#else - WBUFL(buf,6) = sd->battle_status.hp; - WBUFL(buf,10) = sd->battle_status.max_hp; -#endif - clif_send(buf,packet_len(cmd),&sd->bl,PARTY_AREA_WOS); -} - - -/*========================================== - * Sends HP bar to a single fd. [Skotlex] - *------------------------------------------*/ -void clif_hpmeter_single(int fd, int id, unsigned int hp, unsigned int maxhp) -{ -#if PACKETVER < 20100126 - const int cmd = 0x106; -#else - const int cmd = 0x80e; -#endif - WFIFOHEAD(fd,packet_len(cmd)); - WFIFOW(fd,0) = cmd; - WFIFOL(fd,2) = id; -#if PACKETVER < 20100126 - if( maxhp > INT16_MAX ) - {// To correctly display the %hp bar. [Skotlex] - WFIFOW(fd,6) = hp/(maxhp/100); - WFIFOW(fd,8) = 100; - } else { - WFIFOW(fd,6) = hp; - WFIFOW(fd,8) = maxhp; - } -#else - WFIFOL(fd,6) = hp; - WFIFOL(fd,10) = maxhp; -#endif - WFIFOSET(fd, packet_len(cmd)); -} - -/// Notifies the client, that it's attack target is too far (ZC_ATTACK_FAILURE_FOR_DISTANCE). -/// 0139 <target id>.L <target x>.W <target y>.W <x>.W <y>.W <atk range>.W -void clif_movetoattack(struct map_session_data *sd,struct block_list *bl) -{ - int fd; - - nullpo_retv(sd); - nullpo_retv(bl); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x139)); - 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->battle_status.rhw.range; - WFIFOSET(fd,packet_len(0x139)); -} - - -/// Notifies the client about the result of an item produce request (ZC_ACK_REQMAKINGITEM). -/// 018f <result>.W <name id>.W -/// result: -/// 0 = success -/// 1 = failure -/// 2 = success (alchemist) -/// 3 = failure (alchemist) -void clif_produceeffect(struct map_session_data* sd,int flag,int nameid) -{ - int view,fd; - - nullpo_retv(sd); - - fd = sd->fd; - clif_solved_charname(fd, sd->status.char_id, sd->status.name); - WFIFOHEAD(fd,packet_len(0x18f)); - 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(0x18f)); -} - - -/// Initiates the pet taming process (ZC_START_CAPTURE). -/// 019e -void clif_catch_process(struct map_session_data *sd) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x19e)); - WFIFOW(fd,0)=0x19e; - WFIFOSET(fd,packet_len(0x19e)); -} - - -/// Displays the result of a pet taming attempt (ZC_TRYCAPTURE_MONSTER). -/// 01a0 <result>.B -/// 0 = failure -/// 1 = success -void clif_pet_roulette(struct map_session_data *sd,int data) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x1a0)); - WFIFOW(fd,0)=0x1a0; - WFIFOB(fd,2)=data; - WFIFOSET(fd,packet_len(0x1a0)); -} - - -/// Presents a list of pet eggs that can be hatched (ZC_PETEGG_LIST). -/// 01a6 <packet len>.W { <index>.W }* -void clif_sendegg(struct map_session_data *sd) -{ - int i,n=0,fd; - - nullpo_retv(sd); - - fd=sd->fd; - if (battle_config.pet_no_gvg && map_flag_gvg(sd->bl.m)) - { //Disable pet hatching in GvG grounds during Guild Wars [Skotlex] - clif_displaymessage(fd, msg_txt(666)); - return; - } - WFIFOHEAD(fd, MAX_INVENTORY * 2 + 4); - WFIFOW(fd,0)=0x1a6; - 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!=IT_PETEGG || - 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)); - - sd->menuskill_id = SA_TAMINGMONSTER; - sd->menuskill_val = -1; -} - - -/// Sends a specific pet data update (ZC_CHANGESTATE_PET). -/// 01a4 <type>.B <id>.L <data>.L -/// type: -/// 0 = pre-init (data = 0) -/// 1 = intimacy (data = 0~4) -/// 2 = hunger (data = 0~4) -/// 3 = accessory -/// 4 = performance (data = 1~3: normal, 4: special) -/// 5 = hairstyle -/// -/// If sd is null, the update is sent to nearby objects, otherwise it is sent only to that player. -void clif_send_petdata(struct map_session_data* sd, struct pet_data* pd, int type, int param) -{ - uint8 buf[16]; - nullpo_retv(pd); - - WBUFW(buf,0) = 0x1a4; - WBUFB(buf,2) = type; - WBUFL(buf,3) = pd->bl.id; - WBUFL(buf,7) = param; - if (sd) - clif_send(buf, packet_len(0x1a4), &sd->bl, SELF); - else - clif_send(buf, packet_len(0x1a4), &pd->bl, AREA); -} - - -/// Pet's base data (ZC_PROPERTY_PET). -/// 01a2 <name>.24B <renamed>.B <level>.W <hunger>.W <intimacy>.W <accessory id>.W <class>.W -void clif_send_petstatus(struct map_session_data *sd) -{ - int fd; - struct s_pet *pet; - - nullpo_retv(sd); - nullpo_retv(sd->pd); - - fd=sd->fd; - pet = &sd->pd->pet; - WFIFOHEAD(fd,packet_len(0x1a2)); - WFIFOW(fd,0)=0x1a2; - memcpy(WFIFOP(fd,2),pet->name,NAME_LENGTH); - WFIFOB(fd,26)=battle_config.pet_rename?0:pet->rename_flag; - WFIFOW(fd,27)=pet->level; - WFIFOW(fd,29)=pet->hungry; - WFIFOW(fd,31)=pet->intimate; - WFIFOW(fd,33)=pet->equip; -#if PACKETVER >= 20081126 - WFIFOW(fd,35)=pet->class_; -#endif - WFIFOSET(fd,packet_len(0x1a2)); -} - - -/// Notification about a pet's emotion/talk (ZC_PET_ACT). -/// 01aa <id>.L <data>.L -/// data: -/// @see CZ_PET_ACT. -void clif_pet_emotion(struct pet_data *pd,int param) -{ - unsigned char buf[16]; - - nullpo_retv(pd); - - memset(buf,0,packet_len(0x1aa)); - - WBUFW(buf,0)=0x1aa; - WBUFL(buf,2)=pd->bl.id; - if(param >= 100 && pd->petDB->talk_convert_class) { - if(pd->petDB->talk_convert_class < 0) - return; - else if(pd->petDB->talk_convert_class > 0) { - // replace mob_id component of talk/act data - param -= (pd->pet.class_ - 100)*100; - param += (pd->petDB->talk_convert_class - 100)*100; - } - } - WBUFL(buf,6)=param; - - clif_send(buf,packet_len(0x1aa),&pd->bl,AREA); -} - - -/// Result of request to feed a pet (ZC_FEED_PET). -/// 01a3 <result>.B <name id>.W -/// result: -/// 0 = failure -/// 1 = success -void clif_pet_food(struct map_session_data *sd,int foodid,int fail) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x1a3)); - WFIFOW(fd,0)=0x1a3; - WFIFOB(fd,2)=fail; - WFIFOW(fd,3)=foodid; - WFIFOSET(fd,packet_len(0x1a3)); -} - - -/// Presents a list of skills that can be auto-spelled (ZC_AUTOSPELLLIST). -/// 01cd { <skill id>.L }*7 -void clif_autospell(struct map_session_data *sd,uint16 skill_lv) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x1cd)); - WFIFOW(fd, 0)=0x1cd; - - if(skill_lv>0 && pc_checkskill(sd,MG_NAPALMBEAT)>0) - WFIFOL(fd,2)= MG_NAPALMBEAT; - else - WFIFOL(fd,2)= 0x00000000; - if(skill_lv>1 && pc_checkskill(sd,MG_COLDBOLT)>0) - WFIFOL(fd,6)= MG_COLDBOLT; - else - WFIFOL(fd,6)= 0x00000000; - if(skill_lv>1 && pc_checkskill(sd,MG_FIREBOLT)>0) - WFIFOL(fd,10)= MG_FIREBOLT; - else - WFIFOL(fd,10)= 0x00000000; - if(skill_lv>1 && pc_checkskill(sd,MG_LIGHTNINGBOLT)>0) - WFIFOL(fd,14)= MG_LIGHTNINGBOLT; - else - WFIFOL(fd,14)= 0x00000000; - if(skill_lv>4 && pc_checkskill(sd,MG_SOULSTRIKE)>0) - WFIFOL(fd,18)= MG_SOULSTRIKE; - else - WFIFOL(fd,18)= 0x00000000; - if(skill_lv>7 && pc_checkskill(sd,MG_FIREBALL)>0) - WFIFOL(fd,22)= MG_FIREBALL; - else - WFIFOL(fd,22)= 0x00000000; - if(skill_lv>9 && pc_checkskill(sd,MG_FROSTDIVER)>0) - WFIFOL(fd,26)= MG_FROSTDIVER; - else - WFIFOL(fd,26)= 0x00000000; - - WFIFOSET(fd,packet_len(0x1cd)); - sd->menuskill_id = SA_AUTOSPELL; - sd->menuskill_val = skill_lv; -} - - -/// Devotion's visual effect (ZC_DEVOTIONLIST). -/// 01cf <devoter id>.L { <devotee id>.L }*5 <max distance>.W -void clif_devotion(struct block_list *src, struct map_session_data *tsd) -{ - unsigned char buf[56]; - int i; - - nullpo_retv(src); - memset(buf,0,packet_len(0x1cf)); - - WBUFW(buf,0) = 0x1cf; - WBUFL(buf,2) = src->id; - if( src->type == BL_MER ) - { - struct mercenary_data *md = BL_CAST(BL_MER,src); - if( md && md->master && md->devotion_flag ) - WBUFL(buf,6) = md->master->bl.id; - - WBUFW(buf,26) = skill_get_range2(src, ML_DEVOTION, mercenary_checkskill(md, ML_DEVOTION)); - } - else - { - struct map_session_data *sd = BL_CAST(BL_PC,src); - if( sd == NULL ) - return; - - for( i = 0; i < 5; i++ ) - WBUFL(buf,6+4*i) = sd->devotion[i]; - WBUFW(buf,26) = skill_get_range2(src, CR_DEVOTION, pc_checkskill(sd, CR_DEVOTION)); - } - - if( tsd ) - clif_send(buf, packet_len(0x1cf), &tsd->bl, SELF); - else - clif_send(buf, packet_len(0x1cf), src, AREA); -} - -/*========================================== - * Server tells clients nearby 'sd' (and himself) to display 'sd->spiritball' number of spiritballs on 'sd' - * Notifies clients in an area of an object's spirits. - * 01d0 <id>.L <amount>.W (ZC_SPIRITS) - * 01e1 <id>.L <amount>.W (ZC_SPIRITS2) - *------------------------------------------*/ -void clif_spiritball(struct block_list *bl) { - unsigned char buf[16]; - TBL_PC *sd = BL_CAST(BL_PC,bl); - TBL_HOM *hd = BL_CAST(BL_HOM,bl); - - nullpo_retv(bl); - - WBUFW(buf, 0) = 0x1d0; - WBUFL(buf, 2) = bl->id; - WBUFW(buf, 6) = 0; //init to 0 - switch(bl->type){ - case BL_PC: WBUFW(buf, 6) = sd->spiritball; break; - case BL_HOM: WBUFW(buf, 6) = hd->homunculus.spiritball; break; - } - clif_send(buf, packet_len(0x1d0), bl, AREA); -} - - -/// Notifies clients in area of a character's combo delay (ZC_COMBODELAY). -/// 01d2 <account id>.L <delay>.L -void clif_combo_delay(struct block_list *bl,int wait) -{ - unsigned char buf[32]; - - nullpo_retv(bl); - - WBUFW(buf,0)=0x1d2; - WBUFL(buf,2)=bl->id; - WBUFL(buf,6)=wait; - clif_send(buf,packet_len(0x1d2),bl,AREA); -} - - -/// Notifies clients in area that a character has blade-stopped another (ZC_BLADESTOP). -/// 01d1 <src id>.L <dst id>.L <flag>.L -/// flag: -/// 0 = inactive -/// 1 = active -void clif_bladestop(struct block_list *src, int dst_id, int active) -{ - unsigned char buf[32]; - - nullpo_retv(src); - - WBUFW(buf,0)=0x1d1; - WBUFL(buf,2)=src->id; - WBUFL(buf,6)=dst_id; - WBUFL(buf,10)=active; - - clif_send(buf,packet_len(0x1d1),src,AREA); -} - - -/// MVP effect (ZC_MVP). -/// 010c <account id>.L -void clif_mvp_effect(struct map_session_data *sd) -{ - unsigned char buf[16]; - - nullpo_retv(sd); - - WBUFW(buf,0)=0x10c; - WBUFL(buf,2)=sd->bl.id; - clif_send(buf,packet_len(0x10c),&sd->bl,AREA); -} - - -/// MVP item reward message (ZC_MVP_GETTING_ITEM). -/// 010a <name id>.W -void clif_mvp_item(struct map_session_data *sd,int nameid) -{ - int view,fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x10a)); - WFIFOW(fd,0)=0x10a; - if((view = itemdb_viewid(nameid)) > 0) - WFIFOW(fd,2)=view; - else - WFIFOW(fd,2)=nameid; - WFIFOSET(fd,packet_len(0x10a)); -} - - -/// MVP EXP reward message (ZC_MVP_GETTING_SPECIAL_EXP). -/// 010b <exp>.L -void clif_mvp_exp(struct map_session_data *sd, unsigned int exp) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x10b)); - WFIFOW(fd,0)=0x10b; - WFIFOL(fd,2)=cap_value(exp,0,INT32_MAX); - WFIFOSET(fd,packet_len(0x10b)); -} - - -/// Dropped MVP item reward message (ZC_THROW_MVPITEM). -/// 010d -/// -/// "You are the MVP, but cannot obtain the reward because -/// you are overweight." -void clif_mvp_noitem(struct map_session_data* sd) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x10d)); - WFIFOW(fd,0) = 0x10d; - WFIFOSET(fd,packet_len(0x10d)); -} - - -/// Guild creation result (ZC_RESULT_MAKE_GUILD). -/// 0167 <result>.B -/// result: -/// 0 = "Guild has been created." -/// 1 = "You are already in a Guild." -/// 2 = "That Guild Name already exists." -/// 3 = "You need the neccessary item to create a Guild." -void clif_guild_created(struct map_session_data *sd,int flag) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x167)); - WFIFOW(fd,0)=0x167; - WFIFOB(fd,2)=flag; - WFIFOSET(fd,packet_len(0x167)); -} - - -/// Notifies the client that it is belonging to a guild (ZC_UPDATE_GDID). -/// 016c <guild id>.L <emblem id>.L <mode>.L <ismaster>.B <inter sid>.L <guild name>.24B -/// mode: -/// &0x01 = allow invite -/// &0x10 = allow expel -void clif_guild_belonginfo(struct map_session_data *sd, struct guild *g) -{ - int ps,fd; - nullpo_retv(sd); - nullpo_retv(g); - - fd=sd->fd; - ps=guild_getposition(g,sd); - WFIFOHEAD(fd,packet_len(0x16c)); - WFIFOW(fd,0)=0x16c; - WFIFOL(fd,2)=g->guild_id; - WFIFOL(fd,6)=g->emblem_id; - WFIFOL(fd,10)=g->position[ps].mode; - WFIFOB(fd,14)=(bool)(sd->state.gmaster_flag==g); - WFIFOL(fd,15)=0; // InterSID (unknown purpose) - memcpy(WFIFOP(fd,19),g->name,NAME_LENGTH); - WFIFOSET(fd,packet_len(0x16c)); -} - - -/// Guild member login notice. -/// 016d <account id>.L <char id>.L <status>.L (ZC_UPDATE_CHARSTAT) -/// 01f2 <account id>.L <char id>.L <status>.L <gender>.W <hair style>.W <hair color>.W (ZC_UPDATE_CHARSTAT2) -/// status: -/// 0 = offline -/// 1 = online -void clif_guild_memberlogin_notice(struct guild *g,int idx,int flag) -{ - unsigned char buf[64]; - struct map_session_data* sd; - - nullpo_retv(g); - - WBUFW(buf, 0)=0x1f2; - WBUFL(buf, 2)=g->member[idx].account_id; - WBUFL(buf, 6)=g->member[idx].char_id; - WBUFL(buf,10)=flag; - - if( ( sd = g->member[idx].sd ) != NULL ) - { - WBUFW(buf,14) = sd->status.sex; - WBUFW(buf,16) = sd->status.hair; - WBUFW(buf,18) = sd->status.hair_color; - clif_send(buf,packet_len(0x1f2),&sd->bl,GUILD_WOS); - } - else if( ( sd = guild_getavailablesd(g) ) != NULL ) - { - WBUFW(buf,14) = 0; - WBUFW(buf,16) = 0; - WBUFW(buf,18) = 0; - clif_send(buf,packet_len(0x1f2),&sd->bl,GUILD); - } -} - -// Function `clif_guild_memberlogin_notice` sends info about -// logins and logouts of a guild member to the rest members. -// But at the 1st time (after a player login or map changing) -// the client won't show the message. -// So I suggest use this function for sending "first-time-info" -// to some player on entering the game or changing location. -// At next time the client would always show the message. -// The function sends all the statuses in the single packet -// to economize traffic. [LuzZza] -void clif_guild_send_onlineinfo(struct map_session_data *sd) -{ - struct guild *g; - unsigned char buf[14*128]; - int i, count=0, p_len; - - nullpo_retv(sd); - - p_len = packet_len(0x16d); - - if(!(g = guild_search(sd->status.guild_id))) - return; - - for(i=0; i<g->max_member; i++) { - - if(g->member[i].account_id > 0 && - g->member[i].account_id != sd->status.account_id) { - - WBUFW(buf,count*p_len) = 0x16d; - WBUFL(buf,count*p_len+2) = g->member[i].account_id; - WBUFL(buf,count*p_len+6) = g->member[i].char_id; - WBUFL(buf,count*p_len+10) = g->member[i].online; - count++; - } - } - - clif_send(buf, p_len*count, &sd->bl, SELF); -} - - -/// Bitmask of enabled guild window tabs (ZC_ACK_GUILD_MENUINTERFACE). -/// 014e <menu flag>.L -/// menu flag: -/// 0x00 = Basic Info (always on) -/// &0x01 = Member manager -/// &0x02 = Positions -/// &0x04 = Skills -/// &0x10 = Expulsion list -/// &0x40 = Unknown (GMENUFLAG_ALLGUILDLIST) -/// &0x80 = Notice -void clif_guild_masterormember(struct map_session_data *sd) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x14e)); - WFIFOW(fd,0) = 0x14e; - WFIFOL(fd,2) = (sd->state.gmaster_flag) ? 0xd7 : 0x57; - WFIFOSET(fd,packet_len(0x14e)); -} - - -/// Guild basic information (Territories [Valaris]) -/// 0150 <guild id>.L <level>.L <member num>.L <member max>.L <exp>.L <max exp>.L <points>.L <honor>.L <virtue>.L <emblem id>.L <name>.24B <master name>.24B <manage land>.16B (ZC_GUILD_INFO) -/// 01b6 <guild id>.L <level>.L <member num>.L <member max>.L <exp>.L <max exp>.L <points>.L <honor>.L <virtue>.L <emblem id>.L <name>.24B <master name>.24B <manage land>.16B <zeny>.L (ZC_GUILD_INFO2) -void clif_guild_basicinfo(struct map_session_data *sd) { - int fd; - struct guild *g; - - nullpo_retv(sd); - fd = sd->fd; - - if( (g = guild_search(sd->status.guild_id)) == NULL ) - return; - - WFIFOHEAD(fd,packet_len(0x1b6)); - 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)=(uint32)cap_value(g->exp,0,INT32_MAX); - WFIFOL(fd,26)=g->next_exp; - WFIFOL(fd,30)=0; // Tax Points - WFIFOL(fd,34)=0; // Honor: (left) Vulgar [-100,100] Famed (right) - WFIFOL(fd,38)=0; // Virtue: (down) Wicked [-100,100] Righteous (up) - WFIFOL(fd,42)=g->emblem_id; - memcpy(WFIFOP(fd,46),g->name, NAME_LENGTH); - memcpy(WFIFOP(fd,70),g->master, NAME_LENGTH); - - safestrncpy((char*)WFIFOP(fd,94),msg_txt(300+guild_checkcastles(g)),16); // "'N' castles" - WFIFOL(fd,110) = 0; // zeny - - WFIFOSET(fd,packet_len(0x1b6)); -} - - -/// Guild alliance and opposition list (ZC_MYGUILD_BASIC_INFO). -/// 014c <packet len>.W { <relation>.L <guild id>.L <guild name>.24B }* -void clif_guild_allianceinfo(struct map_session_data *sd) -{ - int fd,i,c; - struct guild *g; - - nullpo_retv(sd); - if( (g = guild_search(sd->status.guild_id)) == NULL ) - return; - - fd = sd->fd; - WFIFOHEAD(fd, MAX_GUILDALLIANCE * 32 + 4); - 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,NAME_LENGTH); - c++; - } - } - WFIFOW(fd, 2)=c*32+4; - WFIFOSET(fd,WFIFOW(fd,2)); -} - - -/// Guild member manager information (ZC_MEMBERMGR_INFO). -/// 0154 <packet len>.W { <account>.L <char id>.L <hair style>.W <hair color>.W <gender>.W <class>.W <level>.W <contrib exp>.L <state>.L <position>.L <memo>.50B <name>.24B }* -/// state: -/// 0 = offline -/// 1 = online -/// memo: -/// probably member's self-introduction (unused, no client UI/packets for editing it) -void clif_guild_memberlist(struct map_session_data *sd) -{ - int fd; - int i,c; - struct guild *g; - nullpo_retv(sd); - - if( (fd = sd->fd) == 0 ) - return; - if( (g = guild_search(sd->status.guild_id)) == NULL ) - return; - - WFIFOHEAD(fd, g->max_member * 104 + 4); - 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)=(int)cap_value(m->exp,0,INT32_MAX); - WFIFOL(fd,c*104+26)=m->online; - WFIFOL(fd,c*104+30)=m->position; - memset(WFIFOP(fd,c*104+34),0,50); //[Ind] - This is displayed in the 'note' column but being you can't edit it it's sent empty. - memcpy(WFIFOP(fd,c*104+84),m->name,NAME_LENGTH); - c++; - } - WFIFOW(fd, 2)=c*104+4; - WFIFOSET(fd,WFIFOW(fd,2)); -} - - -/// Guild position name information (ZC_POSITION_ID_NAME_INFO). -/// 0166 <packet len>.W { <position id>.L <position name>.24B }* -void clif_guild_positionnamelist(struct map_session_data *sd) -{ - int i,fd; - struct guild *g; - - nullpo_retv(sd); - if( (g = guild_search(sd->status.guild_id)) == NULL ) - return; - - fd = sd->fd; - WFIFOHEAD(fd, MAX_GUILDPOSITION * 28 + 4); - 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,NAME_LENGTH); - } - WFIFOW(fd,2)=i*28+4; - WFIFOSET(fd,WFIFOW(fd,2)); -} - - -/// Guild position information (ZC_POSITION_INFO). -/// 0160 <packet len>.W { <position id>.L <mode>.L <ranking>.L <pay rate>.L }* -/// mode: -/// &0x01 = allow invite -/// &0x10 = allow expel -/// ranking: -/// TODO -void clif_guild_positioninfolist(struct map_session_data *sd) -{ - int i,fd; - struct guild *g; - - nullpo_retv(sd); - if( (g = guild_search(sd->status.guild_id)) == NULL ) - return; - - fd = sd->fd; - WFIFOHEAD(fd, MAX_GUILDPOSITION * 16 + 4); - 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)); -} - - -/// Notifies clients in a guild about updated position information (ZC_ACK_CHANGE_GUILD_POSITIONINFO). -/// 0174 <packet len>.W { <position id>.L <mode>.L <ranking>.L <pay rate>.L <position name>.24B }* -/// mode: -/// &0x01 = allow invite -/// &0x10 = allow expel -/// ranking: -/// TODO -void clif_guild_positionchanged(struct guild *g,int idx) -{ - // FIXME: This packet is intended to update the clients after a - // commit of position info changes, not sending one packet per - // position. - struct map_session_data *sd; - unsigned char buf[128]; - - nullpo_retv(g); - - WBUFW(buf, 0)=0x174; - WBUFW(buf, 2)=44; // packet len - // GUILD_REG_POSITION_INFO{ - 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,NAME_LENGTH); - // }* - if( (sd=guild_getavailablesd(g))!=NULL ) - clif_send(buf,WBUFW(buf,2),&sd->bl,GUILD); -} - - -/// Notifies clients in a guild about updated member position assignments (ZC_ACK_REQ_CHANGE_MEMBERS). -/// 0156 <packet len>.W { <account id>.L <char id>.L <position id>.L }* -void clif_guild_memberpositionchanged(struct guild *g,int idx) -{ - // FIXME: This packet is intended to update the clients after a - // commit of member position assignment changes, not sending one - // packet per position. - struct map_session_data *sd; - unsigned char buf[64]; - - nullpo_retv(g); - - WBUFW(buf, 0)=0x156; - WBUFW(buf, 2)=16; // packet len - // MEMBER_POSITION_INFO{ - 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); -} - - -/// Sends emblems bitmap data to the client that requested it (ZC_GUILD_EMBLEM_IMG). -/// 0152 <packet len>.W <guild id>.L <emblem id>.L <emblem data>.?B -void clif_guild_emblem(struct map_session_data *sd,struct guild *g) -{ - int fd; - nullpo_retv(sd); - nullpo_retv(g); - - fd = sd->fd; - if( g->emblem_len <= 0 ) - return; - - WFIFOHEAD(fd,g->emblem_len+12); - 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)); -} - - -/// Sends update of the guild id/emblem id to everyone in the area (ZC_CHANGE_GUILD). -/// 01b4 <id>.L <guild id>.L <emblem id>.W -void clif_guild_emblem_area(struct block_list* bl) -{ - uint8 buf[12]; - - nullpo_retv(bl); - - // TODO this packet doesn't force the update of ui components that have the emblem visible - // (emblem in the flag npcs and emblem over the head in agit maps) [FlavioJS] - WBUFW(buf,0) = 0x1b4; - WBUFL(buf,2) = bl->id; - WBUFL(buf,6) = status_get_guild_id(bl); - WBUFW(buf,10) = status_get_emblem_id(bl); - clif_send(buf, 12, bl, AREA_WOS); -} - - -/// Sends guild skills (ZC_GUILD_SKILLINFO). -/// 0162 <packet len>.W <skill points>.W { <skill id>.W <type>.L <level>.W <sp cost>.W <atk range>.W <skill name>.24B <upgradable>.B }* -void clif_guild_skillinfo(struct map_session_data* sd) -{ - int fd; - struct guild* g; - int i,c; - - nullpo_retv(sd); - if( (g = guild_search(sd->status.guild_id)) == NULL ) - return; - - fd = sd->fd; - WFIFOHEAD(fd, 6 + MAX_GUILDSKILL*37); - WFIFOW(fd,0) = 0x0162; - WFIFOW(fd,4) = g->skill_point; - for(i = 0, c = 0; i < MAX_GUILDSKILL; i++) - { - if(g->skill[i].id > 0 && guild_check_skill_require(g, g->skill[i].id)) - { - int id = g->skill[i].id; - int p = 6 + c*37; - WFIFOW(fd,p+0) = id; - WFIFOL(fd,p+2) = skill_get_inf(id); - WFIFOW(fd,p+6) = g->skill[i].lv; - WFIFOW(fd,p+8) = skill_get_sp(id, g->skill[i].lv); - WFIFOW(fd,p+10) = skill_get_range(id, g->skill[i].lv); - safestrncpy((char*)WFIFOP(fd,p+12), skill_get_name(id), NAME_LENGTH); - WFIFOB(fd,p+36)= (g->skill[i].lv < guild_skill_get_max(id) && sd == g->member[0].sd) ? 1 : 0; - c++; - } - } - WFIFOW(fd,2) = 6 + c*37; - WFIFOSET(fd,WFIFOW(fd,2)); -} - - -/// Sends guild notice to client (ZC_GUILD_NOTICE). -/// 016f <subject>.60B <notice>.120B -void clif_guild_notice(struct map_session_data* sd, struct guild* g) -{ - int fd; - - nullpo_retv(sd); - nullpo_retv(g); - - fd = sd->fd; - - if ( !session_isActive(fd) ) - return; - - if(g->mes1[0] == '\0' && g->mes2[0] == '\0') - return; - - WFIFOHEAD(fd,packet_len(0x16f)); - WFIFOW(fd,0) = 0x16f; - memcpy(WFIFOP(fd,2), g->mes1, MAX_GUILDMES1); - memcpy(WFIFOP(fd,62), g->mes2, MAX_GUILDMES2); - WFIFOSET(fd,packet_len(0x16f)); -} - - -/// Guild invite (ZC_REQ_JOIN_GUILD). -/// 016a <guild id>.L <guild name>.24B -void clif_guild_invite(struct map_session_data *sd,struct guild *g) -{ - int fd; - - nullpo_retv(sd); - nullpo_retv(g); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x16a)); - WFIFOW(fd,0)=0x16a; - WFIFOL(fd,2)=g->guild_id; - memcpy(WFIFOP(fd,6),g->name,NAME_LENGTH); - WFIFOSET(fd,packet_len(0x16a)); -} - - -/// Reply to invite request (ZC_ACK_REQ_JOIN_GUILD). -/// 0169 <answer>.B -/// answer: -/// 0 = Already in guild. -/// 1 = Offer rejected. -/// 2 = Offer accepted. -/// 3 = Guild full. -void clif_guild_inviteack(struct map_session_data *sd,int flag) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x169)); - WFIFOW(fd,0)=0x169; - WFIFOB(fd,2)=flag; - WFIFOSET(fd,packet_len(0x169)); -} - - -/// Notifies clients of a guild of a leaving member (ZC_ACK_LEAVE_GUILD). -/// 015a <char name>.24B <reason>.40B -void clif_guild_leave(struct map_session_data *sd,const char *name,const char *mes) -{ - unsigned char buf[128]; - - nullpo_retv(sd); - - WBUFW(buf, 0)=0x15a; - memcpy(WBUFP(buf, 2),name,NAME_LENGTH); - memcpy(WBUFP(buf,26),mes,40); - clif_send(buf,packet_len(0x15a),&sd->bl,GUILD_NOBG); -} - - -/// Notifies clients of a guild of an expelled member. -/// 015c <char name>.24B <reason>.40B <account name>.24B (ZC_ACK_BAN_GUILD) -/// 0839 <char name>.24B <reason>.40B (ZC_ACK_BAN_GUILD_SSO) -void clif_guild_expulsion(struct map_session_data* sd, const char* name, const char* mes, int account_id) -{ - unsigned char buf[128]; -#if PACKETVER < 20100803 - const unsigned short cmd = 0x15c; -#else - const unsigned short cmd = 0x839; -#endif - - nullpo_retv(sd); - - WBUFW(buf,0) = cmd; - safestrncpy((char*)WBUFP(buf,2), name, NAME_LENGTH); - safestrncpy((char*)WBUFP(buf,26), mes, 40); -#if PACKETVER < 20100803 - memset(WBUFP(buf,66), 0, NAME_LENGTH); // account name (not used for security reasons) -#endif - clif_send(buf, packet_len(cmd), &sd->bl, GUILD_NOBG); -} - - -/// Guild expulsion list (ZC_BAN_LIST). -/// 0163 <packet len>.W { <char name>.24B <account name>.24B <reason>.40B }* -/// 0163 <packet len>.W { <char name>.24B <reason>.40B }* (PACKETVER >= 20100803) -void clif_guild_expulsionlist(struct map_session_data* sd) -{ -#if PACKETVER < 20100803 - const int offset = NAME_LENGTH*2+40; -#else - const int offset = NAME_LENGTH+40; -#endif - int fd, i, c = 0; - struct guild* g; - - nullpo_retv(sd); - - if( (g = guild_search(sd->status.guild_id)) == NULL ) - return; - - fd = sd->fd; - - WFIFOHEAD(fd,4 + MAX_GUILDEXPULSION * offset); - WFIFOW(fd,0) = 0x163; - - for( i = 0; i < MAX_GUILDEXPULSION; i++ ) - { - struct guild_expulsion* e = &g->expulsion[i]; - - if( e->account_id > 0 ) - { - memcpy(WFIFOP(fd,4 + c*offset), e->name, NAME_LENGTH); -#if PACKETVER < 20100803 - memset(WFIFOP(fd,4 + c*offset+24), 0, NAME_LENGTH); // account name (not used for security reasons) - memcpy(WFIFOP(fd,4 + c*offset+48), e->mes, 40); -#else - memcpy(WFIFOP(fd,4 + c*offset+24), e->mes, 40); -#endif - c++; - } - } - WFIFOW(fd,2) = 4 + c*offset; - WFIFOSET(fd,WFIFOW(fd,2)); -} - - -/// Guild chat message (ZC_GUILD_CHAT). -/// 017f <packet len>.W <message>.?B -void clif_guild_message(struct guild *g,int account_id,const char *mes,int len) -{// TODO: account_id is not used, candidate for deletion? [Ai4rei] - struct map_session_data *sd; - uint8 buf[256]; - - if( len == 0 ) - { - return; - } - else if( len > sizeof(buf)-5 ) - { - ShowWarning("clif_guild_message: Truncated message '%s' (len=%d, max=%d, guild_id=%d).\n", mes, len, sizeof(buf)-5, g->guild_id); - len = sizeof(buf)-5; - } - - WBUFW(buf, 0) = 0x17f; - WBUFW(buf, 2) = len + 5; - safestrncpy((char*)WBUFP(buf,4), mes, len+1); - - if ((sd = guild_getavailablesd(g)) != NULL) - clif_send(buf, WBUFW(buf,2), &sd->bl, GUILD_NOBG); -} - - -/*========================================== - * Server tells client 'sd' that his guild skill 'skill_id' gone to level 'lv' - *------------------------------------------*/ -int clif_guild_skillup(struct map_session_data *sd,uint16 skill_id,int lv) -{// TODO: Merge with clif_skillup (same packet). - int fd; - - nullpo_ret(sd); - - fd=sd->fd; - WFIFOHEAD(fd,11); - WFIFOW(fd,0) = 0x10e; - WFIFOW(fd,2) = skill_id; - WFIFOW(fd,4) = lv; - WFIFOW(fd,6) = skill_get_sp(skill_id,lv); - WFIFOW(fd,8) = skill_get_range(skill_id,lv); - WFIFOB(fd,10) = 1; - WFIFOSET(fd,11); - return 0; -} - - -/// Request for guild alliance (ZC_REQ_ALLY_GUILD). -/// 0171 <inviter account id>.L <guild name>.24B -void clif_guild_reqalliance(struct map_session_data *sd,int account_id,const char *name) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x171)); - WFIFOW(fd,0)=0x171; - WFIFOL(fd,2)=account_id; - memcpy(WFIFOP(fd,6),name,NAME_LENGTH); - WFIFOSET(fd,packet_len(0x171)); -} - - -/// Notifies the client about the result of a alliance request (ZC_ACK_REQ_ALLY_GUILD). -/// 0173 <answer>.B -/// answer: -/// 0 = Already allied. -/// 1 = You rejected the offer. -/// 2 = You accepted the offer. -/// 3 = They have too any alliances. -/// 4 = You have too many alliances. -/// 5 = Alliances are disabled. -void clif_guild_allianceack(struct map_session_data *sd,int flag) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x173)); - WFIFOW(fd,0)=0x173; - WFIFOL(fd,2)=flag; - WFIFOSET(fd,packet_len(0x173)); -} - - -/// Notifies the client that a alliance or opposition has been removed (ZC_DELETE_RELATED_GUILD). -/// 0184 <other guild id>.L <relation>.L -/// relation: -/// 0 = Ally -/// 1 = Enemy -void clif_guild_delalliance(struct map_session_data *sd,int guild_id,int flag) -{ - int fd; - - nullpo_retv(sd); - - fd = sd->fd; - if (fd <= 0) - return; - WFIFOHEAD(fd,packet_len(0x184)); - WFIFOW(fd,0)=0x184; - WFIFOL(fd,2)=guild_id; - WFIFOL(fd,6)=flag; - WFIFOSET(fd,packet_len(0x184)); -} - - -/// Notifies the client about the result of a opposition request (ZC_ACK_REQ_HOSTILE_GUILD). -/// 0181 <result>.B -/// result: -/// 0 = Antagonist has been set. -/// 1 = Guild has too many Antagonists. -/// 2 = Already set as an Antagonist. -/// 3 = Antagonists are disabled. -void clif_guild_oppositionack(struct map_session_data *sd,int flag) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x181)); - WFIFOW(fd,0)=0x181; - WFIFOB(fd,2)=flag; - WFIFOSET(fd,packet_len(0x181)); -} - - -/// Adds alliance or opposition (ZC_ADD_RELATED_GUILD). -/// 0185 <relation>.L <guild id>.L <guild name>.24B -/* -void clif_guild_allianceadded(struct guild *g,int idx) -{ - unsigned char buf[64]; - WBUFW(buf,0)=0x185; - WBUFL(buf,2)=g->alliance[idx].opposition; - WBUFL(buf,6)=g->alliance[idx].guild_id; - memcpy(WBUFP(buf,10),g->alliance[idx].name,NAME_LENGTH); - clif_send(buf,packet_len(0x185),guild_getavailablesd(g),GUILD); -} -*/ - - -/// Notifies the client about the result of a guild break (ZC_ACK_DISORGANIZE_GUILD_RESULT). -/// 015e <reason>.L -/// 0 = success -/// 1 = invalid key (guild name, @see clif_parse_GuildBreak) -/// 2 = there are still members in the guild -void clif_guild_broken(struct map_session_data *sd,int flag) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x15e)); - WFIFOW(fd,0)=0x15e; - WFIFOL(fd,2)=flag; - WFIFOSET(fd,packet_len(0x15e)); -} - - -/// Displays emotion on an object (ZC_EMOTION). -/// 00c0 <id>.L <type>.B -/// type: -/// enum emotion_type -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(0xc0),bl,AREA); -} - - -/// Displays the contents of a talkiebox trap (ZC_TALKBOX_CHATCONTENTS). -/// 0191 <id>.L <contents>.80B -void clif_talkiebox(struct block_list* bl, const char* talkie) -{ - unsigned char buf[MESSAGE_SIZE+6]; - nullpo_retv(bl); - - WBUFW(buf,0) = 0x191; - WBUFL(buf,2) = bl->id; - safestrncpy((char*)WBUFP(buf,6),talkie,MESSAGE_SIZE); - clif_send(buf,packet_len(0x191),bl,AREA); -} - - -/// Displays wedding effect centered on an object (ZC_CONGRATULATION). -/// 01ea <id>.L -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(0x1ea), bl, AREA); -} - - -/// Notifies the client of the name of the partner character (ZC_COUPLENAME). -/// 01e6 <partner name>.24B -void clif_callpartner(struct map_session_data *sd) -{ - unsigned char buf[26]; - const char *p; - - nullpo_retv(sd); - - WBUFW(buf,0) = 0x1e6; - - if( sd->status.partner_id ) - { - if( ( p = map_charid2nick(sd->status.partner_id) ) != NULL ) - { - memcpy(WBUFP(buf,2), p, NAME_LENGTH); - } - else - { - WBUFB(buf,2) = 0; - } - } - else - {// Send zero-length name if no partner, to initialize the client buffer. - WBUFB(buf,2) = 0; - } - - clif_send(buf, packet_len(0x1e6), &sd->bl, AREA); -} - - -/// Initiates the partner "taming" process [DracoRPG] (ZC_START_COUPLE). -/// 01e4 -/// This packet while still implemented by the client is no longer being officially used. -/* -void clif_marriage_process(struct map_session_data *sd) -{ - int fd; - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x1e4)); - WFIFOW(fd,0)=0x1e4; - WFIFOSET(fd,packet_len(0x1e4)); -} -*/ - - -/// Notice of divorce (ZC_DIVORCE). -/// 0205 <partner name>.24B -void clif_divorced(struct map_session_data* sd, const char* name) -{ - int fd; - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x205)); - WFIFOW(fd,0)=0x205; - memcpy(WFIFOP(fd,2), name, NAME_LENGTH); - WFIFOSET(fd, packet_len(0x205)); -} - - -/// Marriage proposal (ZC_REQ_COUPLE). -/// 01e2 <account id>.L <char id>.L <char name>.24B -/// This packet while still implemented by the client is no longer being officially used. -/* -void clif_marriage_proposal(int fd, struct map_session_data *sd, struct map_session_data* ssd) -{ - nullpo_retv(sd); - - WFIFOHEAD(fd,packet_len(0x1e2)); - WFIFOW(fd,0) = 0x1e2; - WFIFOL(fd,2) = ssd->status.account_id; - WFIFOL(fd,6) = ssd->status.char_id; - safestrncpy((char*)WFIFOP(fd,10), ssd->status.name, NAME_LENGTH); - WFIFOSET(fd, packet_len(0x1e2)); -} -*/ - - -/*========================================== - * - *------------------------------------------*/ -void clif_disp_onlyself(struct map_session_data *sd, const char *mes, int len) -{ - clif_disp_message(&sd->bl, mes, len, SELF); -} - -/*========================================== - * Displays a message using the guild-chat colors to the specified targets. [Skotlex] - *------------------------------------------*/ -void clif_disp_message(struct block_list* src, const char* mes, int len, enum send_target target) -{ - unsigned char buf[256]; - - if( len == 0 ) - { - return; - } - else if( len > sizeof(buf)-5 ) - { - ShowWarning("clif_disp_message: Truncated message '%s' (len=%d, max=%d, aid=%d).\n", mes, len, sizeof(buf)-5, src->id); - len = sizeof(buf)-5; - } - - WBUFW(buf, 0) = 0x17f; - WBUFW(buf, 2) = len + 5; - safestrncpy((char*)WBUFP(buf,4), mes, len+1); - clif_send(buf, WBUFW(buf,2), src, target); -} - - -/// Notifies the client about the result of a request to disconnect another player (ZC_ACK_DISCONNECT_CHARACTER). -/// 00cd <result>.L (unknown packet version or invalid information at packet_len_table) -/// 00cd <result>.B -/// result: -/// 0 = failure -/// 1 = success -void clif_GM_kickack(struct map_session_data *sd, int id) -{ - int fd; - - nullpo_retv(sd); - - fd = sd->fd; - WFIFOHEAD(fd,packet_len(0xcd)); - WFIFOW(fd,0) = 0xcd; - WFIFOB(fd,2) = id; // FIXME: this is not account id - WFIFOSET(fd, packet_len(0xcd)); -} - - -void clif_GM_kick(struct map_session_data *sd,struct map_session_data *tsd) -{ - int fd = tsd->fd; - - if( fd > 0 ) - clif_authfail_fd(fd, 15); - else - map_quit(tsd); - - if( sd ) - clif_GM_kickack(sd,tsd->status.account_id); -} - - -/// Displays various manner-related status messages (ZC_ACK_GIVE_MANNER_POINT). -/// 014a <result>.L -/// result: -/// 0 = "A manner point has been successfully aligned." -/// 1 = MP_FAILURE_EXHAUST -/// 2 = MP_FAILURE_ALREADY_GIVING -/// 3 = "Chat Block has been applied by GM due to your ill-mannerous action." -/// 4 = "Automated Chat Block has been applied due to Anti-Spam System." -/// 5 = "You got a good point from %s." -void clif_manner_message(struct map_session_data* sd, uint32 type) -{ - int fd; - nullpo_retv(sd); - - fd = sd->fd; - WFIFOHEAD(fd,packet_len(0x14a)); - WFIFOW(fd,0) = 0x14a; - WFIFOL(fd,2) = type; - WFIFOSET(fd, packet_len(0x14a)); -} - - -/// Followup to 0x14a type 3/5, informs who did the manner adjustment action (ZC_NOTIFY_MANNER_POINT_GIVEN). -/// 014b <type>.B <GM name>.24B -/// type: -/// 0 = positive (unmute) -/// 1 = negative (mute) -void clif_GM_silence(struct map_session_data* sd, struct map_session_data* tsd, uint8 type) -{ - int fd; - nullpo_retv(sd); - nullpo_retv(tsd); - - fd = tsd->fd; - WFIFOHEAD(fd,packet_len(0x14b)); - WFIFOW(fd,0) = 0x14b; - WFIFOB(fd,2) = type; - safestrncpy((char*)WFIFOP(fd,3), sd->status.name, NAME_LENGTH); - WFIFOSET(fd, packet_len(0x14b)); -} - - -/// Notifies the client about the result of a request to allow/deny whispers from a player (ZC_SETTING_WHISPER_PC). -/// 00d1 <type>.B <result>.B -/// type: -/// 0 = /ex (deny) -/// 1 = /in (allow) -/// result: -/// 0 = success -/// 1 = failure -/// 2 = too many blocks -void clif_wisexin(struct map_session_data *sd,int type,int flag) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0xd1)); - WFIFOW(fd,0)=0xd1; - WFIFOB(fd,2)=type; - WFIFOB(fd,3)=flag; - WFIFOSET(fd,packet_len(0xd1)); -} - -/// Notifies the client about the result of a request to allow/deny whispers from anyone (ZC_SETTING_WHISPER_STATE). -/// 00d2 <type>.B <result>.B -/// type: -/// 0 = /exall (deny) -/// 1 = /inall (allow) -/// result: -/// 0 = success -/// 1 = failure -void clif_wisall(struct map_session_data *sd,int type,int flag) -{ - int fd; - - nullpo_retv(sd); - - fd=sd->fd; - WFIFOHEAD(fd,packet_len(0xd2)); - WFIFOW(fd,0)=0xd2; - WFIFOB(fd,2)=type; - WFIFOB(fd,3)=flag; - WFIFOSET(fd,packet_len(0xd2)); -} - - -/// Play a BGM! [Rikter/Yommy] (ZC_PLAY_NPC_BGM). -/// 07fe <bgm>.24B -void clif_playBGM(struct map_session_data* sd, const char* name) -{ - int fd; - - nullpo_retv(sd); - - fd = sd->fd; - WFIFOHEAD(fd,packet_len(0x7fe)); - WFIFOW(fd,0) = 0x7fe; - safestrncpy((char*)WFIFOP(fd,2), name, NAME_LENGTH); - WFIFOSET(fd,packet_len(0x7fe)); -} - - -/// Plays/stops a wave sound (ZC_SOUND). -/// 01d3 <file name>.24B <act>.B <term>.L <npc id>.L -/// file name: -/// relative to data\wav -/// act: -/// 0 = play (once) -/// 1 = play (repeat, does not work) -/// 2 = stops all sound instances of file name (does not work) -/// term: -/// unknown purpose, only relevant to act = 1 -/// npc id: -/// The accustic direction of the sound is determined by the -/// relative position of the NPC to the player (3D sound). -void clif_soundeffect(struct map_session_data* sd, struct block_list* bl, const char* name, int type) -{ - int fd; - - nullpo_retv(sd); - nullpo_retv(bl); - - fd = sd->fd; - WFIFOHEAD(fd,packet_len(0x1d3)); - WFIFOW(fd,0) = 0x1d3; - safestrncpy((char*)WFIFOP(fd,2), name, NAME_LENGTH); - WFIFOB(fd,26) = type; - WFIFOL(fd,27) = 0; - WFIFOL(fd,31) = bl->id; - WFIFOSET(fd,packet_len(0x1d3)); -} - -void clif_soundeffectall(struct block_list* bl, const char* name, int type, enum send_target coverage) -{ - unsigned char buf[40]; - - nullpo_retv(bl); - - WBUFW(buf,0) = 0x1d3; - safestrncpy((char*)WBUFP(buf,2), name, NAME_LENGTH); - WBUFB(buf,26) = type; - WBUFL(buf,27) = 0; - WBUFL(buf,31) = bl->id; - clif_send(buf, packet_len(0x1d3), bl, coverage); -} - - -/// Displays special effects (npcs, weather, etc) [Valaris] (ZC_NOTIFY_EFFECT2). -/// 01f3 <id>.L <effect id>.L -/// effect id: -/// @see doc/effect_list.txt -void clif_specialeffect(struct block_list* bl, int type, enum send_target target) -{ - unsigned char buf[24]; - - nullpo_retv(bl); - - memset(buf, 0, packet_len(0x1f3)); - - WBUFW(buf,0) = 0x1f3; - WBUFL(buf,2) = bl->id; - WBUFL(buf,6) = type; - - clif_send(buf, packet_len(0x1f3), bl, target); - - if (disguised(bl)) { - WBUFL(buf,2) = -bl->id; - clif_send(buf, packet_len(0x1f3), bl, SELF); - } -} - -void clif_specialeffect_single(struct block_list* bl, int type, int fd) -{ - WFIFOHEAD(fd,10); - WFIFOW(fd,0) = 0x1f3; - WFIFOL(fd,2) = bl->id; - WFIFOL(fd,6) = type; - WFIFOSET(fd,10); -} - - -/// Notifies clients of an special/visual effect that accepts an value (ZC_NOTIFY_EFFECT3). -/// 0284 <id>.L <effect id>.L <num data>.L -/// effect id: -/// @see doc/effect_list.txt -/// num data: -/// effect-dependent value -void clif_specialeffect_value(struct block_list* bl, int effect_id, int num, send_target target) -{ - uint8 buf[14]; - - WBUFW(buf,0) = 0x284; - WBUFL(buf,2) = bl->id; - WBUFL(buf,6) = effect_id; - WBUFL(buf,10) = num; - - clif_send(buf, packet_len(0x284), bl, target); - - if( disguised(bl) ) - { - WBUFL(buf,2) = -bl->id; - clif_send(buf, packet_len(0x284), bl, SELF); - } -} -// Modification of clif_messagecolor to send colored messages to players to chat log only (doesn't display overhead) -/// 02c1 <packet len>.W <id>.L <color>.L <message>.?B -int clif_colormes(struct map_session_data * sd, enum clif_colors color, const char* msg) { - unsigned short msg_len = strlen(msg) + 1; - - WFIFOHEAD(sd->fd,msg_len + 12); - WFIFOW(sd->fd,0) = 0x2C1; - WFIFOW(sd->fd,2) = msg_len + 12; - WFIFOL(sd->fd,4) = 0; - WFIFOL(sd->fd,8) = color_table[color]; - safestrncpy((char*)WFIFOP(sd->fd,12), msg, msg_len); - clif_send(WFIFOP(sd->fd,0), WFIFOW(sd->fd,2), &sd->bl, SELF); - - return 0; -} - -/// Monster/NPC color chat [SnakeDrak] (ZC_NPC_CHAT). -/// 02c1 <packet len>.W <id>.L <color>.L <message>.?B -void clif_messagecolor(struct block_list* bl, unsigned long color, const char* msg) { - unsigned short msg_len = strlen(msg) + 1; - uint8 buf[256]; - color = (color & 0x0000FF) << 16 | (color & 0x00FF00) | (color & 0xFF0000) >> 16; // RGB to BGR - - nullpo_retv(bl); - - if( msg_len > sizeof(buf)-12 ) - { - ShowWarning("clif_messagecolor: Truncating too long message '%s' (len=%u).\n", msg, msg_len); - msg_len = sizeof(buf)-12; - } - - WBUFW(buf,0) = 0x2C1; - WBUFW(buf,2) = msg_len + 12; - WBUFL(buf,4) = bl->id; - WBUFL(buf,8) = color; - memcpy(WBUFP(buf,12), msg, msg_len); - - clif_send(buf, WBUFW(buf,2), bl, AREA_CHAT_WOC); -} - -/// Public chat message [Valaris] (ZC_NOTIFY_CHAT). -/// 008d <packet len>.W <id>.L <message>.?B -void clif_message(struct block_list* bl, const char* msg) { - unsigned short msg_len = strlen(msg) + 1; - uint8 buf[256]; - nullpo_retv(bl); - - if( msg_len > sizeof(buf)-8 ) { - ShowWarning("clif_message: Truncating too long message '%s' (len=%u).\n", msg, msg_len); - msg_len = sizeof(buf)-8; - } - - WBUFW(buf,0) = 0x8d; - WBUFW(buf,2) = msg_len + 8; - WBUFL(buf,4) = bl->id; - safestrncpy((char*)WBUFP(buf,8), msg, msg_len); - - clif_send(buf, WBUFW(buf,2), bl, AREA_CHAT_WOC); -} - -// refresh the client's screen, getting rid of any effects -void clif_refresh(struct map_session_data *sd) -{ - int i; - nullpo_retv(sd); - - clif_changemap(sd,sd->mapindex,sd->bl.x,sd->bl.y); - clif_inventorylist(sd); - if(pc_iscarton(sd)) { - clif_cartlist(sd); - clif_updatestatus(sd,SP_CARTINFO); - } - clif_updatestatus(sd,SP_WEIGHT); - clif_updatestatus(sd,SP_MAXWEIGHT); - 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); - if (sd->spiritball) - clif_spiritball_single(sd->fd, sd); - for(i = 1; i < 5; i++){ - if( sd->talisman[i] > 0 ) - clif_talisman_single(sd->fd, sd, i); - } - if (sd->vd.cloth_color) - clif_refreshlook(&sd->bl,sd->bl.id,LOOK_CLOTHES_COLOR,sd->vd.cloth_color,SELF); - if(merc_is_hom_active(sd->hd)) - clif_send_homdata(sd,SP_ACK,0); - if( sd->md ) { - clif_mercenary_info(sd); - clif_mercenary_skillblock(sd); - } - if( sd->ed ) - clif_elemental_info(sd); - map_foreachinrange(clif_getareachar,&sd->bl,AREA_SIZE,BL_ALL,sd); - clif_weather_check(sd); - if( sd->chatID ) - chat_leavechat(sd,0); - if( sd->state.vending ) - clif_openvending(sd, sd->bl.id, sd->vending); - if( pc_issit(sd) ) - clif_sitting(&sd->bl); // FIXME: just send to self, not area - if( pc_isdead(sd) ) // When you refresh, resend the death packet. - clif_clearunit_single(sd->bl.id,CLR_DEAD,sd->fd); - else - clif_changed_dir(&sd->bl, SELF); - - // unlike vending, resuming buyingstore crashes the client. - buyingstore_close(sd); - - mail_clear(sd); -} - - -/// Updates the object's (bl) name on client. -/// 0095 <id>.L <char name>.24B (ZC_ACK_REQNAME) -/// 0195 <id>.L <char name>.24B <party name>.24B <guild name>.24B <position name>.24B (ZC_ACK_REQNAMEALL) -void clif_charnameack (int fd, struct block_list *bl) -{ - unsigned char buf[103]; - int cmd = 0x95, i, ps = -1; - - nullpo_retv(bl); - - WBUFW(buf,0) = cmd; - WBUFL(buf,2) = bl->id; - - switch( bl->type ) - { - case BL_PC: - { - struct map_session_data *ssd = (struct map_session_data *)bl; - struct party_data *p = NULL; - struct guild *g = NULL; - - //Requesting your own "shadow" name. [Skotlex] - if (ssd->fd == fd && ssd->disguise) - WBUFL(buf,2) = -bl->id; - - if( ssd->fakename[0] ) - { - WBUFW(buf, 0) = cmd = 0x195; - memcpy(WBUFP(buf,6), ssd->fakename, NAME_LENGTH); - WBUFB(buf,30) = WBUFB(buf,54) = WBUFB(buf,78) = 0; - break; - } - memcpy(WBUFP(buf,6), ssd->status.name, NAME_LENGTH); - - if( ssd->status.party_id ) - { - p = party_search(ssd->status.party_id); - } - if( ssd->status.guild_id ) - { - if( ( g = guild_search(ssd->status.guild_id) ) != NULL ) - { - ARR_FIND(0, g->max_member, i, g->member[i].account_id == ssd->status.account_id && g->member[i].char_id == ssd->status.char_id); - if( i < g->max_member ) ps = g->member[i].position; - } - } - - if( !battle_config.display_party_name && g == NULL ) - {// do not display party unless the player is also in a guild - p = NULL; - } - - if (p == NULL && g == NULL) - break; - - WBUFW(buf, 0) = cmd = 0x195; - if (p) - memcpy(WBUFP(buf,30), p->party.name, NAME_LENGTH); - else - WBUFB(buf,30) = 0; - - if (g && ps >= 0 && ps < MAX_GUILDPOSITION) - { - memcpy(WBUFP(buf,54), g->name,NAME_LENGTH); - memcpy(WBUFP(buf,78), g->position[ps].name, NAME_LENGTH); - } else { //Assume no guild. - WBUFB(buf,54) = 0; - WBUFB(buf,78) = 0; - } - } - break; - //[blackhole89] - case BL_HOM: - memcpy(WBUFP(buf,6), ((TBL_HOM*)bl)->homunculus.name, NAME_LENGTH); - break; - case BL_MER: - memcpy(WBUFP(buf,6), ((TBL_MER*)bl)->db->name, NAME_LENGTH); - break; - case BL_PET: - memcpy(WBUFP(buf,6), ((TBL_PET*)bl)->pet.name, NAME_LENGTH); - break; - case BL_NPC: - memcpy(WBUFP(buf,6), ((TBL_NPC*)bl)->name, NAME_LENGTH); - break; - case BL_MOB: - { - struct mob_data *md = (struct mob_data *)bl; - nullpo_retv(md); - - memcpy(WBUFP(buf,6), md->name, NAME_LENGTH); - if( md->guardian_data && md->guardian_data->guild_id ) - { - WBUFW(buf, 0) = cmd = 0x195; - WBUFB(buf,30) = 0; - memcpy(WBUFP(buf,54), md->guardian_data->guild_name, NAME_LENGTH); - memcpy(WBUFP(buf,78), md->guardian_data->castle->castle_name, NAME_LENGTH); - } - else if( battle_config.show_mob_info ) - { - char mobhp[50], *str_p = mobhp; - WBUFW(buf, 0) = cmd = 0x195; - if( battle_config.show_mob_info&4 ) - str_p += sprintf(str_p, "Lv. %d | ", md->level); - if( battle_config.show_mob_info&1 ) - str_p += sprintf(str_p, "HP: %u/%u | ", md->status.hp, md->status.max_hp); - if( battle_config.show_mob_info&2 ) - str_p += sprintf(str_p, "HP: %d%% | ", get_percentage(md->status.hp, md->status.max_hp)); - //Even thought mobhp ain't a name, we send it as one so the client - //can parse it. [Skotlex] - if( str_p != mobhp ) - { - *(str_p-3) = '\0'; //Remove trailing space + pipe. - memcpy(WBUFP(buf,30), mobhp, NAME_LENGTH); - WBUFB(buf,54) = 0; - WBUFB(buf,78) = 0; - } - } - } - break; - case BL_CHAT: //FIXME: Clients DO request this... what should be done about it? The chat's title may not fit... [Skotlex] -// memcpy(WBUFP(buf,6), (struct chat*)->title, NAME_LENGTH); -// break; - return; - case BL_ELEM: - memcpy(WBUFP(buf,6), ((TBL_ELEM*)bl)->db->name, NAME_LENGTH); - break; - default: - ShowError("clif_charnameack: bad type %d(%d)\n", bl->type, bl->id); - return; - } - - // if no receipient specified just update nearby clients - if (fd == 0) - clif_send(buf, packet_len(cmd), bl, AREA); - else { - WFIFOHEAD(fd, packet_len(cmd)); - memcpy(WFIFOP(fd, 0), buf, packet_len(cmd)); - WFIFOSET(fd, packet_len(cmd)); - } -} - - -//Used to update when a char leaves a party/guild. [Skotlex] -//Needed because when you send a 0x95 packet, the client will not remove the cached party/guild info that is not sent. -void clif_charnameupdate (struct map_session_data *ssd) -{ - unsigned char buf[103]; - int cmd = 0x195, ps = -1, i; - struct party_data *p = NULL; - struct guild *g = NULL; - - nullpo_retv(ssd); - - if( ssd->fakename[0] ) - return; //No need to update as the party/guild was not displayed anyway. - - WBUFW(buf,0) = cmd; - WBUFL(buf,2) = ssd->bl.id; - - memcpy(WBUFP(buf,6), ssd->status.name, NAME_LENGTH); - - if (!battle_config.display_party_name) { - if (ssd->status.party_id > 0 && ssd->status.guild_id > 0 && (g = guild_search(ssd->status.guild_id)) != NULL) - p = party_search(ssd->status.party_id); - }else{ - if (ssd->status.party_id > 0) - p = party_search(ssd->status.party_id); - } - - if( ssd->status.guild_id > 0 && (g = guild_search(ssd->status.guild_id)) != NULL ) - { - ARR_FIND(0, g->max_member, i, g->member[i].account_id == ssd->status.account_id && g->member[i].char_id == ssd->status.char_id); - if( i < g->max_member ) ps = g->member[i].position; - } - - if( p ) - memcpy(WBUFP(buf,30), p->party.name, NAME_LENGTH); - else - WBUFB(buf,30) = 0; - - if( g && ps >= 0 && ps < MAX_GUILDPOSITION ) - { - memcpy(WBUFP(buf,54), g->name,NAME_LENGTH); - memcpy(WBUFP(buf,78), g->position[ps].name, NAME_LENGTH); - } - else - { - WBUFB(buf,54) = 0; - WBUFB(buf,78) = 0; - } - - // Update nearby clients - clif_send(buf, packet_len(cmd), &ssd->bl, AREA); -} - - -/// Taekwon Jump (TK_HIGHJUMP) effect (ZC_HIGHJUMP). -/// 01ff <id>.L <x>.W <y>.W -/// -/// Visually moves(instant) a character to x,y. The char moves even -/// when the target cell isn't walkable. If the char is sitting it -/// stays that way. -void clif_slide(struct block_list *bl, int x, int y) -{ - unsigned char buf[10]; - nullpo_retv(bl); - - WBUFW(buf, 0) = 0x01ff; - WBUFL(buf, 2) = bl->id; - WBUFW(buf, 6) = x; - WBUFW(buf, 8) = y; - clif_send(buf, packet_len(0x1ff), bl, AREA); - - if( disguised(bl) ) - { - WBUFL(buf,2) = -bl->id; - clif_send(buf, packet_len(0x1ff), bl, SELF); - } -} - - -/*------------------------------------------ - * @me command by lordalfa, rewritten implementation by Skotlex - *------------------------------------------*/ -void clif_disp_overhead(struct map_session_data *sd, const char* mes) -{ - unsigned char buf[256]; //This should be more than sufficient, the theorical max is CHAT_SIZE + 8 (pads and extra inserted crap) - int len_mes = strlen(mes)+1; //Account for \0 - - if (len_mes > sizeof(buf)-8) { - ShowError("clif_disp_overhead: Message too long (length %d)\n", len_mes); - len_mes = sizeof(buf)-8; //Trunk it to avoid problems. - } - // send message to others - WBUFW(buf,0) = 0x8d; - WBUFW(buf,2) = len_mes + 8; // len of message + 8 (command+len+id) - WBUFL(buf,4) = sd->bl.id; - safestrncpy((char*)WBUFP(buf,8), mes, len_mes); - clif_send(buf, WBUFW(buf,2), &sd->bl, AREA_CHAT_WOC); - - // send back message to the speaker - WBUFW(buf,0) = 0x8e; - WBUFW(buf, 2) = len_mes + 4; - safestrncpy((char*)WBUFP(buf,4), mes, len_mes); - clif_send(buf, WBUFW(buf,2), &sd->bl, SELF); -} - -/*========================== - * Minimap fix [Kevin] - * Remove dot from minimap - *--------------------------*/ -void clif_party_xy_remove(struct map_session_data *sd) -{ - unsigned char buf[16]; - nullpo_retv(sd); - WBUFW(buf,0)=0x107; - WBUFL(buf,2)=sd->status.account_id; - WBUFW(buf,6)=-1; - WBUFW(buf,8)=-1; - clif_send(buf,packet_len(0x107),&sd->bl,PARTY_SAMEMAP_WOS); -} - - -/// Displays a skill message (thanks to Rayce) (ZC_SKILLMSG). -/// 0215 <msg id>.L -/// msg id: -/// 0x15 = End all negative status (PA_GOSPEL) -/// 0x16 = Immunity to all status (PA_GOSPEL) -/// 0x17 = MaxHP +100% (PA_GOSPEL) -/// 0x18 = MaxSP +100% (PA_GOSPEL) -/// 0x19 = All stats +20 (PA_GOSPEL) -/// 0x1c = Enchant weapon with Holy element (PA_GOSPEL) -/// 0x1d = Enchant armor with Holy element (PA_GOSPEL) -/// 0x1e = DEF +25% (PA_GOSPEL) -/// 0x1f = ATK +100% (PA_GOSPEL) -/// 0x20 = HIT/Flee +50 (PA_GOSPEL) -/// 0x28 = Full strip failed because of coating (ST_FULLSTRIP) -/// ? = nothing -void clif_gospel_info(struct map_session_data *sd, int type) -{ - int fd=sd->fd; - WFIFOHEAD(fd,packet_len(0x215)); - WFIFOW(fd,0)=0x215; - WFIFOL(fd,2)=type; - WFIFOSET(fd, packet_len(0x215)); - -} - - -/// Multi-purpose mission information packet (ZC_STARSKILL). -/// 020e <mapname>.24B <monster_id>.L <star>.B <result>.B -/// result: -/// 0 = Star Gladiator %s has designed <mapname>'s as the %s. -/// star: -/// 0 = Place of the Sun -/// 1 = Place of the Moon -/// 2 = Place of the Stars -/// 1 = Star Gladiator %s's %s: <mapname> -/// star: -/// 0 = Place of the Sun -/// 1 = Place of the Moon -/// 2 = Place of the Stars -/// 10 = Star Gladiator %s has designed <mapname>'s as the %s. -/// star: -/// 0 = Target of the Sun -/// 1 = Target of the Moon -/// 2 = Target of the Stars -/// 11 = Star Gladiator %s's %s: <mapname used as monster name> -/// star: -/// 0 = Monster of the Sun -/// 1 = Monster of the Moon -/// 2 = Monster of the Stars -/// 20 = [TaeKwon Mission] Target Monster : <mapname used as monster name> (<star>%) -/// 21 = [Taming Mission] Target Monster : <mapname used as monster name> -/// 22 = [Collector Rank] Target Item : <monster_id used as item id> -/// 30 = [Sun, Moon and Stars Angel] Designed places and monsters have been reset. -/// 40 = Target HP : <monster_id used as HP> -void clif_starskill(struct map_session_data* sd, const char* mapname, int monster_id, unsigned char star, unsigned char result) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x20e)); - WFIFOW(fd,0) = 0x20e; - safestrncpy((char*)WFIFOP(fd,2), mapname, NAME_LENGTH); - WFIFOL(fd,26) = monster_id; - WFIFOB(fd,30) = star; - WFIFOB(fd,31) = result; - WFIFOSET(fd,packet_len(0x20e)); -} - -/*========================================== - * Info about Star Glaldiator save map [Komurka] - * type: 1: Information, 0: Map registered - *------------------------------------------*/ -void clif_feel_info(struct map_session_data* sd, unsigned char feel_level, unsigned char type) -{ - char mapname[MAP_NAME_LENGTH_EXT]; - - mapindex_getmapname_ext(mapindex_id2name(sd->feel_map[feel_level].index), mapname); - clif_starskill(sd, mapname, 0, feel_level, type ? 1 : 0); -} - -/*========================================== - * Info about Star Glaldiator hate mob [Komurka] - * type: 1: Register mob, 0: Information. - *------------------------------------------*/ -void clif_hate_info(struct map_session_data *sd, unsigned char hate_level,int class_, unsigned char type) -{ - if( pcdb_checkid(class_) ) - { - clif_starskill(sd, job_name(class_), class_, hate_level, type ? 10 : 11); - } - else if( mobdb_checkid(class_) ) - { - clif_starskill(sd, mob_db(class_)->jname, class_, hate_level, type ? 10 : 11); - } - else - { - ShowWarning("clif_hate_info: Received invalid class %d for this packet (char_id=%d, hate_level=%u, type=%u).\n", class_, sd->status.char_id, (unsigned int)hate_level, (unsigned int)type); - } -} - -/*========================================== - * Info about TaeKwon Do TK_MISSION mob [Skotlex] - *------------------------------------------*/ -void clif_mission_info(struct map_session_data *sd, int mob_id, unsigned char progress) -{ - clif_starskill(sd, mob_db(mob_id)->jname, mob_id, progress, 20); -} - -/*========================================== - * Feel/Hate reset (thanks to Rayce) [Skotlex] - *------------------------------------------*/ -void clif_feel_hate_reset(struct map_session_data *sd) -{ - clif_starskill(sd, "", 0, 0, 30); -} - - -/// Equip window (un)tick ack (ZC_CONFIG). -/// 02d9 <type>.L <value>.L -/// type: -/// 0 = open equip window -/// value: -/// 0 = disabled -/// 1 = enabled -void clif_equiptickack(struct map_session_data* sd, int flag) -{ - int fd; - nullpo_retv(sd); - fd = sd->fd; - - WFIFOHEAD(fd, packet_len(0x2d9)); - WFIFOW(fd, 0) = 0x2d9; - WFIFOL(fd, 2) = 0; - WFIFOL(fd, 6) = flag; - WFIFOSET(fd, packet_len(0x2d9)); -} - - -/// The player's 'view equip' state, sent during login (ZC_CONFIG_NOTIFY). -/// 02da <open equip window>.B -/// open equip window: -/// 0 = disabled -/// 1 = enabled -void clif_equipcheckbox(struct map_session_data* sd) -{ - int fd; - nullpo_retv(sd); - fd = sd->fd; - - WFIFOHEAD(fd, packet_len(0x2da)); - WFIFOW(fd, 0) = 0x2da; - WFIFOB(fd, 2) = (sd->status.show_equip ? 1 : 0); - WFIFOSET(fd, packet_len(0x2da)); -} - - -/// Sends info about a player's equipped items. -/// 02d7 <packet len>.W <name>.24B <class>.W <hairstyle>.W <up-viewid>.W <mid-viewid>.W <low-viewid>.W <haircolor>.W <cloth-dye>.W <gender>.B {equip item}.26B* (ZC_EQUIPWIN_MICROSCOPE) -/// 02d7 <packet len>.W <name>.24B <class>.W <hairstyle>.W <bottom-viewid>.W <mid-viewid>.W <up-viewid>.W <haircolor>.W <cloth-dye>.W <gender>.B {equip item}.28B* (ZC_EQUIPWIN_MICROSCOPE, PACKETVER >= 20100629) -/// 0859 <packet len>.W <name>.24B <class>.W <hairstyle>.W <bottom-viewid>.W <mid-viewid>.W <up-viewid>.W <haircolor>.W <cloth-dye>.W <gender>.B {equip item}.28B* (ZC_EQUIPWIN_MICROSCOPE2, PACKETVER >= 20101124) -/// 0859 <packet len>.W <name>.24B <class>.W <hairstyle>.W <bottom-viewid>.W <mid-viewid>.W <up-viewid>.W <robe>.W <haircolor>.W <cloth-dye>.W <gender>.B {equip item}.28B* (ZC_EQUIPWIN_MICROSCOPE2, PACKETVER >= 20110111) -void clif_viewequip_ack(struct map_session_data* sd, struct map_session_data* tsd) -{ - uint8* buf; - int i, n, fd, offset = 0; -#if PACKETVER < 20100629 - const int s = 26; -#else - const int s = 28; -#endif - nullpo_retv(sd); - nullpo_retv(tsd); - fd = sd->fd; - - WFIFOHEAD(fd, MAX_INVENTORY * s + 43); - buf = WFIFOP(fd,0); - -#if PACKETVER < 20101124 - WBUFW(buf, 0) = 0x2d7; -#else - WBUFW(buf, 0) = 0x859; -#endif - safestrncpy((char*)WBUFP(buf, 4), tsd->status.name, NAME_LENGTH); - WBUFW(buf,28) = tsd->status.class_; - WBUFW(buf,30) = tsd->vd.hair_style; - WBUFW(buf,32) = tsd->vd.head_bottom; - WBUFW(buf,34) = tsd->vd.head_mid; - WBUFW(buf,36) = tsd->vd.head_top; -#if PACKETVER >= 20110111 - WBUFW(buf,38) = tsd->vd.robe; - offset+= 2; - buf = WBUFP(buf,2); -#endif - WBUFW(buf,38) = tsd->vd.hair_color; - WBUFW(buf,40) = tsd->vd.cloth_color; - WBUFB(buf,42) = tsd->vd.sex; - - for(i=0,n=0; i < MAX_INVENTORY; i++) - { - if (tsd->status.inventory[i].nameid <= 0 || tsd->inventory_data[i] == NULL) // Item doesn't exist - continue; - if (!itemdb_isequip2(tsd->inventory_data[i])) // Is not equippable - continue; - - // Inventory position - WBUFW(buf, n*s+43) = i + 2; - // Add refine, identify flag, element, etc. - clif_item_sub(WBUFP(buf,0), n*s+45, &tsd->status.inventory[i], tsd->inventory_data[i], pc_equippoint(tsd, i)); - // Add cards - clif_addcards(WBUFP(buf, n*s+55), &tsd->status.inventory[i]); - // Expiration date stuff, if all of those are set to 0 then the client doesn't show anything related (6 bytes) - WBUFL(buf, n*s+63) = tsd->status.inventory[i].expire_time; - WBUFW(buf, n*s+67) = 0; -#if PACKETVER >= 20100629 - if (tsd->inventory_data[i]->equip&EQP_VISIBLE) - WBUFW(buf, n*s+69) = tsd->inventory_data[i]->look; - else - WBUFW(buf, n*s+69) = 0; -#endif - n++; - } - - WFIFOW(fd, 2) = 43+offset+n*s; // Set length - WFIFOSET(fd, WFIFOW(fd, 2)); -} - - -/// Display msgstringtable.txt string (ZC_MSG). -/// 0291 <message>.W -void clif_msg(struct map_session_data* sd, unsigned short id) -{ - int fd; - nullpo_retv(sd); - fd = sd->fd; - - WFIFOHEAD(fd, packet_len(0x291)); - WFIFOW(fd, 0) = 0x291; - WFIFOW(fd, 2) = id; // zero-based msgstringtable.txt index - WFIFOSET(fd, packet_len(0x291)); -} - - -/// Display msgstringtable.txt string and fill in a valid for %d format (ZC_MSG_VALUE). -/// 0x7e2 <message>.W <value>.L -void clif_msg_value(struct map_session_data* sd, unsigned short id, int value) -{ - int fd = sd->fd; - - WFIFOHEAD(fd, packet_len(0x7e2)); - WFIFOW(fd,0) = 0x7e2; - WFIFOW(fd,2) = id; - WFIFOL(fd,4) = value; - WFIFOSET(fd, packet_len(0x7e2)); -} - - -/// Displays msgstringtable.txt string, prefixed with a skill name. (ZC_MSG_SKILL). -/// 07e6 <skill id>.W <msg id>.L -/// -/// NOTE: Message has following format and is printed in color 0xCDCDFF (purple): -/// "[SkillName] Message" -void clif_msg_skill(struct map_session_data* sd, uint16 skill_id, int msg_id) -{ - int fd = sd->fd; - - WFIFOHEAD(fd, packet_len(0x7e6)); - WFIFOW(fd,0) = 0x7e6; - WFIFOW(fd,2) = skill_id; - WFIFOL(fd,4) = msg_id; - WFIFOSET(fd, packet_len(0x7e6)); -} - - -/// View player equip request denied -void clif_viewequip_fail(struct map_session_data* sd) -{ - clif_msg(sd, 0x54d); -} - - -/// Validates one global/guild/party/whisper message packet and tries to recognize its components. -/// Returns true if the packet was parsed successfully. -/// Formats: 0 - <packet id>.w <packet len>.w (<name> : <message>).?B 00 -/// 1 - <packet id>.w <packet len>.w <name>.24B <message>.?B 00 -static bool clif_process_message(struct map_session_data* sd, int format, char** name_, int* namelen_, char** message_, int* messagelen_) -{ - char *text, *name, *message; - unsigned int packetlen, textlen, namelen, messagelen; - int fd = sd->fd; - - *name_ = NULL; - *namelen_ = 0; - *message_ = NULL; - *messagelen_ = 0; - - packetlen = RFIFOW(fd,2); - // basic structure checks - if( packetlen < 4 + 1 ) - { // 4-byte header and at least an empty string is expected - ShowWarning("clif_process_message: Received malformed packet from player '%s' (no message data)!\n", sd->status.name); - return false; - } - - text = (char*)RFIFOP(fd,4); - textlen = packetlen - 4; - - // process <name> part of the packet - if( format == 0 ) - {// name and message are separated by ' : ' - // validate name - name = text; - namelen = strnlen(sd->status.name, NAME_LENGTH-1); // name length (w/o zero byte) - - if( strncmp(name, sd->status.name, namelen) || // the text must start with the speaker's name - name[namelen] != ' ' || name[namelen+1] != ':' || name[namelen+2] != ' ' ) // followed by ' : ' - { - //Hacked message, or infamous "client desynch" issue where they pick one char while loading another. - ShowWarning("clif_process_message: Player '%s' sent a message using an incorrect name! Forcing a relog...\n", sd->status.name); - set_eof(fd); // Just kick them out to correct it. - return false; - } - - message = name + namelen + 3; - messagelen = textlen - namelen - 3; // this should be the message length (w/ zero byte included) - } - else - {// name has fixed width - if( textlen < NAME_LENGTH + 1 ) - { - ShowWarning("clif_process_message: Received malformed packet from player '%s' (packet length is incorrect)!\n", sd->status.name); - return false; - } - - // validate name - name = text; - namelen = strnlen(name, NAME_LENGTH-1); // name length (w/o zero byte) - - if( name[namelen] != '\0' ) - { // only restriction is that the name must be zero-terminated - ShowWarning("clif_process_message: Player '%s' sent an unterminated name!\n", sd->status.name); - return false; - } - - message = name + NAME_LENGTH; - messagelen = textlen - NAME_LENGTH; // this should be the message length (w/ zero byte included) - } - - if( messagelen != strnlen(message, messagelen)+1 ) - { // the declared length must match real length - ShowWarning("clif_process_message: Received malformed packet from player '%s' (length is incorrect)!\n", sd->status.name); - return false; - } - // verify <message> part of the packet - if( message[messagelen-1] != '\0' ) - { // message must be zero-terminated - ShowWarning("clif_process_message: Player '%s' sent an unterminated message string!\n", sd->status.name); - return false; - } - if( messagelen > CHAT_SIZE_MAX-1 ) - { // messages mustn't be too long - // Normally you can only enter CHATBOX_SIZE-1 letters into the chat box, but Frost Joke / Dazzler's text can be longer. - // Also, the physical size of strings that use multibyte encoding can go multiple times over the chatbox capacity. - // Neither the official client nor server place any restriction on the length of the data in the packet, - // but we'll only allow reasonably long strings here. This also makes sure that they fit into the `chatlog` table. - ShowWarning("clif_process_message: Player '%s' sent a message too long ('%.*s')!\n", sd->status.name, CHAT_SIZE_MAX-1, message); - return false; - } - - *name_ = name; - *namelen_ = namelen; - *message_ = message; - *messagelen_ = messagelen; - return true; -} - -// --------------------- -// clif_guess_PacketVer -// --------------------- -// Parses a WantToConnection packet to try to identify which is the packet version used. [Skotlex] -// error codes: -// 0 - Success -// 1 - Unknown packet_ver -// 2 - Invalid account_id -// 3 - Invalid char_id -// 4 - Invalid login_id1 (reserved) -// 5 - Invalid client_tick (reserved) -// 6 - Invalid sex -// Only the first 'invalid' error that appears is used. -static int clif_guess_PacketVer(int fd, int get_previous, int *error) -{ - static int err = 1; - static int packet_ver = -1; - int cmd, packet_len, value; //Value is used to temporarily store account/char_id/sex - - if (get_previous) - {//For quick reruns, since the normal code flow is to fetch this once to identify the packet version, then again in the wanttoconnect function. [Skotlex] - if( error ) - *error = err; - return packet_ver; - } - - //By default, start searching on the default one. - err = 1; - packet_ver = clif_config.packet_db_ver; - cmd = RFIFOW(fd,0); - packet_len = RFIFOREST(fd); - -#define SET_ERROR(n) \ - if( err == 1 )\ - err = n;\ -//define SET_ERROR - - // FIXME: If the packet is not received at once, this will FAIL. - // Figure out, when it happens, that only part of the packet is - // received, or fix the function to be able to deal with that - // case. -#define CHECK_PACKET_VER() \ - if( cmd != clif_config.connect_cmd[packet_ver] || packet_len != packet_db[packet_ver][cmd].len )\ - ;/* not wanttoconnection or wrong length */\ - else if( (value=(int)RFIFOL(fd, packet_db[packet_ver][cmd].pos[0])) < START_ACCOUNT_NUM || value > END_ACCOUNT_NUM )\ - { SET_ERROR(2); }/* invalid account_id */\ - else if( (value=(int)RFIFOL(fd, packet_db[packet_ver][cmd].pos[1])) <= 0 )\ - { SET_ERROR(3); }/* invalid char_id */\ - /* RFIFOL(fd, packet_db[packet_ver][cmd].pos[2]) - don't care about login_id1 */\ - /* RFIFOL(fd, packet_db[packet_ver][cmd].pos[3]) - don't care about client_tick */\ - else if( (value=(int)RFIFOB(fd, packet_db[packet_ver][cmd].pos[4])) != 0 && value != 1 )\ - { SET_ERROR(6); }/* invalid sex */\ - else\ - {\ - err = 0;\ - if( error )\ - *error = 0;\ - return packet_ver;\ - }\ -//define CHECK_PACKET_VER - - CHECK_PACKET_VER();//Default packet version found. - - for (packet_ver = MAX_PACKET_VER; packet_ver > 0; packet_ver--) - { //Start guessing the version, giving priority to the newer ones. [Skotlex] - CHECK_PACKET_VER(); - } - if( error ) - *error = err; - packet_ver = -1; - return -1; -#undef SET_ERROR -#undef CHECK_PACKET_VER -} - -// ------------ -// clif_parse_* -// ------------ -// Parses incoming (player) connection - - -/// Request to connect to map-server. -/// 0072 <account id>.L <char id>.L <auth code>.L <client time>.L <gender>.B (CZ_ENTER) -/// 0436 <account id>.L <char id>.L <auth code>.L <client time>.L <gender>.B (CZ_ENTER2) -/// There are various variants of this packet, some of them have padding between fields. -void clif_parse_WantToConnection(int fd, TBL_PC* sd) -{ - struct block_list* bl; - struct auth_node* node; - int cmd, account_id, char_id, login_id1, sex; - unsigned int client_tick; //The client tick is a tick, therefore it needs be unsigned. [Skotlex] - int packet_ver; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04, 11: 21sept04, 12: 18oct04, 13: 25oct04 (by [Yor]) - - if (sd) { - ShowError("clif_parse_WantToConnection : invalid request (character already logged in)\n"); - return; - } - - // Only valid packet version get here - packet_ver = clif_guess_PacketVer(fd, 1, NULL); - - cmd = RFIFOW(fd,0); - account_id = RFIFOL(fd, packet_db[packet_ver][cmd].pos[0]); - char_id = RFIFOL(fd, packet_db[packet_ver][cmd].pos[1]); - login_id1 = RFIFOL(fd, packet_db[packet_ver][cmd].pos[2]); - client_tick = RFIFOL(fd, packet_db[packet_ver][cmd].pos[3]); - sex = RFIFOB(fd, packet_db[packet_ver][cmd].pos[4]); - - if( packet_ver < 5 || // reject really old client versions - (packet_ver <= 9 && (battle_config.packet_ver_flag & 1) == 0) || // older than 6sept04 - (packet_ver > 9 && (battle_config.packet_ver_flag & 1<<(packet_ver-9)) == 0)) // version not allowed - {// packet version rejected - ShowInfo("Rejected connection attempt, forbidden packet version (AID/CID: '"CL_WHITE"%d/%d"CL_RESET"', Packet Ver: '"CL_WHITE"%d"CL_RESET"', IP: '"CL_WHITE"%s"CL_RESET"').\n", account_id, char_id, packet_ver, ip2str(session[fd]->client_addr, NULL)); - WFIFOHEAD(fd,packet_len(0x6a)); - WFIFOW(fd,0) = 0x6a; - WFIFOB(fd,2) = 5; // Your Game's EXE file is not the latest version - WFIFOSET(fd,packet_len(0x6a)); - set_eof(fd); - return; - } - - if( runflag != MAPSERVER_ST_RUNNING ) - {// not allowed - clif_authfail_fd(fd,1);// server closed - return; - } - - //Check for double login. - bl = map_id2bl(account_id); - if(bl && bl->type != BL_PC) { - ShowError("clif_parse_WantToConnection: a non-player object already has id %d, please increase the starting account number\n", account_id); - WFIFOHEAD(fd,packet_len(0x6a)); - WFIFOW(fd,0) = 0x6a; - WFIFOB(fd,2) = 3; // Rejected by server - WFIFOSET(fd,packet_len(0x6a)); - set_eof(fd); - return; - } - - if (bl || - ((node=chrif_search(account_id)) && //An already existing node is valid only if it is for this login. - !(node->account_id == account_id && node->char_id == char_id && node->state == ST_LOGIN))) - { - clif_authfail_fd(fd, 8); //Still recognizes last connection - return; - } - - CREATE(sd, TBL_PC, 1); - sd->fd = fd; - sd->packet_ver = packet_ver; - session[fd]->session_data = sd; - - pc_setnewpc(sd, account_id, char_id, login_id1, client_tick, sex, fd); - -#if PACKETVER < 20070521 - WFIFOHEAD(fd,4); - WFIFOL(fd,0) = sd->bl.id; - WFIFOSET(fd,4); -#else - WFIFOHEAD(fd,packet_len(0x283)); - WFIFOW(fd,0) = 0x283; - WFIFOL(fd,2) = sd->bl.id; - WFIFOSET(fd,packet_len(0x283)); -#endif - - chrif_authreq(sd); -} - - -/// Notification from the client, that it has finished map loading and is about to display player's character (CZ_NOTIFY_ACTORINIT). -/// 007d -void clif_parse_LoadEndAck(int fd,struct map_session_data *sd) -{ - if(sd->bl.prev != NULL) - return; - - if (!sd->state.active) - { //Character loading is not complete yet! - //Let pc_reg_received reinvoke this when ready. - sd->state.connect_new = 0; - return; - } - - if (sd->state.rewarp) - { //Rewarp player. - sd->state.rewarp = 0; - clif_changemap(sd, sd->mapindex, sd->bl.x, sd->bl.y); - return; - } - - sd->state.warping = 0; - - // look -#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(sd->vd.cloth_color) - clif_refreshlook(&sd->bl,sd->bl.id,LOOK_CLOTHES_COLOR,sd->vd.cloth_color,SELF); - - // item - clif_inventorylist(sd); // inventory list first, otherwise deleted items in pc_checkitem show up as 'unknown item' - pc_checkitem(sd); - - // cart - if(pc_iscarton(sd)) { - clif_cartlist(sd); - clif_updatestatus(sd,SP_CARTINFO); - } - - // weight - clif_updatestatus(sd,SP_WEIGHT); - clif_updatestatus(sd,SP_MAXWEIGHT); - - // guild - // (needs to go before clif_spawn() to show guild emblems correctly) - if(sd->status.guild_id) - guild_send_memberinfoshort(sd,1); - - if(battle_config.pc_invincible_time > 0) { - if(map_flag_gvg(sd->bl.m)) - pc_setinvincibletimer(sd,battle_config.pc_invincible_time<<1); - else - pc_setinvincibletimer(sd,battle_config.pc_invincible_time); - } - - if( map[sd->bl.m].users++ == 0 && battle_config.dynamic_mobs ) - map_spawnmobs(sd->bl.m); - if( !(sd->sc.option&OPTION_INVISIBLE) ) - {// increment the number of pvp players on the map - map[sd->bl.m].users_pvp++; - } - if( map[sd->bl.m].instance_id ) - { - instance[map[sd->bl.m].instance_id].users++; - instance_check_idle(map[sd->bl.m].instance_id); - } - sd->state.debug_remove_map = 0; // temporary state to track double remove_map's [FlavioJS] - - // reset the callshop flag if the player changes map - sd->state.callshop = 0; - - map_addblock(&sd->bl); - clif_spawn(&sd->bl); - - // Party - // (needs to go after clif_spawn() to show hp bars correctly) - if(sd->status.party_id) { - party_send_movemap(sd); - clif_party_hp(sd); // Show hp after displacement [LuzZza] - } - - if( sd->bg_id ) clif_bg_hp(sd); // BattleGround System - - if(map[sd->bl.m].flag.pvp && !(sd->sc.option&OPTION_INVISIBLE)) { - if(!battle_config.pk_mode) { // remove pvp stuff for pk_mode [Valaris] - if (!map[sd->bl.m].flag.pvp_nocalcrank) - 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; - sd->pvp_won = 0; - sd->pvp_lost = 0; - } - clif_map_property(sd, MAPPROPERTY_FREEPVPZONE); - } else - // set flag, if it's a duel [LuzZza] - if(sd->duel_group) - clif_map_property(sd, MAPPROPERTY_FREEPVPZONE); - - if (map[sd->bl.m].flag.gvg_dungeon) - clif_map_property(sd, MAPPROPERTY_FREEPVPZONE); //TODO: Figure out the real packet to send here. - - if( map_flag_gvg(sd->bl.m) ) - clif_map_property(sd, MAPPROPERTY_AGITZONE); - - // info about nearby objects - // must use foreachinarea (CIRCULAR_AREA interferes with foreachinrange) - 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, BL_ALL, sd); - - // pet - if( sd->pd ) - { - if( battle_config.pet_no_gvg && map_flag_gvg(sd->bl.m) ) - { //Return the pet to egg. [Skotlex] - clif_displaymessage(sd->fd, msg_txt(666)); - pet_menu(sd, 3); //Option 3 is return to egg. - } - else - { - map_addblock(&sd->pd->bl); - clif_spawn(&sd->pd->bl); - clif_send_petdata(sd,sd->pd,0,0); - clif_send_petstatus(sd); -// skill_unit_move(&sd->pd->bl,gettick(),1); - } - } - - //homunculus [blackhole89] - if( merc_is_hom_active(sd->hd) ) - { - map_addblock(&sd->hd->bl); - clif_spawn(&sd->hd->bl); - clif_send_homdata(sd,SP_ACK,0); - clif_hominfo(sd,sd->hd,1); - clif_hominfo(sd,sd->hd,0); //for some reason, at least older clients want this sent twice - clif_homskillinfoblock(sd); - if( battle_config.hom_setting&0x8 ) - status_calc_bl(&sd->hd->bl, SCB_SPEED); //Homunc mimic their master's speed on each map change - if( !(battle_config.hom_setting&0x2) ) - skill_unit_move(&sd->hd->bl,gettick(),1); // apply land skills immediately - } - - if( sd->md ) { - map_addblock(&sd->md->bl); - clif_spawn(&sd->md->bl); - clif_mercenary_info(sd); - clif_mercenary_skillblock(sd); - status_calc_bl(&sd->md->bl, SCB_SPEED); // Mercenary mimic their master's speed on each map change - } - - if( sd->ed ) { - map_addblock(&sd->ed->bl); - clif_spawn(&sd->ed->bl); - clif_elemental_info(sd); - clif_elemental_updatestatus(sd,SP_HP); - clif_hpmeter_single(sd->fd,sd->ed->bl.id,sd->ed->battle_status.hp,sd->ed->battle_status.max_hp); - clif_elemental_updatestatus(sd,SP_SP); - status_calc_bl(&sd->ed->bl, SCB_SPEED); //Elemental mimic their master's speed on each map change - } - - if(sd->state.connect_new) { - int lv; - sd->state.connect_new = 0; - clif_skillinfoblock(sd); - clif_hotkeys_send(sd); - clif_updatestatus(sd,SP_BASEEXP); - clif_updatestatus(sd,SP_NEXTBASEEXP); - clif_updatestatus(sd,SP_JOBEXP); - clif_updatestatus(sd,SP_NEXTJOBEXP); - clif_updatestatus(sd,SP_SKILLPOINT); - clif_initialstatus(sd); - - if (sd->sc.option&OPTION_FALCON) - clif_status_load(&sd->bl, SI_FALCON, 1); - - if (sd->sc.option&OPTION_RIDING) - clif_status_load(&sd->bl, SI_RIDING, 1); - else if (sd->sc.option&OPTION_WUGRIDER) - clif_status_load(&sd->bl, SI_WUGRIDER, 1); - - if(sd->status.manner < 0) - sc_start(&sd->bl,SC_NOCHAT,100,0,0); - - //Auron reported that This skill only triggers when you logon on the map o.O [Skotlex] - if ((lv = pc_checkskill(sd,SG_KNOWLEDGE)) > 0) { - if(sd->bl.m == sd->feel_map[0].m - || sd->bl.m == sd->feel_map[1].m - || sd->bl.m == sd->feel_map[2].m) - sc_start(&sd->bl, SC_KNOWLEDGE, 100, lv, skill_get_time(SG_KNOWLEDGE, lv)); - } - - if(sd->pd && sd->pd->pet.intimate > 900) - clif_pet_emotion(sd->pd,(sd->pd->pet.class_ - 100)*100 + 50 + pet_hungry_val(sd->pd)); - - if(merc_is_hom_active(sd->hd)) - merc_hom_init_timers(sd->hd); - - if (night_flag && map[sd->bl.m].flag.nightenabled) { - sd->state.night = 1; - clif_status_load(&sd->bl, SI_NIGHT, 1); - } - - // Notify everyone that this char logged in [Skotlex]. - map_foreachpc(clif_friendslist_toggle_sub, sd->status.account_id, sd->status.char_id, 1); - - //Login Event - npc_script_event(sd, NPCE_LOGIN); - } else { - //For some reason the client "loses" these on warp/map-change. - 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); - - // abort currently running script - sd->state.using_fake_npc = 0; - sd->state.menu_or_input = 0; - sd->npc_menu = 0; - - if(sd->npc_id) - npc_event_dequeue(sd); - } - - if( sd->state.changemap ) - {// restore information that gets lost on map-change -#if PACKETVER >= 20070918 - clif_partyinvitationstate(sd); - clif_equipcheckbox(sd); -#endif - if( (battle_config.bg_flee_penalty != 100 || battle_config.gvg_flee_penalty != 100) && - (map_flag_gvg(sd->state.pmap) || map_flag_gvg(sd->bl.m) || map[sd->state.pmap].flag.battleground || map[sd->bl.m].flag.battleground) ) - status_calc_bl(&sd->bl, SCB_FLEE); //Refresh flee penalty - - if( night_flag && map[sd->bl.m].flag.nightenabled ) - { //Display night. - if( !sd->state.night ) - { - sd->state.night = 1; - clif_status_load(&sd->bl, SI_NIGHT, 1); - } - } - else if( sd->state.night ) - { //Clear night display. - sd->state.night = 0; - clif_status_load(&sd->bl, SI_NIGHT, 0); - } - - if( map[sd->bl.m].flag.battleground ) - { - clif_map_type(sd, MAPTYPE_BATTLEFIELD); // Battleground Mode - if( map[sd->bl.m].flag.battleground == 2 ) - clif_bg_updatescore_single(sd); - } - - if( map[sd->bl.m].flag.allowks && !map_flag_ks(sd->bl.m) ) - { - char output[128]; - sprintf(output, "[ Kill Steal Protection Disable. KS is allowed in this map ]"); - clif_broadcast(&sd->bl, output, strlen(output) + 1, 0x10, SELF); - } - - map_iwall_get(sd); // Updates Walls Info on this Map to Client - sd->state.changemap = false; - } - - mail_clear(sd); - - /* Guild Aura Init */ - if( sd->state.gmaster_flag ) { - guild_guildaura_refresh(sd,GD_LEADERSHIP,guild_checkskill(sd->state.gmaster_flag,GD_LEADERSHIP)); - guild_guildaura_refresh(sd,GD_GLORYWOUNDS,guild_checkskill(sd->state.gmaster_flag,GD_GLORYWOUNDS)); - guild_guildaura_refresh(sd,GD_SOULCOLD,guild_checkskill(sd->state.gmaster_flag,GD_SOULCOLD)); - guild_guildaura_refresh(sd,GD_HAWKEYES,guild_checkskill(sd->state.gmaster_flag,GD_HAWKEYES)); - } - - if( sd->state.vending ) { /* show we have a vending */ - clif_openvending(sd,sd->bl.id,sd->vending); - clif_showvendingboard(&sd->bl,sd->message,0); - } - - if(map[sd->bl.m].flag.loadevent) // Lance - npc_script_event(sd, NPCE_LOADMAP); - - if (pc_checkskill(sd, SG_DEVIL) && !pc_nextjobexp(sd)) - clif_status_load(&sd->bl, SI_DEVIL, 1); //blindness [Komurka] - - if (sd->sc.opt2) //Client loses these on warp. - clif_changeoption(&sd->bl); - - clif_weather_check(sd); - - // For automatic triggering of NPCs after map loading (so you don't need to walk 1 step first) - if (map_getcell(sd->bl.m,sd->bl.x,sd->bl.y,CELL_CHKNPC)) - npc_touch_areanpc(sd,sd->bl.m,sd->bl.x,sd->bl.y); - else - sd->areanpc_id = 0; - - /* it broke at some point (e.g. during a crash), so we make it visibly dead again. */ - if( !sd->status.hp && !pc_isdead(sd) && status_isdead(&sd->bl) ) - pc_setdead(sd); - - // If player is dead, and is spawned (such as @refresh) send death packet. [Valaris] - if(pc_isdead(sd)) - clif_clearunit_area(&sd->bl, CLR_DEAD); - else { - skill_usave_trigger(sd); - clif_changed_dir(&sd->bl, SELF); - } - -// Trigger skill effects if you appear standing on them - if(!battle_config.pc_invincible_time) - skill_unit_move(&sd->bl,gettick(),1); -} - - -/// Server's tick (ZC_NOTIFY_TIME). -/// 007f <time>.L -void clif_notify_time(struct map_session_data* sd, unsigned long time) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x7f)); - WFIFOW(fd,0) = 0x7f; - WFIFOL(fd,2) = time; - WFIFOSET(fd,packet_len(0x7f)); -} - - -/// Request for server's tick. -/// 007e <client tick>.L (CZ_REQUEST_TIME) -/// 0360 <client tick>.L (CZ_REQUEST_TIME2) -/// There are various variants of this packet, some of them have padding between fields. -void clif_parse_TickSend(int fd, struct map_session_data *sd) -{ - sd->client_tick = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); - - clif_notify_time(sd, gettick()); -} - - -/// Sends hotkey bar. -/// 02b9 { <is skill>.B <id>.L <count>.W }*27 (ZC_SHORTCUT_KEY_LIST) -/// 07d9 { <is skill>.B <id>.L <count>.W }*36 (ZC_SHORTCUT_KEY_LIST_V2, PACKETVER >= 20090603) -/// 07d9 { <is skill>.B <id>.L <count>.W }*38 (ZC_SHORTCUT_KEY_LIST_V2, PACKETVER >= 20090617) -void clif_hotkeys_send(struct map_session_data *sd) { -#ifdef HOTKEY_SAVING - const int fd = sd->fd; - int i; -#if PACKETVER < 20090603 - const int cmd = 0x2b9; -#else - const int cmd = 0x7d9; -#endif - if (!fd) return; - WFIFOHEAD(fd, 2+MAX_HOTKEYS*7); - WFIFOW(fd, 0) = cmd; - for(i = 0; i < MAX_HOTKEYS; i++) { - WFIFOB(fd, 2 + 0 + i * 7) = sd->status.hotkeys[i].type; // type: 0: item, 1: skill - WFIFOL(fd, 2 + 1 + i * 7) = sd->status.hotkeys[i].id; // item or skill ID - WFIFOW(fd, 2 + 5 + i * 7) = sd->status.hotkeys[i].lv; // skill level - } - WFIFOSET(fd, packet_len(cmd)); -#endif -} - - -/// Request to update a position on the hotkey bar (CZ_SHORTCUT_KEY_CHANGE). -/// 02ba <index>.W <is skill>.B <id>.L <count>.W -void clif_parse_Hotkey(int fd, struct map_session_data *sd) { -#ifdef HOTKEY_SAVING - unsigned short idx; - int cmd; - - cmd = RFIFOW(fd, 0); - idx = RFIFOW(fd, packet_db[sd->packet_ver][cmd].pos[0]); - if (idx >= MAX_HOTKEYS) return; - - sd->status.hotkeys[idx].type = RFIFOB(fd, packet_db[sd->packet_ver][cmd].pos[1]); - sd->status.hotkeys[idx].id = RFIFOL(fd, packet_db[sd->packet_ver][cmd].pos[2]); - sd->status.hotkeys[idx].lv = RFIFOW(fd, packet_db[sd->packet_ver][cmd].pos[3]); -#endif -} - - -/// Displays cast-like progress bar (ZC_PROGRESS). -/// 02f0 <color>.L <time>.L -void clif_progressbar(struct map_session_data * sd, unsigned long color, unsigned int second) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x2f0)); - WFIFOW(fd,0) = 0x2f0; - WFIFOL(fd,2) = color; - WFIFOL(fd,6) = second; - WFIFOSET(fd,packet_len(0x2f0)); -} - - -/// Removes an ongoing progress bar (ZC_PROGRESS_CANCEL). -/// 02f2 -void clif_progressbar_abort(struct map_session_data * sd) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x2f2)); - WFIFOW(fd,0) = 0x2f2; - WFIFOSET(fd,packet_len(0x2f2)); -} - - -/// Notification from the client, that the progress bar has reached 100% (CZ_PROGRESS). -/// 02f1 -void clif_parse_progressbar(int fd, struct map_session_data * sd) -{ - int npc_id = sd->progressbar.npc_id; - - if( gettick() < sd->progressbar.timeout && sd->st ) - sd->st->state = END; - - sd->progressbar.npc_id = sd->progressbar.timeout = 0; - npc_scriptcont(sd, npc_id); -} - - -/// Request to walk to a certain position on the current map. -/// 0085 <dest>.3B (CZ_REQUEST_MOVE) -/// 035f <dest>.3B (CZ_REQUEST_MOVE2) -/// There are various variants of this packet, some of them have padding between fields. -void clif_parse_WalkToXY(int fd, struct map_session_data *sd) -{ - short x, y; - - if (pc_isdead(sd)) { - clif_clearunit_area(&sd->bl, CLR_DEAD); - return; - } - - if (sd->sc.opt1 && ( sd->sc.opt1 == OPT1_STONEWAIT || sd->sc.opt1 == OPT1_BURNING )) - ; //You CAN walk on this OPT1 value. - else if( sd->progressbar.npc_id ) - clif_progressbar_abort(sd); - else if (pc_cant_act(sd)) - return; - - if(sd->sc.data[SC_RUN] || sd->sc.data[SC_WUGDASH]) - return; - - pc_delinvincibletimer(sd); - - RFIFOPOS(fd, packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0], &x, &y, NULL); - - //Set last idle time... [Skotlex] - sd->idletime = last_tick; - - unit_walktoxy(&sd->bl, x, y, 4); -} - - -/// Notification about the result of a disconnect request (ZC_ACK_REQ_DISCONNECT). -/// 018b <result>.W -/// result: -/// 0 = disconnect (quit) -/// 1 = cannot disconnect (wait 10 seconds) -/// ? = ignored -void clif_disconnect_ack(struct map_session_data* sd, short result) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x18b)); - WFIFOW(fd,0) = 0x18b; - WFIFOW(fd,2) = result; - WFIFOSET(fd,packet_len(0x18b)); -} - - -/// Request to disconnect from server (CZ_REQ_DISCONNECT). -/// 018a <type>.W -/// type: -/// 0 = quit -void clif_parse_QuitGame(int fd, struct map_session_data *sd) -{ - /* Rovert's prevent logout option fixed [Valaris] */ - if( !sd->sc.data[SC_CLOAKING] && !sd->sc.data[SC_HIDING] && !sd->sc.data[SC_CHASEWALK] && !sd->sc.data[SC_CLOAKINGEXCEED] && - (!battle_config.prevent_logout || DIFF_TICK(gettick(), sd->canlog_tick) > battle_config.prevent_logout) ) - { - set_eof(fd); - clif_disconnect_ack(sd, 0); - } else { - clif_disconnect_ack(sd, 1); - } -} - - -/// Requesting unit's name. -/// 0094 <id>.L (CZ_REQNAME) -/// 0368 <id>.L (CZ_REQNAME2) -/// There are various variants of this packet, some of them have padding between fields. -void clif_parse_GetCharNameRequest(int fd, struct map_session_data *sd) -{ - int id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); - struct block_list* bl; - //struct status_change *sc; - - if( id < 0 && -id == sd->bl.id ) // for disguises [Valaris] - id = sd->bl.id; - - bl = map_id2bl(id); - if( bl == NULL ) - return; // Lagged clients could request names of already gone mobs/players. [Skotlex] - - if( sd->bl.m != bl->m || !check_distance_bl(&sd->bl, bl, AREA_SIZE) ) - return; // Block namerequests past view range - - // 'see people in GM hide' cheat detection - /* disabled due to false positives (network lag + request name of char that's about to hide = race condition) - sc = status_get_sc(bl); - if (sc && sc->option&OPTION_INVISIBLE && !disguised(bl) && - bl->type != BL_NPC && //Skip hidden NPCs which can be seen using Maya Purple - pc_get_group_level(sd) < battle_config.hack_info_GM_level - ) { - char gm_msg[256]; - sprintf(gm_msg, "Hack on NameRequest: character '%s' (account: %d) requested the name of an invisible target (id: %d).\n", sd->status.name, sd->status.account_id, id); - ShowWarning(gm_msg); - // information is sent to all online GMs - intif_wis_message_to_gm(wisp_server_name, battle_config.hack_info_GM_level, gm_msg); - return; - } - */ - - clif_charnameack(fd, bl); -} - - -/// Validates and processes global messages -/// 008c <packet len>.W <text>.?B (<name> : <message>) 00 (CZ_REQUEST_CHAT) -/// There are various variants of this packet. -void clif_parse_GlobalMessage(int fd, struct map_session_data* sd) -{ - const char* text = (char*)RFIFOP(fd,4); - int textlen = RFIFOW(fd,2) - 4; - - char *name, *message, *fakename = NULL; - int namelen, messagelen; - - bool is_fake; - - // validate packet and retrieve name and message - if( !clif_process_message(sd, 0, &name, &namelen, &message, &messagelen) ) - return; - - if( is_atcommand(fd, sd, message, 1) ) - return; - - if( sd->sc.data[SC_BERSERK] || sd->sc.data[SC__BLOODYLUST] || (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOCHAT) ) - return; - - if( battle_config.min_chat_delay ) - { //[Skotlex] - if (DIFF_TICK(sd->cantalk_tick, gettick()) > 0) - return; - sd->cantalk_tick = gettick() + battle_config.min_chat_delay; - } - /** - * Fake Name Design by FatalEror (bug report #9) - **/ - if( ( is_fake = ( sd->fakename[0] ) ) ) { - fakename = (char*) aMalloc(strlen(sd->fakename)+messagelen+3); - strcpy(fakename, sd->fakename); - strcat(fakename, " : "); - strcat(fakename, message); - textlen = strlen(fakename) + 1; - } - // send message to others (using the send buffer for temp. storage) - WFIFOHEAD(fd, 8 + textlen); - WFIFOW(fd,0) = 0x8d; - WFIFOW(fd,2) = 8 + textlen; - WFIFOL(fd,4) = sd->bl.id; - safestrncpy((char*)WFIFOP(fd,8), is_fake ? fakename : text, textlen); - //FIXME: chat has range of 9 only - clif_send(WFIFOP(fd,0), WFIFOW(fd,2), &sd->bl, sd->chatID ? CHAT_WOS : AREA_CHAT_WOC); - - // send back message to the speaker - if( is_fake ) { - WFIFOW(fd,0) = 0x8e; - WFIFOW(fd,2) = textlen + 4; - safestrncpy((char*)WFIFOP(fd,4), fakename, textlen); - aFree(fakename); - } else { - memcpy(WFIFOP(fd,0), RFIFOP(fd,0), RFIFOW(fd,2)); - WFIFOW(fd,0) = 0x8e; - } - WFIFOSET(fd, WFIFOW(fd,2)); -#ifdef PCRE_SUPPORT - // trigger listening npcs - map_foreachinrange(npc_chat_sub, &sd->bl, AREA_SIZE, BL_NPC, text, textlen, &sd->bl); -#endif - - // Chat logging type 'O' / Global Chat - log_chat(LOG_CHAT_GLOBAL, 0, sd->status.char_id, sd->status.account_id, mapindex_id2name(sd->mapindex), sd->bl.x, sd->bl.y, NULL, message); -} - - -/// /mm /mapmove (as @rura GM command) (CZ_MOVETO_MAP). -/// Request to warp to a map on given coordinates. -/// 0140 <map name>.16B <x>.W <y>.W -void clif_parse_MapMove(int fd, struct map_session_data *sd) -{ - char command[MAP_NAME_LENGTH_EXT+25]; - char* map_name; - - map_name = (char*)RFIFOP(fd,2); - map_name[MAP_NAME_LENGTH_EXT-1]='\0'; - sprintf(command, "%cmapmove %s %d %d", atcommand_symbol, map_name, RFIFOW(fd,18), RFIFOW(fd,20)); - is_atcommand(fd, sd, command, 1); -} - - -/// Updates body and head direction of an object (ZC_CHANGE_DIRECTION). -/// 009c <id>.L <head dir>.W <dir>.B -/// head dir: -/// 0 = straight -/// 1 = turned CW -/// 2 = turned CCW -/// dir: -/// 0 = north -/// 1 = northwest -/// 2 = west -/// 3 = southwest -/// 4 = south -/// 5 = southeast -/// 6 = east -/// 7 = northeast -void clif_changed_dir(struct block_list *bl, enum send_target target) -{ - unsigned char buf[64]; - - WBUFW(buf,0) = 0x9c; - WBUFL(buf,2) = bl->id; - WBUFW(buf,6) = bl->type==BL_PC?((TBL_PC*)bl)->head_dir:0; - WBUFB(buf,8) = unit_getdir(bl); - - clif_send(buf, packet_len(0x9c), bl, target); - - if (disguised(bl)) { - WBUFL(buf,2) = -bl->id; - WBUFW(buf,6) = 0; - clif_send(buf, packet_len(0x9c), bl, SELF); - } -} - - -/// Request to change own body and head direction. -/// 009b <head dir>.W <dir>.B (CZ_CHANGE_DIRECTION) -/// 0361 <head dir>.W <dir>.B (CZ_CHANGE_DIRECTION2) -/// There are various variants of this packet, some of them have padding between fields. -void clif_parse_ChangeDir(int fd, struct map_session_data *sd) -{ - unsigned char headdir, dir; - - headdir = RFIFOB(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); - dir = RFIFOB(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]); - pc_setdir(sd, dir, headdir); - - clif_changed_dir(&sd->bl, AREA_WOS); -} - - -/// Request to show an emotion (CZ_REQ_EMOTION). -/// 00bf <type>.B -/// type: -/// @see enum emotion_type -void clif_parse_Emotion(int fd, struct map_session_data *sd) -{ - int emoticon = RFIFOB(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); - - if (battle_config.basic_skill_check == 0 || pc_checkskill(sd, NV_BASIC) >= 2) { - if (emoticon == E_MUTE) {// prevent use of the mute emote [Valaris] - clif_skill_fail(sd, 1, USESKILL_FAIL_LEVEL, 1); - return; - } - // fix flood of emotion icon (ro-proxy): flood only the hacker player - if (sd->emotionlasttime + 1 >= time(NULL)) { // not more than 1 per second - sd->emotionlasttime = time(NULL); - clif_skill_fail(sd, 1, USESKILL_FAIL_LEVEL, 1); - return; - } - sd->emotionlasttime = time(NULL); - - if(battle_config.client_reshuffle_dice && emoticon>=E_DICE1 && emoticon<=E_DICE6) - {// re-roll dice - emoticon = rnd()%6+E_DICE1; - } - - clif_emotion(&sd->bl, emoticon); - } else - clif_skill_fail(sd, 1, USESKILL_FAIL_LEVEL, 1); -} - - -/// Amount of currently online players, reply to /w /who (ZC_USER_COUNT). -/// 00c2 <count>.L -void clif_user_count(struct map_session_data* sd, int count) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0xc2)); - WFIFOW(fd,0) = 0xc2; - WFIFOL(fd,2) = count; - WFIFOSET(fd,packet_len(0xc2)); -} - - -/// /w /who (CZ_REQ_USER_COUNT). -/// Request to display amount of currently connected players. -/// 00c1 -void clif_parse_HowManyConnections(int fd, struct map_session_data *sd) -{ - clif_user_count(sd, map_getusers()); -} - - -void clif_parse_ActionRequest_sub(struct map_session_data *sd, int action_type, int target_id, unsigned int tick) -{ - if (pc_isdead(sd)) { - clif_clearunit_area(&sd->bl, CLR_DEAD); - return; - } - - if (sd->sc.count && - (sd->sc.data[SC_TRICKDEAD] || - sd->sc.data[SC_AUTOCOUNTER] || - sd->sc.data[SC_BLADESTOP] || - sd->sc.data[SC__MANHOLE] || - sd->sc.data[SC_CURSEDCIRCLE_ATKER] || - sd->sc.data[SC_CURSEDCIRCLE_TARGET] )) - return; - - pc_stop_walking(sd, 1); - pc_stop_attack(sd); - - if(target_id<0 && -target_id == sd->bl.id) // for disguises [Valaris] - target_id = sd->bl.id; - - switch(action_type) - { - case 0x00: // once attack - case 0x07: // continuous attack - - if( pc_cant_act(sd) || sd->sc.option&OPTION_HIDE ) - return; - - if( sd->sc.option&(OPTION_WEDDING|OPTION_XMAS|OPTION_SUMMER) ) - return; - - if( sd->sc.data[SC_BASILICA] || sd->sc.data[SC__SHADOWFORM] ) - return; - - if (!battle_config.sdelay_attack_enable && pc_checkskill(sd, SA_FREECAST) <= 0) { - if (DIFF_TICK(tick, sd->ud.canact_tick) < 0) { - clif_skill_fail(sd, 1, USESKILL_FAIL_SKILLINTERVAL, 0); - return; - } - } - - pc_delinvincibletimer(sd); - sd->idletime = last_tick; - unit_attack(&sd->bl, target_id, action_type != 0); - break; - case 0x02: // sitdown - if (battle_config.basic_skill_check && pc_checkskill(sd, NV_BASIC) < 3) { - clif_skill_fail(sd, 1, USESKILL_FAIL_LEVEL, 2); - break; - } - - if(pc_issit(sd)) { - //Bugged client? Just refresh them. - clif_sitting(&sd->bl); - return; - } - - if (sd->ud.skilltimer != INVALID_TIMER || (sd->sc.opt1 && sd->sc.opt1 != OPT1_BURNING )) - break; - - if (sd->sc.count && ( - sd->sc.data[SC_DANCING] || - (sd->sc.data[SC_GRAVITATION] && sd->sc.data[SC_GRAVITATION]->val3 == BCT_SELF) - )) //No sitting during these states either. - break; - - pc_setsit(sd); - skill_sit(sd,1); - clif_sitting(&sd->bl); - break; - case 0x03: // standup - if (!pc_issit(sd)) { - //Bugged client? Just refresh them. - clif_standing(&sd->bl); - return; - } - pc_setstand(sd); - skill_sit(sd,0); - clif_standing(&sd->bl); - break; - } -} - - -/// Request for an action. -/// 0089 <target id>.L <action>.B (CZ_REQUEST_ACT) -/// 0437 <target id>.L <action>.B (CZ_REQUEST_ACT2) -/// action: -/// 0 = attack -/// 1 = pick up item -/// 2 = sit down -/// 3 = stand up -/// 7 = continous attack -/// 12 = (touch skill?) -/// There are various variants of this packet, some of them have padding between fields. -void clif_parse_ActionRequest(int fd, struct map_session_data *sd) -{ - clif_parse_ActionRequest_sub(sd, - RFIFOB(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]), - RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]), - gettick() - ); -} - - -/// Response to the death/system menu (CZ_RESTART). -/// 00b2 <type>.B -/// type: -/// 0 = restart (respawn) -/// 1 = char-select (disconnect) -void clif_parse_Restart(int fd, struct map_session_data *sd) -{ - switch(RFIFOB(fd,2)) { - case 0x00: - pc_respawn(sd,CLR_RESPAWN); - break; - case 0x01: - /* Rovert's Prevent logout option - Fixed [Valaris] */ - if( !sd->sc.data[SC_CLOAKING] && !sd->sc.data[SC_HIDING] && !sd->sc.data[SC_CHASEWALK] && !sd->sc.data[SC_CLOAKINGEXCEED] && - (!battle_config.prevent_logout || DIFF_TICK(gettick(), sd->canlog_tick) > battle_config.prevent_logout) ) - { //Send to char-server for character selection. - chrif_charselectreq(sd, session[fd]->client_addr); - } else { - clif_disconnect_ack(sd, 1); - } - break; - } -} - - -/// Validates and processes whispered messages (CZ_WHISPER). -/// 0096 <packet len>.W <nick>.24B <message>.?B -void clif_parse_WisMessage(int fd, struct map_session_data* sd) -{ - struct map_session_data* dstsd; - int i; - - char *target, *message; - int namelen, messagelen; - - // validate packet and retrieve name and message - if( !clif_process_message(sd, 1, &target, &namelen, &message, &messagelen) ) - return; - - if ( is_atcommand(fd, sd, message, 1) ) - return; - - if (sd->sc.data[SC_BERSERK] || sd->sc.data[SC__BLOODYLUST] || (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOCHAT)) - return; - - if (battle_config.min_chat_delay) { //[Skotlex] - if (DIFF_TICK(sd->cantalk_tick, gettick()) > 0) { - return; - } - sd->cantalk_tick = gettick() + battle_config.min_chat_delay; - } - - // Chat logging type 'W' / Whisper - log_chat(LOG_CHAT_WHISPER, 0, sd->status.char_id, sd->status.account_id, mapindex_id2name(sd->mapindex), sd->bl.x, sd->bl.y, target, message); - - //-------------------------------------------------------// - // Lordalfa - Paperboy - To whisper NPC commands // - //-------------------------------------------------------// - if (target[0] && (strncasecmp(target,"NPC:",4) == 0) && (strlen(target) > 4)) - { - char* str = target+4; //Skip the NPC: string part. - struct npc_data* npc; - if ((npc = npc_name2id(str))) { - char split_data[NUM_WHISPER_VAR][CHAT_SIZE_MAX]; - char *split; - char output[256]; - - str = message; - // skip codepage indicator, if detected - if( str[0] == '|' && strlen(str) >= 4 ) - str += 3; - for( i = 0; i < NUM_WHISPER_VAR; ++i ) {// Splits the message using '#' as separators - split = strchr(str,'#'); - if( split == NULL ) { // use the remaining string - safestrncpy(split_data[i], str, ARRAYLENGTH(split_data[i])); - for( ++i; i < NUM_WHISPER_VAR; ++i ) - split_data[i][0] = '\0'; - break; - } - *split = '\0'; - safestrncpy(split_data[i], str, ARRAYLENGTH(split_data[i])); - str = split+1; - } - - for( i = 0; i < NUM_WHISPER_VAR; ++i ) { - sprintf(output, "@whispervar%d$", i); - set_var(sd,output,(char *) split_data[i]); - } - - sprintf(output, "%s::OnWhisperGlobal", npc->exname); - npc_event(sd,output,0); // Calls the NPC label - - return; - } - } else if(strcmpi(target, main_chat_nick) == 0) { // Main chat [LuzZza] - if(!sd->state.mainchat) - clif_displaymessage(fd, msg_txt(388)); // You should enable main chat with "@main on" command. - else { - // send the main message using inter-server system - intif_main_message( sd, message ); - } - - return; - } - - // searching destination character - dstsd = map_nick2sd(target); - - if (dstsd == NULL || strcmp(dstsd->status.name, target) != 0) { - // player is not on this map-server - // 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). - intif_wis_message(sd, target, message, messagelen); - return; - } - - // if player ignores everyone - if (dstsd->state.ignoreAll) { - if (dstsd->sc.option & OPTION_INVISIBLE && pc_get_group_level(sd) < pc_get_group_level(dstsd)) - clif_wis_end(fd, 1); // 1: target character is not loged in - else - clif_wis_end(fd, 3); // 3: everyone ignored by target - return; - } - - // if player is autotrading - if( dstsd->state.autotrade == 1 ) { - char output[256]; - sprintf(output, "%s is in autotrade mode and cannot receive whispered messages.", dstsd->status.name); - clif_wis_message(fd, wisp_server_name, output, strlen(output) + 1); - return; - } - - // if player ignores the source character - ARR_FIND(0, MAX_IGNORE_LIST, i, dstsd->ignore[i].name[0] == '\0' || strcmp(dstsd->ignore[i].name, sd->status.name) == 0); - if(i < MAX_IGNORE_LIST && dstsd->ignore[i].name[0] != '\0') { // source char present in ignore list - clif_wis_end(fd, 2); // 2: ignored by target - return; - } - - // notify sender of success - clif_wis_end(fd, 0); // 0: success to send wisper - - // Normal message - clif_wis_message(dstsd->fd, sd->status.name, message, messagelen); -} - - -/// /b /nb (CZ_BROADCAST). -/// Request to broadcast a message on whole server. -/// 0099 <packet len>.W <text>.?B 00 -void clif_parse_Broadcast(int fd, struct map_session_data* sd) { - char command[CHAT_SIZE_MAX+11]; - char* msg = (char*)RFIFOP(fd,4); - unsigned int len = RFIFOW(fd,2)-4; - - // as the length varies depending on the command used, just block unreasonably long strings - mes_len_check(msg, len, CHAT_SIZE_MAX); - - sprintf(command, "%ckami %s", atcommand_symbol, msg); - is_atcommand(fd, sd, command, 1); -} - - -/// Request to pick up an item. -/// 009f <id>.L (CZ_ITEM_PICKUP) -/// 0362 <id>.L (CZ_ITEM_PICKUP2) -/// There are various variants of this packet, some of them have padding between fields. -void clif_parse_TakeItem(int fd, struct map_session_data *sd) -{ - struct flooritem_data *fitem; - int map_object_id; - - map_object_id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); - - fitem = (struct flooritem_data*)map_id2bl(map_object_id); - - do { - if (pc_isdead(sd)) { - clif_clearunit_area(&sd->bl, CLR_DEAD); - break; - } - - if (fitem == NULL || fitem->bl.type != BL_ITEM || fitem->bl.m != sd->bl.m) - break; - - if( sd->sc.cant.pickup ) - break; - - if (pc_cant_act(sd)) - break; - - if (!pc_takeitem(sd, fitem)) - break; - - return; - } while (0); - // Client REQUIRES a fail packet or you can no longer pick items. - clif_additem(sd,0,0,6); -} - - -/// Request to drop an item. -/// 00a2 <index>.W <amount>.W (CZ_ITEM_THROW) -/// 0363 <index>.W <amount>.W (CZ_ITEM_THROW2) -/// There are various variants of this packet, some of them have padding between fields. -void clif_parse_DropItem(int fd, struct map_session_data *sd) -{ - int item_index = RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0])-2; - int item_amount = RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]); - - for(;;) { - if (pc_isdead(sd)) - break; - - if (pc_cant_act(sd)) - break; - - if (sd->sc.count && ( - sd->sc.data[SC_AUTOCOUNTER] || - sd->sc.data[SC_BLADESTOP] || - (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOITEM) - )) - break; - - if (!pc_dropitem(sd, item_index, item_amount)) - break; - - return; - } - - //Because the client does not like being ignored. - clif_dropitem(sd, item_index,0); -} - - -/// Request to use an item. -/// 00a7 <index>.W <account id>.L (CZ_USE_ITEM) -/// 0439 <index>.W <account id>.L (CZ_USE_ITEM2) -/// There are various variants of this packet, some of them have padding between fields. -void clif_parse_UseItem(int fd, struct map_session_data *sd) -{ - int n; - - if (pc_isdead(sd)) { - clif_clearunit_area(&sd->bl, CLR_DEAD); - return; - } - - //This flag enables you to use items while in an NPC. [Skotlex] - if (sd->npc_id) { - if (sd->npc_id != sd->npc_item_flag) - return; - } - else if (pc_istrading(sd)) - return; - - //Whether the item is used or not is irrelevant, the char ain't idle. [Skotlex] - sd->idletime = last_tick; - n = RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0])-2; - - if(n <0 || n >= MAX_INVENTORY) - return; - if (!pc_useitem(sd,n)) - clif_useitemack(sd,n,0,false); //Send an empty ack packet or the client gets stuck. -} - - -/// Request to equip an item (CZ_REQ_WEAR_EQUIP). -/// 00a9 <index>.W <position>.W -void clif_parse_EquipItem(int fd,struct map_session_data *sd) -{ - int index; - - if(pc_isdead(sd)) { - clif_clearunit_area(&sd->bl,CLR_DEAD); - return; - } - index = RFIFOW(fd,2)-2; - if (index < 0 || index >= MAX_INVENTORY) - return; //Out of bounds check. - - if(sd->npc_id) { - if (sd->npc_id != sd->npc_item_flag) - return; - } else if (sd->state.storage_flag || sd->sc.opt1) - ; //You can equip/unequip stuff while storage is open/under status changes - else if (pc_cant_act(sd)) - return; - - if(!sd->status.inventory[index].identify) { - clif_equipitemack(sd,index,0,0); // fail - return; - } - - if(!sd->inventory_data[index]) - return; - - if(sd->inventory_data[index]->type == IT_PETARMOR){ - pet_equipitem(sd,index); - return; - } - - //Client doesn't send the position for ammo. - if(sd->inventory_data[index]->type == IT_AMMO) - pc_equipitem(sd,index,EQP_AMMO); - else - pc_equipitem(sd,index,RFIFOW(fd,4)); -} - - -/// Request to take off an equip (CZ_REQ_TAKEOFF_EQUIP). -/// 00ab <index>.W -void clif_parse_UnequipItem(int fd,struct map_session_data *sd) -{ - int index; - - if(pc_isdead(sd)) { - clif_clearunit_area(&sd->bl,CLR_DEAD); - return; - } - - if (sd->state.storage_flag || sd->sc.opt1) - ; //You can equip/unequip stuff while storage is open/under status changes - else if (pc_cant_act(sd)) - return; - - index = RFIFOW(fd,2)-2; - - pc_unequipitem(sd,index,1); -} - - -/// Request to start a conversation with an NPC (CZ_CONTACTNPC). -/// 0090 <id>.L <type>.B -/// type: -/// 1 = click -void clif_parse_NpcClicked(int fd,struct map_session_data *sd) -{ - struct block_list *bl; - - if(pc_isdead(sd)) { - clif_clearunit_area(&sd->bl,CLR_DEAD); - return; - } - - if (pc_cant_act(sd)) - return; - - bl = map_id2bl(RFIFOL(fd,2)); - if (!bl) return; - switch (bl->type) { - case BL_MOB: - case BL_PC: - clif_parse_ActionRequest_sub(sd, 0x07, bl->id, gettick()); - break; - case BL_NPC: - if( bl->m != -1 )// the user can't click floating npcs directly (hack attempt) - npc_click(sd,(TBL_NPC*)bl); - break; - } -} - - -/// Selection between buy/sell was made (CZ_ACK_SELECT_DEALTYPE). -/// 00c5 <id>.L <type>.B -/// type: -/// 0 = buy -/// 1 = sell -void clif_parse_NpcBuySellSelected(int fd,struct map_session_data *sd) -{ - if (sd->state.trading) - return; - npc_buysellsel(sd,RFIFOL(fd,2),RFIFOB(fd,6)); -} - - -/// Notification about the result of a purchase attempt from an NPC shop (ZC_PC_PURCHASE_RESULT). -/// 00ca <result>.B -/// result: -/// 0 = "The deal has successfully completed." -/// 1 = "You do not have enough zeny." -/// 2 = "You are over your Weight Limit." -/// 3 = "Out of the maximum capacity, you have too many items." -void clif_npc_buy_result(struct map_session_data* sd, unsigned char result) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0xca)); - WFIFOW(fd,0) = 0xca; - WFIFOB(fd,2) = result; - WFIFOSET(fd,packet_len(0xca)); -} - - -/// Request to buy chosen items from npc shop (CZ_PC_PURCHASE_ITEMLIST). -/// 00c8 <packet len>.W { <amount>.W <name id>.W }* -void clif_parse_NpcBuyListSend(int fd, struct map_session_data* sd) -{ - int n = (RFIFOW(fd,2)-4) /4; - unsigned short* item_list = (unsigned short*)RFIFOP(fd,4); - int result; - - if( sd->state.trading || !sd->npc_shopid ) - result = 1; - else - result = npc_buylist(sd,n,item_list); - - sd->npc_shopid = 0; //Clear shop data. - - clif_npc_buy_result(sd, result); -} - - -/// Notification about the result of a sell attempt to an NPC shop (ZC_PC_SELL_RESULT). -/// 00cb <result>.B -/// result: -/// 0 = "The deal has successfully completed." -/// 1 = "The deal has failed." -void clif_npc_sell_result(struct map_session_data* sd, unsigned char result) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0xcb)); - WFIFOW(fd,0) = 0xcb; - WFIFOB(fd,2) = result; - WFIFOSET(fd,packet_len(0xcb)); -} - - -/// Request to sell chosen items to npc shop (CZ_PC_SELL_ITEMLIST). -/// 00c9 <packet len>.W { <index>.W <amount>.W }* -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); - - if (sd->state.trading || !sd->npc_shopid) - fail = 1; - else - fail = npc_selllist(sd,n,item_list); - - sd->npc_shopid = 0; //Clear shop data. - - clif_npc_sell_result(sd, fail); -} - - -/// Chatroom creation request (CZ_CREATE_CHATROOM). -/// 00d5 <packet len>.W <limit>.W <type>.B <passwd>.8B <title>.?B -/// type: -/// 0 = private -/// 1 = public -void clif_parse_CreateChatRoom(int fd, struct map_session_data* sd) -{ - int len = RFIFOW(fd,2)-15; - int limit = RFIFOW(fd,4); - bool pub = (RFIFOB(fd,6) != 0); - const char* password = (char*)RFIFOP(fd,7); //not zero-terminated - const char* title = (char*)RFIFOP(fd,15); // not zero-terminated - char s_password[CHATROOM_PASS_SIZE]; - char s_title[CHATROOM_TITLE_SIZE]; - - if (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOROOM) - return; - if(battle_config.basic_skill_check && pc_checkskill(sd,NV_BASIC) < 4) { - clif_skill_fail(sd,1,USESKILL_FAIL_LEVEL,3); - return; - } - - if( len <= 0 ) - return; // invalid input - - safestrncpy(s_password, password, CHATROOM_PASS_SIZE); - safestrncpy(s_title, title, min(len+1,CHATROOM_TITLE_SIZE)); //NOTE: assumes that safestrncpy will not access the len+1'th byte - - chat_createpcchat(sd, s_title, s_password, limit, pub); -} - - -/// Chatroom join request (CZ_REQ_ENTER_ROOM). -/// 00d9 <chat ID>.L <passwd>.8B -void clif_parse_ChatAddMember(int fd, struct map_session_data* sd) -{ - int chatid = RFIFOL(fd,2); - const char* password = (char*)RFIFOP(fd,6); // not zero-terminated - - chat_joinchat(sd,chatid,password); -} - - -/// Chatroom properties adjustment request (CZ_CHANGE_CHATROOM). -/// 00de <packet len>.W <limit>.W <type>.B <passwd>.8B <title>.?B -/// type: -/// 0 = private -/// 1 = public -void clif_parse_ChatRoomStatusChange(int fd, struct map_session_data* sd) -{ - int len = RFIFOW(fd,2)-15; - int limit = RFIFOW(fd,4); - bool pub = (RFIFOB(fd,6) != 0); - const char* password = (char*)RFIFOP(fd,7); // not zero-terminated - const char* title = (char*)RFIFOP(fd,15); // not zero-terminated - char s_password[CHATROOM_PASS_SIZE]; - char s_title[CHATROOM_TITLE_SIZE]; - - if( len <= 0 ) - return; // invalid input - - safestrncpy(s_password, password, CHATROOM_PASS_SIZE); - safestrncpy(s_title, title, min(len+1,CHATROOM_TITLE_SIZE)); //NOTE: assumes that safestrncpy will not access the len+1'th byte - - chat_changechatstatus(sd, s_title, s_password, limit, pub); -} - - -/// Request to change the chat room ownership (CZ_REQ_ROLE_CHANGE). -/// 00e0 <role>.L <nick>.24B -/// role: -/// 0 = owner -/// 1 = normal -void clif_parse_ChangeChatOwner(int fd, struct map_session_data* sd) -{ - chat_changechatowner(sd,(char*)RFIFOP(fd,6)); -} - - -/// Request to expel a player from chat room (CZ_REQ_EXPEL_MEMBER). -/// 00e2 <name>.24B -void clif_parse_KickFromChat(int fd,struct map_session_data *sd) -{ - chat_kickchat(sd,(char*)RFIFOP(fd,2)); -} - - -/// Request to leave the current chatroom (CZ_EXIT_ROOM). -/// 00e3 -void clif_parse_ChatLeave(int fd, struct map_session_data* sd) -{ - chat_leavechat(sd,0); -} - - -//Handles notifying asker and rejecter of what has just ocurred. -//Type is used to determine the correct msg_txt to use: -//0: -static void clif_noask_sub(struct map_session_data *src, struct map_session_data *target, int type) -{ - const char* msg; - char output[256]; - // Your request has been rejected by autoreject option. - msg = msg_txt(392); - clif_disp_onlyself(src, msg, strlen(msg)); - //Notice that a request was rejected. - snprintf(output, 256, msg_txt(393+type), src->status.name, 256); - clif_disp_onlyself(target, output, strlen(output)); -} - - -/// Request to begin a trade (CZ_REQ_EXCHANGE_ITEM). -/// 00e4 <account id>.L -void clif_parse_TradeRequest(int fd,struct map_session_data *sd) -{ - struct map_session_data *t_sd; - - t_sd = map_id2sd(RFIFOL(fd,2)); - - if(!sd->chatID && pc_cant_act(sd)) - return; //You can trade while in a chatroom. - - // @noask [LuzZza] - if(t_sd && t_sd->state.noask) { - clif_noask_sub(sd, t_sd, 0); - return; - } - - if( battle_config.basic_skill_check && pc_checkskill(sd,NV_BASIC) < 1) - { - clif_skill_fail(sd,1,USESKILL_FAIL_LEVEL,0); - return; - } - - trade_traderequest(sd,t_sd); -} - - -/// Answer to a trade request (CZ_ACK_EXCHANGE_ITEM). -/// 00e6 <result>.B -/// result: -/// 3 = accepted -/// 4 = rejected -void clif_parse_TradeAck(int fd,struct map_session_data *sd) -{ - trade_tradeack(sd,RFIFOB(fd,2)); -} - - -/// Request to add an item to current trade (CZ_ADD_EXCHANGE_ITEM). -/// 00e8 <index>.W <amount>.L -void clif_parse_TradeAddItem(int fd,struct map_session_data *sd) -{ - short index = RFIFOW(fd,2); - int amount = RFIFOL(fd,4); - - if( index == 0 ) - trade_tradeaddzeny(sd, amount); - else - trade_tradeadditem(sd, index, (short)amount); -} - - -/// Request to lock items in current trade (CZ_CONCLUDE_EXCHANGE_ITEM). -/// 00eb -void clif_parse_TradeOk(int fd,struct map_session_data *sd) -{ - trade_tradeok(sd); -} - - -/// Request to cancel current trade (CZ_CANCEL_EXCHANGE_ITEM). -/// 00ed -void clif_parse_TradeCancel(int fd,struct map_session_data *sd) -{ - trade_tradecancel(sd); -} - - -/// Request to commit current trade (CZ_EXEC_EXCHANGE_ITEM). -/// 00ef -void clif_parse_TradeCommit(int fd,struct map_session_data *sd) -{ - trade_tradecommit(sd); -} - - -/// Request to stop chasing/attacking an unit (CZ_CANCEL_LOCKON). -/// 0118 -void clif_parse_StopAttack(int fd,struct map_session_data *sd) -{ - pc_stop_attack(sd); -} - - -/// Request to move an item from inventory to cart (CZ_MOVE_ITEM_FROM_BODY_TO_CART). -/// 0126 <index>.W <amount>.L -void clif_parse_PutItemToCart(int fd,struct map_session_data *sd) -{ - if (pc_istrading(sd)) - return; - if (!pc_iscarton(sd)) - return; - pc_putitemtocart(sd,RFIFOW(fd,2)-2,RFIFOL(fd,4)); -} - - -/// Request to move an item from cart to inventory (CZ_MOVE_ITEM_FROM_CART_TO_BODY). -/// 0127 <index>.W <amount>.L -void clif_parse_GetItemFromCart(int fd,struct map_session_data *sd) -{ - if (!pc_iscarton(sd)) - return; - pc_getitemfromcart(sd,RFIFOW(fd,2)-2,RFIFOL(fd,4)); -} - - -/// Request to remove cart/falcon/peco/dragon (CZ_REQ_CARTOFF). -/// 012a -void clif_parse_RemoveOption(int fd,struct map_session_data *sd) -{ - /** - * Attempts to remove these options when this function is called (will remove all available) - **/ -#ifdef NEW_CARTS - pc_setoption(sd,sd->sc.option&~(OPTION_RIDING|OPTION_FALCON|OPTION_DRAGON|OPTION_MADOGEAR)); - if( sd->sc.data[SC_PUSH_CART] ) - pc_setcart(sd,0); -#else - pc_setoption(sd,sd->sc.option&~(OPTION_CART|OPTION_RIDING|OPTION_FALCON|OPTION_DRAGON|OPTION_MADOGEAR)); -#endif -} - - -/// Request to change cart's visual look (CZ_REQ_CHANGECART). -/// 01af <num>.W -void clif_parse_ChangeCart(int fd,struct map_session_data *sd) -{// TODO: State tracking? - int type; - - if( sd && pc_checkskill(sd, MC_CHANGECART) < 1 ) - return; - - type = (int)RFIFOW(fd,2); -#ifdef NEW_CARTS - if( (type == 9 && sd->status.base_level > 131) || - (type == 8 && sd->status.base_level > 121) || - (type == 7 && sd->status.base_level > 111) || - (type == 6 && sd->status.base_level > 101) || - (type == 5 && sd->status.base_level > 90) || - (type == 4 && sd->status.base_level > 80) || - (type == 3 && sd->status.base_level > 65) || - (type == 2 && sd->status.base_level > 40) || - (type == 1)) -#else - if( (type == 5 && sd->status.base_level > 90) || - (type == 4 && sd->status.base_level > 80) || - (type == 3 && sd->status.base_level > 65) || - (type == 2 && sd->status.base_level > 40) || - (type == 1)) -#endif - pc_setcart(sd,type); -} - - -/// Request to increase status (CZ_STATUS_CHANGE). -/// 00bb <status id>.W <amount>.B -/// status id: -/// SP_STR ~ SP_LUK -/// amount: -/// client sends always 1 for this, even when using /str+ and -/// the like -void clif_parse_StatusUp(int fd,struct map_session_data *sd) -{ - pc_statusup(sd,RFIFOW(fd,2)); -} - - -/// Request to increase level of a skill (CZ_UPGRADE_SKILLLEVEL). -/// 0112 <skill id>.W -void clif_parse_SkillUp(int fd,struct map_session_data *sd) -{ - pc_skillup(sd,RFIFOW(fd,2)); -} - -static void clif_parse_UseSkillToId_homun(struct homun_data *hd, struct map_session_data *sd, unsigned int tick, uint16 skill_id, uint16 skill_lv, int target_id) -{ - int lv; - - if( !hd ) - return; - if( skillnotok_hom(skill_id, hd) ) - return; - if( hd->bl.id != target_id && skill_get_inf(skill_id)&INF_SELF_SKILL ) - target_id = hd->bl.id; - if( hd->ud.skilltimer != INVALID_TIMER ) - { - if( skill_id != SA_CASTCANCEL && skill_id != SO_SPELLFIST ) return; - } - else if( DIFF_TICK(tick, hd->ud.canact_tick) < 0 ) - return; - - lv = merc_hom_checkskill(hd, skill_id); - if( skill_lv > lv ) - skill_lv = lv; - if( skill_lv ) - unit_skilluse_id(&hd->bl, target_id, skill_id, skill_lv); -} - -static void clif_parse_UseSkillToPos_homun(struct homun_data *hd, struct map_session_data *sd, unsigned int tick, uint16 skill_id, uint16 skill_lv, short x, short y, int skillmoreinfo) -{ - int lv; - if( !hd ) - return; - if( skillnotok_hom(skill_id, hd) ) - return; - if( hd->ud.skilltimer != INVALID_TIMER ) { - if( skill_id != SA_CASTCANCEL && skill_id != SO_SPELLFIST ) return; - } else if( DIFF_TICK(tick, hd->ud.canact_tick) < 0 ) - return; - - if( hd->sc.data[SC_BASILICA] ) - return; - lv = merc_hom_checkskill(hd, skill_id); - if( skill_lv > lv ) - skill_lv = lv; - if( skill_lv ) - unit_skilluse_pos(&hd->bl, x, y, skill_id, skill_lv); -} - -static void clif_parse_UseSkillToId_mercenary(struct mercenary_data *md, struct map_session_data *sd, unsigned int tick, uint16 skill_id, uint16 skill_lv, int target_id) -{ - int lv; - - if( !md ) - return; - if( skillnotok_mercenary(skill_id, md) ) - return; - if( md->bl.id != target_id && skill_get_inf(skill_id)&INF_SELF_SKILL ) - target_id = md->bl.id; - if( md->ud.skilltimer != INVALID_TIMER ) - { - if( skill_id != SA_CASTCANCEL && skill_id != SO_SPELLFIST ) return; - } - else if( DIFF_TICK(tick, md->ud.canact_tick) < 0 ) - return; - - lv = mercenary_checkskill(md, skill_id); - if( skill_lv > lv ) - skill_lv = lv; - if( skill_lv ) - unit_skilluse_id(&md->bl, target_id, skill_id, skill_lv); -} - -static void clif_parse_UseSkillToPos_mercenary(struct mercenary_data *md, struct map_session_data *sd, unsigned int tick, uint16 skill_id, uint16 skill_lv, short x, short y, int skillmoreinfo) -{ - int lv; - if( !md ) - return; - if( skillnotok_mercenary(skill_id, md) ) - return; - if( md->ud.skilltimer != INVALID_TIMER ) - return; - if( DIFF_TICK(tick, md->ud.canact_tick) < 0 ) - { - clif_skill_fail(md->master, skill_id, USESKILL_FAIL_SKILLINTERVAL, 0); - return; - } - - if( md->sc.data[SC_BASILICA] ) - return; - lv = mercenary_checkskill(md, skill_id); - if( skill_lv > lv ) - skill_lv = lv; - if( skill_lv ) - unit_skilluse_pos(&md->bl, x, y, skill_id, skill_lv); -} - - -/// Request to use a targeted skill. -/// 0113 <skill lv>.W <skill id>.W <target id>.L (CZ_USE_SKILL) -/// 0438 <skill lv>.W <skill id>.W <target id>.L (CZ_USE_SKILL2) -/// There are various variants of this packet, some of them have padding between fields. -void clif_parse_UseSkillToId(int fd, struct map_session_data *sd) -{ - uint16 skill_id, skill_lv; - int tmp, target_id; - unsigned int tick = gettick(); - - skill_lv = RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); - skill_id = RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]); - target_id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[2]); - - if( skill_lv < 1 ) skill_lv = 1; //No clue, I have seen the client do this with guild skills :/ [Skotlex] - - tmp = skill_get_inf(skill_id); - if (tmp&INF_GROUND_SKILL || !tmp) - return; //Using a ground/passive skill on a target? WRONG. - - if( skill_id >= HM_SKILLBASE && skill_id < HM_SKILLBASE + MAX_HOMUNSKILL ) - { - clif_parse_UseSkillToId_homun(sd->hd, sd, tick, skill_id, skill_lv, target_id); - return; - } - - if( skill_id >= MC_SKILLBASE && skill_id < MC_SKILLBASE + MAX_MERCSKILL ) - { - clif_parse_UseSkillToId_mercenary(sd->md, sd, tick, skill_id, skill_lv, target_id); - return; - } - - // Whether skill fails or not is irrelevant, the char ain't idle. [Skotlex] - sd->idletime = last_tick; - - if( pc_cant_act(sd) && skill_id != RK_REFRESH && !(skill_id == SR_GENTLETOUCH_CURE && (sd->sc.opt1 == OPT1_STONE || sd->sc.opt1 == OPT1_FREEZE || sd->sc.opt1 == OPT1_STUN)) ) - return; - if( pc_issit(sd) ) - return; - - if( skillnotok(skill_id, sd) ) - return; - - if( sd->bl.id != target_id && tmp&INF_SELF_SKILL ) - target_id = sd->bl.id; // never trust the client - - if( target_id < 0 && -target_id == sd->bl.id ) // for disguises [Valaris] - target_id = sd->bl.id; - - if( sd->ud.skilltimer != INVALID_TIMER ) - { - if( skill_id != SA_CASTCANCEL && skill_id != SO_SPELLFIST ) - return; - } - else if( DIFF_TICK(tick, sd->ud.canact_tick) < 0 ) - { - if( sd->skillitem != skill_id ) - { - clif_skill_fail(sd, skill_id, USESKILL_FAIL_SKILLINTERVAL, 0); - return; - } - } - - if( sd->sc.option&(OPTION_WEDDING|OPTION_XMAS|OPTION_SUMMER) ) - return; - - if( sd->sc.data[SC_BASILICA] && (skill_id != HP_BASILICA || sd->sc.data[SC_BASILICA]->val4 != sd->bl.id) ) - return; // On basilica only caster can use Basilica again to stop it. - - if( sd->menuskill_id ) { - if( sd->menuskill_id == SA_TAMINGMONSTER ) { - clif_menuskill_clear(sd); //Cancel pet capture. - } else if( sd->menuskill_id != SA_AUTOSPELL ) - return; //Can't use skills while a menu is open. - } - if( sd->skillitem == skill_id ) { - if( skill_lv != sd->skillitemlv ) - skill_lv = sd->skillitemlv; - if( !(tmp&INF_SELF_SKILL) ) - pc_delinvincibletimer(sd); // Target skills thru items cancel invincibility. [Inkfish] - unit_skilluse_id(&sd->bl, target_id, skill_id, skill_lv); - return; - } - - sd->skillitem = sd->skillitemlv = 0; - - if( skill_id >= GD_SKILLBASE ) { - if( sd->state.gmaster_flag ) - skill_lv = guild_checkskill(sd->state.gmaster_flag, skill_id); - else - skill_lv = 0; - } else { - tmp = pc_checkskill(sd, skill_id); - if( skill_lv > tmp ) - skill_lv = tmp; - } - - pc_delinvincibletimer(sd); - - if( skill_lv ) - unit_skilluse_id(&sd->bl, target_id, skill_id, skill_lv); -} - -/*========================================== - * Client tells server he'd like to use AoE skill id 'skill_id' of level 'skill_lv' on 'x','y' location - *------------------------------------------*/ -static void clif_parse_UseSkillToPosSub(int fd, struct map_session_data *sd, uint16 skill_lv, uint16 skill_id, short x, short y, int skillmoreinfo) -{ - unsigned int tick = gettick(); - - if( !(skill_get_inf(skill_id)&INF_GROUND_SKILL) ) - return; //Using a target skill on the ground? WRONG. - - if( skill_id >= HM_SKILLBASE && skill_id < HM_SKILLBASE + MAX_HOMUNSKILL ) { - clif_parse_UseSkillToPos_homun(sd->hd, sd, tick, skill_id, skill_lv, x, y, skillmoreinfo); - return; - } - - if( skill_id >= MC_SKILLBASE && skill_id < MC_SKILLBASE + MAX_MERCSKILL ) - { - clif_parse_UseSkillToPos_mercenary(sd->md, sd, tick, skill_id, skill_lv, x, y, skillmoreinfo); - return; - } - - //Whether skill fails or not is irrelevant, the char ain't idle. [Skotlex] - sd->idletime = last_tick; - - if( skillnotok(skill_id, sd) ) - return; - if( skillmoreinfo != -1 ) - { - if( pc_issit(sd) ) - { - clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); - return; - } - //You can't use Graffiti/TalkieBox AND have a vending open, so this is safe. - safestrncpy(sd->message, (char*)RFIFOP(fd,skillmoreinfo), MESSAGE_SIZE); - } - - if( sd->ud.skilltimer != INVALID_TIMER ) - return; - - if( DIFF_TICK(tick, sd->ud.canact_tick) < 0 ) { - if( sd->skillitem != skill_id ) { - clif_skill_fail(sd, skill_id, USESKILL_FAIL_SKILLINTERVAL, 0); - return; - } - } - - if( sd->sc.option&(OPTION_WEDDING|OPTION_XMAS|OPTION_SUMMER) ) - return; - - if( sd->sc.data[SC_BASILICA] && (skill_id != HP_BASILICA || sd->sc.data[SC_BASILICA]->val4 != sd->bl.id) ) - return; // On basilica only caster can use Basilica again to stop it. - - if( sd->menuskill_id ) { - if( sd->menuskill_id == SA_TAMINGMONSTER ) { - clif_menuskill_clear(sd); //Cancel pet capture. - } else if( sd->menuskill_id != SA_AUTOSPELL ) - return; //Can't use skills while a menu is open. - } - - pc_delinvincibletimer(sd); - - if( sd->skillitem == skill_id ) { - if( skill_lv != sd->skillitemlv ) - skill_lv = sd->skillitemlv; - unit_skilluse_pos(&sd->bl, x, y, skill_id, skill_lv); - } else { - int lv; - sd->skillitem = sd->skillitemlv = 0; - if( (lv = pc_checkskill(sd, skill_id)) > 0 ) { - if( skill_lv > lv ) - skill_lv = lv; - unit_skilluse_pos(&sd->bl, x, y, skill_id,skill_lv); - } - } -} - - -/// Request to use a ground skill. -/// 0116 <skill lv>.W <skill id>.W <x>.W <y>.W (CZ_USE_SKILL_TOGROUND) -/// 0366 <skill lv>.W <skill id>.W <x>.W <y>.W (CZ_USE_SKILL_TOGROUND2) -/// There are various variants of this packet, some of them have padding between fields. -void clif_parse_UseSkillToPos(int fd, struct map_session_data *sd) -{ - if (pc_cant_act(sd)) - return; - if (pc_issit(sd)) - return; - - clif_parse_UseSkillToPosSub(fd, sd, - RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]), //skill lv - RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]), //skill num - RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[2]), //pos x - RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[3]), //pos y - -1 //Skill more info. - ); -} - - -/// Request to use a ground skill with text. -/// 0190 <skill lv>.W <skill id>.W <x>.W <y>.W <contents>.80B (CZ_USE_SKILL_TOGROUND_WITHTALKBOX) -/// 0367 <skill lv>.W <skill id>.W <x>.W <y>.W <contents>.80B (CZ_USE_SKILL_TOGROUND_WITHTALKBOX2) -/// There are various variants of this packet, some of them have padding between fields. -void clif_parse_UseSkillToPosMoreInfo(int fd, struct map_session_data *sd) -{ - if (pc_cant_act(sd)) - return; - if (pc_issit(sd)) - return; - - clif_parse_UseSkillToPosSub(fd, sd, - RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]), //Skill lv - RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]), //Skill num - RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[2]), //pos x - RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[3]), //pos y - packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[4] //skill more info - ); -} - - -/// Answer to map selection dialog (CZ_SELECT_WARPPOINT). -/// 011b <skill id>.W <map name>.16B -void clif_parse_UseSkillMap(int fd, struct map_session_data* sd) -{ - uint16 skill_id = RFIFOW(fd,2); - char map_name[MAP_NAME_LENGTH]; - mapindex_getmapname((char*)RFIFOP(fd,4), map_name); - - if(skill_id != sd->menuskill_id) - return; - - if( pc_cant_act(sd) ) { - clif_menuskill_clear(sd); - return; - } - - pc_delinvincibletimer(sd); - skill_castend_map(sd,skill_id,map_name); -} - - -/// Request to set a memo on current map (CZ_REMEMBER_WARPPOINT). -/// 011d -void clif_parse_RequestMemo(int fd,struct map_session_data *sd) -{ - if (!pc_isdead(sd)) - pc_memo(sd,-1); -} - - -/// Answer to pharmacy item selection dialog (CZ_REQMAKINGITEM). -/// 018e <name id>.W { <material id>.W }*3 -void clif_parse_ProduceMix(int fd,struct map_session_data *sd) -{ - switch( sd->menuskill_id ) { - case -1: - case AM_PHARMACY: - case RK_RUNEMASTERY: - case GC_RESEARCHNEWPOISON: - break; - default: - return; - } - if (pc_istrading(sd)) { - //Make it fail to avoid shop exploits where you sell something different than you see. - clif_skill_fail(sd,sd->ud.skill_id,USESKILL_FAIL_LEVEL,0); - clif_menuskill_clear(sd); - return; - } - if( skill_can_produce_mix(sd,RFIFOW(fd,2),sd->menuskill_val, 1) ) - skill_produce_mix(sd,0,RFIFOW(fd,2),RFIFOW(fd,4),RFIFOW(fd,6),RFIFOW(fd,8), 1); - clif_menuskill_clear(sd); -} - - -/// Answer to mixing item selection dialog (CZ_REQ_MAKINGITEM). -/// 025b <mk type>.W <name id>.W -/// mk type: -/// 1 = cooking -/// 2 = arrow -/// 3 = elemental -/// 4 = GN_MIX_COOKING -/// 5 = GN_MAKEBOMB -/// 6 = GN_S_PHARMACY -void clif_parse_Cooking(int fd,struct map_session_data *sd) { - int type = RFIFOW(fd,2); - int nameid = RFIFOW(fd,4); - int amount = sd->menuskill_val2?sd->menuskill_val2:1; - if( type == 6 && sd->menuskill_id != GN_MIX_COOKING && sd->menuskill_id != GN_S_PHARMACY ) - return; - - if (pc_istrading(sd)) { - //Make it fail to avoid shop exploits where you sell something different than you see. - clif_skill_fail(sd,sd->ud.skill_id,USESKILL_FAIL_LEVEL,0); - clif_menuskill_clear(sd); - return; - } - if( skill_can_produce_mix(sd,nameid,sd->menuskill_val, amount) ) - skill_produce_mix(sd,sd->menuskill_id,nameid,0,0,0,amount); - clif_menuskill_clear(sd); -} - - -/// Answer to repair weapon item selection dialog (CZ_REQ_ITEMREPAIR). -/// 01fd <index>.W <name id>.W <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W -void clif_parse_RepairItem(int fd, struct map_session_data *sd) -{ - if (sd->menuskill_id != BS_REPAIRWEAPON) - return; - if (pc_istrading(sd)) { - //Make it fail to avoid shop exploits where you sell something different than you see. - clif_skill_fail(sd,sd->ud.skill_id,USESKILL_FAIL_LEVEL,0); - clif_menuskill_clear(sd); - return; - } - skill_repairweapon(sd,RFIFOW(fd,2)); - clif_menuskill_clear(sd); -} - - -/// Answer to refine weapon item selection dialog (CZ_REQ_WEAPONREFINE). -/// 0222 <index>.L -void clif_parse_WeaponRefine(int fd, struct map_session_data *sd) -{ - int idx; - - if (sd->menuskill_id != WS_WEAPONREFINE) //Packet exploit? - return; - if (pc_istrading(sd)) { - //Make it fail to avoid shop exploits where you sell something different than you see. - clif_skill_fail(sd,sd->ud.skill_id,USESKILL_FAIL_LEVEL,0); - clif_menuskill_clear(sd); - return; - } - idx = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); - skill_weaponrefine(sd, idx-2); - clif_menuskill_clear(sd); -} - - -/// Answer to script menu dialog (CZ_CHOOSE_MENU). -/// 00b8 <npc id>.L <choice>.B -/// choice: -/// 1~254 = menu item -/// 255 = cancel -/// NOTE: If there were more than 254 items in the list, choice -/// overflows to choice%256. -void clif_parse_NpcSelectMenu(int fd,struct map_session_data *sd) -{ - int npc_id = RFIFOL(fd,2); - uint8 select = RFIFOB(fd,6); - - if( (select > sd->npc_menu && select != 0xff) || select == 0 ) - { - TBL_NPC* nd = map_id2nd(npc_id); - ShowWarning("Invalid menu selection on npc %d:'%s' - got %d, valid range is [%d..%d] (player AID:%d, CID:%d, name:'%s')!\n", npc_id, (nd)?nd->name:"invalid npc id", select, 1, sd->npc_menu, sd->bl.id, sd->status.char_id, sd->status.name); - clif_GM_kick(NULL,sd); - return; - } - - sd->npc_menu = select; - npc_scriptcont(sd,npc_id); -} - - -/// NPC dialog 'next' click (CZ_REQ_NEXT_SCRIPT). -/// 00b9 <npc id>.L -void clif_parse_NpcNextClicked(int fd,struct map_session_data *sd) -{ - npc_scriptcont(sd,RFIFOL(fd,2)); -} - - -/// NPC numeric input dialog value (CZ_INPUT_EDITDLG). -/// 0143 <npc id>.L <value>.L -void clif_parse_NpcAmountInput(int fd,struct map_session_data *sd) -{ - int npcid = RFIFOL(fd,2); - int amount = (int)RFIFOL(fd,6); - - sd->npc_amount = amount; - npc_scriptcont(sd, npcid); -} - - -/// NPC text input dialog value (CZ_INPUT_EDITDLGSTR). -/// 01d5 <packet len>.W <npc id>.L <string>.?B -void clif_parse_NpcStringInput(int fd, struct map_session_data* sd) -{ - int message_len = RFIFOW(fd,2)-8; - int npcid = RFIFOL(fd,4); - const char* message = (char*)RFIFOP(fd,8); - - if( message_len <= 0 ) - return; // invalid input - - safestrncpy(sd->npc_str, message, min(message_len,CHATBOX_SIZE)); - npc_scriptcont(sd, npcid); -} - - -/// NPC dialog 'close' click (CZ_CLOSE_DIALOG). -/// 0146 <npc id>.L -void clif_parse_NpcCloseClicked(int fd,struct map_session_data *sd) -{ - if (!sd->npc_id) //Avoid parsing anything when the script was done with. [Skotlex] - return; - npc_scriptcont(sd,RFIFOL(fd,2)); -} - - -/// Answer to identify item selection dialog (CZ_REQ_ITEMIDENTIFY). -/// 0178 <index>.W -/// index: -/// -1 = cancel -void clif_parse_ItemIdentify(int fd,struct map_session_data *sd) -{ - short idx = RFIFOW(fd,2); - - if (sd->menuskill_id != MC_IDENTIFY) - return; - if( idx == -1 ) {// cancel pressed - clif_menuskill_clear(sd); - return; - } - skill_identify(sd,idx-2); - clif_menuskill_clear(sd); -} - - -/// Answer to arrow crafting item selection dialog (CZ_REQ_MAKINGARROW). -/// 01ae <name id>.W -void clif_parse_SelectArrow(int fd,struct map_session_data *sd) -{ - if (pc_istrading(sd)) { - //Make it fail to avoid shop exploits where you sell something different than you see. - clif_skill_fail(sd,sd->ud.skill_id,USESKILL_FAIL_LEVEL,0); - clif_menuskill_clear(sd); - return; - } - switch( sd->menuskill_id ) { - case AC_MAKINGARROW: - skill_arrow_create(sd,RFIFOW(fd,2)); - break; - case SA_CREATECON: - skill_produce_mix(sd,SA_CREATECON,RFIFOW(fd,2),0,0,0, 1); - break; - case WL_READING_SB: - skill_spellbook(sd,RFIFOW(fd,2)); - break; - case GC_POISONINGWEAPON: - skill_poisoningweapon(sd,RFIFOW(fd,2)); - break; - case NC_MAGICDECOY: - skill_magicdecoy(sd,RFIFOW(fd,2)); - break; - } - - clif_menuskill_clear(sd); -} - - -/// Answer to SA_AUTOSPELL skill selection dialog (CZ_SELECTAUTOSPELL). -/// 01ce <skill id>.L -void clif_parse_AutoSpell(int fd,struct map_session_data *sd) -{ - if (sd->menuskill_id != SA_AUTOSPELL) - return; - skill_autospell(sd,RFIFOL(fd,2)); - clif_menuskill_clear(sd); -} - - -/// Request to display item carding/composition list (CZ_REQ_ITEMCOMPOSITION_LIST). -/// 017a <card index>.W -void clif_parse_UseCard(int fd,struct map_session_data *sd) -{ - if (sd->state.trading != 0) - return; - clif_use_card(sd,RFIFOW(fd,2)-2); -} - - -/// Answer to carding/composing item selection dialog (CZ_REQ_ITEMCOMPOSITION). -/// 017c <card index>.W <equip index>.W -void clif_parse_InsertCard(int fd,struct map_session_data *sd) -{ - if (sd->state.trading != 0) - return; - pc_insert_card(sd,RFIFOW(fd,2)-2,RFIFOW(fd,4)-2); -} - - -/// Request of character's name by char ID. -/// 0193 <char id>.L (CZ_REQNAME_BYGID) -/// 0369 <char id>.L (CZ_REQNAME_BYGID2) -/// There are various variants of this packet, some of them have padding between fields. -void clif_parse_SolveCharName(int fd, struct map_session_data *sd) -{ - int charid; - - charid = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); - map_reqnickdb(sd, charid); -} - - -/// /resetskill /resetstate (CZ_RESET). -/// Request to reset stats or skills. -/// 0197 <type>.W -/// type: -/// 0 = state -/// 1 = skill -void clif_parse_ResetChar(int fd, struct map_session_data *sd) { - char cmd[15]; - - if( RFIFOW(fd,2) ) - sprintf(cmd,"%cresetskill",atcommand_symbol); - else - sprintf(cmd,"%cresetstat",atcommand_symbol); - - is_atcommand(fd, sd, cmd, 1); -} - - -/// /lb /nlb (CZ_LOCALBROADCAST). -/// Request to broadcast a message on current map. -/// 019c <packet len>.W <text>.?B -void clif_parse_LocalBroadcast(int fd, struct map_session_data* sd) -{ - char command[CHAT_SIZE_MAX+16]; - char* msg = (char*)RFIFOP(fd,4); - unsigned int len = RFIFOW(fd,2)-4; - - // as the length varies depending on the command used, just block unreasonably long strings - mes_len_check(msg, len, CHAT_SIZE_MAX); - - sprintf(command, "%clkami %s", atcommand_symbol, msg); - is_atcommand(fd, sd, command, 1); -} - - -/// Request to move an item from inventory to storage. -/// 00f3 <index>.W <amount>.L (CZ_MOVE_ITEM_FROM_BODY_TO_STORE) -/// 0364 <index>.W <amount>.L (CZ_MOVE_ITEM_FROM_BODY_TO_STORE2) -/// There are various variants of this packet, some of them have padding between fields. -void clif_parse_MoveToKafra(int fd, struct map_session_data *sd) -{ - int item_index, item_amount; - - if (pc_istrading(sd)) - return; - - item_index = RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0])-2; - item_amount = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]); - if (item_index < 0 || item_index >= MAX_INVENTORY || item_amount < 1) - return; - - if (sd->state.storage_flag == 1) - storage_storageadd(sd, item_index, item_amount); - else - if (sd->state.storage_flag == 2) - storage_guild_storageadd(sd, item_index, item_amount); -} - - -/// Request to move an item from storage to inventory. -/// 00f5 <index>.W <amount>.L (CZ_MOVE_ITEM_FROM_STORE_TO_BODY) -/// 0365 <index>.W <amount>.L (CZ_MOVE_ITEM_FROM_STORE_TO_BODY2) -/// There are various variants of this packet, some of them have padding between fields. -void clif_parse_MoveFromKafra(int fd,struct map_session_data *sd) -{ - int item_index, item_amount; - - item_index = RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0])-1; - item_amount = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]); - - if (sd->state.storage_flag == 1) - storage_storageget(sd, item_index, item_amount); - else - if(sd->state.storage_flag == 2) - storage_guild_storageget(sd, item_index, item_amount); -} - - -/// Request to move an item from cart to storage (CZ_MOVE_ITEM_FROM_CART_TO_STORE). -/// 0129 <index>.W <amount>.L -void clif_parse_MoveToKafraFromCart(int fd, struct map_session_data *sd) -{ - if( sd->state.vending ) - return; - if (!pc_iscarton(sd)) - return; - - if (sd->state.storage_flag == 1) - storage_storageaddfromcart(sd, RFIFOW(fd,2) - 2, RFIFOL(fd,4)); - else - if (sd->state.storage_flag == 2) - storage_guild_storageaddfromcart(sd, RFIFOW(fd,2) - 2, RFIFOL(fd,4)); -} - - -/// Request to move an item from storage to cart (CZ_MOVE_ITEM_FROM_STORE_TO_CART). -/// 0128 <index>.W <amount>.L -void clif_parse_MoveFromKafraToCart(int fd, struct map_session_data *sd) -{ - if( sd->state.vending ) - return; - if (!pc_iscarton(sd)) - return; - - if (sd->state.storage_flag == 1) - storage_storagegettocart(sd, RFIFOW(fd,2)-1, RFIFOL(fd,4)); - else - if (sd->state.storage_flag == 2) - storage_guild_storagegettocart(sd, RFIFOW(fd,2)-1, RFIFOL(fd,4)); -} - - -/// Request to close storage (CZ_CLOSE_STORE). -/// 00f7 -void clif_parse_CloseKafra(int fd, struct map_session_data *sd) -{ - if( sd->state.storage_flag == 1 ) - storage_storageclose(sd); - else - if( sd->state.storage_flag == 2 ) - storage_guild_storageclose(sd); -} - - -/// Displays kafra storage password dialog (ZC_REQ_STORE_PASSWORD). -/// 023a <info>.W -/// info: -/// 0 = password has not been set yet -/// 1 = storage is password-protected -/// 8 = too many wrong passwords -/// ? = ignored -/// NOTE: This packet is only available on certain non-kRO clients. -void clif_storagepassword(struct map_session_data* sd, short info) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x23a)); - WFIFOW(fd,0) = 0x23a; - WFIFOW(fd,2) = info; - WFIFOSET(fd,packet_len(0x23a)); -} - - -/// Answer to the kafra storage password dialog (CZ_ACK_STORE_PASSWORD). -/// 023b <type>.W <password>.16B <new password>.16B -/// type: -/// 2 = change password -/// 3 = check password -/// NOTE: This packet is only available on certain non-kRO clients. -void clif_parse_StoragePassword(int fd, struct map_session_data *sd) -{ - //TODO -} - - -/// Result of kafra storage password validation (ZC_RESULT_STORE_PASSWORD). -/// 023c <result>.W <error count>.W -/// result: -/// 4 = password change success -/// 5 = password change failure -/// 6 = password check success -/// 7 = password check failure -/// 8 = too many wrong passwords -/// ? = ignored -/// NOTE: This packet is only available on certain non-kRO clients. -void clif_storagepassword_result(struct map_session_data* sd, short result, short error_count) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x23c)); - WFIFOW(fd,0) = 0x23c; - WFIFOW(fd,2) = result; - WFIFOW(fd,4) = error_count; - WFIFOSET(fd,packet_len(0x23c)); -} - - -/// Party creation request -/// 00f9 <party name>.24B (CZ_MAKE_GROUP) -/// 01e8 <party name>.24B <item pickup rule>.B <item share rule>.B (CZ_MAKE_GROUP2) -void clif_parse_CreateParty(int fd, struct map_session_data *sd) -{ - char* name = (char*)RFIFOP(fd,2); - name[NAME_LENGTH-1] = '\0'; - - if( map[sd->bl.m].flag.partylock ) - {// Party locked. - clif_displaymessage(fd, msg_txt(227)); - return; - } - if( battle_config.basic_skill_check && pc_checkskill(sd,NV_BASIC) < 7 ) - { - clif_skill_fail(sd,1,USESKILL_FAIL_LEVEL,4); - return; - } - - party_create(sd,name,0,0); -} - -void clif_parse_CreateParty2(int fd, struct map_session_data *sd) -{ - char* name = (char*)RFIFOP(fd,2); - int item1 = RFIFOB(fd,26); - int item2 = RFIFOB(fd,27); - name[NAME_LENGTH-1] = '\0'; - - if( map[sd->bl.m].flag.partylock ) - {// Party locked. - clif_displaymessage(fd, msg_txt(227)); - return; - } - if( battle_config.basic_skill_check && pc_checkskill(sd,NV_BASIC) < 7 ) - { - clif_skill_fail(sd,1,USESKILL_FAIL_LEVEL,4); - return; - } - - party_create(sd,name,item1,item2); -} - - -/// Party invitation request -/// 00fc <account id>.L (CZ_REQ_JOIN_GROUP) -/// 02c4 <char name>.24B (CZ_PARTY_JOIN_REQ) -void clif_parse_PartyInvite(int fd, struct map_session_data *sd) -{ - struct map_session_data *t_sd; - - if(map[sd->bl.m].flag.partylock) - {// Party locked. - clif_displaymessage(fd, msg_txt(227)); - return; - } - - t_sd = map_id2sd(RFIFOL(fd,2)); - - if(t_sd && t_sd->state.noask) - {// @noask [LuzZza] - clif_noask_sub(sd, t_sd, 1); - return; - } - - party_invite(sd, t_sd); -} - -void clif_parse_PartyInvite2(int fd, struct map_session_data *sd) -{ - struct map_session_data *t_sd; - char *name = (char*)RFIFOP(fd,2); - name[NAME_LENGTH-1] = '\0'; - - if(map[sd->bl.m].flag.partylock) - {// Party locked. - clif_displaymessage(fd, msg_txt(227)); - return; - } - - t_sd = map_nick2sd(name); - - if(t_sd && t_sd->state.noask) - {// @noask [LuzZza] - clif_noask_sub(sd, t_sd, 1); - return; - } - - party_invite(sd, t_sd); -} - - -/// Party invitation reply -/// 00ff <party id>.L <flag>.L (CZ_JOIN_GROUP) -/// 02c7 <party id>.L <flag>.B (CZ_PARTY_JOIN_REQ_ACK) -/// flag: -/// 0 = reject -/// 1 = accept -void clif_parse_ReplyPartyInvite(int fd,struct map_session_data *sd) -{ - party_reply_invite(sd,RFIFOL(fd,2),RFIFOL(fd,6)); -} - -void clif_parse_ReplyPartyInvite2(int fd,struct map_session_data *sd) -{ - party_reply_invite(sd,RFIFOL(fd,2),RFIFOB(fd,6)); -} - - -/// Request to leave party (CZ_REQ_LEAVE_GROUP). -/// 0100 -void clif_parse_LeaveParty(int fd, struct map_session_data *sd) -{ - if(map[sd->bl.m].flag.partylock) - { //Guild locked. - clif_displaymessage(fd, msg_txt(227)); - return; - } - party_leave(sd); -} - - -/// Request to expel a party member (CZ_REQ_EXPEL_GROUP_MEMBER). -/// 0103 <account id>.L <char name>.24B -void clif_parse_RemovePartyMember(int fd, struct map_session_data *sd) -{ - if(map[sd->bl.m].flag.partylock) - { //Guild locked. - clif_displaymessage(fd, msg_txt(227)); - return; - } - party_removemember(sd,RFIFOL(fd,2),(char*)RFIFOP(fd,6)); -} - - -/// Request to change party options. -/// 0102 <exp share rule>.L (CZ_CHANGE_GROUPEXPOPTION) -/// 07d7 <exp share rule>.L <item pickup rule>.B <item share rule>.B (CZ_GROUPINFO_CHANGE_V2) -void clif_parse_PartyChangeOption(int fd, struct map_session_data *sd) -{ - struct party_data *p; - int i; - - if( !sd->status.party_id ) - return; - - p = party_search(sd->status.party_id); - if( p == NULL ) - return; - - ARR_FIND( 0, MAX_PARTY, i, p->data[i].sd == sd ); - if( i == MAX_PARTY ) - return; //Shouldn't happen - - if( !p->party.member[i].leader ) - return; - -#if PACKETVER < 20090603 - //Client can't change the item-field - party_changeoption(sd, RFIFOL(fd,2), p->party.item); -#else - party_changeoption(sd, RFIFOL(fd,2), ((RFIFOB(fd,6)?1:0)|(RFIFOB(fd,7)?2:0))); -#endif -} - - -/// Validates and processes party messages (CZ_REQUEST_CHAT_PARTY). -/// 0108 <packet len>.W <text>.?B (<name> : <message>) 00 -void clif_parse_PartyMessage(int fd, struct map_session_data* sd) -{ - const char* text = (char*)RFIFOP(fd,4); - int textlen = RFIFOW(fd,2) - 4; - - char *name, *message; - int namelen, messagelen; - - // validate packet and retrieve name and message - if( !clif_process_message(sd, 0, &name, &namelen, &message, &messagelen) ) - return; - - if( is_atcommand(fd, sd, message, 1) ) - return; - - if( sd->sc.data[SC_BERSERK] || sd->sc.data[SC__BLOODYLUST] || (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOCHAT) ) - return; - - if( battle_config.min_chat_delay ) - { //[Skotlex] - if (DIFF_TICK(sd->cantalk_tick, gettick()) > 0) - return; - sd->cantalk_tick = gettick() + battle_config.min_chat_delay; - } - - party_send_message(sd, text, textlen); -} - - -/// Changes Party Leader (CZ_CHANGE_GROUP_MASTER). -/// 07da <account id>.L -void clif_parse_PartyChangeLeader(int fd, struct map_session_data* sd) -{ - party_changeleader(sd, map_id2sd(RFIFOL(fd,2))); -} - - -/// Party Booking in KRO [Spiria] -/// - -/// Request to register a party booking advertisment (CZ_PARTY_BOOKING_REQ_REGISTER). -/// 0802 <level>.W <map id>.W { <job>.W }*6 -void clif_parse_PartyBookingRegisterReq(int fd, struct map_session_data* sd) -{ - short level = RFIFOW(fd,2); - short mapid = RFIFOW(fd,4); - short job[PARTY_BOOKING_JOBS]; - int i; - - for(i=0; i<PARTY_BOOKING_JOBS; i++) - job[i] = RFIFOB(fd,6+i*2); - - party_booking_register(sd, level, mapid, job); -} - - -/// Result of request to register a party booking advertisment (ZC_PARTY_BOOKING_ACK_REGISTER). -/// 0803 <result>.W -/// result: -/// 0 = success -/// 1 = failure -/// 2 = already registered -void clif_PartyBookingRegisterAck(struct map_session_data *sd, int flag) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x803)); - WFIFOW(fd,0) = 0x803; - WFIFOW(fd,2) = flag; - WFIFOSET(fd,packet_len(0x803)); -} - - -/// Request to search for party booking advertisments (CZ_PARTY_BOOKING_REQ_SEARCH). -/// 0804 <level>.W <map id>.W <job>.W <last index>.L <result count>.W -void clif_parse_PartyBookingSearchReq(int fd, struct map_session_data* sd) -{ - short level = RFIFOW(fd,2); - short mapid = RFIFOW(fd,4); - short job = RFIFOW(fd,6); - unsigned long lastindex = RFIFOL(fd,8); - short resultcount = RFIFOW(fd,12); - - party_booking_search(sd, level, mapid, job, lastindex, resultcount); -} - - -/// Party booking search results (ZC_PARTY_BOOKING_ACK_SEARCH). -/// 0805 <packet len>.W <more results>.B { <index>.L <char name>.24B <expire time>.L <level>.W <map id>.W { <job>.W }*6 }* -/// more results: -/// 0 = no -/// 1 = yes -void clif_PartyBookingSearchAck(int fd, struct party_booking_ad_info** results, int count, bool more_result) -{ - int i, j; - int size = sizeof(struct party_booking_ad_info); // structure size (48) - struct party_booking_ad_info *pb_ad; - WFIFOHEAD(fd,size*count + 5); - WFIFOW(fd,0) = 0x805; - WFIFOW(fd,2) = size*count + 5; - WFIFOB(fd,4) = more_result; - for(i=0; i<count; i++) - { - pb_ad = results[i]; - WFIFOL(fd,i*size+5) = pb_ad->index; - memcpy(WFIFOP(fd,i*size+9),pb_ad->charname,NAME_LENGTH); - WFIFOL(fd,i*size+33) = pb_ad->starttime; // FIXME: This is expire time - WFIFOW(fd,i*size+37) = pb_ad->p_detail.level; - WFIFOW(fd,i*size+39) = pb_ad->p_detail.mapid; - for(j=0; j<PARTY_BOOKING_JOBS; j++) - WFIFOW(fd,i*size+41+j*2) = pb_ad->p_detail.job[j]; - } - WFIFOSET(fd,WFIFOW(fd,2)); -} - - -/// Request to delete own party booking advertisment (CZ_PARTY_BOOKING_REQ_DELETE). -/// 0806 -void clif_parse_PartyBookingDeleteReq(int fd, struct map_session_data* sd) -{ - if(party_booking_delete(sd)) - clif_PartyBookingDeleteAck(sd, 0); -} - - -/// Result of request to delete own party booking advertisment (ZC_PARTY_BOOKING_ACK_DELETE). -/// 0807 <result>.W -/// result: -/// 0 = success -/// 1 = success (auto-removed expired ad) -/// 2 = failure -/// 3 = nothing registered -void clif_PartyBookingDeleteAck(struct map_session_data* sd, int flag) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x807)); - WFIFOW(fd,0) = 0x807; - WFIFOW(fd,2) = flag; - WFIFOSET(fd,packet_len(0x807)); -} - - -/// Request to update party booking advertisment (CZ_PARTY_BOOKING_REQ_UPDATE). -/// 0808 { <job>.W }*6 -void clif_parse_PartyBookingUpdateReq(int fd, struct map_session_data* sd) -{ - short job[PARTY_BOOKING_JOBS]; - int i; - - for(i=0; i<PARTY_BOOKING_JOBS; i++) - job[i] = RFIFOW(fd,2+i*2); - - party_booking_update(sd, job); -} - - -/// Notification about new party booking advertisment (ZC_PARTY_BOOKING_NOTIFY_INSERT). -/// 0809 <index>.L <char name>.24B <expire time>.L <level>.W <map id>.W { <job>.W }*6 -void clif_PartyBookingInsertNotify(struct map_session_data* sd, struct party_booking_ad_info* pb_ad) -{ - int i; - uint8 buf[38+PARTY_BOOKING_JOBS*2]; - - if(pb_ad == NULL) return; - - WBUFW(buf,0) = 0x809; - WBUFL(buf,2) = pb_ad->index; - memcpy(WBUFP(buf,6),pb_ad->charname,NAME_LENGTH); - WBUFL(buf,30) = pb_ad->starttime; // FIXME: This is expire time - WBUFW(buf,34) = pb_ad->p_detail.level; - WBUFW(buf,36) = pb_ad->p_detail.mapid; - for(i=0; i<PARTY_BOOKING_JOBS; i++) - WBUFW(buf,38+i*2) = pb_ad->p_detail.job[i]; - - clif_send(buf, packet_len(0x809), &sd->bl, ALL_CLIENT); -} - - -/// Notification about updated party booking advertisment (ZC_PARTY_BOOKING_NOTIFY_UPDATE). -/// 080a <index>.L { <job>.W }*6 -void clif_PartyBookingUpdateNotify(struct map_session_data* sd, struct party_booking_ad_info* pb_ad) -{ - int i; - uint8 buf[6+PARTY_BOOKING_JOBS*2]; - - if(pb_ad == NULL) return; - - WBUFW(buf,0) = 0x80a; - WBUFL(buf,2) = pb_ad->index; - for(i=0; i<PARTY_BOOKING_JOBS; i++) - WBUFW(buf,6+i*2) = pb_ad->p_detail.job[i]; - clif_send(buf,packet_len(0x80a),&sd->bl,ALL_CLIENT); // Now UPDATE all client. -} - - -/// Notification about deleted party booking advertisment (ZC_PARTY_BOOKING_NOTIFY_DELETE). -/// 080b <index>.L -void clif_PartyBookingDeleteNotify(struct map_session_data* sd, int index) -{ - uint8 buf[6]; - - WBUFW(buf,0) = 0x80b; - WBUFL(buf,2) = index; - - clif_send(buf, packet_len(0x80b), &sd->bl, ALL_CLIENT); // Now UPDATE all client. -} - - -/// Request to close own vending (CZ_REQ_CLOSESTORE). -/// 012e -void clif_parse_CloseVending(int fd, struct map_session_data* sd) -{ - vending_closevending(sd); -} - - -/// Request to open a vending shop (CZ_REQ_BUY_FROMMC). -/// 0130 <account id>.L -void clif_parse_VendingListReq(int fd, struct map_session_data* sd) -{ - if( sd->npc_id ) - {// using an NPC - return; - } - vending_vendinglistreq(sd,RFIFOL(fd,2)); -} - - -/// Shop item(s) purchase request (CZ_PC_PURCHASE_ITEMLIST_FROMMC). -/// 0134 <packet len>.W <account id>.L { <amount>.W <index>.W }* -void clif_parse_PurchaseReq(int fd, struct map_session_data* sd) -{ - int len = (int)RFIFOW(fd,2) - 8; - int id = (int)RFIFOL(fd,4); - const uint8* data = (uint8*)RFIFOP(fd,8); - - vending_purchasereq(sd, id, sd->vended_id, data, len/4); - - // whether it fails or not, the buy window is closed - sd->vended_id = 0; -} - - -/// Shop item(s) purchase request (CZ_PC_PURCHASE_ITEMLIST_FROMMC2). -/// 0801 <packet len>.W <account id>.L <unique id>.L { <amount>.W <index>.W }* -void clif_parse_PurchaseReq2(int fd, struct map_session_data* sd) -{ - int len = (int)RFIFOW(fd,2) - 12; - int aid = (int)RFIFOL(fd,4); - int uid = (int)RFIFOL(fd,8); - const uint8* data = (uint8*)RFIFOP(fd,12); - - vending_purchasereq(sd, aid, uid, data, len/4); - - // whether it fails or not, the buy window is closed - sd->vended_id = 0; -} - - -/// Confirm or cancel the shop preparation window. -/// 012f <packet len>.W <shop name>.80B { <index>.W <amount>.W <price>.L }* (CZ_REQ_OPENSTORE) -/// 01b2 <packet len>.W <shop name>.80B <result>.B { <index>.W <amount>.W <price>.L }* (CZ_REQ_OPENSTORE2) -/// result: -/// 0 = canceled -/// 1 = open -void clif_parse_OpenVending(int fd, struct map_session_data* sd) -{ - short len = (short)RFIFOW(fd,2) - 85; - const char* message = (char*)RFIFOP(fd,4); - bool flag = (bool)RFIFOB(fd,84); - const uint8* data = (uint8*)RFIFOP(fd,85); - - if( sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOROOM ) - return; - if( map[sd->bl.m].flag.novending ) { - clif_displaymessage (sd->fd, msg_txt(276)); // "You can't open a shop on this map" - return; - } - if( map_getcell(sd->bl.m,sd->bl.x,sd->bl.y,CELL_CHKNOVENDING) ) { - clif_displaymessage (sd->fd, msg_txt(204)); // "You can't open a shop on this cell." - return; - } - - if( vending_checknearnpc(&sd->bl) ) { - char output[150]; - sprintf(output, msg_txt(662), battle_config.min_npc_vending_distance); - clif_displaymessage(sd->fd, output); - clif_skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0); - return; - } - - if( message[0] == '\0' ) // invalid input - return; - - vending_openvending(sd, message, flag, data, len/8); -} - - -/// Guild creation request (CZ_REQ_MAKE_GUILD). -/// 0165 <char id>.L <guild name>.24B -void clif_parse_CreateGuild(int fd,struct map_session_data *sd) -{ - char* name = (char*)RFIFOP(fd,6); - name[NAME_LENGTH-1] = '\0'; - - if(map[sd->bl.m].flag.guildlock) - { //Guild locked. - clif_displaymessage(fd, msg_txt(228)); - return; - } - - guild_create(sd, name); -} - - -/// Request for guild window interface permissions (CZ_REQ_GUILD_MENUINTERFACE). -/// 014d -void clif_parse_GuildCheckMaster(int fd, struct map_session_data *sd) -{ - clif_guild_masterormember(sd); -} - - -/// Request for guild window information (CZ_REQ_GUILD_MENU). -/// 014f <type>.L -/// type: -/// 0 = basic info -/// 1 = member manager -/// 2 = positions -/// 3 = skills -/// 4 = expulsion list -/// 5 = unknown (GM_ALLGUILDLIST) -/// 6 = notice -void clif_parse_GuildRequestInfo(int fd, struct map_session_data *sd) -{ - if( !sd->status.guild_id && !sd->bg_id ) - return; - - switch( RFIFOL(fd,2) ) - { - case 0: // Basic Information Guild, hostile alliance information - clif_guild_basicinfo(sd); - clif_guild_allianceinfo(sd); - break; - case 1: // Members list, list job title - clif_guild_positionnamelist(sd); - clif_guild_memberlist(sd); - break; - case 2: // List job title, title information list - clif_guild_positionnamelist(sd); - clif_guild_positioninfolist(sd); - break; - case 3: // Skill list - clif_guild_skillinfo(sd); - break; - case 4: // Expulsion list - clif_guild_expulsionlist(sd); - break; - default: - ShowError("clif: guild request info: unknown type %d\n", RFIFOL(fd,2)); - break; - } -} - - -/// Request to update guild positions (CZ_REG_CHANGE_GUILD_POSITIONINFO). -/// 0161 <packet len>.W { <position id>.L <mode>.L <ranking>.L <pay rate>.L <name>.24B }* -void clif_parse_GuildChangePositionInfo(int fd, struct map_session_data *sd) -{ - int i; - - if(!sd->state.gmaster_flag) - return; - - for(i = 4; i < RFIFOW(fd,2); i += 40 ){ - guild_change_position(sd->status.guild_id, RFIFOL(fd,i), RFIFOL(fd,i+4), RFIFOL(fd,i+12), (char*)RFIFOP(fd,i+16)); - } -} - - -/// Request to update the position of guild members (CZ_REQ_CHANGE_MEMBERPOS). -/// 0155 <packet len>.W { <account id>.L <char id>.L <position id>.L }* -void clif_parse_GuildChangeMemberPosition(int fd, struct map_session_data *sd) -{ - int i; - - if(!sd->state.gmaster_flag) - return; - - 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)); - } -} - - -/// Request for guild emblem data (CZ_REQ_GUILD_EMBLEM_IMG). -/// 0151 <guild id>.L -void clif_parse_GuildRequestEmblem(int fd,struct map_session_data *sd) -{ - struct guild* g; - int guild_id = RFIFOL(fd,2); - - if( (g = guild_search(guild_id)) != NULL ) - clif_guild_emblem(sd,g); -} - - -/// Validates data of a guild emblem (compressed bitmap) -static bool clif_validate_emblem(const uint8* emblem, unsigned long emblem_len) -{ - bool success; - uint8 buf[1800]; // no well-formed emblem bitmap is larger than 1782 (24 bit) / 1654 (8 bit) bytes - unsigned long buf_len = sizeof(buf); - - success = ( decode_zip(buf, &buf_len, emblem, emblem_len) == 0 && buf_len >= 18 ) // sizeof(BITMAPFILEHEADER) + sizeof(biSize) of the following info header struct - && RBUFW(buf,0) == 0x4d42 // BITMAPFILEHEADER.bfType (signature) - && RBUFL(buf,2) == buf_len // BITMAPFILEHEADER.bfSize (file size) - && RBUFL(buf,10) < buf_len // BITMAPFILEHEADER.bfOffBits (offset to bitmap bits) - ; - - return success; -} - - -/// Request to update the guild emblem (CZ_REGISTER_GUILD_EMBLEM_IMG). -/// 0153 <packet len>.W <emblem data>.?B -void clif_parse_GuildChangeEmblem(int fd,struct map_session_data *sd) -{ - unsigned long emblem_len = RFIFOW(fd,2)-4; - const uint8* emblem = RFIFOP(fd,4); - - if( !emblem_len || !sd->state.gmaster_flag ) - return; - - if( !clif_validate_emblem(emblem, emblem_len) ) - { - ShowWarning("clif_parse_GuildChangeEmblem: Rejected malformed guild emblem (size=%lu, accound_id=%d, char_id=%d, guild_id=%d).\n", emblem_len, sd->status.account_id, sd->status.char_id, sd->status.guild_id); - return; - } - - guild_change_emblem(sd, emblem_len, (const char*)emblem); -} - - -/// Guild notice update request (CZ_GUILD_NOTICE). -/// 016e <guild id>.L <msg1>.60B <msg2>.120B -void clif_parse_GuildChangeNotice(int fd, struct map_session_data* sd) -{ - int guild_id = RFIFOL(fd,2); - char* msg1 = (char*)RFIFOP(fd,6); - char* msg2 = (char*)RFIFOP(fd,66); - - if(!sd->state.gmaster_flag) - return; - - // compensate for some client defects when using multilanguage mode - if (msg1[0] == '|' && msg1[3] == '|') msg1+= 3; // skip duplicate marker - if (msg2[0] == '|' && msg2[3] == '|') msg2+= 3; // skip duplicate marker - if (msg2[0] == '|') msg2[strnlen(msg2, MAX_GUILDMES2)-1] = '\0'; // delete extra space at the end of string - - guild_change_notice(sd, guild_id, msg1, msg2); -} - - -/// Guild invite request (CZ_REQ_JOIN_GUILD). -/// 0168 <account id>.L <inviter account id>.L <inviter char id>.L -void clif_parse_GuildInvite(int fd,struct map_session_data *sd) -{ - struct map_session_data *t_sd; - - if(map[sd->bl.m].flag.guildlock) - { //Guild locked. - clif_displaymessage(fd, msg_txt(228)); - return; - } - - t_sd = map_id2sd(RFIFOL(fd,2)); - - // @noask [LuzZza] - if(t_sd && t_sd->state.noask) { - clif_noask_sub(sd, t_sd, 2); - return; - } - - guild_invite(sd,t_sd); -} - - -/// Answer to guild invitation (CZ_JOIN_GUILD). -/// 016b <guild id>.L <answer>.L -/// answer: -/// 0 = refuse -/// 1 = accept -void clif_parse_GuildReplyInvite(int fd,struct map_session_data *sd) -{ - guild_reply_invite(sd,RFIFOL(fd,2),RFIFOL(fd,6)); -} - - -/// Request to leave guild (CZ_REQ_LEAVE_GUILD). -/// 0159 <guild id>.L <account id>.L <char id>.L <reason>.40B -void clif_parse_GuildLeave(int fd,struct map_session_data *sd) -{ - if(map[sd->bl.m].flag.guildlock) - { //Guild locked. - clif_displaymessage(fd, msg_txt(228)); - return; - } - if( sd->bg_id ) - { - clif_displaymessage(fd, msg_txt(670)); //"You can't leave battleground guilds." - return; - } - - guild_leave(sd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),(char*)RFIFOP(fd,14)); -} - - -/// Request to expel a member of a guild (CZ_REQ_BAN_GUILD). -/// 015b <guild id>.L <account id>.L <char id>.L <reason>.40B -void clif_parse_GuildExpulsion(int fd,struct map_session_data *sd) -{ - if( map[sd->bl.m].flag.guildlock || sd->bg_id ) - { // Guild locked. - clif_displaymessage(fd, msg_txt(228)); - return; - } - guild_expulsion(sd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),(char*)RFIFOP(fd,14)); -} - - -/// Validates and processes guild messages (CZ_GUILD_CHAT). -/// 017e <packet len>.W <text>.?B (<name> : <message>) 00 -void clif_parse_GuildMessage(int fd, struct map_session_data* sd) -{ - const char* text = (char*)RFIFOP(fd,4); - int textlen = RFIFOW(fd,2) - 4; - - char *name, *message; - int namelen, messagelen; - - // validate packet and retrieve name and message - if( !clif_process_message(sd, 0, &name, &namelen, &message, &messagelen) ) - return; - - if( is_atcommand(fd, sd, message, 1) ) - return; - - if( sd->sc.data[SC_BERSERK] || sd->sc.data[SC__BLOODYLUST] || (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOCHAT) ) - return; - - if( battle_config.min_chat_delay ) - { //[Skotlex] - if (DIFF_TICK(sd->cantalk_tick, gettick()) > 0) - return; - sd->cantalk_tick = gettick() + battle_config.min_chat_delay; - } - - if( sd->bg_id ) - bg_send_message(sd, text, textlen); - else - guild_send_message(sd, text, textlen); -} - - -/// Guild alliance request (CZ_REQ_ALLY_GUILD). -/// 0170 <account id>.L <inviter account id>.L <inviter char id>.L -void clif_parse_GuildRequestAlliance(int fd, struct map_session_data *sd) -{ - struct map_session_data *t_sd; - - if(!sd->state.gmaster_flag) - return; - - if(map[sd->bl.m].flag.guildlock) - { //Guild locked. - clif_displaymessage(fd, msg_txt(228)); - return; - } - - t_sd = map_id2sd(RFIFOL(fd,2)); - - // @noask [LuzZza] - if(t_sd && t_sd->state.noask) { - clif_noask_sub(sd, t_sd, 3); - return; - } - - guild_reqalliance(sd,t_sd); -} - - -/// Answer to a guild alliance request (CZ_ALLY_GUILD). -/// 0172 <inviter account id>.L <answer>.L -/// answer: -/// 0 = refuse -/// 1 = accept -void clif_parse_GuildReplyAlliance(int fd, struct map_session_data *sd) -{ - guild_reply_reqalliance(sd,RFIFOL(fd,2),RFIFOL(fd,6)); -} - - -/// Request to delete a guild alliance or opposition (CZ_REQ_DELETE_RELATED_GUILD). -/// 0183 <opponent guild id>.L <relation>.L -/// relation: -/// 0 = Ally -/// 1 = Enemy -void clif_parse_GuildDelAlliance(int fd, struct map_session_data *sd) -{ - if(!sd->state.gmaster_flag) - return; - - if(map[sd->bl.m].flag.guildlock) - { //Guild locked. - clif_displaymessage(fd, msg_txt(228)); - return; - } - guild_delalliance(sd,RFIFOL(fd,2),RFIFOL(fd,6)); -} - - -/// Request to set a guild as opposition (CZ_REQ_HOSTILE_GUILD). -/// 0180 <account id>.L -void clif_parse_GuildOpposition(int fd, struct map_session_data *sd) -{ - struct map_session_data *t_sd; - - if(!sd->state.gmaster_flag) - return; - - if(map[sd->bl.m].flag.guildlock) - { //Guild locked. - clif_displaymessage(fd, msg_txt(228)); - return; - } - - t_sd = map_id2sd(RFIFOL(fd,2)); - - // @noask [LuzZza] - if(t_sd && t_sd->state.noask) { - clif_noask_sub(sd, t_sd, 4); - return; - } - - guild_opposition(sd,t_sd); -} - - -/// Request to delete own guild (CZ_REQ_DISORGANIZE_GUILD). -/// 015d <key>.40B -/// key: -/// now guild name; might have been (intended) email, since the -/// field name and size is same as the one in CH_DELETE_CHAR. -void clif_parse_GuildBreak(int fd, struct map_session_data *sd) -{ - if( map[sd->bl.m].flag.guildlock ) - { //Guild locked. - clif_displaymessage(fd, msg_txt(228)); - return; - } - guild_break(sd,(char*)RFIFOP(fd,2)); -} - - -/// Pet -/// - -/// Request to invoke a pet menu action (CZ_COMMAND_PET). -/// 01a1 <type>.B -/// type: -/// 0 = pet information -/// 1 = feed -/// 2 = performance -/// 3 = return to egg -/// 4 = unequip accessory -void clif_parse_PetMenu(int fd, struct map_session_data *sd) -{ - pet_menu(sd,RFIFOB(fd,2)); -} - - -/// Attempt to tame a monster (CZ_TRYCAPTURE_MONSTER). -/// 019f <id>.L -void clif_parse_CatchPet(int fd, struct map_session_data *sd) -{ - pet_catch_process2(sd,RFIFOL(fd,2)); -} - - -/// Answer to pet incubator egg selection dialog (CZ_SELECT_PETEGG). -/// 01a7 <index>.W -void clif_parse_SelectEgg(int fd, struct map_session_data *sd) -{ - if (sd->menuskill_id != SA_TAMINGMONSTER || sd->menuskill_val != -1) - { - //Forged packet, disconnect them [Kevin] - clif_authfail_fd(fd, 0); - return; - } - pet_select_egg(sd,RFIFOW(fd,2)-2); - clif_menuskill_clear(sd); -} - - -/// Request to display pet's emotion/talk (CZ_PET_ACT). -/// 01a9 <data>.L -/// data: -/// is either emotion (@see enum emotion_type) or a compound value -/// (((mob id)-100)*100+(act id)*10+(hunger)) that describes an -/// entry (given in parentheses) in data\pettalktable.xml -/// act id: -/// 0 = feeding -/// 1 = hunting -/// 2 = danger -/// 3 = dead -/// 4 = normal (stand) -/// 5 = special performance (perfor_s) -/// 6 = level up (levelup) -/// 7 = performance 1 (perfor_1) -/// 8 = performance 2 (perfor_2) -/// 9 = performance 3 (perfor_3) -/// 10 = log-in greeting (connect) -/// hungry value: -/// 0 = very hungry (hungry) -/// 1 = hungry (bit_hungry) -/// 2 = satisfied (noting) -/// 3 = stuffed (full) -/// 4 = full (so_full) -void clif_parse_SendEmotion(int fd, struct map_session_data *sd) -{ - if(sd->pd) - clif_pet_emotion(sd->pd,RFIFOL(fd,2)); -} - - -/// Request to change pet's name (CZ_RENAME_PET). -/// 01a5 <name>.24B -void clif_parse_ChangePetName(int fd, struct map_session_data *sd) -{ - pet_change_name(sd,(char*)RFIFOP(fd,2)); -} - - -/// /kill (CZ_DISCONNECT_CHARACTER). -/// Request to disconnect a character. -/// 00cc <account id>.L -/// NOTE: Also sent when using GM right click menu "(name) force to quit" -void clif_parse_GMKick(int fd, struct map_session_data *sd) -{ - struct block_list *target; - int tid; - - tid = RFIFOL(fd,2); - target = map_id2bl(tid); - if (!target) { - clif_GM_kickack(sd, 0); - return; - } - - switch (target->type) { - case BL_PC: - { - char command[NAME_LENGTH+6]; - sprintf(command, "%ckick %s", atcommand_symbol, status_get_name(target)); - is_atcommand(fd, sd, command, 1); - } - break; - - /** - * This one does not invoke any atcommand, so we need to check for permissions. - */ - case BL_MOB: - { - char command[100]; - if( !pc_can_use_command(sd, "killmonster", COMMAND_ATCOMMAND)) { - clif_GM_kickack(sd, 0); - return; - } - sprintf(command, "/kick %s (%d)", status_get_name(target), status_get_class(target)); - log_atcommand(sd, command); - status_percent_damage(&sd->bl, target, 100, 0, true); // can invalidate 'target' - } - break; - - case BL_NPC: - { - char command[NAME_LENGTH+11]; - sprintf(command, "%cunloadnpc %s", atcommand_symbol, status_get_name(target)); - is_atcommand(fd, sd, command, 1); - } - break; - - default: - clif_GM_kickack(sd, 0); - } -} - - -/// /killall (CZ_DISCONNECT_ALL_CHARACTER). -/// Request to disconnect all characters. -/// 00ce -void clif_parse_GMKickAll(int fd, struct map_session_data* sd) { - char cmd[15]; - sprintf(cmd,"%ckickall",atcommand_symbol); - is_atcommand(fd, sd, cmd, 1); -} - - -/// /remove (CZ_REMOVE_AID). -/// Request to warp to a character with given login ID. -/// 01ba <account name>.24B - -/// /shift (CZ_SHIFT). -/// Request to warp to a character with given name. -/// 01bb <char name>.24B -void clif_parse_GMShift(int fd, struct map_session_data *sd) -{// FIXME: remove is supposed to receive account name for clients prior 20100803RE - char *player_name; - char command[NAME_LENGTH+8]; - - player_name = (char*)RFIFOP(fd,2); - player_name[NAME_LENGTH-1] = '\0'; - - sprintf(command, "%cjumpto %s", atcommand_symbol, player_name); - is_atcommand(fd, sd, command, 1); -} - - -/// /remove (CZ_REMOVE_AID_SSO). -/// Request to warp to a character with given account ID. -/// 0843 <account id>.L -void clif_parse_GMRemove2(int fd, struct map_session_data* sd) -{ - int account_id; - struct map_session_data* pl_sd; - - account_id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); - if( (pl_sd = map_id2sd(account_id)) != NULL ) - { - char command[NAME_LENGTH+8]; - sprintf(command, "%cjumpto %s", atcommand_symbol, pl_sd->status.name); - is_atcommand(fd, sd, command, 1); - } -} - - -/// /recall (CZ_RECALL). -/// Request to summon a player with given login ID to own position. -/// 01bc <account name>.24B - -/// /summon (CZ_RECALL_GID). -/// Request to summon a player with given name to own position. -/// 01bd <char name>.24B -void clif_parse_GMRecall(int fd, struct map_session_data *sd) -{// FIXME: recall is supposed to receive account name for clients prior 20100803RE - char *player_name; - char command [NAME_LENGTH+8]; - - player_name = (char*)RFIFOP(fd,2); - player_name[NAME_LENGTH-1] = '\0'; - - sprintf(command, "%crecall %s", atcommand_symbol, player_name); - is_atcommand(fd, sd, command, 1); -} - - -/// /recall (CZ_RECALL_SSO). -/// Request to summon a player with given account ID to own position. -/// 0842 <account id>.L -void clif_parse_GMRecall2(int fd, struct map_session_data* sd) -{ - int account_id; - struct map_session_data* pl_sd; - - account_id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); - if( (pl_sd = map_id2sd(account_id)) != NULL ) - { - char command[NAME_LENGTH+8]; - sprintf(command, "%crecall %s", atcommand_symbol, pl_sd->status.name); - is_atcommand(fd, sd, command, 1); - } -} - - -/// /item /monster (CZ_ITEM_CREATE). -/// Request to make items or spawn monsters. -/// 013f <item/mob name>.24B -void clif_parse_GM_Monster_Item(int fd, struct map_session_data *sd) -{ - char *monster_item_name; - char command[NAME_LENGTH+10]; - - monster_item_name = (char*)RFIFOP(fd,2); - monster_item_name[NAME_LENGTH-1] = '\0'; - - // FIXME: Should look for item first, then for monster. - // FIXME: /monster takes mob_db Sprite_Name as argument - if( mobdb_searchname(monster_item_name) ) { - snprintf(command, sizeof(command)-1, "%cmonster %s", atcommand_symbol, monster_item_name); - is_atcommand(fd, sd, command, 1); - return; - } - // FIXME: Stackables have a quantity of 20. - // FIXME: Equips are supposed to be unidentified. - - if( itemdb_searchname(monster_item_name) ) { - snprintf(command, sizeof(command)-1, "%citem %s", atcommand_symbol, monster_item_name); - is_atcommand(fd, sd, command, 1); - return; - } -} - - -/// /hide (CZ_CHANGE_EFFECTSTATE). -/// 019d <effect state>.L -/// effect state: -/// TODO: Any OPTION_* ? -void clif_parse_GMHide(int fd, struct map_session_data *sd) { - char cmd[6]; - - sprintf(cmd,"%chide",atcommand_symbol); - - is_atcommand(fd, sd, cmd, 1); -} - - -/// Request to adjust player's manner points (CZ_REQ_GIVE_MANNER_POINT). -/// 0149 <account id>.L <type>.B <value>.W -/// type: -/// 0 = positive points -/// 1 = negative points -/// 2 = self mute (+10 minutes) -void clif_parse_GMReqNoChat(int fd,struct map_session_data *sd) -{ - int id, type, value; - struct map_session_data *dstsd; - char command[NAME_LENGTH+15]; - - id = RFIFOL(fd,2); - type = RFIFOB(fd,6); - value = RFIFOW(fd,7); - - if( type == 0 ) - value = -value; - - //If type is 2 and the ids don't match, this is a crafted hacked packet! - //Disabled because clients keep self-muting when you give players public @ commands... [Skotlex] - if (type == 2 /* && (pc_get_group_level(sd) > 0 || sd->bl.id != id)*/) - return; - - dstsd = map_id2sd(id); - if( dstsd == NULL ) - return; - - sprintf(command, "%cmute %d %s", atcommand_symbol, value, dstsd->status.name); - is_atcommand(fd, sd, command, 1); -} - - -/// /rc (CZ_REQ_GIVE_MANNER_BYNAME). -/// GM adjustment of a player's manner value by -60. -/// 0212 <char name>.24B -void clif_parse_GMRc(int fd, struct map_session_data* sd) -{ - char command[NAME_LENGTH+15]; - char *name = (char*)RFIFOP(fd,2); - - name[NAME_LENGTH-1] = '\0'; - sprintf(command, "%cmute %d %s", atcommand_symbol, 60, name); - is_atcommand(fd, sd, command, 1); -} - - -/// Result of request to resolve account name (ZC_ACK_ACCOUNTNAME). -/// 01e0 <account id>.L <account name>.24B -void clif_account_name(struct map_session_data* sd, int account_id, const char* accname) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x1e0)); - WFIFOW(fd,0) = 0x1e0; - WFIFOL(fd,2) = account_id; - safestrncpy((char*)WFIFOP(fd,6), accname, NAME_LENGTH); - WFIFOSET(fd,packet_len(0x1e0)); -} - - -/// GM requesting account name (for right-click gm menu) (CZ_REQ_ACCOUNTNAME). -/// 01df <account id>.L -void clif_parse_GMReqAccountName(int fd, struct map_session_data *sd) -{ - int account_id = RFIFOL(fd,2); - - //TODO: find out if this works for any player or only for authorized GMs - clif_account_name(sd, account_id, ""); // insert account name here >_< -} - - -/// /changemaptype <x> <y> <type> (CZ_CHANGE_MAPTYPE). -/// GM single cell type change request. -/// 0198 <x>.W <y>.W <type>.W -/// type: -/// 0 = not walkable -/// 1 = walkable -void clif_parse_GMChangeMapType(int fd, struct map_session_data *sd) -{ - int x,y,type; - - if( pc_has_permission(sd, PC_PERM_USE_CHANGEMAPTYPE) ) - return; - - x = RFIFOW(fd,2); - y = RFIFOW(fd,4); - type = RFIFOW(fd,6); - - map_setgatcell(sd->bl.m,x,y,type); - clif_changemapcell(0,sd->bl.m,x,y,type,ALL_SAMEMAP); - //FIXME: once players leave the map, the client 'forgets' this information. -} - - -/// /in /ex (CZ_SETTING_WHISPER_PC). -/// Request to allow/deny whispers from a nick. -/// 00cf <nick>.24B <type>.B -/// type: -/// 0 = (/ex nick) deny speech from nick -/// 1 = (/in nick) allow speech from nick -void clif_parse_PMIgnore(int fd, struct map_session_data* sd) -{ - char* nick; - uint8 type; - int i; - - nick = (char*)RFIFOP(fd,2); // speed up - nick[NAME_LENGTH-1] = '\0'; // to be sure that the player name has at most 23 characters - type = RFIFOB(fd,26); - - if( type == 0 ) - { // Add name to ignore list (block) - if (strcmp(wisp_server_name, nick) == 0) { - clif_wisexin(sd, type, 1); // fail - return; - } - - // try to find a free spot, while checking for duplicates at the same time - ARR_FIND( 0, MAX_IGNORE_LIST, i, sd->ignore[i].name[0] == '\0' || strcmp(sd->ignore[i].name, nick) == 0 ); - if( i == MAX_IGNORE_LIST ) - {// no space for new entry - clif_wisexin(sd, type, 2); // too many blocks - return; - } - if( sd->ignore[i].name[0] != '\0' ) - {// name already exists - clif_wisexin(sd, type, 0); // Aegis reports success. - return; - } - - //Insert in position i - safestrncpy(sd->ignore[i].name, nick, NAME_LENGTH); - } - else - { // Remove name from ignore list (unblock) - - // find entry - ARR_FIND( 0, MAX_IGNORE_LIST, i, sd->ignore[i].name[0] == '\0' || strcmp(sd->ignore[i].name, nick) == 0 ); - if( i == MAX_IGNORE_LIST || sd->ignore[i].name[i] == '\0' ) - { //Not found - clif_wisexin(sd, type, 1); // fail - return; - } - // move everything one place down to overwrite removed entry - memmove(sd->ignore[i].name, sd->ignore[i+1].name, (MAX_IGNORE_LIST-i-1)*sizeof(sd->ignore[0].name)); - // wipe last entry - memset(sd->ignore[MAX_IGNORE_LIST-1].name, 0, sizeof(sd->ignore[0].name)); - } - - clif_wisexin(sd, type, 0); // success -} - - -/// /inall /exall (CZ_SETTING_WHISPER_STATE). -/// Request to allow/deny all whispers. -/// 00d0 <type>.B -/// type: -/// 0 = (/exall) deny all speech -/// 1 = (/inall) allow all speech -void clif_parse_PMIgnoreAll(int fd, struct map_session_data *sd) -{ - int type = RFIFOB(fd,2), flag; - - if( type == 0 ) - {// Deny all - if( sd->state.ignoreAll ) { - flag = 1; // fail - } else { - sd->state.ignoreAll = 1; - flag = 0; // success - } - } - else - {//Unblock everyone - if( sd->state.ignoreAll ) { - sd->state.ignoreAll = 0; - flag = 0; // success - } else { - if (sd->ignore[0].name[0] != '\0') - { //Wipe the ignore list. - memset(sd->ignore, 0, sizeof(sd->ignore)); - flag = 0; // success - } else { - flag = 1; // fail - } - } - } - - clif_wisall(sd, type, flag); -} - - -/// Whisper ignore list (ZC_WHISPER_LIST). -/// 00d4 <packet len>.W { <char name>.24B }* -void clif_PMIgnoreList(struct map_session_data* sd) -{ - int i, fd = sd->fd; - - WFIFOHEAD(fd,4+ARRAYLENGTH(sd->ignore)*NAME_LENGTH); - WFIFOW(fd,0) = 0xd4; - - for( i = 0; i < ARRAYLENGTH(sd->ignore) && sd->ignore[i].name[0]; i++ ) - { - memcpy(WFIFOP(fd,4+i*NAME_LENGTH), sd->ignore[i].name, NAME_LENGTH); - } - - WFIFOW(fd,2) = 4+i*NAME_LENGTH; - WFIFOSET(fd,WFIFOW(fd,2)); -} - - -/// Whisper ignore list request (CZ_REQ_WHISPER_LIST). -/// 00d3 -void clif_parse_PMIgnoreList(int fd,struct map_session_data *sd) -{ - clif_PMIgnoreList(sd); -} - - -/// Request to invoke the /doridori recovery bonus (CZ_DORIDORI). -/// 01e7 -void clif_parse_NoviceDoriDori(int fd, struct map_session_data *sd) -{ - if (sd->state.doridori) return; - - switch (sd->class_&MAPID_UPPERMASK) - { - case MAPID_SOUL_LINKER: - case MAPID_STAR_GLADIATOR: - case MAPID_TAEKWON: - if (!sd->state.rest) - break; - case MAPID_SUPER_NOVICE: - sd->state.doridori=1; - break; - } -} - - -/// Request to invoke the effect of super novice's guardian angel prayer (CZ_CHOPOKGI). -/// 01ed -/// Note: This packet is caused by 7 lines of any text, followed by -/// the prayer and an another line of any text. The prayer is -/// defined by lines 790~793 in data\msgstringtable.txt -/// "Dear angel, can you hear my voice?" -/// "I am" (space separated player name) "Super Novice~" -/// "Help me out~ Please~ T_T" -void clif_parse_NoviceExplosionSpirits(int fd, struct map_session_data *sd) -{ - if( ( sd->class_&MAPID_UPPERMASK ) == MAPID_SUPER_NOVICE ) - { - unsigned int next = pc_nextbaseexp(sd); - if( next == 0 ) next = pc_thisbaseexp(sd); - if( next ) - { - int percent = (int)( ( (float)sd->status.base_exp/(float)next )*1000. ); - - if( percent && ( percent%100 ) == 0 ) - {// 10.0%, 20.0%, ..., 90.0% - sc_start(&sd->bl, status_skill2sc(MO_EXPLOSIONSPIRITS), 100, 17, skill_get_time(MO_EXPLOSIONSPIRITS, 5)); //Lv17-> +50 critical (noted by Poki) [Skotlex] - clif_skill_nodamage(&sd->bl, &sd->bl, MO_EXPLOSIONSPIRITS, 5, 1); // prayer always shows successful Lv5 cast and disregards noskill restrictions - } - } - } -} - - -/// Friends List -/// - -/// Toggles a single friend online/offline [Skotlex] (ZC_FRIENDS_STATE). -/// 0206 <account id>.L <char id>.L <state>.B -/// state: -/// 0 = online -/// 1 = offline -void clif_friendslist_toggle(struct map_session_data *sd,int account_id, int char_id, int online) -{ - int i, fd = sd->fd; - - //Seek friend. - for (i = 0; i < MAX_FRIENDS && sd->status.friends[i].char_id && - (sd->status.friends[i].char_id != char_id || sd->status.friends[i].account_id != account_id); i++); - - if(i == MAX_FRIENDS || sd->status.friends[i].char_id == 0) - return; //Not found - - WFIFOHEAD(fd,packet_len(0x206)); - WFIFOW(fd, 0) = 0x206; - WFIFOL(fd, 2) = sd->status.friends[i].account_id; - WFIFOL(fd, 6) = sd->status.friends[i].char_id; - WFIFOB(fd,10) = !online; //Yeah, a 1 here means "logged off", go figure... - WFIFOSET(fd, packet_len(0x206)); -} - - -//Subfunction called from clif_foreachclient to toggle friends on/off [Skotlex] -int clif_friendslist_toggle_sub(struct map_session_data *sd,va_list ap) -{ - int account_id, char_id, online; - account_id = va_arg(ap, int); - char_id = va_arg(ap, int); - online = va_arg(ap, int); - clif_friendslist_toggle(sd, account_id, char_id, online); - return 0; -} - - -/// Sends the whole friends list (ZC_FRIENDS_LIST). -/// 0201 <packet len>.W { <account id>.L <char id>.L <name>.24B }* -void clif_friendslist_send(struct map_session_data *sd) -{ - int i = 0, n, fd = sd->fd; - - // Send friends list - WFIFOHEAD(fd, MAX_FRIENDS * 32 + 4); - WFIFOW(fd, 0) = 0x201; - for(i = 0; i < MAX_FRIENDS && sd->status.friends[i].char_id; i++) - { - WFIFOL(fd, 4 + 32 * i + 0) = sd->status.friends[i].account_id; - WFIFOL(fd, 4 + 32 * i + 4) = sd->status.friends[i].char_id; - memcpy(WFIFOP(fd, 4 + 32 * i + 8), &sd->status.friends[i].name, NAME_LENGTH); - } - - if (i) { - WFIFOW(fd,2) = 4 + 32 * i; - WFIFOSET(fd, WFIFOW(fd,2)); - } - - for (n = 0; n < i; n++) - { //Sending the online players - if (map_charid2sd(sd->status.friends[n].char_id)) - clif_friendslist_toggle(sd, sd->status.friends[n].account_id, sd->status.friends[n].char_id, 1); - } -} - - -/// Notification about the result of a friend add request (ZC_ADD_FRIENDS_LIST). -/// 0209 <result>.W <account id>.L <char id>.L <name>.24B -/// result: -/// 0 = MsgStringTable[821]="You have become friends with (%s)." -/// 1 = MsgStringTable[822]="(%s) does not want to be friends with you." -/// 2 = MsgStringTable[819]="Your Friend List is full." -/// 3 = MsgStringTable[820]="(%s)'s Friend List is full." -void clif_friendslist_reqack(struct map_session_data *sd, struct map_session_data *f_sd, int type) -{ - int fd; - nullpo_retv(sd); - - fd = sd->fd; - WFIFOHEAD(fd,packet_len(0x209)); - WFIFOW(fd,0) = 0x209; - WFIFOW(fd,2) = type; - if (f_sd) - { - WFIFOL(fd,4) = f_sd->status.account_id; - WFIFOL(fd,8) = f_sd->status.char_id; - memcpy(WFIFOP(fd, 12), f_sd->status.name,NAME_LENGTH); - } - WFIFOSET(fd, packet_len(0x209)); -} - - -/// Asks a player for permission to be added as friend (ZC_REQ_ADD_FRIENDS). -/// 0207 <req account id>.L <req char id>.L <req char name>.24B -void clif_friendlist_req(struct map_session_data* sd, int account_id, int char_id, const char* name) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x207)); - WFIFOW(fd,0) = 0x207; - WFIFOL(fd,2) = account_id; - WFIFOL(fd,6) = char_id; - memcpy(WFIFOP(fd,10), name, NAME_LENGTH); - WFIFOSET(fd,packet_len(0x207)); -} - - -/// Request to add a player as friend (CZ_ADD_FRIENDS). -/// 0202 <name>.24B -void clif_parse_FriendsListAdd(int fd, struct map_session_data *sd) -{ - struct map_session_data *f_sd; - int i; - - f_sd = map_nick2sd((char*)RFIFOP(fd,2)); - - // ensure that the request player's friend list is not full - ARR_FIND(0, MAX_FRIENDS, i, sd->status.friends[i].char_id == 0); - - if( i == MAX_FRIENDS ) { - clif_friendslist_reqack(sd, f_sd, 2); - return; - } - - // Friend doesn't exist (no player with this name) - if (f_sd == NULL) { - clif_displaymessage(fd, msg_txt(3)); - return; - } - - if( sd->bl.id == f_sd->bl.id ) - {// adding oneself as friend - return; - } - - // @noask [LuzZza] - if(f_sd->state.noask) { - clif_noask_sub(sd, f_sd, 5); - return; - } - - // Friend already exists - for (i = 0; i < MAX_FRIENDS && sd->status.friends[i].char_id != 0; i++) { - if (sd->status.friends[i].char_id == f_sd->status.char_id) { - clif_displaymessage(fd, msg_txt(671)); //"Friend already exists." - return; - } - } - - f_sd->friend_req = sd->status.char_id; - sd->friend_req = f_sd->status.char_id; - - clif_friendlist_req(f_sd, sd->status.account_id, sd->status.char_id, sd->status.name); -} - - -/// Answer to a friend add request (CZ_ACK_REQ_ADD_FRIENDS). -/// 0208 <inviter account id>.L <inviter char id>.L <result>.B -/// 0208 <inviter account id>.L <inviter char id>.L <result>.L (PACKETVER >= 6) -/// result: -/// 0 = rejected -/// 1 = accepted -void clif_parse_FriendsListReply(int fd, struct map_session_data *sd) -{ - struct map_session_data *f_sd; - int account_id; - char reply; - - account_id = RFIFOL(fd,2); - //char_id = RFIFOL(fd,6); -#if PACKETVER < 6 - reply = RFIFOB(fd,10); -#else - reply = RFIFOL(fd,10); -#endif - - if( sd->bl.id == account_id ) - {// adding oneself as friend - return; - } - - f_sd = map_id2sd(account_id); //The account id is the same as the bl.id of players. - if (f_sd == NULL) - return; - - if (reply == 0 || !( sd->friend_req == f_sd->status.char_id && f_sd->friend_req == sd->status.char_id ) ) - clif_friendslist_reqack(f_sd, sd, 1); - else { - int i; - // Find an empty slot - for (i = 0; i < MAX_FRIENDS; i++) - if (f_sd->status.friends[i].char_id == 0) - break; - if (i == MAX_FRIENDS) { - clif_friendslist_reqack(f_sd, sd, 2); - return; - } - - f_sd->status.friends[i].account_id = sd->status.account_id; - f_sd->status.friends[i].char_id = sd->status.char_id; - memcpy(f_sd->status.friends[i].name, sd->status.name, NAME_LENGTH); - clif_friendslist_reqack(f_sd, sd, 0); - - if (battle_config.friend_auto_add) { - // Also add f_sd to sd's friendlist. - for (i = 0; i < MAX_FRIENDS; i++) { - if (sd->status.friends[i].char_id == f_sd->status.char_id) - return; //No need to add anything. - if (sd->status.friends[i].char_id == 0) - break; - } - if (i == MAX_FRIENDS) { - clif_friendslist_reqack(sd, f_sd, 2); - return; - } - - sd->status.friends[i].account_id = f_sd->status.account_id; - sd->status.friends[i].char_id = f_sd->status.char_id; - memcpy(sd->status.friends[i].name, f_sd->status.name, NAME_LENGTH); - clif_friendslist_reqack(sd, f_sd, 0); - } - } -} - - -/// Request to delete a friend (CZ_DELETE_FRIENDS). -/// 0203 <account id>.L <char id>.L -void clif_parse_FriendsListRemove(int fd, struct map_session_data *sd) -{ - struct map_session_data *f_sd = NULL; - int account_id, char_id; - int i, j; - - account_id = RFIFOL(fd,2); - char_id = RFIFOL(fd,6); - - // Search friend - for (i = 0; i < MAX_FRIENDS && - (sd->status.friends[i].char_id != char_id || sd->status.friends[i].account_id != account_id); i++); - - if (i == MAX_FRIENDS) { - clif_displaymessage(fd, msg_txt(672)); //"Name not found in list." - return; - } - - //remove from friend's list first - if( (f_sd = map_id2sd(account_id)) && f_sd->status.char_id == char_id) { - for (i = 0; i < MAX_FRIENDS && - (f_sd->status.friends[i].char_id != sd->status.char_id || f_sd->status.friends[i].account_id != sd->status.account_id); i++); - - if (i != MAX_FRIENDS) { - // move all chars up - for(j = i + 1; j < MAX_FRIENDS; j++) - memcpy(&f_sd->status.friends[j-1], &f_sd->status.friends[j], sizeof(f_sd->status.friends[0])); - - memset(&f_sd->status.friends[MAX_FRIENDS-1], 0, sizeof(f_sd->status.friends[MAX_FRIENDS-1])); - //should the guy be notified of some message? we should add it here if so - WFIFOHEAD(f_sd->fd,packet_len(0x20a)); - WFIFOW(f_sd->fd,0) = 0x20a; - WFIFOL(f_sd->fd,2) = sd->status.account_id; - WFIFOL(f_sd->fd,6) = sd->status.char_id; - WFIFOSET(f_sd->fd, packet_len(0x20a)); - } - - } else { //friend not online -- ask char server to delete from his friendlist - if(chrif_removefriend(char_id,sd->status.char_id)) { // char-server offline, abort - clif_displaymessage(fd, msg_txt(673)); //"This action can't be performed at the moment. Please try again later." - return; - } - } - - // We can now delete from original requester - for (i = 0; i < MAX_FRIENDS && - (sd->status.friends[i].char_id != char_id || sd->status.friends[i].account_id != account_id); i++); - // move all chars up - for(j = i + 1; j < MAX_FRIENDS; j++) - memcpy(&sd->status.friends[j-1], &sd->status.friends[j], sizeof(sd->status.friends[0])); - - memset(&sd->status.friends[MAX_FRIENDS-1], 0, sizeof(sd->status.friends[MAX_FRIENDS-1])); - clif_displaymessage(fd, msg_txt(674)); //"Friend removed" - - WFIFOHEAD(fd,packet_len(0x20a)); - WFIFOW(fd,0) = 0x20a; - WFIFOL(fd,2) = account_id; - WFIFOL(fd,6) = char_id; - WFIFOSET(fd, packet_len(0x20a)); -} - - -/// /pvpinfo list (ZC_ACK_PVPPOINT). -/// 0210 <char id>.L <account id>.L <win point>.L <lose point>.L <point>.L -void clif_PVPInfo(struct map_session_data* sd) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x210)); - WFIFOW(fd,0) = 0x210; - WFIFOL(fd,2) = sd->status.char_id; - WFIFOL(fd,6) = sd->status.account_id; - WFIFOL(fd,10) = sd->pvp_won; // times won - WFIFOL(fd,14) = sd->pvp_lost; // times lost - WFIFOL(fd,18) = sd->pvp_point; - WFIFOSET(fd, packet_len(0x210)); -} - - -/// /pvpinfo (CZ_REQ_PVPPOINT). -/// 020f <char id>.L <account id>.L -void clif_parse_PVPInfo(int fd,struct map_session_data *sd) -{ - // TODO: Is there a way to use this on an another player (char/acc id)? - clif_PVPInfo(sd); -} - - -/// /blacksmith list (ZC_BLACKSMITH_RANK). -/// 0219 { <name>.24B }*10 { <point>.L }*10 -void clif_blacksmith(struct map_session_data* sd) -{ - int i, fd = sd->fd; - const char* name; - - WFIFOHEAD(fd,packet_len(0x219)); - WFIFOW(fd,0) = 0x219; - //Packet size limits this list to 10 elements. [Skotlex] - for (i = 0; i < 10 && i < MAX_FAME_LIST; i++) { - if (smith_fame_list[i].id > 0) { - if (strcmp(smith_fame_list[i].name, "-") == 0 && - (name = map_charid2nick(smith_fame_list[i].id)) != NULL) - { - strncpy((char *)(WFIFOP(fd, 2 + 24 * i)), name, NAME_LENGTH); - } else - strncpy((char *)(WFIFOP(fd, 2 + 24 * i)), smith_fame_list[i].name, NAME_LENGTH); - } else - strncpy((char *)(WFIFOP(fd, 2 + 24 * i)), "None", 5); - WFIFOL(fd, 242 + i * 4) = smith_fame_list[i].fame; - } - for(;i < 10; i++) { //In case the MAX is less than 10. - strncpy((char *)(WFIFOP(fd, 2 + 24 * i)), "Unavailable", 12); - WFIFOL(fd, 242 + i * 4) = 0; - } - - WFIFOSET(fd, packet_len(0x219)); -} - - -/// /blacksmith (CZ_BLACKSMITH_RANK). -/// 0217 -void clif_parse_Blacksmith(int fd,struct map_session_data *sd) -{ - clif_blacksmith(sd); -} - - -/// Notification about backsmith points (ZC_BLACKSMITH_POINT). -/// 021b <points>.L <total points>.L -void clif_fame_blacksmith(struct map_session_data *sd, int points) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x21b)); - WFIFOW(fd,0) = 0x21b; - WFIFOL(fd,2) = points; - WFIFOL(fd,6) = sd->status.fame; - WFIFOSET(fd, packet_len(0x21b)); -} - - -/// /alchemist list (ZC_ALCHEMIST_RANK). -/// 021a { <name>.24B }*10 { <point>.L }*10 -void clif_alchemist(struct map_session_data* sd) -{ - int i, fd = sd->fd; - const char* name; - - WFIFOHEAD(fd,packet_len(0x21a)); - WFIFOW(fd,0) = 0x21a; - //Packet size limits this list to 10 elements. [Skotlex] - for (i = 0; i < 10 && i < MAX_FAME_LIST; i++) { - if (chemist_fame_list[i].id > 0) { - if (strcmp(chemist_fame_list[i].name, "-") == 0 && - (name = map_charid2nick(chemist_fame_list[i].id)) != NULL) - { - memcpy(WFIFOP(fd, 2 + 24 * i), name, NAME_LENGTH); - } else - memcpy(WFIFOP(fd, 2 + 24 * i), chemist_fame_list[i].name, NAME_LENGTH); - } else - memcpy(WFIFOP(fd, 2 + 24 * i), "None", NAME_LENGTH); - WFIFOL(fd, 242 + i * 4) = chemist_fame_list[i].fame; - } - for(;i < 10; i++) { //In case the MAX is less than 10. - memcpy(WFIFOP(fd, 2 + 24 * i), "Unavailable", NAME_LENGTH); - WFIFOL(fd, 242 + i * 4) = 0; - } - - WFIFOSET(fd, packet_len(0x21a)); -} - - -/// /alchemist (CZ_ALCHEMIST_RANK). -/// 0218 -void clif_parse_Alchemist(int fd,struct map_session_data *sd) -{ - clif_alchemist(sd); -} - - -/// Notification about alchemist points (ZC_ALCHEMIST_POINT). -/// 021c <points>.L <total points>.L -void clif_fame_alchemist(struct map_session_data *sd, int points) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x21c)); - WFIFOW(fd,0) = 0x21c; - WFIFOL(fd,2) = points; - WFIFOL(fd,6) = sd->status.fame; - WFIFOSET(fd, packet_len(0x21c)); -} - - -/// /taekwon list (ZC_TAEKWON_RANK). -/// 0226 { <name>.24B }*10 { <point>.L }*10 -void clif_taekwon(struct map_session_data* sd) -{ - int i, fd = sd->fd; - const char* name; - - WFIFOHEAD(fd,packet_len(0x226)); - WFIFOW(fd,0) = 0x226; - //Packet size limits this list to 10 elements. [Skotlex] - for (i = 0; i < 10 && i < MAX_FAME_LIST; i++) { - if (taekwon_fame_list[i].id > 0) { - if (strcmp(taekwon_fame_list[i].name, "-") == 0 && - (name = map_charid2nick(taekwon_fame_list[i].id)) != NULL) - { - memcpy(WFIFOP(fd, 2 + 24 * i), name, NAME_LENGTH); - } else - memcpy(WFIFOP(fd, 2 + 24 * i), taekwon_fame_list[i].name, NAME_LENGTH); - } else - memcpy(WFIFOP(fd, 2 + 24 * i), "None", NAME_LENGTH); - WFIFOL(fd, 242 + i * 4) = taekwon_fame_list[i].fame; - } - for(;i < 10; i++) { //In case the MAX is less than 10. - memcpy(WFIFOP(fd, 2 + 24 * i), "Unavailable", NAME_LENGTH); - WFIFOL(fd, 242 + i * 4) = 0; - } - WFIFOSET(fd, packet_len(0x226)); -} - - -/// /taekwon (CZ_TAEKWON_RANK). -/// 0225 -void clif_parse_Taekwon(int fd,struct map_session_data *sd) -{ - clif_taekwon(sd); -} - - -/// Notification about taekwon points (ZC_TAEKWON_POINT). -/// 0224 <points>.L <total points>.L -void clif_fame_taekwon(struct map_session_data *sd, int points) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x224)); - WFIFOW(fd,0) = 0x224; - WFIFOL(fd,2) = points; - WFIFOL(fd,6) = sd->status.fame; - WFIFOSET(fd, packet_len(0x224)); -} - - -/// /pk list (ZC_KILLER_RANK). -/// 0238 { <name>.24B }*10 { <point>.L }*10 -void clif_ranking_pk(struct map_session_data* sd) -{ - int i, fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x238)); - WFIFOW(fd,0) = 0x238; - for(i=0;i<10;i++){ - memcpy(WFIFOP(fd,i*24+2), "Unknown", NAME_LENGTH); - WFIFOL(fd,i*4+242) = 0; - } - WFIFOSET(fd, packet_len(0x238)); -} - - -/// /pk (CZ_KILLER_RANK). -/// 0237 -void clif_parse_RankingPk(int fd,struct map_session_data *sd) -{ - clif_ranking_pk(sd); -} - - -/// SG Feel save OK [Komurka] (CZ_AGREE_STARPLACE). -/// 0254 <which>.B -/// which: -/// 0 = sun -/// 1 = moon -/// 2 = star -void clif_parse_FeelSaveOk(int fd,struct map_session_data *sd) -{ - int i; - if (sd->menuskill_id != SG_FEEL) - return; - i = sd->menuskill_val-1; - if (i<0 || i >= MAX_PC_FEELHATE) return; //Bug? - - sd->feel_map[i].index = map_id2index(sd->bl.m); - sd->feel_map[i].m = sd->bl.m; - pc_setglobalreg(sd,sg_info[i].feel_var,sd->feel_map[i].index); - -//Are these really needed? Shouldn't they show up automatically from the feel save packet? -// clif_misceffect2(&sd->bl, 0x1b0); -// clif_misceffect2(&sd->bl, 0x21f); - clif_feel_info(sd, i, 0); - clif_menuskill_clear(sd); -} - - -/// Star Gladiator's Feeling map confirmation prompt (ZC_STARPLACE). -/// 0253 <which>.B -/// which: -/// 0 = sun -/// 1 = moon -/// 2 = star -void clif_feel_req(int fd, struct map_session_data *sd, uint16 skill_lv) -{ - WFIFOHEAD(fd,packet_len(0x253)); - WFIFOW(fd,0)=0x253; - WFIFOB(fd,2)=TOB(skill_lv-1); - WFIFOSET(fd, packet_len(0x253)); - sd->menuskill_id = SG_FEEL; - sd->menuskill_val = skill_lv; -} - - -/// Request to change homunculus' name (CZ_RENAME_MER). -/// 0231 <name>.24B -void clif_parse_ChangeHomunculusName(int fd, struct map_session_data *sd) -{ - merc_hom_change_name(sd,(char*)RFIFOP(fd,2)); -} - - -/// Request to warp/move homunculus/mercenary to it's owner (CZ_REQUEST_MOVETOOWNER). -/// 0234 <id>.L -void clif_parse_HomMoveToMaster(int fd, struct map_session_data *sd) -{ - int id = RFIFOL(fd,2); // Mercenary or Homunculus - struct block_list *bl = NULL; - struct unit_data *ud = NULL; - - if( sd->md && sd->md->bl.id == id ) - bl = &sd->md->bl; - else if( merc_is_hom_active(sd->hd) && sd->hd->bl.id == id ) - bl = &sd->hd->bl; // Moving Homunculus - else - return; - - unit_calc_pos(bl, sd->bl.x, sd->bl.y, sd->ud.dir); - ud = unit_bl2ud(bl); - unit_walktoxy(bl, ud->to_x, ud->to_y, 4); -} - - -/// Request to move homunculus/mercenary (CZ_REQUEST_MOVENPC). -/// 0232 <id>.L <position data>.3B -void clif_parse_HomMoveTo(int fd, struct map_session_data *sd) -{ - int id = RFIFOL(fd,2); // Mercenary or Homunculus - struct block_list *bl = NULL; - short x, y; - - RFIFOPOS(fd, packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1], &x, &y, NULL); - - if( sd->md && sd->md->bl.id == id ) - bl = &sd->md->bl; // Moving Mercenary - else if( merc_is_hom_active(sd->hd) && sd->hd->bl.id == id ) - bl = &sd->hd->bl; // Moving Homunculus - else - return; - - unit_walktoxy(bl, x, y, 4); -} - - -/// Request to do an action with homunculus/mercenary (CZ_REQUEST_ACTNPC). -/// 0233 <id>.L <target id>.L <action>.B -/// action: -/// always 0 -void clif_parse_HomAttack(int fd,struct map_session_data *sd) -{ - struct block_list *bl = NULL; - int id = RFIFOL(fd,2), - target_id = RFIFOL(fd,6), - action_type = RFIFOB(fd,10); - - if( merc_is_hom_active(sd->hd) && sd->hd->bl.id == id ) - bl = &sd->hd->bl; - else if( sd->md && sd->md->bl.id == id ) - bl = &sd->md->bl; - else return; - - unit_stop_attack(bl); - unit_attack(bl, target_id, action_type != 0); -} - - -/// Request to invoke a homunculus menu action (CZ_COMMAND_MER). -/// 022d <type>.W <command>.B -/// type: -/// always 0 -/// command: -/// 0 = homunculus information -/// 1 = feed -/// 2 = delete -void clif_parse_HomMenu(int fd, struct map_session_data *sd) -{ //[orn] - int cmd; - - cmd = RFIFOW(fd,0); - - if(!merc_is_hom_active(sd->hd)) - return; - - merc_menu(sd,RFIFOB(fd,packet_db[sd->packet_ver][cmd].pos[1])); -} - - -/// Request to resurrect oneself using Token of Siegfried (CZ_STANDING_RESURRECTION). -/// 0292 -void clif_parse_AutoRevive(int fd, struct map_session_data *sd) -{ - int item_position = pc_search_inventory(sd, ITEMID_TOKEN_OF_SIEGFRIED); - - if (item_position < 0) - return; - - if (sd->sc.data[SC_HELLPOWER]) //Cannot res while under the effect of SC_HELLPOWER. - return; - - if (!status_revive(&sd->bl, 100, 100)) - return; - - clif_skill_nodamage(&sd->bl,&sd->bl,ALL_RESURRECTION,4,1); - pc_delitem(sd, item_position, 1, 0, 1, LOG_TYPE_CONSUME); -} - - -/// Information about character's status values (ZC_ACK_STATUS_GM). -/// 0214 <str>.B <standardStr>.B <agi>.B <standardAgi>.B <vit>.B <standardVit>.B -/// <int>.B <standardInt>.B <dex>.B <standardDex>.B <luk>.B <standardLuk>.B -/// <attPower>.W <refiningPower>.W <max_mattPower>.W <min_mattPower>.W -/// <itemdefPower>.W <plusdefPower>.W <mdefPower>.W <plusmdefPower>.W -/// <hitSuccessValue>.W <avoidSuccessValue>.W <plusAvoidSuccessValue>.W -/// <criticalSuccessValue>.W <ASPD>.W <plusASPD>.W -void clif_check(int fd, struct map_session_data* pl_sd) -{ - WFIFOHEAD(fd,packet_len(0x214)); - WFIFOW(fd, 0) = 0x214; - WFIFOB(fd, 2) = min(pl_sd->status.str, UINT8_MAX); - WFIFOB(fd, 3) = pc_need_status_point(pl_sd, SP_STR, 1); - WFIFOB(fd, 4) = min(pl_sd->status.agi, UINT8_MAX); - WFIFOB(fd, 5) = pc_need_status_point(pl_sd, SP_AGI, 1); - WFIFOB(fd, 6) = min(pl_sd->status.vit, UINT8_MAX); - WFIFOB(fd, 7) = pc_need_status_point(pl_sd, SP_VIT, 1); - WFIFOB(fd, 8) = min(pl_sd->status.int_, UINT8_MAX); - WFIFOB(fd, 9) = pc_need_status_point(pl_sd, SP_INT, 1); - WFIFOB(fd,10) = min(pl_sd->status.dex, UINT8_MAX); - WFIFOB(fd,11) = pc_need_status_point(pl_sd, SP_DEX, 1); - WFIFOB(fd,12) = min(pl_sd->status.luk, UINT8_MAX); - WFIFOB(fd,13) = pc_need_status_point(pl_sd, SP_LUK, 1); - WFIFOW(fd,14) = pl_sd->battle_status.batk+pl_sd->battle_status.rhw.atk+pl_sd->battle_status.lhw.atk; - WFIFOW(fd,16) = pl_sd->battle_status.rhw.atk2+pl_sd->battle_status.lhw.atk2; - WFIFOW(fd,18) = pl_sd->battle_status.matk_max; - WFIFOW(fd,20) = pl_sd->battle_status.matk_min; - WFIFOW(fd,22) = pl_sd->battle_status.def; - WFIFOW(fd,24) = pl_sd->battle_status.def2; - WFIFOW(fd,26) = pl_sd->battle_status.mdef; - WFIFOW(fd,28) = pl_sd->battle_status.mdef2; - WFIFOW(fd,30) = pl_sd->battle_status.hit; - WFIFOW(fd,32) = pl_sd->battle_status.flee; - WFIFOW(fd,34) = pl_sd->battle_status.flee2/10; - WFIFOW(fd,36) = pl_sd->battle_status.cri/10; - WFIFOW(fd,38) = (2000-pl_sd->battle_status.amotion)/10; // aspd - WFIFOW(fd,40) = 0; // FIXME: What is 'plusASPD' supposed to be? Maybe adelay? - WFIFOSET(fd,packet_len(0x214)); -} - - -/// /check (CZ_REQ_STATUS_GM). -/// Request character's status values. -/// 0213 <char name>.24B -void clif_parse_Check(int fd, struct map_session_data *sd) -{ - char charname[NAME_LENGTH]; - struct map_session_data* pl_sd; - - if(!pc_has_permission(sd, PC_PERM_USE_CHECK)) - return; - - safestrncpy(charname, (const char*)RFIFOP(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]), sizeof(charname)); - - if( ( pl_sd = map_nick2sd(charname) ) == NULL || pc_get_group_level(sd) < pc_get_group_level(pl_sd) ) - { - return; - } - - clif_check(fd, pl_sd); -} - - - -/// MAIL SYSTEM -/// By Zephyrus -/// - -/// Notification about the result of adding an item to mail (ZC_ACK_MAIL_ADD_ITEM). -/// 0255 <index>.W <result>.B -/// result: -/// 0 = success -/// 1 = failure -void clif_Mail_setattachment(int fd, int index, uint8 flag) -{ - WFIFOHEAD(fd,packet_len(0x255)); - WFIFOW(fd,0) = 0x255; - WFIFOW(fd,2) = index; - WFIFOB(fd,4) = flag; - WFIFOSET(fd,packet_len(0x255)); -} - - -/// Notification about the result of retrieving a mail attachment (ZC_MAIL_REQ_GET_ITEM). -/// 0245 <result>.B -/// result: -/// 0 = success -/// 1 = failure -/// 2 = too many items -void clif_Mail_getattachment(int fd, uint8 flag) -{ - WFIFOHEAD(fd,packet_len(0x245)); - WFIFOW(fd,0) = 0x245; - WFIFOB(fd,2) = flag; - WFIFOSET(fd,packet_len(0x245)); -} - - -/// Notification about the result of sending a mail (ZC_MAIL_REQ_SEND). -/// 0249 <result>.B -/// result: -/// 0 = success -/// 1 = recipinent does not exist -void clif_Mail_send(int fd, bool fail) -{ - WFIFOHEAD(fd,packet_len(0x249)); - WFIFOW(fd,0) = 0x249; - WFIFOB(fd,2) = fail; - WFIFOSET(fd,packet_len(0x249)); -} - - -/// Notification about the result of deleting a mail (ZC_ACK_MAIL_DELETE). -/// 0257 <mail id>.L <result>.W -/// result: -/// 0 = success -/// 1 = failure -void clif_Mail_delete(int fd, int mail_id, short fail) -{ - WFIFOHEAD(fd, packet_len(0x257)); - WFIFOW(fd,0) = 0x257; - WFIFOL(fd,2) = mail_id; - WFIFOW(fd,6) = fail; - WFIFOSET(fd, packet_len(0x257)); -} - - -/// Notification about the result of returning a mail (ZC_ACK_MAIL_RETURN). -/// 0274 <mail id>.L <result>.W -/// result: -/// 0 = success -/// 1 = failure -void clif_Mail_return(int fd, int mail_id, short fail) -{ - WFIFOHEAD(fd,packet_len(0x274)); - WFIFOW(fd,0) = 0x274; - WFIFOL(fd,2) = mail_id; - WFIFOW(fd,6) = fail; - WFIFOSET(fd,packet_len(0x274)); -} - - -/// Notification about new mail (ZC_MAIL_RECEIVE). -/// 024a <mail id>.L <title>.40B <sender>.24B -void clif_Mail_new(int fd, int mail_id, const char *sender, const char *title) -{ - WFIFOHEAD(fd,packet_len(0x24a)); - WFIFOW(fd,0) = 0x24a; - WFIFOL(fd,2) = mail_id; - safestrncpy((char*)WFIFOP(fd,6), title, MAIL_TITLE_LENGTH); - safestrncpy((char*)WFIFOP(fd,46), sender, NAME_LENGTH); - WFIFOSET(fd,packet_len(0x24a)); -} - - -/// Opens/closes the mail window (ZC_MAIL_WINDOWS). -/// 0260 <type>.L -/// type: -/// 0 = open -/// 1 = close -void clif_Mail_window(int fd, int flag) -{ - WFIFOHEAD(fd,packet_len(0x260)); - WFIFOW(fd,0) = 0x260; - WFIFOL(fd,2) = flag; - WFIFOSET(fd,packet_len(0x260)); -} - - -/// Lists mails stored in inbox (ZC_MAIL_REQ_GET_LIST). -/// 0240 <packet len>.W <amount>.L { <mail id>.L <title>.40B <read>.B <sender>.24B <time>.L }*amount -/// read: -/// 0 = unread -/// 1 = read -void clif_Mail_refreshinbox(struct map_session_data *sd) -{ - int fd = sd->fd; - struct mail_data *md = &sd->mail.inbox; - struct mail_message *msg; - int len, i, j; - - len = 8 + (73 * md->amount); - - WFIFOHEAD(fd,len); - WFIFOW(fd,0) = 0x240; - WFIFOW(fd,2) = len; - WFIFOL(fd,4) = md->amount; - for( i = j = 0; i < MAIL_MAX_INBOX && j < md->amount; i++ ) - { - msg = &md->msg[i]; - if (msg->id < 1) - continue; - - WFIFOL(fd,8+73*j) = msg->id; - memcpy(WFIFOP(fd,12+73*j), msg->title, MAIL_TITLE_LENGTH); - WFIFOB(fd,52+73*j) = (msg->status != MAIL_UNREAD); - memcpy(WFIFOP(fd,53+73*j), msg->send_name, NAME_LENGTH); - WFIFOL(fd,77+73*j) = (uint32)msg->timestamp; - j++; - } - WFIFOSET(fd,len); - - if( md->full ) - {// TODO: is this official? - char output[100]; - sprintf(output, "Inbox is full (Max %d). Delete some mails.", MAIL_MAX_INBOX); - clif_disp_onlyself(sd, output, strlen(output)); - } -} - - -/// Mail inbox list request (CZ_MAIL_GET_LIST). -/// 023f -void clif_parse_Mail_refreshinbox(int fd, struct map_session_data *sd) -{ - struct mail_data* md = &sd->mail.inbox; - - if( md->amount < MAIL_MAX_INBOX && (md->full || sd->mail.changed) ) - intif_Mail_requestinbox(sd->status.char_id, 1); - else - clif_Mail_refreshinbox(sd); - - mail_removeitem(sd, 0); - mail_removezeny(sd, 0); -} - - -/// Opens a mail (ZC_MAIL_REQ_OPEN). -/// 0242 <packet len>.W <mail id>.L <title>.40B <sender>.24B <time>.L <zeny>.L -/// <amount>.L <name id>.W <item type>.W <identified>.B <damaged>.B <refine>.B -/// <card1>.W <card2>.W <card3>.W <card4>.W <message>.?B -void clif_Mail_read(struct map_session_data *sd, int mail_id) -{ - int i, fd = sd->fd; - - ARR_FIND(0, MAIL_MAX_INBOX, i, sd->mail.inbox.msg[i].id == mail_id); - if( i == MAIL_MAX_INBOX ) - { - clif_Mail_return(sd->fd, mail_id, 1); // Mail doesn't exist - ShowWarning("clif_parse_Mail_read: char '%s' trying to read a message not the inbox.\n", sd->status.name); - return; - } - else - { - struct mail_message *msg = &sd->mail.inbox.msg[i]; - struct item *item = &msg->item; - struct item_data *data; - int msg_len = strlen(msg->body), len; - - if( msg_len == 0 ) { - strcpy(msg->body, "(no message)"); - msg_len = strlen(msg->body); - } - - len = 101 + msg_len; - - WFIFOHEAD(fd,len); - WFIFOW(fd,0) = 0x242; - WFIFOW(fd,2) = len; - WFIFOL(fd,4) = msg->id; - safestrncpy((char*)WFIFOP(fd,8), msg->title, MAIL_TITLE_LENGTH + 1); - safestrncpy((char*)WFIFOP(fd,48), msg->send_name, NAME_LENGTH + 1); - WFIFOL(fd,72) = 0; - WFIFOL(fd,76) = msg->zeny; - - if( item->nameid && (data = itemdb_exists(item->nameid)) != NULL ) - { - WFIFOL(fd,80) = item->amount; - WFIFOW(fd,84) = (data->view_id)?data->view_id:item->nameid; - WFIFOW(fd,86) = data->type; - WFIFOB(fd,88) = item->identify; - WFIFOB(fd,89) = item->attribute; - WFIFOB(fd,90) = item->refine; - WFIFOW(fd,91) = item->card[0]; - WFIFOW(fd,93) = item->card[1]; - WFIFOW(fd,95) = item->card[2]; - WFIFOW(fd,97) = item->card[3]; - } - else // no item, set all to zero - memset(WFIFOP(fd,80), 0x00, 19); - - WFIFOB(fd,99) = (unsigned char)msg_len; - safestrncpy((char*)WFIFOP(fd,100), msg->body, msg_len + 1); - WFIFOSET(fd,len); - - if (msg->status == MAIL_UNREAD) { - msg->status = MAIL_READ; - intif_Mail_read(mail_id); - clif_parse_Mail_refreshinbox(fd, sd); - } - } -} - - -/// Request to open a mail (CZ_MAIL_OPEN). -/// 0241 <mail id>.L -void clif_parse_Mail_read(int fd, struct map_session_data *sd) -{ - int mail_id = RFIFOL(fd,2); - - if( mail_id <= 0 ) - return; - if( mail_invalid_operation(sd) ) - return; - - clif_Mail_read(sd, RFIFOL(fd,2)); -} - - -/// Request to receive mail's attachment (CZ_MAIL_GET_ITEM). -/// 0244 <mail id>.L -void clif_parse_Mail_getattach(int fd, struct map_session_data *sd) -{ - int mail_id = RFIFOL(fd,2); - int i; - bool fail = false; - - if( !chrif_isconnected() ) - return; - if( mail_id <= 0 ) - return; - if( mail_invalid_operation(sd) ) - return; - - ARR_FIND(0, MAIL_MAX_INBOX, i, sd->mail.inbox.msg[i].id == mail_id); - if( i == MAIL_MAX_INBOX ) - return; - - if( sd->mail.inbox.msg[i].zeny < 1 && (sd->mail.inbox.msg[i].item.nameid < 1 || sd->mail.inbox.msg[i].item.amount < 1) ) - return; - - if( sd->mail.inbox.msg[i].zeny + sd->status.zeny > MAX_ZENY ) - { - clif_Mail_getattachment(fd, 1); - return; - } - - if( sd->mail.inbox.msg[i].item.nameid > 0 ) - { - struct item_data *data; - unsigned int weight; - - if ((data = itemdb_exists(sd->mail.inbox.msg[i].item.nameid)) == NULL) - return; - - switch( pc_checkadditem(sd, data->nameid, sd->mail.inbox.msg[i].item.amount) ) - { - case ADDITEM_NEW: - fail = ( pc_inventoryblank(sd) == 0 ); - break; - case ADDITEM_OVERAMOUNT: - fail = true; - } - - if( fail ) - { - clif_Mail_getattachment(fd, 1); - return; - } - - weight = data->weight * sd->mail.inbox.msg[i].item.amount; - if( sd->weight + weight > sd->max_weight ) - { - clif_Mail_getattachment(fd, 2); - return; - } - } - - sd->mail.inbox.msg[i].zeny = 0; - memset(&sd->mail.inbox.msg[i].item, 0, sizeof(struct item)); - clif_Mail_read(sd, mail_id); - - intif_Mail_getattach(sd->status.char_id, mail_id); -} - - -/// Request to delete a mail (CZ_MAIL_DELETE). -/// 0243 <mail id>.L -void clif_parse_Mail_delete(int fd, struct map_session_data *sd) -{ - int mail_id = RFIFOL(fd,2); - int i; - - if( !chrif_isconnected() ) - return; - if( mail_id <= 0 ) - return; - if( mail_invalid_operation(sd) ) - return; - - ARR_FIND(0, MAIL_MAX_INBOX, i, sd->mail.inbox.msg[i].id == mail_id); - if (i < MAIL_MAX_INBOX) - { - struct mail_message *msg = &sd->mail.inbox.msg[i]; - - if( (msg->item.nameid > 0 && msg->item.amount > 0) || msg->zeny > 0 ) - {// can't delete mail without removing attachment first - clif_Mail_delete(sd->fd, mail_id, 1); - return; - } - - intif_Mail_delete(sd->status.char_id, mail_id); - } -} - - -/// Request to return a mail (CZ_REQ_MAIL_RETURN). -/// 0273 <mail id>.L <receive name>.24B -void clif_parse_Mail_return(int fd, struct map_session_data *sd) -{ - int mail_id = RFIFOL(fd,2); - int i; - - if( mail_id <= 0 ) - return; - if( mail_invalid_operation(sd) ) - return; - - ARR_FIND(0, MAIL_MAX_INBOX, i, sd->mail.inbox.msg[i].id == mail_id); - if( i < MAIL_MAX_INBOX && sd->mail.inbox.msg[i].send_id != 0 ) - intif_Mail_return(sd->status.char_id, mail_id); - else - clif_Mail_return(sd->fd, mail_id, 1); -} - - -/// Request to add an item or Zeny to mail (CZ_MAIL_ADD_ITEM). -/// 0247 <index>.W <amount>.L -void clif_parse_Mail_setattach(int fd, struct map_session_data *sd) -{ - int idx = RFIFOW(fd,2); - int amount = RFIFOL(fd,4); - unsigned char flag; - - if( !chrif_isconnected() ) - return; - if (idx < 0 || amount < 0) - return; - - flag = mail_setitem(sd, idx, amount); - clif_Mail_setattachment(fd,idx,flag); -} - - -/// Request to reset mail item and/or Zeny (CZ_MAIL_RESET_ITEM). -/// 0246 <type>.W -/// type: -/// 0 = reset all -/// 1 = remove item -/// 2 = remove zeny -void clif_parse_Mail_winopen(int fd, struct map_session_data *sd) -{ - int flag = RFIFOW(fd,2); - - if (flag == 0 || flag == 1) - mail_removeitem(sd, 0); - if (flag == 0 || flag == 2) - mail_removezeny(sd, 0); -} - - -/// Request to send mail (CZ_MAIL_SEND). -/// 0248 <packet len>.W <recipient>.24B <title>.40B <body len>.B <body>.?B -void clif_parse_Mail_send(int fd, struct map_session_data *sd) -{ - struct mail_message msg; - int body_len; - - if( !chrif_isconnected() ) - return; - if( sd->state.trading ) - return; - - if( RFIFOW(fd,2) < 69 ) { - ShowWarning("Invalid Msg Len from account %d.\n", sd->status.account_id); - return; - } - - if( DIFF_TICK(sd->cansendmail_tick, gettick()) > 0 ) - { - clif_displaymessage(sd->fd,msg_txt(675)); //"Cannot send mails too fast!!." - clif_Mail_send(fd, true); // fail - return; - } - - body_len = RFIFOB(fd,68); - - if (body_len > MAIL_BODY_LENGTH) - body_len = MAIL_BODY_LENGTH; - - if( !mail_setattachment(sd, &msg) ) - { // Invalid Append condition - clif_Mail_send(sd->fd, true); // fail - mail_removeitem(sd,0); - mail_removezeny(sd,0); - return; - } - - msg.id = 0; // id will be assigned by charserver - msg.send_id = sd->status.char_id; - msg.dest_id = 0; // will attempt to resolve name - safestrncpy(msg.send_name, sd->status.name, NAME_LENGTH); - safestrncpy(msg.dest_name, (char*)RFIFOP(fd,4), NAME_LENGTH); - safestrncpy(msg.title, (char*)RFIFOP(fd,28), MAIL_TITLE_LENGTH); - - if (msg.title[0] == '\0') { - return; // Message has no length and somehow client verification was skipped. - } - - if (body_len) - safestrncpy(msg.body, (char*)RFIFOP(fd,69), body_len + 1); - else - memset(msg.body, 0x00, MAIL_BODY_LENGTH); - - msg.timestamp = time(NULL); - if( !intif_Mail_send(sd->status.account_id, &msg) ) - mail_deliveryfail(sd, &msg); - - sd->cansendmail_tick = gettick() + 1000; // 1 Second flood Protection -} - - -/// AUCTION SYSTEM -/// By Zephyrus -/// - -/// Opens/closes the auction window (ZC_AUCTION_WINDOWS). -/// 025f <type>.L -/// type: -/// 0 = open -/// 1 = close -void clif_Auction_openwindow(struct map_session_data *sd) -{ - int fd = sd->fd; - - if( sd->state.storage_flag || sd->state.vending || sd->state.buyingstore || sd->state.trading ) - return; - - WFIFOHEAD(fd,packet_len(0x25f)); - WFIFOW(fd,0) = 0x25f; - WFIFOL(fd,2) = 0; - WFIFOSET(fd,packet_len(0x25f)); -} - - -/// Returns auction item search results (ZC_AUCTION_ITEM_REQ_SEARCH). -/// 0252 <packet len>.W <pages>.L <count>.L { <auction id>.L <seller name>.24B <name id>.W <type>.L <amount>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W <now price>.L <max price>.L <buyer name>.24B <delete time>.L }* -void clif_Auction_results(struct map_session_data *sd, short count, short pages, uint8 *buf) -{ - int i, fd = sd->fd, len = sizeof(struct auction_data); - struct auction_data auction; - struct item_data *item; - int k; - - WFIFOHEAD(fd,12 + (count * 83)); - WFIFOW(fd,0) = 0x252; - WFIFOW(fd,2) = 12 + (count * 83); - WFIFOL(fd,4) = pages; - WFIFOL(fd,8) = count; - - for( i = 0; i < count; i++ ) - { - memcpy(&auction, RBUFP(buf,i * len), len); - k = 12 + (i * 83); - - WFIFOL(fd,k) = auction.auction_id; - safestrncpy((char*)WFIFOP(fd,4+k), auction.seller_name, NAME_LENGTH); - - if( (item = itemdb_exists(auction.item.nameid)) != NULL && item->view_id > 0 ) - WFIFOW(fd,28+k) = item->view_id; - else - WFIFOW(fd,28+k) = auction.item.nameid; - - WFIFOL(fd,30+k) = auction.type; - WFIFOW(fd,34+k) = auction.item.amount; // Always 1 - WFIFOB(fd,36+k) = auction.item.identify; - WFIFOB(fd,37+k) = auction.item.attribute; - WFIFOB(fd,38+k) = auction.item.refine; - WFIFOW(fd,39+k) = auction.item.card[0]; - WFIFOW(fd,41+k) = auction.item.card[1]; - WFIFOW(fd,43+k) = auction.item.card[2]; - WFIFOW(fd,45+k) = auction.item.card[3]; - WFIFOL(fd,47+k) = auction.price; - WFIFOL(fd,51+k) = auction.buynow; - safestrncpy((char*)WFIFOP(fd,55+k), auction.buyer_name, NAME_LENGTH); - WFIFOL(fd,79+k) = (uint32)auction.timestamp; - } - WFIFOSET(fd,WFIFOW(fd,2)); -} - - -/// Result from request to add an item (ZC_ACK_AUCTION_ADD_ITEM). -/// 0256 <index>.W <result>.B -/// result: -/// 0 = success -/// 1 = failure -static void clif_Auction_setitem(int fd, int index, bool fail) -{ - WFIFOHEAD(fd,packet_len(0x256)); - WFIFOW(fd,0) = 0x256; - WFIFOW(fd,2) = index; - WFIFOB(fd,4) = fail; - WFIFOSET(fd,packet_len(0x256)); -} - - -/// Request to initialize 'new auction' data (CZ_AUCTION_CREATE). -/// 024b <type>.W -/// type: -/// 0 = create (any other action in auction window) -/// 1 = cancel (cancel pressed on register tab) -/// ? = junk, uninitialized value (ex. when switching between list filters) -void clif_parse_Auction_cancelreg(int fd, struct map_session_data *sd) -{ - if( sd->auction.amount > 0 ) - clif_additem(sd, sd->auction.index, sd->auction.amount, 0); - - sd->auction.amount = 0; -} - - -/// Request to add an item to the action (CZ_AUCTION_ADD_ITEM). -/// 024c <index>.W <count>.L -void clif_parse_Auction_setitem(int fd, struct map_session_data *sd) -{ - int idx = RFIFOW(fd,2) - 2; - int amount = RFIFOL(fd,4); // Always 1 - struct item_data *item; - - if( sd->auction.amount > 0 ) - sd->auction.amount = 0; - - if( idx < 0 || idx >= MAX_INVENTORY ) - { - ShowWarning("Character %s trying to set invalid item index in auctions.\n", sd->status.name); - return; - } - - if( amount != 1 || amount > sd->status.inventory[idx].amount ) - { // By client, amount is always set to 1. Maybe this is a future implementation. - ShowWarning("Character %s trying to set invalid amount in auctions.\n", sd->status.name); - return; - } - - if( (item = itemdb_exists(sd->status.inventory[idx].nameid)) != NULL && !(item->type == IT_ARMOR || item->type == IT_PETARMOR || item->type == IT_WEAPON || item->type == IT_CARD || item->type == IT_ETC) ) - { // Consumable or pets are not allowed - clif_Auction_setitem(sd->fd, idx, true); - return; - } - - if( !pc_can_give_items(sd) || sd->status.inventory[idx].expire_time || - !sd->status.inventory[idx].identify || - !itemdb_canauction(&sd->status.inventory[idx],pc_get_group_level(sd)) ) { // Quest Item or something else - clif_Auction_setitem(sd->fd, idx, true); - return; - } - - sd->auction.index = idx; - sd->auction.amount = amount; - clif_Auction_setitem(fd, idx + 2, false); -} - -/// Result from an auction action (ZC_AUCTION_RESULT). -/// 0250 <result>.B -/// result: -/// 0 = You have failed to bid into the auction -/// 1 = You have successfully bid in the auction -/// 2 = The auction has been canceled -/// 3 = An auction with at least one bidder cannot be canceled -/// 4 = You cannot register more than 5 items in an auction at a time -/// 5 = You do not have enough Zeny to pay the Auction Fee -/// 6 = You have won the auction -/// 7 = You have failed to win the auction -/// 8 = You do not have enough Zeny -/// 9 = You cannot place more than 5 bids at a time -void clif_Auction_message(int fd, unsigned char flag) -{ - WFIFOHEAD(fd,packet_len(0x250)); - WFIFOW(fd,0) = 0x250; - WFIFOB(fd,2) = flag; - WFIFOSET(fd,packet_len(0x250)); -} - - -/// Result of the auction close request (ZC_AUCTION_ACK_MY_SELL_STOP). -/// 025e <result>.W -/// result: -/// 0 = You have ended the auction -/// 1 = You cannot end the auction -/// 2 = Auction ID is incorrect -void clif_Auction_close(int fd, unsigned char flag) -{ - WFIFOHEAD(fd,packet_len(0x25e)); - WFIFOW(fd,0) = 0x25d; // BUG: The client identifies this packet as 0x25d (CZ_AUCTION_REQ_MY_SELL_STOP) - WFIFOW(fd,2) = flag; - WFIFOSET(fd,packet_len(0x25e)); -} - - -/// Request to add an auction (CZ_AUCTION_ADD). -/// 024d <now money>.L <max money>.L <delete hour>.W -void clif_parse_Auction_register(int fd, struct map_session_data *sd) -{ - struct auction_data auction; - struct item_data *item; - - auction.price = RFIFOL(fd,2); - auction.buynow = RFIFOL(fd,6); - auction.hours = RFIFOW(fd,10); - - // Invalid Situations... - if( sd->auction.amount < 1 ) - { - ShowWarning("Character %s trying to register auction without item.\n", sd->status.name); - return; - } - - if( auction.price >= auction.buynow ) - { - ShowWarning("Character %s trying to alter auction prices.\n", sd->status.name); - return; - } - - if( auction.hours < 1 || auction.hours > 48 ) - { - ShowWarning("Character %s trying to enter an invalid time for auction.\n", sd->status.name); - return; - } - - // Auction checks... - if( sd->status.zeny < (auction.hours * battle_config.auction_feeperhour) ) - { - clif_Auction_message(fd, 5); // You do not have enough zeny to pay the Auction Fee. - return; - } - - if( auction.buynow > battle_config.auction_maximumprice ) - { // Zeny Limits - auction.buynow = battle_config.auction_maximumprice; - if( auction.price >= auction.buynow ) - auction.price = auction.buynow - 1; - } - - auction.auction_id = 0; - auction.seller_id = sd->status.char_id; - safestrncpy(auction.seller_name, sd->status.name, sizeof(auction.seller_name)); - auction.buyer_id = 0; - memset(auction.buyer_name, '\0', sizeof(auction.buyer_name)); - - if( sd->status.inventory[sd->auction.index].nameid == 0 || sd->status.inventory[sd->auction.index].amount < sd->auction.amount ) - { - clif_Auction_message(fd, 2); // The auction has been canceled - return; - } - - if( (item = itemdb_exists(sd->status.inventory[sd->auction.index].nameid)) == NULL ) - { // Just in case - clif_Auction_message(fd, 2); // The auction has been canceled - return; - } - - safestrncpy(auction.item_name, item->jname, sizeof(auction.item_name)); - auction.type = item->type; - memcpy(&auction.item, &sd->status.inventory[sd->auction.index], sizeof(struct item)); - auction.item.amount = 1; - auction.timestamp = 0; - - if( !intif_Auction_register(&auction) ) - clif_Auction_message(fd, 4); // No Char Server? lets say something to the client - else - { - int zeny = auction.hours*battle_config.auction_feeperhour; - - pc_delitem(sd, sd->auction.index, sd->auction.amount, 1, 6, LOG_TYPE_AUCTION); - sd->auction.amount = 0; - - pc_payzeny(sd, zeny, LOG_TYPE_AUCTION, NULL); - } -} - - -/// Cancels an auction (CZ_AUCTION_ADD_CANCEL). -/// 024e <auction id>.L -void clif_parse_Auction_cancel(int fd, struct map_session_data *sd) -{ - unsigned int auction_id = RFIFOL(fd,2); - - intif_Auction_cancel(sd->status.char_id, auction_id); -} - - -/// Closes an auction (CZ_AUCTION_REQ_MY_SELL_STOP). -/// 025d <auction id>.L -void clif_parse_Auction_close(int fd, struct map_session_data *sd) -{ - unsigned int auction_id = RFIFOL(fd,2); - - intif_Auction_close(sd->status.char_id, auction_id); -} - - -/// Places a bid on an auction (CZ_AUCTION_BUY). -/// 024f <auction id>.L <money>.L -void clif_parse_Auction_bid(int fd, struct map_session_data *sd) -{ - unsigned int auction_id = RFIFOL(fd,2); - int bid = RFIFOL(fd,6); - - if( !pc_can_give_items(sd) ) { //They aren't supposed to give zeny [Inkfish] - clif_displaymessage(sd->fd, msg_txt(246)); - return; - } - - if( bid <= 0 ) - clif_Auction_message(fd, 0); // You have failed to bid into the auction - else if( bid > sd->status.zeny ) - clif_Auction_message(fd, 8); // You do not have enough zeny - else if ( CheckForCharServer() ) // char server is down (bugreport:1138) - clif_Auction_message(fd, 0); // You have failed to bid into the auction - else { - pc_payzeny(sd, bid, LOG_TYPE_AUCTION, NULL); - intif_Auction_bid(sd->status.char_id, sd->status.name, auction_id, bid); - } -} - - -/// Auction Search (CZ_AUCTION_ITEM_SEARCH). -/// 0251 <search type>.W <auction id>.L <search text>.24B <page number>.W -/// search type: -/// 0 = armor -/// 1 = weapon -/// 2 = card -/// 3 = misc -/// 4 = name search -/// 5 = auction id search -void clif_parse_Auction_search(int fd, struct map_session_data* sd) -{ - char search_text[NAME_LENGTH]; - short type = RFIFOW(fd,2), page = RFIFOW(fd,32); - int price = RFIFOL(fd,4); // FIXME: bug #5071 - - clif_parse_Auction_cancelreg(fd, sd); - - safestrncpy(search_text, (char*)RFIFOP(fd,8), sizeof(search_text)); - intif_Auction_requestlist(sd->status.char_id, type, price, search_text, page); -} - - -/// Requests list of own currently active bids or auctions (CZ_AUCTION_REQ_MY_INFO). -/// 025c <type>.W -/// type: -/// 0 = sell (own auctions) -/// 1 = buy (own bids) -void clif_parse_Auction_buysell(int fd, struct map_session_data* sd) -{ - short type = RFIFOW(fd,2) + 6; - clif_parse_Auction_cancelreg(fd, sd); - - intif_Auction_requestlist(sd->status.char_id, type, 0, "", 1); -} - - -/// CASH/POINT SHOP -/// - -/// List of items offered in a cash shop (ZC_PC_CASH_POINT_ITEMLIST). -/// 0287 <packet len>.W <cash point>.L { <sell price>.L <discount price>.L <item type>.B <name id>.W }* -/// 0287 <packet len>.W <cash point>.L <kafra point>.L { <sell price>.L <discount price>.L <item type>.B <name id>.W }* (PACKETVER >= 20070711) -void clif_cashshop_show(struct map_session_data *sd, struct npc_data *nd) -{ - int fd,i; -#if PACKETVER < 20070711 - const int offset = 8; -#else - const int offset = 12; -#endif - - nullpo_retv(sd); - nullpo_retv(nd); - - fd = sd->fd; - sd->npc_shopid = nd->bl.id; - WFIFOHEAD(fd,offset+nd->u.shop.count*11); - WFIFOW(fd,0) = 0x287; - WFIFOW(fd,2) = offset+nd->u.shop.count*11; - WFIFOL(fd,4) = sd->cashPoints; // Cash Points -#if PACKETVER >= 20070711 - WFIFOL(fd,8) = sd->kafraPoints; // Kafra Points -#endif - - for( i = 0; i < nd->u.shop.count; i++ ) - { - struct item_data* id = itemdb_search(nd->u.shop.shop_item[i].nameid); - WFIFOL(fd,offset+0+i*11) = nd->u.shop.shop_item[i].value; - WFIFOL(fd,offset+4+i*11) = nd->u.shop.shop_item[i].value; // Discount Price - WFIFOB(fd,offset+8+i*11) = itemtype(id->type); - WFIFOW(fd,offset+9+i*11) = ( id->view_id > 0 ) ? id->view_id : id->nameid; - } - WFIFOSET(fd,WFIFOW(fd,2)); -} - - -/// Cashshop Buy Ack (ZC_PC_CASH_POINT_UPDATE). -/// 0289 <cash point>.L <error>.W -/// 0289 <cash point>.L <kafra point>.L <error>.W (PACKETVER >= 20070711) -/// error: -/// 0 = The deal has successfully completed. (ERROR_TYPE_NONE) -/// 1 = The Purchase has failed because the NPC does not exist. (ERROR_TYPE_NPC) -/// 2 = The Purchase has failed because the Kafra Shop System is not working correctly. (ERROR_TYPE_SYSTEM) -/// 3 = You are over your Weight Limit. (ERROR_TYPE_INVENTORY_WEIGHT) -/// 4 = You cannot purchase items while you are in a trade. (ERROR_TYPE_EXCHANGE) -/// 5 = The Purchase has failed because the Item Information was incorrect. (ERROR_TYPE_ITEM_ID) -/// 6 = You do not have enough Kafra Credit Points. (ERROR_TYPE_MONEY) -/// 7 = You can purchase up to 10 items. -/// 8 = Some items could not be purchased. -void clif_cashshop_ack(struct map_session_data* sd, int error) -{ - int fd = sd->fd; - - WFIFOHEAD(fd, packet_len(0x289)); - WFIFOW(fd,0) = 0x289; - WFIFOL(fd,2) = sd->cashPoints; -#if PACKETVER < 20070711 - WFIFOW(fd,6) = TOW(error); -#else - WFIFOL(fd,6) = sd->kafraPoints; - WFIFOW(fd,10) = TOW(error); -#endif - WFIFOSET(fd, packet_len(0x289)); -} - - -/// Request to buy item(s) from cash shop (CZ_PC_BUY_CASH_POINT_ITEM). -/// 0288 <name id>.W <amount>.W -/// 0288 <name id>.W <amount>.W <kafra points>.L (PACKETVER >= 20070711) -/// 0288 <packet len>.W <kafra points>.L <count>.W { <amount>.W <name id>.W }.4B*count (PACKETVER >= 20100803) -void clif_parse_cashshop_buy(int fd, struct map_session_data *sd) -{ - int fail = 0; - nullpo_retv(sd); - - if( sd->state.trading || !sd->npc_shopid ) - fail = 1; - else - { -#if PACKETVER < 20101116 - short nameid = RFIFOW(fd,2); - short amount = RFIFOW(fd,4); - int points = RFIFOL(fd,6); - - fail = npc_cashshop_buy(sd, nameid, amount, points); -#else - int len = RFIFOW(fd,2); - int points = RFIFOL(fd,4); - int count = RFIFOW(fd,8); - unsigned short* item_list = (unsigned short*)RFIFOP(fd,10); - - if( len < 10 || len != 10 + count * 4) - { - ShowWarning("Player %u sent incorrect cash shop buy packet (len %u:%u)!\n", sd->status.char_id, len, 10 + count * 4); - return; - } - fail = npc_cashshop_buylist(sd,points,count,item_list); -#endif - } - - clif_cashshop_ack(sd,fail); -} - - -/// Adoption System -/// - -/// Adoption message (ZC_BABYMSG). -/// 0216 <msg>.L -/// msg: -/// 0 = "You cannot adopt more than 1 child." -/// 1 = "You must be at least character level 70 in order to adopt someone." -/// 2 = "You cannot adopt a married person." -void clif_Adopt_reply(struct map_session_data *sd, int type) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,6); - WFIFOW(fd,0) = 0x216; - WFIFOL(fd,2) = type; - WFIFOSET(fd,6); -} - - -/// Adoption confirmation (ZC_REQ_BABY). -/// 01f6 <account id>.L <char id>.L <name>.B -void clif_Adopt_request(struct map_session_data *sd, struct map_session_data *src, int p_id) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,34); - WFIFOW(fd,0) = 0x1f6; - WFIFOL(fd,2) = src->status.account_id; - WFIFOL(fd,6) = p_id; - memcpy(WFIFOP(fd,10), src->status.name, NAME_LENGTH); - WFIFOSET(fd,34); -} - - -/// Request to adopt a player (CZ_REQ_JOIN_BABY). -/// 01f9 <account id>.L -void clif_parse_Adopt_request(int fd, struct map_session_data *sd) -{ - struct map_session_data *tsd = map_id2sd(RFIFOL(fd,2)), *p_sd = map_charid2sd(sd->status.partner_id); - - if( pc_can_Adopt(sd, p_sd, tsd) ) - { - tsd->adopt_invite = sd->status.account_id; - clif_Adopt_request(tsd, sd, p_sd->status.account_id); - } -} - - -/// Answer to adopt confirmation (CZ_JOIN_BABY). -/// 01f7 <account id>.L <char id>.L <answer>.L -/// answer: -/// 0 = rejected -/// 1 = accepted -void clif_parse_Adopt_reply(int fd, struct map_session_data *sd) -{ - int p1_id = RFIFOL(fd,2); - int p2_id = RFIFOL(fd,6); - int result = RFIFOL(fd,10); - struct map_session_data* p1_sd = map_id2sd(p1_id); - struct map_session_data* p2_sd = map_id2sd(p2_id); - - int pid = sd->adopt_invite; - sd->adopt_invite = 0; - - if( p1_sd == NULL || p2_sd == NULL ) - return; // Both players need to be online - - if( pid != p1_sd->status.account_id ) - return; // Incorrect values - - if( result == 0 ) - return; // Rejected - - pc_adoption(p1_sd, p2_sd, sd); -} - - -/// Convex Mirror (ZC_BOSS_INFO). -/// 0293 <infoType>.B <x>.L <y>.L <minHours>.W <minMinutes>.W <maxHours>.W <maxMinutes>.W <monster name>.51B -/// infoType: -/// 0 = No boss on this map (BOSS_INFO_NOT). -/// 1 = Boss is alive (position update) (BOSS_INFO_ALIVE). -/// 2 = Boss is alive (initial announce) (BOSS_INFO_ALIVE_WITHMSG). -/// 3 = Boss is dead (BOSS_INFO_DEAD). -void clif_bossmapinfo(int fd, struct mob_data *md, short flag) -{ - WFIFOHEAD(fd,70); - memset(WFIFOP(fd,0),0,70); - WFIFOW(fd,0) = 0x293; - - if( md != NULL ) - { - if( md->bl.prev != NULL ) - { // Boss on This Map - if( flag ) - { - WFIFOB(fd,2) = 1; - WFIFOL(fd,3) = md->bl.x; - WFIFOL(fd,7) = md->bl.y; - } - else - WFIFOB(fd,2) = 2; // First Time - } - else if (md->spawn_timer != INVALID_TIMER) - { // Boss is Dead - const struct TimerData * timer_data = get_timer(md->spawn_timer); - unsigned int seconds; - int hours, minutes; - - seconds = DIFF_TICK(timer_data->tick, gettick()) / 1000 + 60; - hours = seconds / (60 * 60); - seconds = seconds - (60 * 60 * hours); - minutes = seconds / 60; - - WFIFOB(fd,2) = 3; - WFIFOW(fd,11) = hours; // Hours - WFIFOW(fd,13) = minutes; // Minutes - } - safestrncpy((char*)WFIFOP(fd,19), md->db->jname, NAME_LENGTH); - } - - WFIFOSET(fd,70); -} - - -/// Requesting equip of a player (CZ_EQUIPWIN_MICROSCOPE). -/// 02d6 <account id>.L -void clif_parse_ViewPlayerEquip(int fd, struct map_session_data* sd) -{ - int charid = RFIFOL(fd, 2); - struct map_session_data* tsd = map_id2sd(charid); - - if (!tsd) - return; - - if( tsd->status.show_equip || pc_has_permission(sd, PC_PERM_VIEW_EQUIPMENT) ) - clif_viewequip_ack(sd, tsd); - else - clif_viewequip_fail(sd); -} - - -/// Request to change equip window tick (CZ_CONFIG). -/// 02d8 <type>.L <value>.L -/// type: -/// 0 = open equip window -/// value: -/// 0 = disabled -/// 1 = enabled -void clif_parse_EquipTick(int fd, struct map_session_data* sd) -{ - bool flag = (bool)RFIFOL(fd,6); - sd->status.show_equip = flag; - clif_equiptickack(sd, flag); -} - - -/// Questlog System [Kevin] [Inkfish] -/// - -/// Sends list of all quest states (ZC_ALL_QUEST_LIST). -/// 02b1 <packet len>.W <num>.L { <quest id>.L <active>.B }*num -void clif_quest_send_list(struct map_session_data * sd) -{ - int fd = sd->fd; - int i; - int len = sd->avail_quests*5+8; - - WFIFOHEAD(fd,len); - WFIFOW(fd, 0) = 0x2b1; - WFIFOW(fd, 2) = len; - WFIFOL(fd, 4) = sd->avail_quests; - - for( i = 0; i < sd->avail_quests; i++ ) - { - WFIFOL(fd, i*5+8) = sd->quest_log[i].quest_id; - WFIFOB(fd, i*5+12) = sd->quest_log[i].state; - } - - WFIFOSET(fd, len); -} - - -/// Sends list of all quest missions (ZC_ALL_QUEST_MISSION). -/// 02b2 <packet len>.W <num>.L { <quest id>.L <start time>.L <expire time>.L <mobs>.W { <mob id>.L <mob count>.W <mob name>.24B }*3 }*num -void clif_quest_send_mission(struct map_session_data * sd) -{ - int fd = sd->fd; - int i, j; - int len = sd->avail_quests*104+8; - struct mob_db *mob; - - WFIFOHEAD(fd, len); - WFIFOW(fd, 0) = 0x2b2; - WFIFOW(fd, 2) = len; - WFIFOL(fd, 4) = sd->avail_quests; - - for( i = 0; i < sd->avail_quests; i++ ) - { - WFIFOL(fd, i*104+8) = sd->quest_log[i].quest_id; - WFIFOL(fd, i*104+12) = sd->quest_log[i].time - quest_db[sd->quest_index[i]].time; - WFIFOL(fd, i*104+16) = sd->quest_log[i].time; - WFIFOW(fd, i*104+20) = quest_db[sd->quest_index[i]].num_objectives; - - for( j = 0 ; j < quest_db[sd->quest_index[i]].num_objectives; j++ ) - { - WFIFOL(fd, i*104+22+j*30) = quest_db[sd->quest_index[i]].mob[j]; - WFIFOW(fd, i*104+26+j*30) = sd->quest_log[i].count[j]; - mob = mob_db(quest_db[sd->quest_index[i]].mob[j]); - memcpy(WFIFOP(fd, i*104+28+j*30), mob?mob->jname:"NULL", NAME_LENGTH); - } - } - - WFIFOSET(fd, len); -} - - -/// Notification about a new quest (ZC_ADD_QUEST). -/// 02b3 <quest id>.L <active>.B <start time>.L <expire time>.L <mobs>.W { <mob id>.L <mob count>.W <mob name>.24B }*3 -void clif_quest_add(struct map_session_data * sd, struct quest * qd, int index) -{ - int fd = sd->fd; - int i; - struct mob_db *mob; - - WFIFOHEAD(fd, packet_len(0x2b3)); - WFIFOW(fd, 0) = 0x2b3; - WFIFOL(fd, 2) = qd->quest_id; - WFIFOB(fd, 6) = qd->state; - WFIFOB(fd, 7) = qd->time - quest_db[index].time; - WFIFOL(fd, 11) = qd->time; - WFIFOW(fd, 15) = quest_db[index].num_objectives; - - for( i = 0; i < quest_db[index].num_objectives; i++ ) - { - WFIFOL(fd, i*30+17) = quest_db[index].mob[i]; - WFIFOW(fd, i*30+21) = qd->count[i]; - mob = mob_db(quest_db[index].mob[i]); - memcpy(WFIFOP(fd, i*30+23), mob?mob->jname:"NULL", NAME_LENGTH); - } - - WFIFOSET(fd, packet_len(0x2b3)); -} - - -/// Notification about a quest being removed (ZC_DEL_QUEST). -/// 02b4 <quest id>.L -void clif_quest_delete(struct map_session_data * sd, int quest_id) -{ - int fd = sd->fd; - - WFIFOHEAD(fd, packet_len(0x2b4)); - WFIFOW(fd, 0) = 0x2b4; - WFIFOL(fd, 2) = quest_id; - WFIFOSET(fd, packet_len(0x2b4)); -} - - -/// Notification of an update to the hunting mission counter (ZC_UPDATE_MISSION_HUNT). -/// 02b5 <packet len>.W <mobs>.W { <quest id>.L <mob id>.L <total count>.W <current count>.W }*3 -void clif_quest_update_objective(struct map_session_data * sd, struct quest * qd, int index) -{ - int fd = sd->fd; - int i; - int len = quest_db[index].num_objectives*12+6; - - WFIFOHEAD(fd, len); - WFIFOW(fd, 0) = 0x2b5; - WFIFOW(fd, 2) = len; - WFIFOW(fd, 4) = quest_db[index].num_objectives; - - for( i = 0; i < quest_db[index].num_objectives; i++ ) - { - WFIFOL(fd, i*12+6) = qd->quest_id; - WFIFOL(fd, i*12+10) = quest_db[index].mob[i]; - WFIFOW(fd, i*12+14) = quest_db[index].count[i]; - WFIFOW(fd, i*12+16) = qd->count[i]; - } - - WFIFOSET(fd, len); -} - - -/// Request to change the state of a quest (CZ_ACTIVE_QUEST). -/// 02b6 <quest id>.L <active>.B -void clif_parse_questStateAck(int fd, struct map_session_data * sd) -{ - quest_update_status(sd, RFIFOL(fd,2), RFIFOB(fd,6)?Q_ACTIVE:Q_INACTIVE); -} - - -/// Notification about the change of a quest state (ZC_ACTIVE_QUEST). -/// 02b7 <quest id>.L <active>.B -void clif_quest_update_status(struct map_session_data * sd, int quest_id, bool active) -{ - int fd = sd->fd; - - WFIFOHEAD(fd, packet_len(0x2b7)); - WFIFOW(fd, 0) = 0x2b7; - WFIFOL(fd, 2) = quest_id; - WFIFOB(fd, 6) = active; - WFIFOSET(fd, packet_len(0x2b7)); -} - - -/// Notification about an NPC's quest state (ZC_QUEST_NOTIFY_EFFECT). -/// 0446 <npc id>.L <x>.W <y>.W <effect>.W <type>.W -/// effect: -/// 0 = none -/// 1 = exclamation mark icon -/// 2 = question mark icon -/// type: -/// 0 = yellow -/// 1 = orange -/// 2 = green -/// 3 = purple -void clif_quest_show_event(struct map_session_data *sd, struct block_list *bl, short state, short color) -{ -#if PACKETVER >= 20090218 - int fd = sd->fd; - - WFIFOHEAD(fd, packet_len(0x446)); - WFIFOW(fd, 0) = 0x446; - WFIFOL(fd, 2) = bl->id; - WFIFOW(fd, 6) = bl->x; - WFIFOW(fd, 8) = bl->y; - WFIFOW(fd, 10) = state; - WFIFOW(fd, 12) = color; - WFIFOSET(fd, packet_len(0x446)); -#endif -} - - -/// Mercenary System -/// - -/// Notification about a mercenary status parameter change (ZC_MER_PAR_CHANGE). -/// 02a2 <var id>.W <value>.L -void clif_mercenary_updatestatus(struct map_session_data *sd, int type) -{ - struct mercenary_data *md; - struct status_data *status; - int fd; - if( sd == NULL || (md = sd->md) == NULL ) - return; - - fd = sd->fd; - status = &md->battle_status; - WFIFOHEAD(fd,packet_len(0x2a2)); - WFIFOW(fd,0) = 0x2a2; - WFIFOW(fd,2) = type; - switch( type ) - { - case SP_ATK1: - { - int atk = rnd()%(status->rhw.atk2 - status->rhw.atk + 1) + status->rhw.atk; - WFIFOL(fd,4) = cap_value(atk, 0, INT16_MAX); - } - break; - case SP_MATK1: - WFIFOL(fd,4) = cap_value(status->matk_max, 0, INT16_MAX); - break; - case SP_HIT: - WFIFOL(fd,4) = status->hit; - break; - case SP_CRITICAL: - WFIFOL(fd,4) = status->cri/10; - break; - case SP_DEF1: - WFIFOL(fd,4) = status->def; - break; - case SP_MDEF1: - WFIFOL(fd,4) = status->mdef; - break; - case SP_MERCFLEE: - WFIFOL(fd,4) = status->flee; - break; - case SP_ASPD: - WFIFOL(fd,4) = status->amotion; - break; - case SP_HP: - WFIFOL(fd,4) = status->hp; - break; - case SP_MAXHP: - WFIFOL(fd,4) = status->max_hp; - break; - case SP_SP: - WFIFOL(fd,4) = status->sp; - break; - case SP_MAXSP: - WFIFOL(fd,4) = status->max_sp; - break; - case SP_MERCKILLS: - WFIFOL(fd,4) = md->mercenary.kill_count; - break; - case SP_MERCFAITH: - WFIFOL(fd,4) = mercenary_get_faith(md); - break; - } - WFIFOSET(fd,packet_len(0x2a2)); -} - - -/// Mercenary base status data (ZC_MER_INIT). -/// 029b <id>.L <atk>.W <matk>.W <hit>.W <crit>.W <def>.W <mdef>.W <flee>.W <aspd>.W -/// <name>.24B <level>.W <hp>.L <maxhp>.L <sp>.L <maxsp>.L <expire time>.L <faith>.W -/// <calls>.L <kills>.L <atk range>.W -void clif_mercenary_info(struct map_session_data *sd) -{ - int fd; - struct mercenary_data *md; - struct status_data *status; - int atk; - - if( sd == NULL || (md = sd->md) == NULL ) - return; - - fd = sd->fd; - status = &md->battle_status; - - WFIFOHEAD(fd,packet_len(0x29b)); - WFIFOW(fd,0) = 0x29b; - WFIFOL(fd,2) = md->bl.id; - - // Mercenary shows ATK as a random value between ATK ~ ATK2 - atk = rnd()%(status->rhw.atk2 - status->rhw.atk + 1) + status->rhw.atk; - WFIFOW(fd,6) = cap_value(atk, 0, INT16_MAX); - WFIFOW(fd,8) = cap_value(status->matk_max, 0, INT16_MAX); - WFIFOW(fd,10) = status->hit; - WFIFOW(fd,12) = status->cri/10; - WFIFOW(fd,14) = status->def; - WFIFOW(fd,16) = status->mdef; - WFIFOW(fd,18) = status->flee; - WFIFOW(fd,20) = status->amotion; - safestrncpy((char*)WFIFOP(fd,22), md->db->name, NAME_LENGTH); - WFIFOW(fd,46) = md->db->lv; - WFIFOL(fd,48) = status->hp; - WFIFOL(fd,52) = status->max_hp; - WFIFOL(fd,56) = status->sp; - WFIFOL(fd,60) = status->max_sp; - WFIFOL(fd,64) = (int)time(NULL) + (mercenary_get_lifetime(md) / 1000); - WFIFOW(fd,68) = mercenary_get_faith(md); - WFIFOL(fd,70) = mercenary_get_calls(md); - WFIFOL(fd,74) = md->mercenary.kill_count; - WFIFOW(fd,78) = md->battle_status.rhw.range; - WFIFOSET(fd,packet_len(0x29b)); -} - - -/// Mercenary skill tree (ZC_MER_SKILLINFO_LIST). -/// 029d <packet len>.W { <skill id>.W <type>.L <level>.W <sp cost>.W <attack range>.W <skill name>.24B <upgradable>.B }* -void clif_mercenary_skillblock(struct map_session_data *sd) -{ - struct mercenary_data *md; - int fd, i, len = 4, id, j; - - if( sd == NULL || (md = sd->md) == NULL ) - return; - - fd = sd->fd; - WFIFOHEAD(fd,4+37*MAX_MERCSKILL); - WFIFOW(fd,0) = 0x29d; - for( i = 0; i < MAX_MERCSKILL; i++ ) - { - if( (id = md->db->skill[i].id) == 0 ) - continue; - j = id - MC_SKILLBASE; - WFIFOW(fd,len) = id; - WFIFOL(fd,len+2) = skill_get_inf(id); - WFIFOW(fd,len+6) = md->db->skill[j].lv; - WFIFOW(fd,len+8) = skill_get_sp(id, md->db->skill[j].lv); - WFIFOW(fd,len+10) = skill_get_range2(&md->bl, id, md->db->skill[j].lv); - safestrncpy((char*)WFIFOP(fd,len+12), skill_get_name(id), NAME_LENGTH); - WFIFOB(fd,len+36) = 0; // Skillable for Mercenary? - len += 37; - } - - WFIFOW(fd,2) = len; - WFIFOSET(fd,len); -} - - -/// Request to invoke a mercenary menu action (CZ_MER_COMMAND). -/// 029f <command>.B -/// 1 = mercenary information -/// 2 = delete -void clif_parse_mercenary_action(int fd, struct map_session_data* sd) -{ - int option = RFIFOB(fd,2); - if( sd->md == NULL ) - return; - - if( option == 2 ) merc_delete(sd->md, 2); -} - - -/// Mercenary Message -/// message: -/// 0 = Mercenary soldier's duty hour is over. -/// 1 = Your mercenary soldier has been killed. -/// 2 = Your mercenary soldier has been fired. -/// 3 = Your mercenary soldier has ran away. -void clif_mercenary_message(struct map_session_data* sd, int message) -{ - clif_msg(sd, 1266 + message); -} - - -/// Notification about the remaining time of a rental item (ZC_CASH_TIME_COUNTER). -/// 0298 <name id>.W <seconds>.L -void clif_rental_time(int fd, int nameid, int seconds) -{ // '<ItemName>' item will disappear in <seconds/60> minutes. - WFIFOHEAD(fd,packet_len(0x298)); - WFIFOW(fd,0) = 0x298; - WFIFOW(fd,2) = nameid; - WFIFOL(fd,4) = seconds; - WFIFOSET(fd,packet_len(0x298)); -} - - -/// Deletes a rental item from client's inventory (ZC_CASH_ITEM_DELETE). -/// 0299 <index>.W <name id>.W -void clif_rental_expired(int fd, int index, int nameid) -{ // '<ItemName>' item has been deleted from the Inventory - WFIFOHEAD(fd,packet_len(0x299)); - WFIFOW(fd,0) = 0x299; - WFIFOW(fd,2) = index+2; - WFIFOW(fd,4) = nameid; - WFIFOSET(fd,packet_len(0x299)); -} - - -/// Book Reading (ZC_READ_BOOK). -/// 0294 <book id>.L <page>.L -void clif_readbook(int fd, int book_id, int page) -{ - WFIFOHEAD(fd,packet_len(0x294)); - WFIFOW(fd,0) = 0x294; - WFIFOL(fd,2) = book_id; - WFIFOL(fd,6) = page; - WFIFOSET(fd,packet_len(0x294)); -} - - -/// Battlegrounds -/// - -/// Updates HP bar of a camp member (ZC_BATTLEFIELD_NOTIFY_HP). -/// 02e0 <account id>.L <name>.24B <hp>.W <max hp>.W -void clif_bg_hp(struct map_session_data *sd) -{ - unsigned char buf[34]; - const int cmd = 0x2e0; - nullpo_retv(sd); - - WBUFW(buf,0) = cmd; - WBUFL(buf,2) = sd->status.account_id; - memcpy(WBUFP(buf,6), sd->status.name, NAME_LENGTH); - - if( sd->battle_status.max_hp > INT16_MAX ) - { // To correctly display the %hp bar. [Skotlex] - WBUFW(buf,30) = sd->battle_status.hp/(sd->battle_status.max_hp/100); - WBUFW(buf,32) = 100; - } - else - { - WBUFW(buf,30) = sd->battle_status.hp; - WBUFW(buf,32) = sd->battle_status.max_hp; - } - - clif_send(buf, packet_len(cmd), &sd->bl, BG_AREA_WOS); -} - - -/// Updates the position of a camp member on the minimap (ZC_BATTLEFIELD_NOTIFY_POSITION). -/// 02df <account id>.L <name>.24B <class>.W <x>.W <y>.W -void clif_bg_xy(struct map_session_data *sd) -{ - unsigned char buf[36]; - nullpo_retv(sd); - - WBUFW(buf,0)=0x2df; - WBUFL(buf,2)=sd->status.account_id; - memcpy(WBUFP(buf,6), sd->status.name, NAME_LENGTH); - WBUFW(buf,30)=sd->status.class_; - WBUFW(buf,32)=sd->bl.x; - WBUFW(buf,34)=sd->bl.y; - - clif_send(buf, packet_len(0x2df), &sd->bl, BG_SAMEMAP_WOS); -} - -void clif_bg_xy_remove(struct map_session_data *sd) -{ - unsigned char buf[36]; - nullpo_retv(sd); - - WBUFW(buf,0)=0x2df; - WBUFL(buf,2)=sd->status.account_id; - memset(WBUFP(buf,6), 0, NAME_LENGTH); - WBUFW(buf,30)=0; - WBUFW(buf,32)=-1; - WBUFW(buf,34)=-1; - - clif_send(buf, packet_len(0x2df), &sd->bl, BG_SAMEMAP_WOS); -} - - -/// Notifies clients of a battleground message (ZC_BATTLEFIELD_CHAT). -/// 02dc <packet len>.W <account id>.L <name>.24B <message>.?B -void clif_bg_message(struct battleground_data *bg, int src_id, const char *name, const char *mes, int len) -{ - struct map_session_data *sd; - unsigned char *buf; - if( (sd = bg_getavailablesd(bg)) == NULL ) - return; - - buf = (unsigned char*)aMalloc((len + NAME_LENGTH + 8)*sizeof(unsigned char)); - - WBUFW(buf,0) = 0x2dc; - WBUFW(buf,2) = len + NAME_LENGTH + 8; - WBUFL(buf,4) = src_id; - memcpy(WBUFP(buf,8), name, NAME_LENGTH); - memcpy(WBUFP(buf,32), mes, len); - clif_send(buf,WBUFW(buf,2), &sd->bl, BG); - - if( buf ) - aFree(buf); -} - - -/// Validates and processes battlechat messages [pakpil] (CZ_BATTLEFIELD_CHAT). -/// 0x2db <packet len>.W <text>.?B (<name> : <message>) 00 -void clif_parse_BattleChat(int fd, struct map_session_data* sd) -{ - const char* text = (char*)RFIFOP(fd,4); - int textlen = RFIFOW(fd,2) - 4; - - char *name, *message; - int namelen, messagelen; - - if( !clif_process_message(sd, 0, &name, &namelen, &message, &messagelen) ) - return; - - if( is_atcommand(fd, sd, message, 1) ) - return; - - if( sd->sc.data[SC_BERSERK] || sd->sc.data[SC__BLOODYLUST] || (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOCHAT) ) - return; - - if( battle_config.min_chat_delay ) - { - if( DIFF_TICK(sd->cantalk_tick, gettick()) > 0 ) - return; - sd->cantalk_tick = gettick() + battle_config.min_chat_delay; - } - - bg_send_message(sd, text, textlen); -} - - -/// Notifies client of a battleground score change (ZC_BATTLEFIELD_NOTIFY_POINT). -/// 02de <camp A points>.W <camp B points>.W -void clif_bg_updatescore(int16 m) -{ - struct block_list bl; - unsigned char buf[6]; - - bl.id = 0; - bl.type = BL_NUL; - bl.m = m; - - WBUFW(buf,0) = 0x2de; - WBUFW(buf,2) = map[m].bgscore_lion; - WBUFW(buf,4) = map[m].bgscore_eagle; - clif_send(buf,packet_len(0x2de),&bl,ALL_SAMEMAP); -} - -void clif_bg_updatescore_single(struct map_session_data *sd) -{ - int fd; - nullpo_retv(sd); - fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x2de)); - WFIFOW(fd,0) = 0x2de; - WFIFOW(fd,2) = map[sd->bl.m].bgscore_lion; - WFIFOW(fd,4) = map[sd->bl.m].bgscore_eagle; - WFIFOSET(fd,packet_len(0x2de)); -} - - -/// Battleground camp belong-information (ZC_BATTLEFIELD_NOTIFY_CAMPINFO). -/// 02dd <account id>.L <name>.24B <camp>.W -void clif_sendbgemblem_area(struct map_session_data *sd) -{ - unsigned char buf[33]; - nullpo_retv(sd); - - WBUFW(buf, 0) = 0x2dd; - WBUFL(buf,2) = sd->bl.id; - safestrncpy((char*)WBUFP(buf,6), sd->status.name, NAME_LENGTH); // name don't show in screen. - WBUFW(buf,30) = sd->bg_id; - clif_send(buf,packet_len(0x2dd), &sd->bl, AREA); -} - -void clif_sendbgemblem_single(int fd, struct map_session_data *sd) -{ - nullpo_retv(sd); - WFIFOHEAD(fd,32); - WFIFOW(fd,0) = 0x2dd; - WFIFOL(fd,2) = sd->bl.id; - safestrncpy((char*)WFIFOP(fd,6), sd->status.name, NAME_LENGTH); - WFIFOW(fd,30) = sd->bg_id; - WFIFOSET(fd,packet_len(0x2dd)); -} - - -/// Custom Fonts (ZC_NOTIFY_FONT). -/// 02ef <account_id>.L <font id>.W -void clif_font(struct map_session_data *sd) -{ -#if PACKETVER >= 20080102 - unsigned char buf[8]; - nullpo_retv(sd); - WBUFW(buf,0) = 0x2ef; - WBUFL(buf,2) = sd->bl.id; - WBUFW(buf,6) = sd->user_font; - clif_send(buf, packet_len(0x2ef), &sd->bl, AREA); -#endif -} - - -/*========================================== - * Instancing Window - *------------------------------------------*/ -int clif_instance(int instance_id, int type, int flag) -{ - struct map_session_data *sd; - struct party_data *p; - unsigned char buf[255]; - - if( (p = party_search(instance[instance_id].party_id)) == NULL || (sd = party_getavailablesd(p)) == NULL ) - return 0; - - switch( type ) - { - case 1: - // S 0x2cb <Instance name>.61B <Standby Position>.W - // Required to start the instancing information window on Client - // This window re-appear each "refresh" of client automatically until type 4 is send to client. - WBUFW(buf,0) = 0x02CB; - memcpy(WBUFP(buf,2),instance[instance_id].name,INSTANCE_NAME_LENGTH); - WBUFW(buf,63) = flag; - clif_send(buf,packet_len(0x02CB),&sd->bl,PARTY); - break; - case 2: - // S 0x2cc <Standby Position>.W - // To announce Instancing queue creation if no maps available - WBUFW(buf,0) = 0x02CC; - WBUFW(buf,2) = flag; - clif_send(buf,packet_len(0x02CC),&sd->bl,PARTY); - break; - case 3: - case 4: - // S 0x2cd <Instance Name>.61B <Instance Remaining Time>.L <Instance Noplayers close time>.L - WBUFW(buf,0) = 0x02CD; - memcpy(WBUFP(buf,2),instance[instance_id].name,61); - if( type == 3 ) - { - WBUFL(buf,63) = (uint32)instance[instance_id].progress_timeout; - WBUFL(buf,67) = 0; - } - else - { - WBUFL(buf,63) = 0; - WBUFL(buf,67) = (uint32)instance[instance_id].idle_timeout; - } - clif_send(buf,packet_len(0x02CD),&sd->bl,PARTY); - break; - case 5: - // S 0x2ce <Message ID>.L - // 0 = Notification (EnterLimitDate update?) - // 1 = The Memorial Dungeon expired; it has been destroyed - // 2 = The Memorial Dungeon's entry time limit expired; it has been destroyed - // 3 = The Memorial Dungeon has been removed. - // 4 = Create failure (removes the instance window) - WBUFW(buf,0) = 0x02CE; - WBUFL(buf,2) = flag; - //WBUFL(buf,6) = EnterLimitDate; - clif_send(buf,packet_len(0x02CE),&sd->bl,PARTY); - break; - } - return 0; -} - -void clif_instance_join(int fd, int instance_id) -{ - if( instance[instance_id].idle_timer != INVALID_TIMER ) - { - WFIFOHEAD(fd,packet_len(0x02CD)); - WFIFOW(fd,0) = 0x02CD; - memcpy(WFIFOP(fd,2),instance[instance_id].name,61); - WFIFOL(fd,63) = 0; - WFIFOL(fd,67) = (uint32)instance[instance_id].idle_timeout; - WFIFOSET(fd,packet_len(0x02CD)); - } - else if( instance[instance_id].progress_timer != INVALID_TIMER ) - { - WFIFOHEAD(fd,packet_len(0x02CD)); - WFIFOW(fd,0) = 0x02CD; - memcpy(WFIFOP(fd,2),instance[instance_id].name,61); - WFIFOL(fd,63) = (uint32)instance[instance_id].progress_timeout;; - WFIFOL(fd,67) = 0; - WFIFOSET(fd,packet_len(0x02CD)); - } - else - { - WFIFOHEAD(fd,packet_len(0x02CB)); - WFIFOW(fd,0) = 0x02CB; - memcpy(WFIFOP(fd,2),instance[instance_id].name,61); - WFIFOW(fd,63) = 0; - WFIFOSET(fd,packet_len(0x02CB)); - } -} - -void clif_instance_leave(int fd) -{ - WFIFOHEAD(fd,packet_len(0x02CE)); - WFIFOW(fd,0) = 0x02ce; - WFIFOL(fd,2) = 4; - WFIFOSET(fd,packet_len(0x02CE)); -} - - -/// Notifies clients about item picked up by a party member (ZC_ITEM_PICKUP_PARTY). -/// 02b8 <account id>.L <name id>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W <equip location>.W <item type>.B -void clif_party_show_picker(struct map_session_data * sd, struct item * item_data) -{ -#if PACKETVER >= 20071002 - unsigned char buf[22]; - struct item_data* id = itemdb_search(item_data->nameid); - - WBUFW(buf,0) = 0x2b8; - WBUFL(buf,2) = sd->status.account_id; - WBUFW(buf,6) = item_data->nameid; - WBUFB(buf,8) = item_data->identify; - WBUFB(buf,9) = item_data->attribute; - WBUFB(buf,10) = item_data->refine; - clif_addcards(WBUFP(buf,11), item_data); - WBUFW(buf,19) = id->equip; // equip location - WBUFB(buf,21) = itemtype(id->type); // item type - clif_send(buf, packet_len(0x2b8), &sd->bl, PARTY_SAMEMAP_WOS); -#endif -} - - -/// Display gained exp (ZC_NOTIFY_EXP). -/// 07f6 <account id>.L <amount>.L <var id>.W <exp type>.W -/// var id: -/// SP_BASEEXP, SP_JOBEXP -/// exp type: -/// 0 = normal exp gain/loss -/// 1 = quest exp gain/loss -void clif_displayexp(struct map_session_data *sd, unsigned int exp, char type, bool quest) -{ - int fd; - - nullpo_retv(sd); - - fd = sd->fd; - - WFIFOHEAD(fd, packet_len(0x7f6)); - WFIFOW(fd,0) = 0x7f6; - WFIFOL(fd,2) = sd->bl.id; - WFIFOL(fd,6) = exp; - WFIFOW(fd,10) = type; - WFIFOW(fd,12) = quest?1:0;// Normal exp is shown in yellow, quest exp is shown in purple. - WFIFOSET(fd,packet_len(0x7f6)); -} - - -/// Displays digital clock digits on top of the screen (ZC_SHOWDIGIT). -/// type: -/// 0 = Displays 'value' for 5 seconds. -/// 1 = Incremental counter (1 tick/second), negated 'value' specifies start value (e.g. using -10 lets the counter start at 10). -/// 2 = Decremental counter (1 tick/second), negated 'value' specifies start value (does not stop when reaching 0, but overflows). -/// 3 = Decremental counter (1 tick/second), 'value' specifies start value (stops when reaching 0, displays at most 2 digits). -/// value: -/// Except for type 3 it is interpreted as seconds for displaying as DD:HH:MM:SS, HH:MM:SS, MM:SS or SS (leftmost '00' is not displayed). -void clif_showdigit(struct map_session_data* sd, unsigned char type, int value) -{ - WFIFOHEAD(sd->fd, packet_len(0x1b1)); - WFIFOW(sd->fd,0) = 0x1b1; - WFIFOB(sd->fd,2) = type; - WFIFOL(sd->fd,3) = value; - WFIFOSET(sd->fd, packet_len(0x1b1)); -} - - -/// Notification of the state of client command /effect (CZ_LESSEFFECT). -/// 021d <state>.L -/// state: -/// 0 = Full effects -/// 1 = Reduced effects -/// -/// NOTE: The state is used on Aegis for sending skill unit packet -/// 0x11f (ZC_SKILL_ENTRY) instead of 0x1c9 (ZC_SKILL_ENTRY2) -/// whenever possible. Due to the way the decision check is -/// constructed, this state tracking was rendered useless, -/// as the only skill unit, that is sent with 0x1c9 is -/// Graffiti. -void clif_parse_LessEffect(int fd, struct map_session_data* sd) -{ - int isLess = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); - - sd->state.lesseffect = ( isLess != 0 ); -} - -/// S 07e4 <length>.w <option>.l <val>.l {<index>.w <amount>.w).4b* -void clif_parse_ItemListWindowSelected(int fd, struct map_session_data* sd) { - int n = (RFIFOW(fd,2)-12) / 4; - int type = RFIFOL(fd,4); - int flag = RFIFOL(fd,8); // Button clicked: 0 = Cancel, 1 = OK - unsigned short* item_list = (unsigned short*)RFIFOP(fd,12); - - if( sd->state.trading || sd->npc_shopid ) - return; - - if( flag == 0 || n == 0) { - clif_menuskill_clear(sd); - return; // Canceled by player. - } - - if( sd->menuskill_id != SO_EL_ANALYSIS && sd->menuskill_id != GN_CHANGEMATERIAL ) { - clif_menuskill_clear(sd); - return; // Prevent hacking. - } - - switch( type ) { - case 0: // Change Material - skill_changematerial(sd,n,item_list); - break; - case 1: // Level 1: Pure to Rough - case 2: // Level 2: Rough to Pure - skill_elementalanalysis(sd,n,type,item_list); - break; - } - clif_menuskill_clear(sd); - - return; -} - -/*========================================== - * Elemental System - *==========================================*/ -void clif_elemental_updatestatus(struct map_session_data *sd, int type) { - struct elemental_data *ed; - struct status_data *status; - int fd; - - if( sd == NULL || (ed = sd->ed) == NULL ) - return; - - fd = sd->fd; - status = &ed->battle_status; - WFIFOHEAD(fd,8); - WFIFOW(fd,0) = 0x81e; - WFIFOW(fd,2) = type; - switch( type ) { - case SP_HP: - WFIFOL(fd,4) = status->hp; - break; - case SP_MAXHP: - WFIFOL(fd,4) = status->max_hp; - break; - case SP_SP: - WFIFOL(fd,4) = status->sp; - break; - case SP_MAXSP: - WFIFOL(fd,4) = status->max_sp; - break; - } - WFIFOSET(fd,8); -} - -void clif_elemental_info(struct map_session_data *sd) { - int fd; - struct elemental_data *ed; - struct status_data *status; - - if( sd == NULL || (ed = sd->ed) == NULL ) - return; - - fd = sd->fd; - status = &ed->battle_status; - - WFIFOHEAD(fd,22); - WFIFOW(fd, 0) = 0x81d; - WFIFOL(fd, 2) = ed->bl.id; - WFIFOL(fd, 6) = status->hp; - WFIFOL(fd,10) = status->max_hp; - WFIFOL(fd,14) = status->sp; - WFIFOL(fd,18) = status->max_sp; - WFIFOSET(fd,22); -} - - -/// Buying Store System -/// - -/// Opens preparation window for buying store (ZC_OPEN_BUYING_STORE). -/// 0810 <slots>.B -void clif_buyingstore_open(struct map_session_data* sd) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x810)); - WFIFOW(fd,0) = 0x810; - WFIFOB(fd,2) = sd->buyingstore.slots; - WFIFOSET(fd,packet_len(0x810)); -} - - -/// Request to create a buying store (CZ_REQ_OPEN_BUYING_STORE). -/// 0811 <packet len>.W <limit zeny>.L <result>.B <store name>.80B { <name id>.W <amount>.W <price>.L }* -/// result: -/// 0 = cancel -/// 1 = open -static void clif_parse_ReqOpenBuyingStore(int fd, struct map_session_data* sd) -{ - const unsigned int blocksize = 8; - uint8* itemlist; - char storename[MESSAGE_SIZE]; - unsigned char result; - int zenylimit; - unsigned int count, packet_len; - struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)]; - - packet_len = RFIFOW(fd,info->pos[0]); - - // TODO: Make this check global for all variable length packets. - if( packet_len < 89 ) - {// minimum packet length - ShowError("clif_parse_ReqOpenBuyingStore: Malformed packet (expected length=%u, length=%u, account_id=%d).\n", 89, packet_len, sd->bl.id); - return; - } - - zenylimit = RFIFOL(fd,info->pos[1]); - result = RFIFOL(fd,info->pos[2]); - safestrncpy(storename, (const char*)RFIFOP(fd,info->pos[3]), sizeof(storename)); - itemlist = RFIFOP(fd,info->pos[4]); - - // so that buyingstore_create knows, how many elements it has access to - packet_len-= info->pos[4]; - - if( packet_len%blocksize ) - { - ShowError("clif_parse_ReqOpenBuyingStore: Unexpected item list size %u (account_id=%d, block size=%u)\n", packet_len, sd->bl.id, blocksize); - return; - } - count = packet_len/blocksize; - - buyingstore_create(sd, zenylimit, result, storename, itemlist, count); -} - - -/// Notification, that the requested buying store could not be created (ZC_FAILED_OPEN_BUYING_STORE_TO_BUYER). -/// 0812 <result>.W <total weight>.L -/// result: -/// 1 = "Failed to open buying store." (0x6cd, MSI_BUYINGSTORE_OPEN_FAILED) -/// 2 = "Total amount of then possessed items exceeds the weight limit by <weight/10-maxweight*90%>. Please re-enter." (0x6ce, MSI_BUYINGSTORE_OVERWEIGHT) -/// 8 = "No sale (purchase) information available." (0x705) -/// ? = nothing -void clif_buyingstore_open_failed(struct map_session_data* sd, unsigned short result, unsigned int weight) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x812)); - WFIFOW(fd,0) = 0x812; - WFIFOW(fd,2) = result; - WFIFOL(fd,4) = weight; - WFIFOSET(fd,packet_len(0x812)); -} - - -/// Notification, that the requested buying store was created (ZC_MYITEMLIST_BUYING_STORE). -/// 0813 <packet len>.W <account id>.L <limit zeny>.L { <price>.L <count>.W <type>.B <name id>.W }* -void clif_buyingstore_myitemlist(struct map_session_data* sd) -{ - int fd = sd->fd; - unsigned int i; - - WFIFOHEAD(fd,12+sd->buyingstore.slots*9); - WFIFOW(fd,0) = 0x813; - WFIFOW(fd,2) = 12+sd->buyingstore.slots*9; - WFIFOL(fd,4) = sd->bl.id; - WFIFOL(fd,8) = sd->buyingstore.zenylimit; - - for( i = 0; i < sd->buyingstore.slots; i++ ) - { - WFIFOL(fd,12+i*9) = sd->buyingstore.items[i].price; - WFIFOW(fd,16+i*9) = sd->buyingstore.items[i].amount; - WFIFOB(fd,18+i*9) = itemtype(itemdb_type(sd->buyingstore.items[i].nameid)); - WFIFOW(fd,19+i*9) = sd->buyingstore.items[i].nameid; - } - - WFIFOSET(fd,WFIFOW(fd,2)); -} - - -/// Notifies clients in area of a buying store (ZC_BUYING_STORE_ENTRY). -/// 0814 <account id>.L <store name>.80B -void clif_buyingstore_entry(struct map_session_data* sd) -{ - uint8 buf[86]; - - WBUFW(buf,0) = 0x814; - WBUFL(buf,2) = sd->bl.id; - memcpy(WBUFP(buf,6), sd->message, MESSAGE_SIZE); - - clif_send(buf, packet_len(0x814), &sd->bl, AREA_WOS); -} -void clif_buyingstore_entry_single(struct map_session_data* sd, struct map_session_data* pl_sd) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x814)); - WFIFOW(fd,0) = 0x814; - WFIFOL(fd,2) = pl_sd->bl.id; - memcpy(WFIFOP(fd,6), pl_sd->message, MESSAGE_SIZE); - WFIFOSET(fd,packet_len(0x814)); -} - - -/// Request to close own buying store (CZ_REQ_CLOSE_BUYING_STORE). -/// 0815 -static void clif_parse_ReqCloseBuyingStore(int fd, struct map_session_data* sd) -{ - buyingstore_close(sd); -} - - -/// Notifies clients in area that a buying store was closed (ZC_DISAPPEAR_BUYING_STORE_ENTRY). -/// 0816 <account id>.L -void clif_buyingstore_disappear_entry(struct map_session_data* sd) -{ - uint8 buf[6]; - - WBUFW(buf,0) = 0x816; - WBUFL(buf,2) = sd->bl.id; - - clif_send(buf, packet_len(0x816), &sd->bl, AREA_WOS); -} -void clif_buyingstore_disappear_entry_single(struct map_session_data* sd, struct map_session_data* pl_sd) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x816)); - WFIFOW(fd,0) = 0x816; - WFIFOL(fd,2) = pl_sd->bl.id; - WFIFOSET(fd,packet_len(0x816)); -} - - -/// Request to open someone else's buying store (CZ_REQ_CLICK_TO_BUYING_STORE). -/// 0817 <account id>.L -static void clif_parse_ReqClickBuyingStore(int fd, struct map_session_data* sd) -{ - int account_id; - - account_id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); - - buyingstore_open(sd, account_id); -} - - -/// Sends buying store item list (ZC_ACK_ITEMLIST_BUYING_STORE). -/// 0818 <packet len>.W <account id>.L <store id>.L <limit zeny>.L { <price>.L <amount>.W <type>.B <name id>.W }* -void clif_buyingstore_itemlist(struct map_session_data* sd, struct map_session_data* pl_sd) -{ - int fd = sd->fd; - unsigned int i; - - WFIFOHEAD(fd,16+pl_sd->buyingstore.slots*9); - WFIFOW(fd,0) = 0x818; - WFIFOW(fd,2) = 16+pl_sd->buyingstore.slots*9; - WFIFOL(fd,4) = pl_sd->bl.id; - WFIFOL(fd,8) = pl_sd->buyer_id; - WFIFOL(fd,12) = pl_sd->buyingstore.zenylimit; - - for( i = 0; i < pl_sd->buyingstore.slots; i++ ) - { - WFIFOL(fd,16+i*9) = pl_sd->buyingstore.items[i].price; - WFIFOW(fd,20+i*9) = pl_sd->buyingstore.items[i].amount; // TODO: Figure out, if no longer needed items (amount == 0) are listed on official. - WFIFOB(fd,22+i*9) = itemtype(itemdb_type(pl_sd->buyingstore.items[i].nameid)); - WFIFOW(fd,23+i*9) = pl_sd->buyingstore.items[i].nameid; - } - - WFIFOSET(fd,WFIFOW(fd,2)); -} - - -/// Request to sell items to a buying store (CZ_REQ_TRADE_BUYING_STORE). -/// 0819 <packet len>.W <account id>.L <store id>.L { <index>.W <name id>.W <amount>.W }* -static void clif_parse_ReqTradeBuyingStore(int fd, struct map_session_data* sd) -{ - const unsigned int blocksize = 6; - uint8* itemlist; - int account_id; - unsigned int count, packet_len, buyer_id; - struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)]; - - packet_len = RFIFOW(fd,info->pos[0]); - - if( packet_len < 12 ) - {// minimum packet length - ShowError("clif_parse_ReqTradeBuyingStore: Malformed packet (expected length=%u, length=%u, account_id=%d).\n", 12, packet_len, sd->bl.id); - return; - } - - account_id = RFIFOL(fd,info->pos[1]); - buyer_id = RFIFOL(fd,info->pos[2]); - itemlist = RFIFOP(fd,info->pos[3]); - - // so that buyingstore_trade knows, how many elements it has access to - packet_len-= info->pos[3]; - - if( packet_len%blocksize ) - { - ShowError("clif_parse_ReqTradeBuyingStore: Unexpected item list size %u (account_id=%d, buyer_id=%d, block size=%u)\n", packet_len, sd->bl.id, account_id, blocksize); - return; - } - count = packet_len/blocksize; - - buyingstore_trade(sd, account_id, buyer_id, itemlist, count); -} - - -/// Notifies the buyer, that the buying store has been closed due to a post-trade condition (ZC_FAILED_TRADE_BUYING_STORE_TO_BUYER). -/// 081a <result>.W -/// result: -/// 3 = "All items within the buy limit were purchased." (0x6cf, MSI_BUYINGSTORE_TRADE_OVERLIMITZENY) -/// 4 = "All items were purchased." (0x6d0, MSI_BUYINGSTORE_TRADE_BUYCOMPLETE) -/// ? = nothing -void clif_buyingstore_trade_failed_buyer(struct map_session_data* sd, short result) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x81a)); - WFIFOW(fd,0) = 0x81a; - WFIFOW(fd,2) = result; - WFIFOSET(fd,packet_len(0x81a)); -} - - -/// Updates the zeny limit and an item in the buying store item list (ZC_UPDATE_ITEM_FROM_BUYING_STORE). -/// 081b <name id>.W <amount>.W <limit zeny>.L -void clif_buyingstore_update_item(struct map_session_data* sd, unsigned short nameid, unsigned short amount) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x81b)); - WFIFOW(fd,0) = 0x81b; - WFIFOW(fd,2) = nameid; - WFIFOW(fd,4) = amount; // amount of nameid received - WFIFOL(fd,6) = sd->buyingstore.zenylimit; - WFIFOSET(fd,packet_len(0x81b)); -} - - -/// Deletes item from inventory, that was sold to a buying store (ZC_ITEM_DELETE_BUYING_STORE). -/// 081c <index>.W <amount>.W <price>.L -/// message: -/// "%s (%d) were sold at %dz." (0x6d2, MSI_BUYINGSTORE_TRADE_SELLCOMPLETE) -/// -/// NOTE: This function has to be called _instead_ of clif_delitem/clif_dropitem. -void clif_buyingstore_delete_item(struct map_session_data* sd, short index, unsigned short amount, int price) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x81c)); - WFIFOW(fd,0) = 0x81c; - WFIFOW(fd,2) = index+2; - WFIFOW(fd,4) = amount; - WFIFOL(fd,6) = price; // price per item, client calculates total Zeny by itself - WFIFOSET(fd,packet_len(0x81c)); -} - - -/// Notifies the seller, that a buying store trade failed (ZC_FAILED_TRADE_BUYING_STORE_TO_SELLER). -/// 0824 <result>.W <name id>.W -/// result: -/// 5 = "The deal has failed." (0x39, MSI_DEAL_FAIL) -/// 6 = "The trade failed, because the entered amount of item %s is higher, than the buyer is willing to buy." (0x6d3, MSI_BUYINGSTORE_TRADE_OVERCOUNT) -/// 7 = "The trade failed, because the buyer is lacking required balance." (0x6d1, MSI_BUYINGSTORE_TRADE_LACKBUYERZENY) -/// ? = nothing -void clif_buyingstore_trade_failed_seller(struct map_session_data* sd, short result, unsigned short nameid) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x824)); - WFIFOW(fd,0) = 0x824; - WFIFOW(fd,2) = result; - WFIFOW(fd,4) = nameid; - WFIFOSET(fd,packet_len(0x824)); -} - - -/// Search Store Info System -/// - -/// Request to search for stores (CZ_SEARCH_STORE_INFO). -/// 0835 <packet len>.W <type>.B <max price>.L <min price>.L <name id count>.B <card count>.B { <name id>.W }* { <card>.W }* -/// type: -/// 0 = Vending -/// 1 = Buying Store -/// -/// NOTE: The client determines the item ids by specifying a name and optionally, -/// amount of card slots. If the client does not know about the item it -/// cannot be searched. -static void clif_parse_SearchStoreInfo(int fd, struct map_session_data* sd) -{ - const unsigned int blocksize = 2; - const uint8* itemlist; - const uint8* cardlist; - unsigned char type; - unsigned int min_price, max_price, packet_len, count, item_count, card_count; - struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)]; - - packet_len = RFIFOW(fd,info->pos[0]); - - if( packet_len < 15 ) - {// minimum packet length - ShowError("clif_parse_SearchStoreInfo: Malformed packet (expected length=%u, length=%u, account_id=%d).\n", 15, packet_len, sd->bl.id); - return; - } - - type = RFIFOB(fd,info->pos[1]); - max_price = RFIFOL(fd,info->pos[2]); - min_price = RFIFOL(fd,info->pos[3]); - item_count = RFIFOB(fd,info->pos[4]); - card_count = RFIFOB(fd,info->pos[5]); - itemlist = RFIFOP(fd,info->pos[6]); - cardlist = RFIFOP(fd,info->pos[6]+blocksize*item_count); - - // check, if there is enough data for the claimed count of items - packet_len-= info->pos[6]; - - if( packet_len%blocksize ) - { - ShowError("clif_parse_SearchStoreInfo: Unexpected item list size %u (account_id=%d, block size=%u)\n", packet_len, sd->bl.id, blocksize); - return; - } - count = packet_len/blocksize; - - if( count < item_count+card_count ) - { - ShowError("clif_parse_SearchStoreInfo: Malformed packet (expected count=%u, count=%u, account_id=%d).\n", item_count+card_count, count, sd->bl.id); - return; - } - - searchstore_query(sd, type, min_price, max_price, (const unsigned short*)itemlist, item_count, (const unsigned short*)cardlist, card_count); -} - - -/// Results for a store search request (ZC_SEARCH_STORE_INFO_ACK). -/// 0836 <packet len>.W <is first page>.B <is next page>.B <remaining uses>.B { <store id>.L <account id>.L <shop name>.80B <nameid>.W <item type>.B <price>.L <amount>.W <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W }* -/// is first page: -/// 0 = appends to existing results -/// 1 = clears previous results before displaying this result set -/// is next page: -/// 0 = no "next" label -/// 1 = "next" label to retrieve more results -void clif_search_store_info_ack(struct map_session_data* sd) -{ - const unsigned int blocksize = MESSAGE_SIZE+26; - int fd = sd->fd; - unsigned int i, start, end; - - start = sd->searchstore.pages*SEARCHSTORE_RESULTS_PER_PAGE; - end = min(sd->searchstore.count, start+SEARCHSTORE_RESULTS_PER_PAGE); - - WFIFOHEAD(fd,7+(end-start)*blocksize); - WFIFOW(fd,0) = 0x836; - WFIFOW(fd,2) = 7+(end-start)*blocksize; - WFIFOB(fd,4) = !sd->searchstore.pages; - WFIFOB(fd,5) = searchstore_querynext(sd); - WFIFOB(fd,6) = (unsigned char)min(sd->searchstore.uses, UINT8_MAX); - - for( i = start; i < end; i++ ) - { - struct s_search_store_info_item* ssitem = &sd->searchstore.items[i]; - struct item it; - - WFIFOL(fd,i*blocksize+ 7) = ssitem->store_id; - WFIFOL(fd,i*blocksize+11) = ssitem->account_id; - memcpy(WFIFOP(fd,i*blocksize+15), ssitem->store_name, MESSAGE_SIZE); - WFIFOW(fd,i*blocksize+15+MESSAGE_SIZE) = ssitem->nameid; - WFIFOB(fd,i*blocksize+17+MESSAGE_SIZE) = itemtype(itemdb_type(ssitem->nameid)); - WFIFOL(fd,i*blocksize+18+MESSAGE_SIZE) = ssitem->price; - WFIFOW(fd,i*blocksize+22+MESSAGE_SIZE) = ssitem->amount; - WFIFOB(fd,i*blocksize+24+MESSAGE_SIZE) = ssitem->refine; - - // make-up an item for clif_addcards - memset(&it, 0, sizeof(it)); - memcpy(&it.card, &ssitem->card, sizeof(it.card)); - it.nameid = ssitem->nameid; - it.amount = ssitem->amount; - - clif_addcards(WFIFOP(fd,i*blocksize+25+MESSAGE_SIZE), &it); - } - - WFIFOSET(fd,WFIFOW(fd,2)); -} - - -/// Notification of failure when searching for stores (ZC_SEARCH_STORE_INFO_FAILED). -/// 0837 <reason>.B -/// reason: -/// 0 = "No matching stores were found." (0x70b) -/// 1 = "There are too many results. Please enter more detailed search term." (0x6f8) -/// 2 = "You cannot search anymore." (0x706) -/// 3 = "You cannot search yet." (0x708) -/// 4 = "No sale (purchase) information available." (0x705) -void clif_search_store_info_failed(struct map_session_data* sd, unsigned char reason) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x837)); - WFIFOW(fd,0) = 0x837; - WFIFOB(fd,2) = reason; - WFIFOSET(fd,packet_len(0x837)); -} - - -/// Request to display next page of results (CZ_SEARCH_STORE_INFO_NEXT_PAGE). -/// 0838 -static void clif_parse_SearchStoreInfoNextPage(int fd, struct map_session_data* sd) -{ - searchstore_next(sd); -} - - -/// Opens the search store window (ZC_OPEN_SEARCH_STORE_INFO). -/// 083a <type>.W <remaining uses>.B -/// type: -/// 0 = Search Stores -/// 1 = Search Stores (Cash), asks for confirmation, when clicking a store -void clif_open_search_store_info(struct map_session_data* sd) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x83a)); - WFIFOW(fd,0) = 0x83a; - WFIFOW(fd,2) = sd->searchstore.effect; -#if PACKETVER > 20100701 - WFIFOB(fd,4) = (unsigned char)min(sd->searchstore.uses, UINT8_MAX); -#endif - WFIFOSET(fd,packet_len(0x83a)); -} - - -/// Request to close the store search window (CZ_CLOSE_SEARCH_STORE_INFO). -/// 083b -static void clif_parse_CloseSearchStoreInfo(int fd, struct map_session_data* sd) -{ - searchstore_close(sd); -} - - -/// Request to invoke catalog effect on a store from search results (CZ_SSILIST_ITEM_CLICK). -/// 083c <account id>.L <store id>.L <nameid>.W -static void clif_parse_SearchStoreInfoListItemClick(int fd, struct map_session_data* sd) -{ - unsigned short nameid; - int account_id, store_id; - struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)]; - - account_id = RFIFOL(fd,info->pos[0]); - store_id = RFIFOL(fd,info->pos[1]); - nameid = RFIFOW(fd,info->pos[2]); - - searchstore_click(sd, account_id, store_id, nameid); -} - - -/// Notification of the store position on current map (ZC_SSILIST_ITEM_CLICK_ACK). -/// 083d <xPos>.W <yPos>.W -void clif_search_store_info_click_ack(struct map_session_data* sd, short x, short y) -{ - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x83d)); - WFIFOW(fd,0) = 0x83d; - WFIFOW(fd,2) = x; - WFIFOW(fd,4) = y; - WFIFOSET(fd,packet_len(0x83d)); -} - - -/// Parse function for packet debugging. -void clif_parse_debug(int fd,struct map_session_data *sd) -{ - int cmd, packet_len; - - // clif_parse ensures, that there is at least 2 bytes of data - cmd = RFIFOW(fd,0); - - if( sd ) - { - packet_len = packet_db[sd->packet_ver][cmd].len; - - if( packet_len == 0 ) - {// unknown - packet_len = RFIFOREST(fd); - } - else if( packet_len == -1 ) - {// variable length - packet_len = RFIFOW(fd,2); // clif_parse ensures, that this amount of data is already received - } - ShowDebug("Packet debug of 0x%04X (length %d), %s session #%d, %d/%d (AID/CID)\n", cmd, packet_len, sd->state.active ? "authed" : "unauthed", fd, sd->status.account_id, sd->status.char_id); - } - else - { - packet_len = RFIFOREST(fd); - ShowDebug("Packet debug of 0x%04X (length %d), session #%d\n", cmd, packet_len, fd); - } - - ShowDump(RFIFOP(fd,0), packet_len); -} -/*========================================== - * Server tells client to display a window similar to Magnifier (item) one - * Server populates the window with avilable elemental converter options according to player's inventory - *------------------------------------------*/ -int clif_elementalconverter_list(struct map_session_data *sd) { - int i,c,view,fd; - - nullpo_ret(sd); - - -/// Main client packet processing function - fd=sd->fd; - WFIFOHEAD(fd, MAX_SKILL_PRODUCE_DB *2+4); - WFIFOW(fd, 0)=0x1ad; - - for(i=0,c=0;i<MAX_SKILL_PRODUCE_DB;i++){ - if( skill_can_produce_mix(sd,skill_produce_db[i].nameid,23, 1) ){ - if((view = itemdb_viewid(skill_produce_db[i].nameid)) > 0) - WFIFOW(fd,c*2+ 4)= view; - else - WFIFOW(fd,c*2+ 4)= skill_produce_db[i].nameid; - c++; - } - } - WFIFOW(fd,2) = c*2+4; - WFIFOSET(fd, WFIFOW(fd,2)); - if (c > 0) { - sd->menuskill_id = SA_CREATECON; - sd->menuskill_val = c; - } - - return 0; -} -/** - * Rune Knight - **/ -void clif_millenniumshield(struct map_session_data *sd, short shields ) { -#if PACKETVER >= 20081217 - unsigned char buf[10]; - - WBUFW(buf,0) = 0x440; - WBUFL(buf,2) = sd->bl.id; - WBUFW(buf,6) = shields; - WBUFW(buf,8) = 0; - clif_send(buf,packet_len(0x440),&sd->bl,AREA); -#endif -} -/** - * Warlock - **/ -/*========================================== - * Spellbook list [LimitLine/3CeAM] - *------------------------------------------*/ -int clif_spellbook_list(struct map_session_data *sd) -{ - int i, c; - int fd; - - nullpo_ret(sd); - - fd = sd->fd; - WFIFOHEAD(fd, 8 * 8 + 8); - WFIFOW(fd,0) = 0x1ad; - - for( i = 0, c = 0; i < MAX_INVENTORY; i ++ ) - { - if( itemdb_is_spellbook(sd->status.inventory[i].nameid) ) - { - WFIFOW(fd, c * 2 + 4) = sd->status.inventory[i].nameid; - c ++; - } - } - - if( c > 0 ) - { - WFIFOW(fd,2) = c * 2 + 4; - WFIFOSET(fd, WFIFOW(fd, 2)); - sd->menuskill_id = WL_READING_SB; - sd->menuskill_val = c; - } - else{ - status_change_end(&sd->bl,SC_STOP,INVALID_TIMER); - clif_skill_fail(sd, WL_READING_SB, USESKILL_FAIL_SPELLBOOK, 0); - } - - return 1; -} -/** - * Mechanic - **/ -/*========================================== - * Magic Decoy Material List - *------------------------------------------*/ -int clif_magicdecoy_list(struct map_session_data *sd, uint16 skill_lv, short x, short y) { - int i, c; - int fd; - - nullpo_ret(sd); - - fd = sd->fd; - WFIFOHEAD(fd, 8 * 8 + 8); - WFIFOW(fd,0) = 0x1ad; // This is the official packet. [pakpil] - - for( i = 0, c = 0; i < MAX_INVENTORY; i ++ ) { - if( itemdb_is_element(sd->status.inventory[i].nameid) ) { - WFIFOW(fd, c * 2 + 4) = sd->status.inventory[i].nameid; - c ++; - } - } - if( c > 0 ) { - sd->menuskill_id = NC_MAGICDECOY; - sd->menuskill_val = skill_lv; - sd->sc.comet_x = x; - sd->sc.comet_y = y; - WFIFOW(fd,2) = c * 2 + 4; - WFIFOSET(fd, WFIFOW(fd, 2)); - } else { - clif_skill_fail(sd,NC_MAGICDECOY,USESKILL_FAIL_LEVEL,0); - return 0; - } - - return 1; -} -/** - * Guilotine Cross - **/ -/*========================================== - * Guillotine Cross Poisons List - *------------------------------------------*/ -int clif_poison_list(struct map_session_data *sd, uint16 skill_lv) { - int i, c; - int fd; - - nullpo_ret(sd); - - fd = sd->fd; - WFIFOHEAD(fd, 8 * 8 + 8); - WFIFOW(fd,0) = 0x1ad; // This is the official packet. [pakpil] - - for( i = 0, c = 0; i < MAX_INVENTORY; i ++ ) { - if( itemdb_is_poison(sd->status.inventory[i].nameid) ) { - WFIFOW(fd, c * 2 + 4) = sd->status.inventory[i].nameid; - c ++; - } - } - if( c > 0 ) { - sd->menuskill_id = GC_POISONINGWEAPON; - sd->menuskill_val = skill_lv; - WFIFOW(fd,2) = c * 2 + 4; - WFIFOSET(fd, WFIFOW(fd, 2)); - } else { - clif_skill_fail(sd,GC_POISONINGWEAPON,USESKILL_FAIL_GUILLONTINE_POISON,0); - return 0; - } - - return 1; -} -int clif_autoshadowspell_list(struct map_session_data *sd) { - int fd, i, c; - nullpo_ret(sd); - fd = sd->fd; - if( !fd ) return 0; - - if( sd->menuskill_id == SC_AUTOSHADOWSPELL ) - return 0; - - WFIFOHEAD(fd, 2 * 6 + 4); - WFIFOW(fd,0) = 0x442; - for( i = 0, c = 0; i < MAX_SKILL; i++ ) - if( sd->status.skill[i].flag == SKILL_FLAG_PLAGIARIZED && sd->status.skill[i].id > 0 && - sd->status.skill[i].id < GS_GLITTERING && skill_get_type(sd->status.skill[i].id) == BF_MAGIC ) - { // Can't auto cast both Extended class and 3rd class skills. - WFIFOW(fd,8+c*2) = sd->status.skill[i].id; - c++; - } - - if( c > 0 ) { - WFIFOW(fd,2) = 8 + c * 2; - WFIFOL(fd,4) = c; - WFIFOSET(fd,WFIFOW(fd,2)); - sd->menuskill_id = SC_AUTOSHADOWSPELL; - sd->menuskill_val = c; - } else { - status_change_end(&sd->bl,SC_STOP,INVALID_TIMER); - clif_skill_fail(sd,SC_AUTOSHADOWSPELL,USESKILL_FAIL_IMITATION_SKILL_NONE,0); - } - - return 1; -} -/*=========================================== - * Skill list for Four Elemental Analysis - * and Change Material skills. - *------------------------------------------*/ -int clif_skill_itemlistwindow( struct map_session_data *sd, uint16 skill_id, uint16 skill_lv ) -{ -#if PACKETVER >= 20090922 - int fd; - - nullpo_ret(sd); - - sd->menuskill_id = skill_id; // To prevent hacking. - sd->menuskill_val = skill_lv; - - if( skill_id == GN_CHANGEMATERIAL ) - skill_lv = 0; // Changematerial - - fd = sd->fd; - WFIFOHEAD(fd,packet_len(0x7e3)); - WFIFOW(fd,0) = 0x7e3; - WFIFOL(fd,2) = skill_lv; - WFIFOL(fd,4) = 0; - WFIFOSET(fd,packet_len(0x7e3)); - -#endif - - return 1; - -} -/** - * Sends a new status without a tick (currently used by the new mounts) - **/ -int clif_status_load_notick(struct block_list *bl,int type,int flag,int val1, int val2, int val3) { - unsigned char buf[32]; - - nullpo_ret(bl); - - WBUFW(buf,0)=0x043f; - WBUFW(buf,2)=type; - WBUFL(buf,4)=bl->id; - WBUFB(buf,8)=flag; - WBUFL(buf,9) = 0; - WBUFL(buf,13) = val1; - WBUFL(buf,17) = val2; - WBUFL(buf,21) = val3; - - clif_send(buf,packet_len(0x043f),bl,AREA); - return 0; -} -//Notifies FD of ID's type -int clif_status_load_single(int fd, int id,int type,int flag,int val1, int val2, int val3) { - WFIFOHEAD(fd, packet_len(0x043f)); - WFIFOW(fd,0)=0x043f; - WFIFOW(fd,2)=type; - WFIFOL(fd,4)=id; - WFIFOB(fd,8)=flag; - WFIFOL(fd,9) = 0; - WFIFOL(fd,13) = val1; - WFIFOL(fd,17) = val2; - WFIFOL(fd,21) = val3; - WFIFOSET(fd, packet_len(0x043f)); - return 0; -} -// msgstringtable.txt -// 0x291 <line>.W -void clif_msgtable(int fd, int line) { - WFIFOHEAD(fd, packet_len(0x291)); - WFIFOW(fd, 0) = 0x291; - WFIFOW(fd, 2) = line; - WFIFOSET(fd, packet_len(0x291)); -} - -// msgstringtable.txt -// 0x7e2 <line>.W <value>.L -void clif_msgtable_num(int fd, int line, int num) { -#if PACKETVER >= 20090805 - WFIFOHEAD(fd, packet_len(0x7e2)); - WFIFOW(fd, 0) = 0x7e2; - WFIFOW(fd, 2) = line; - WFIFOL(fd, 4) = num; - WFIFOSET(fd, packet_len(0x7e2)); -#endif -} -/*========================================== - * used by SC_AUTOSHADOWSPELL - * RFIFOL(fd,2) - flag (currently not used) - *------------------------------------------*/ -void clif_parse_SkillSelectMenu(int fd, struct map_session_data *sd) { - - if( sd->menuskill_id != SC_AUTOSHADOWSPELL ) - return; - - if( pc_istrading(sd) ) { - clif_skill_fail(sd,sd->ud.skill_id,0,0); - clif_menuskill_clear(sd); - return; - } - - skill_select_menu(sd,RFIFOW(fd,6)); - - clif_menuskill_clear(sd); -} -/*========================================== - * Kagerou/Oboro amulet spirit - *------------------------------------------*/ -void clif_talisman(struct map_session_data *sd,short type) -{ - unsigned char buf[10]; - - nullpo_retv(sd); - - WBUFW(buf,0)=0x08cf; - WBUFL(buf,2)=sd->bl.id; - WBUFW(buf,6)=type; - WBUFW(buf,8)=sd->talisman[type]; - clif_send(buf,packet_len(0x08cf),&sd->bl,AREA); -} -/// Move Item from or to Personal Tab (CZ_WHATSOEVER) [FE] -/// 0907 <index>.W -/// -/// R 0908 <index>.w <type>.b -/// type: -/// 0 = move item to personal tab -/// 1 = move item to normal tab -void clif_parse_MoveItem(int fd, struct map_session_data *sd) { -#if PACKETVER >= 20111122 - int index; - - /* can't move while dead. */ - if(pc_isdead(sd)) { - return; - } - - index = RFIFOW(fd,2)-2; - - if (index < 0 || index >= MAX_INVENTORY) - return; - - if ( sd->status.inventory[index].favorite && RFIFOB(fd, 4) == 1 ) - sd->status.inventory[index].favorite = 0; - else if( RFIFOB(fd, 4) == 0 ) - sd->status.inventory[index].favorite = 1; - else - return;/* nothing to do. */ - - clif_favorite_item(sd, index); -#endif -} - - -/// Items that are in favorite tab of inventory (ZC_ITEM_FAVORITE). -/// 0900 <index>.W <favorite>.B -void clif_favorite_item(struct map_session_data* sd, unsigned short index) { - int fd = sd->fd; - - WFIFOHEAD(fd,packet_len(0x908)); - WFIFOW(fd,0) = 0x908; - WFIFOW(fd,2) = index+2; - WFIFOL(fd,4) = (sd->status.inventory[index].favorite == 1) ? 0 : 1; - WFIFOSET(fd,packet_len(0x908)); -} - -void clif_snap( struct block_list *bl, short x, short y ) { - unsigned char buf[10]; - - WBUFW(buf,0) = 0x8d2; - WBUFL(buf,2) = bl->id; - WBUFW(buf,6) = x; - WBUFW(buf,8) = y; - - clif_send(buf,packet_len(0x8d2),bl,AREA); -} - -void clif_monster_hp_bar( struct mob_data* md, int fd ) { -#if PACKETVER >= 20120404 - WFIFOHEAD(fd,packet_len(0x977)); - - WFIFOW(fd,0) = 0x977; - WFIFOL(fd,2) = md->bl.id; - WFIFOL(fd,6) = md->status.hp; - WFIFOL(fd,10) = md->status.max_hp; - - WFIFOSET(fd,packet_len(0x977)); -#endif -} - -/*========================================== - * Main client packet processing function - *------------------------------------------*/ -static int clif_parse(int fd) -{ - int cmd, packet_ver, packet_len, err; - TBL_PC* sd; - int pnum; - - //TODO apply delays or disconnect based on packet throughput [FlavioJS] - // Note: "click masters" can do 80+ clicks in 10 seconds - - for( pnum = 0; pnum < 3; ++pnum )// Limit max packets per cycle to 3 (delay packet spammers) [FlavioJS] -- This actually aids packet spammers, but stuff like /str+ gets slow without it [Ai4rei] - { // begin main client packet processing loop - - sd = (TBL_PC *)session[fd]->session_data; - if (session[fd]->flag.eof) { - if (sd) { - if (sd->state.autotrade) { - //Disassociate character from the socket connection. - session[fd]->session_data = NULL; - sd->fd = 0; - ShowInfo("Character '"CL_WHITE"%s"CL_RESET"' logged off (using @autotrade).\n", sd->status.name); - } else - if (sd->state.active) { - // Player logout display [Valaris] - ShowInfo("Character '"CL_WHITE"%s"CL_RESET"' logged off.\n", sd->status.name); - clif_quitsave(fd, sd); - } else { - //Unusual logout (during log on/off/map-changer procedure) - ShowInfo("Player AID:%d/CID:%d logged off.\n", sd->status.account_id, sd->status.char_id); - map_quit(sd); - } - } else { - ShowInfo("Closed connection from '"CL_WHITE"%s"CL_RESET"'.\n", ip2str(session[fd]->client_addr, NULL)); - } - do_close(fd); - return 0; - } - - if (RFIFOREST(fd) < 2) - return 0; - - cmd = RFIFOW(fd,0); - - // identify client's packet version - if (sd) { - packet_ver = sd->packet_ver; - } else { - // check authentification packet to know packet version - packet_ver = clif_guess_PacketVer(fd, 0, &err); - if( err ) {// failed to identify packet version - ShowInfo("clif_parse: Disconnecting session #%d with unknown packet version%s (p:0x%04x,l:%d).\n", fd, ( - err == 1 ? "" : - err == 2 ? ", possibly for having an invalid account_id" : - err == 3 ? ", possibly for having an invalid char_id." : - /* Uncomment when checks are added in clif_guess_PacketVer. [FlavioJS] - err == 4 ? ", possibly for having an invalid login_id1." : - err == 5 ? ", possibly for having an invalid client_tick." : - */ - err == 6 ? ", possibly for having an invalid sex." : - ". ERROR invalid error code"), cmd, RFIFOREST(fd)); - WFIFOHEAD(fd,packet_len(0x6a)); - WFIFOW(fd,0) = 0x6a; - WFIFOB(fd,2) = 3; // Rejected from Server - WFIFOSET(fd,packet_len(0x6a)); - -#ifdef DUMP_INVALID_PACKET - ShowDump(RFIFOP(fd,0), RFIFOREST(fd)); -#endif - - RFIFOSKIP(fd, RFIFOREST(fd)); - set_eof(fd); - return 0; - } - } - - // filter out invalid / unsupported packets - if (cmd > MAX_PACKET_DB || packet_db[packet_ver][cmd].len == 0) { - ShowWarning("clif_parse: Received unsupported packet (packet 0x%04x, %d bytes received), disconnecting session #%d.\n", cmd, RFIFOREST(fd), fd); -#ifdef DUMP_INVALID_PACKET - ShowDump(RFIFOP(fd,0), RFIFOREST(fd)); -#endif - set_eof(fd); - return 0; - } - - // determine real packet length - packet_len = packet_db[packet_ver][cmd].len; - if (packet_len == -1) { // variable-length packet - if (RFIFOREST(fd) < 4) - return 0; - - packet_len = RFIFOW(fd,2); - if (packet_len < 4 || packet_len > 32768) { - ShowWarning("clif_parse: Received packet 0x%04x specifies invalid packet_len (%d), disconnecting session #%d.\n", cmd, packet_len, fd); -#ifdef DUMP_INVALID_PACKET - ShowDump(RFIFOP(fd,0), RFIFOREST(fd)); -#endif - set_eof(fd); - return 0; - } - } - if ((int)RFIFOREST(fd) < packet_len) - return 0; // not enough data received to form the packet - - if( packet_db[packet_ver][cmd].func == clif_parse_debug ) - packet_db[packet_ver][cmd].func(fd, sd); - else if( packet_db[packet_ver][cmd].func != NULL ) { - if( !sd && packet_db[packet_ver][cmd].func != clif_parse_WantToConnection ) - ; //Only valid packet when there is no session - else - if( sd && sd->bl.prev == NULL && packet_db[packet_ver][cmd].func != clif_parse_LoadEndAck ) - ; //Only valid packet when player is not on a map - else - if( sd && session[sd->fd]->flag.eof ) - ; //No more packets accepted - else - packet_db[packet_ver][cmd].func(fd, sd); - } -#ifdef DUMP_UNKNOWN_PACKET - else { - const char* packet_txt = "save/packet.txt"; - FILE* fp; - - if( ( fp = fopen( packet_txt , "a" ) ) != NULL ) { - if( sd ) { - fprintf(fp, "Unknown packet 0x%04X (length %d), %s session #%d, %d/%d (AID/CID)\n", cmd, packet_len, sd->state.active ? "authed" : "unauthed", fd, sd->status.account_id, sd->status.char_id); - } else { - fprintf(fp, "Unknown packet 0x%04X (length %d), session #%d\n", cmd, packet_len, fd); - } - - WriteDump(fp, RFIFOP(fd,0), packet_len); - fprintf(fp, "\n"); - fclose(fp); - } else { - ShowError("Failed to write '%s'.\n", packet_txt); - - // Dump on console instead - if( sd ) { - ShowDebug("Unknown packet 0x%04X (length %d), %s session #%d, %d/%d (AID/CID)\n", cmd, packet_len, sd->state.active ? "authed" : "unauthed", fd, sd->status.account_id, sd->status.char_id); - } else { - ShowDebug("Unknown packet 0x%04X (length %d), session #%d\n", cmd, packet_len, fd); - } - - ShowDump(RFIFOP(fd,0), packet_len); - } - } -#endif - - RFIFOSKIP(fd, packet_len); - - }; // main loop end - - return 0; -} - -/*========================================== - * Reads packet_db.txt and setups its array reference - *------------------------------------------*/ -static int packetdb_readdb(void) -{ - FILE *fp; - char line[1024]; - int ln=0; - int cmd,i,j,packet_ver; - int max_cmd=-1; - int skip_ver = 0; - int warned = 0; - char *str[64],*p,*str2[64],*p2,w1[64],w2[64]; - int packet_len_table[MAX_PACKET_DB] = { - 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, -#if PACKETVER <= 20081217 - 0, 0, 0, 0, 55, 17, 3, 37, 46, -1, 23, -1, 3,110, 3, 2, -#else - 0, 0, 0, 0, 55, 17, 3, 37, 46, -1, 23, -1, 3,114, 3, 2, -#endif -#if PACKETVER < 2 - 3, 28, 19, 11, 3, -1, 9, 5, 52, 51, 56, 58, 41, 2, 6, 6, -#elif PACKETVER < 20071106 // 78-7b Lv99 effect for later Kameshima - 3, 28, 19, 11, 3, -1, 9, 5, 54, 53, 58, 60, 41, 2, 6, 6, -#elif PACKETVER <= 20081217 // change in 0x78 and 0x7c - 3, 28, 19, 11, 3, -1, 9, 5, 55, 53, 58, 60, 42, 2, 6, 6, -#else - 3, 28, 19, 11, 3, -1, 9, 5, 55, 53, 58, 60, 44, 2, 6, 6, -#endif - //#0x0080 - 7, 3, 2, 2, 2, 5, 16, 12, 10, 7, 29, 2, -1, -1, -1, 0, // 0x8b changed to 2 (was 23) - 7, 22, 28, 2, 6, 30, -1, -1, 3, -1, -1, 5, 9, 17, 17, 6, -#if PACKETVER <= 20100622 - 23, 6, 6, -1, -1, -1, -1, 8, 7, 6, 7, 4, 7, 0, -1, 6, -#else - 23, 6, 6, -1, -1, -1, -1, 8, 7, 6, 9, 4, 7, 0, -1, 6, // 0xaa changed to 9 (was 7) -#endif - 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, 3, 2, 27, // 0xcd change to 3 (was 6) - 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 icon status display for later - 90, 86, 24, 6, 30,102, 9, 4, 8, 4, 14, 10, -1, 6, 2, 6, -#endif -#if PACKETVER < 20081126 - 3, 3, 35, 5, 11, 26, -1, 4, 4, 6, 10, 12, 6, -1, 4, 4, -#else // 0x1a2 changed (35->37) - 3, 3, 37, 5, 11, 26, -1, 4, 4, 6, 10, 12, 6, -1, 4, 4, -#endif - 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, 3, 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, - //#0x0200 - 26, -1, 26, 10, 18, 26, 11, 34, 14, 36, 10, 0, 0, -1, 32, 10, // 0x20c change to 0 (was 19) - 22, 0, 26, 26, 42, 6, 6, 2, 2,282,282, 10, 10, -1, -1, 66, -#if PACKETVER < 20071106 - 10, -1, -1, 8, 10, 2,282, 18, 18, 15, 58, 57, 64, 5, 71, 5, -#else // 0x22c changed - 10, -1, -1, 8, 10, 2,282, 18, 18, 15, 58, 57, 65, 5, 71, 5, -#endif - 12, 26, 9, 11, -1, -1, 10, 2,282, 11, 4, 36, 6, -1, 4, 2, - //#0x0240 - -1, -1, -1, -1, -1, 3, 4, 8, -1, 3, 70, 4, 8, 12, 4, 10, - 3, 32, -1, 3, 3, 5, 5, 8, 2, 3, -1, 6, 4, 6, 4, 6, - 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - //#0x0280 -#if PACKETVER < 20070711 - 0, 0, 0, 6, 14, 0, 0, -1, 6, 8, 18, 0, 0, 0, 0, 0, -#else - 0, 0, 0, 6, 14, 0, 0, -1, 10, 12, 18, 0, 0, 0, 0, 0, // 0x288, 0x289 increase by 4 (kafra points) -#endif - 0, 4, 0, 70, 10, 0, 0, 0, 8, 6, 27, 80, 0, -1, 0, 0, - 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 85, -1, -1,107, 6, -1, 7, 7, 22,191, 0, 8, 0, 0, 0, 0, - //#0x02C0 - 0, -1, 0, 0, 0, 30, 30, 0, 0, 3, 0, 65, 4, 71, 10, 0, - -1, -1, -1, 0, 29, 0, 6, -1, 10, 10, 3, 0, -1, 32, 6, 36, - 34, 33, 0, 0, 0, 0, 0, 0, -1, -1, -1, 13, 67, 59, 60, 8, - 10, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - //#0x0300 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - //#0x0340 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - //#0x0380 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - //#0x03C0 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - //#0x0400 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 25, - //#0x0440 - 10, 4, -1, 0, 0, 0, 14, 0, 0, 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, - //#0x0480 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - //#0x04C0 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - //#0x0500 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, - //#0x0540 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - //#0x0580 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - //#0x05C0 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - //#0x0600 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, - //#0x0640 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - //#0x0680 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - //#0x06C0 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - //#0x0700 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, - //#0x0740 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - //#0x0780 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - //#0x07C0 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -#if PACKETVER < 20090617 - 6, 2, -1, 4, 4, 4, 4, 8, 8,254, 6, 8, 6, 54, 30, 54, -#else // 0x7d9 changed - 6, 2, -1, 4, 4, 4, 4, 8, 8,268, 6, 8, 6, 54, 30, 54, -#endif - 0, 15, 8, 6, -1, 8, 8, 32, -1, 5, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 14, -1, -1, -1, 8, 25, 0, 0, 26, 0, - //#0x0800 -#if PACKETVER < 20091229 - -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 20, -#else // for Party booking ( PACKETVER >= 20091229 ) - -1, -1, 18, 4, 8, 6, 2, 4, 14, 50, 18, 6, 2, 3, 14, 20, -#endif - 3, -1, 8, -1, 86, 2, 6, 6, -1, -1, 4, 10, 10, 0, 0, 0, - 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, -1, -1, 3, 2, 66, 5, 2, 12, 6, 0, 0, - //#0x0840 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, -1, -1, -1, -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, - //#0x0880 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - //#0x08C0 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, - 0, 0, 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, - //#0x0900 - 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, - //#0x0940 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, - - }; - struct { - void (*func)(int, struct map_session_data *); - char *name; - } clif_parse_func[]={ - {clif_parse_WantToConnection,"wanttoconnection"}, - {clif_parse_LoadEndAck,"loadendack"}, - {clif_parse_TickSend,"ticksend"}, - {clif_parse_WalkToXY,"walktoxy"}, - {clif_parse_QuitGame,"quitgame"}, - {clif_parse_GetCharNameRequest,"getcharnamerequest"}, - {clif_parse_GlobalMessage,"globalmessage"}, - {clif_parse_MapMove,"mapmove"}, - {clif_parse_ChangeDir,"changedir"}, - {clif_parse_Emotion,"emotion"}, - {clif_parse_HowManyConnections,"howmanyconnections"}, - {clif_parse_ActionRequest,"actionrequest"}, - {clif_parse_Restart,"restart"}, - {clif_parse_WisMessage,"wis"}, - {clif_parse_Broadcast,"broadcast"}, - {clif_parse_TakeItem,"takeitem"}, - {clif_parse_DropItem,"dropitem"}, - {clif_parse_UseItem,"useitem"}, - {clif_parse_EquipItem,"equipitem"}, - {clif_parse_UnequipItem,"unequipitem"}, - {clif_parse_NpcClicked,"npcclicked"}, - {clif_parse_NpcBuySellSelected,"npcbuysellselected"}, - {clif_parse_NpcBuyListSend,"npcbuylistsend"}, - {clif_parse_NpcSellListSend,"npcselllistsend"}, - {clif_parse_CreateChatRoom,"createchatroom"}, - {clif_parse_ChatAddMember,"chataddmember"}, - {clif_parse_ChatRoomStatusChange,"chatroomstatuschange"}, - {clif_parse_ChangeChatOwner,"changechatowner"}, - {clif_parse_KickFromChat,"kickfromchat"}, - {clif_parse_ChatLeave,"chatleave"}, - {clif_parse_TradeRequest,"traderequest"}, - {clif_parse_TradeAck,"tradeack"}, - {clif_parse_TradeAddItem,"tradeadditem"}, - {clif_parse_TradeOk,"tradeok"}, - {clif_parse_TradeCancel,"tradecancel"}, - {clif_parse_TradeCommit,"tradecommit"}, - {clif_parse_StopAttack,"stopattack"}, - {clif_parse_PutItemToCart,"putitemtocart"}, - {clif_parse_GetItemFromCart,"getitemfromcart"}, - {clif_parse_RemoveOption,"removeoption"}, - {clif_parse_ChangeCart,"changecart"}, - {clif_parse_StatusUp,"statusup"}, - {clif_parse_SkillUp,"skillup"}, - {clif_parse_UseSkillToId,"useskilltoid"}, - {clif_parse_UseSkillToPos,"useskilltopos"}, - {clif_parse_UseSkillToPosMoreInfo,"useskilltoposinfo"}, - {clif_parse_UseSkillMap,"useskillmap"}, - {clif_parse_RequestMemo,"requestmemo"}, - {clif_parse_ProduceMix,"producemix"}, - {clif_parse_Cooking,"cooking"}, - {clif_parse_NpcSelectMenu,"npcselectmenu"}, - {clif_parse_NpcNextClicked,"npcnextclicked"}, - {clif_parse_NpcAmountInput,"npcamountinput"}, - {clif_parse_NpcStringInput,"npcstringinput"}, - {clif_parse_NpcCloseClicked,"npccloseclicked"}, - {clif_parse_ItemIdentify,"itemidentify"}, - {clif_parse_SelectArrow,"selectarrow"}, - {clif_parse_AutoSpell,"autospell"}, - {clif_parse_UseCard,"usecard"}, - {clif_parse_InsertCard,"insertcard"}, - {clif_parse_RepairItem,"repairitem"}, - {clif_parse_WeaponRefine,"weaponrefine"}, - {clif_parse_SolveCharName,"solvecharname"}, - {clif_parse_ResetChar,"resetchar"}, - {clif_parse_LocalBroadcast,"localbroadcast"}, - {clif_parse_MoveToKafra,"movetokafra"}, - {clif_parse_MoveFromKafra,"movefromkafra"}, - {clif_parse_MoveToKafraFromCart,"movetokafrafromcart"}, - {clif_parse_MoveFromKafraToCart,"movefromkafratocart"}, - {clif_parse_CloseKafra,"closekafra"}, - {clif_parse_CreateParty,"createparty"}, - {clif_parse_CreateParty2,"createparty2"}, - {clif_parse_PartyInvite,"partyinvite"}, - {clif_parse_PartyInvite2,"partyinvite2"}, - {clif_parse_ReplyPartyInvite,"replypartyinvite"}, - {clif_parse_ReplyPartyInvite2,"replypartyinvite2"}, - {clif_parse_LeaveParty,"leaveparty"}, - {clif_parse_RemovePartyMember,"removepartymember"}, - {clif_parse_PartyChangeOption,"partychangeoption"}, - {clif_parse_PartyMessage,"partymessage"}, - {clif_parse_PartyChangeLeader,"partychangeleader"}, - {clif_parse_CloseVending,"closevending"}, - {clif_parse_VendingListReq,"vendinglistreq"}, - {clif_parse_PurchaseReq,"purchasereq"}, - {clif_parse_PurchaseReq2,"purchasereq2"}, - {clif_parse_OpenVending,"openvending"}, - {clif_parse_CreateGuild,"createguild"}, - {clif_parse_GuildCheckMaster,"guildcheckmaster"}, - {clif_parse_GuildRequestInfo,"guildrequestinfo"}, - {clif_parse_GuildChangePositionInfo,"guildchangepositioninfo"}, - {clif_parse_GuildChangeMemberPosition,"guildchangememberposition"}, - {clif_parse_GuildRequestEmblem,"guildrequestemblem"}, - {clif_parse_GuildChangeEmblem,"guildchangeemblem"}, - {clif_parse_GuildChangeNotice,"guildchangenotice"}, - {clif_parse_GuildInvite,"guildinvite"}, - {clif_parse_GuildReplyInvite,"guildreplyinvite"}, - {clif_parse_GuildLeave,"guildleave"}, - {clif_parse_GuildExpulsion,"guildexpulsion"}, - {clif_parse_GuildMessage,"guildmessage"}, - {clif_parse_GuildRequestAlliance,"guildrequestalliance"}, - {clif_parse_GuildReplyAlliance,"guildreplyalliance"}, - {clif_parse_GuildDelAlliance,"guilddelalliance"}, - {clif_parse_GuildOpposition,"guildopposition"}, - {clif_parse_GuildBreak,"guildbreak"}, - {clif_parse_PetMenu,"petmenu"}, - {clif_parse_CatchPet,"catchpet"}, - {clif_parse_SelectEgg,"selectegg"}, - {clif_parse_SendEmotion,"sendemotion"}, - {clif_parse_ChangePetName,"changepetname"}, - - {clif_parse_GMKick,"gmkick"}, - {clif_parse_GMHide,"gmhide"}, - {clif_parse_GMReqNoChat,"gmreqnochat"}, - {clif_parse_GMReqAccountName,"gmreqaccname"}, - {clif_parse_GMKickAll,"killall"}, - {clif_parse_GMRecall,"recall"}, - {clif_parse_GMRecall,"summon"}, - {clif_parse_GM_Monster_Item,"itemmonster"}, - {clif_parse_GMShift,"remove"}, - {clif_parse_GMShift,"shift"}, - {clif_parse_GMChangeMapType,"changemaptype"}, - {clif_parse_GMRc,"rc"}, - {clif_parse_GMRecall2,"recall2"}, - {clif_parse_GMRemove2,"remove2"}, - - {clif_parse_NoviceDoriDori,"sndoridori"}, - {clif_parse_NoviceExplosionSpirits,"snexplosionspirits"}, - {clif_parse_PMIgnore,"wisexin"}, - {clif_parse_PMIgnoreList,"wisexlist"}, - {clif_parse_PMIgnoreAll,"wisall"}, - {clif_parse_FriendsListAdd,"friendslistadd"}, - {clif_parse_FriendsListRemove,"friendslistremove"}, - {clif_parse_FriendsListReply,"friendslistreply"}, - {clif_parse_Blacksmith,"blacksmith"}, - {clif_parse_Alchemist,"alchemist"}, - {clif_parse_Taekwon,"taekwon"}, - {clif_parse_RankingPk,"rankingpk"}, - {clif_parse_FeelSaveOk,"feelsaveok"}, - {clif_parse_debug,"debug"}, - {clif_parse_ChangeHomunculusName,"changehomunculusname"}, - {clif_parse_HomMoveToMaster,"hommovetomaster"}, - {clif_parse_HomMoveTo,"hommoveto"}, - {clif_parse_HomAttack,"homattack"}, - {clif_parse_HomMenu,"hommenu"}, - {clif_parse_StoragePassword,"storagepassword"}, - {clif_parse_Hotkey,"hotkey"}, - {clif_parse_AutoRevive,"autorevive"}, - {clif_parse_Check,"check"}, - {clif_parse_Adopt_request,"adoptrequest"}, - {clif_parse_Adopt_reply,"adoptreply"}, - // MAIL SYSTEM - {clif_parse_Mail_refreshinbox,"mailrefresh"}, - {clif_parse_Mail_read,"mailread"}, - {clif_parse_Mail_getattach,"mailgetattach"}, - {clif_parse_Mail_delete,"maildelete"}, - {clif_parse_Mail_return,"mailreturn"}, - {clif_parse_Mail_setattach,"mailsetattach"}, - {clif_parse_Mail_winopen,"mailwinopen"}, - {clif_parse_Mail_send,"mailsend"}, - // AUCTION SYSTEM - {clif_parse_Auction_search,"auctionsearch"}, - {clif_parse_Auction_buysell,"auctionbuysell"}, - {clif_parse_Auction_setitem,"auctionsetitem"}, - {clif_parse_Auction_cancelreg,"auctioncancelreg"}, - {clif_parse_Auction_register,"auctionregister"}, - {clif_parse_Auction_cancel,"auctioncancel"}, - {clif_parse_Auction_close,"auctionclose"}, - {clif_parse_Auction_bid,"auctionbid"}, - // Quest Log System - {clif_parse_questStateAck,"queststate"}, - {clif_parse_cashshop_buy,"cashshopbuy"}, - {clif_parse_ViewPlayerEquip,"viewplayerequip"}, - {clif_parse_EquipTick,"equiptickbox"}, - {clif_parse_BattleChat,"battlechat"}, - {clif_parse_mercenary_action,"mermenu"}, - {clif_parse_progressbar,"progressbar"}, - {clif_parse_SkillSelectMenu,"skillselectmenu"}, - {clif_parse_ItemListWindowSelected,"itemlistwindowselected"}, -#if PACKETVER >= 20091229 - {clif_parse_PartyBookingRegisterReq,"bookingregreq"}, - {clif_parse_PartyBookingSearchReq,"bookingsearchreq"}, - {clif_parse_PartyBookingUpdateReq,"bookingupdatereq"}, - {clif_parse_PartyBookingDeleteReq,"bookingdelreq"}, -#endif - {clif_parse_PVPInfo,"pvpinfo"}, - {clif_parse_LessEffect,"lesseffect"}, - // Buying Store - {clif_parse_ReqOpenBuyingStore,"reqopenbuyingstore"}, - {clif_parse_ReqCloseBuyingStore,"reqclosebuyingstore"}, - {clif_parse_ReqClickBuyingStore,"reqclickbuyingstore"}, - {clif_parse_ReqTradeBuyingStore,"reqtradebuyingstore"}, - // Store Search - {clif_parse_SearchStoreInfo,"searchstoreinfo"}, - {clif_parse_SearchStoreInfoNextPage,"searchstoreinfonextpage"}, - {clif_parse_CloseSearchStoreInfo,"closesearchstoreinfo"}, - {clif_parse_SearchStoreInfoListItemClick,"searchstoreinfolistitemclick"}, - /* */ - { clif_parse_MoveItem , "moveitem" }, - {NULL,NULL} - }; - - // initialize packet_db[SERVER] from hardcoded packet_len_table[] values - memset(packet_db,0,sizeof(packet_db)); - for( i = 0; i < ARRAYLENGTH(packet_len_table); ++i ) - packet_len(i) = packet_len_table[i]; - - sprintf(line, "%s/packet_db.txt", db_path); - if( (fp=fopen(line,"r"))==NULL ){ - ShowFatalError("can't read %s\n", line); - exit(EXIT_FAILURE); - } - - clif_config.packet_db_ver = MAX_PACKET_VER; - packet_ver = MAX_PACKET_VER; // read into packet_db's version by default - while( fgets(line, sizeof(line), fp) ) - { - ln++; - if(line[0]=='/' && line[1]=='/') - continue; - if (sscanf(line,"%256[^:]: %256[^\r\n]",w1,w2) == 2) - { - if(strcmpi(w1,"packet_ver")==0) { - int prev_ver = packet_ver; - skip_ver = 0; - packet_ver = atoi(w2); - if ( packet_ver > MAX_PACKET_VER ) - { //Check to avoid overflowing. [Skotlex] - if( (warned&1) == 0 ) - ShowWarning("The packet_db table only has support up to version %d.\n", MAX_PACKET_VER); - warned &= 1; - skip_ver = 1; - } - else if( packet_ver < 0 ) - { - if( (warned&2) == 0 ) - ShowWarning("Negative packet versions are not supported.\n"); - warned &= 2; - skip_ver = 1; - } - else if( packet_ver == SERVER ) - { - if( (warned&4) == 0 ) - ShowWarning("Packet version %d is reserved for server use only.\n", SERVER); - warned &= 4; - skip_ver = 1; - } - - if( skip_ver ) - { - ShowWarning("Skipping packet version %d.\n", packet_ver); - packet_ver = prev_ver; - continue; - } - // copy from previous version into new version and continue - // - indicating all following packets should be read into the newer version - memcpy(&packet_db[packet_ver], &packet_db[prev_ver], sizeof(packet_db[0])); - continue; - } else if(strcmpi(w1,"packet_db_ver")==0) { - if(strcmpi(w2,"default")==0) //This is the preferred version. - clif_config.packet_db_ver = MAX_PACKET_VER; - else // to manually set the packet DB version - clif_config.packet_db_ver = cap_value(atoi(w2), 0, MAX_PACKET_VER); - - continue; - } - } - - if( skip_ver != 0 ) - continue; // Skipping current packet version - - memset(str,0,sizeof(str)); - for(j=0,p=line;j<4 && p; ++j) - { - str[j]=p; - p=strchr(p,','); - if(p) *p++=0; - } - if(str[0]==NULL) - continue; - cmd=strtol(str[0],(char **)NULL,0); - if(max_cmd < cmd) - max_cmd = cmd; - if(cmd <= 0 || cmd > MAX_PACKET_DB) - continue; - if(str[1]==NULL){ - ShowError("packet_db: packet len error\n"); - continue; - } - - packet_db[packet_ver][cmd].len = (short)atoi(str[1]); - - if(str[2]==NULL){ - packet_db[packet_ver][cmd].func = NULL; - ln++; - continue; - } - - // look up processing function by name - ARR_FIND( 0, ARRAYLENGTH(clif_parse_func), j, clif_parse_func[j].name != NULL && strcmp(str[2],clif_parse_func[j].name)==0 ); - if( j < ARRAYLENGTH(clif_parse_func) ) - packet_db[packet_ver][cmd].func = clif_parse_func[j].func; - - // set the identifying cmd for the packet_db version - if (strcmp(str[2],"wanttoconnection")==0) - clif_config.connect_cmd[packet_ver] = cmd; - - if(str[3]==NULL){ - ShowError("packet_db: packet error\n"); - exit(EXIT_FAILURE); - } - for(j=0,p2=str[3];p2;j++){ - short k; - str2[j]=p2; - p2=strchr(p2,':'); - if(p2) *p2++=0; - k = atoi(str2[j]); - // if (packet_db[packet_ver][cmd].pos[j] != k && clif_config.prefer_packet_db) // not used for now - - if( j >= MAX_PACKET_POS ) - { - ShowError("Too many positions found for packet 0x%04x (max=%d).\n", cmd, MAX_PACKET_POS); - break; - } - - packet_db[packet_ver][cmd].pos[j] = k; - } - } - fclose(fp); - if(max_cmd > MAX_PACKET_DB) - { - ShowWarning("Found packets up to 0x%X, ignored 0x%X and above.\n", max_cmd, MAX_PACKET_DB); - ShowWarning("Please increase MAX_PACKET_DB and recompile.\n"); - } - if (!clif_config.connect_cmd[clif_config.packet_db_ver]) - { //Locate the nearest version that we still support. [Skotlex] - for(j = clif_config.packet_db_ver; j >= 0 && !clif_config.connect_cmd[j]; j--); - - clif_config.packet_db_ver = j?j:MAX_PACKET_VER; - } - ShowStatus("Done reading packet database from '"CL_WHITE"%s"CL_RESET"'. Using default packet version: "CL_WHITE"%d"CL_RESET".\n", "packet_db.txt", clif_config.packet_db_ver); - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -int do_init_clif(void) { - const char* colors[COLOR_MAX] = { "0xFF0000" }; - int i; - /** - * Setup Color Table (saves unnecessary load of strtoul on every call) - **/ - for(i = 0; i < COLOR_MAX; i++) { - color_table[i] = strtoul(colors[i],NULL,0); - color_table[i] = (color_table[i] & 0x0000FF) << 16 | (color_table[i] & 0x00FF00) | (color_table[i] & 0xFF0000) >> 16;//RGB to BGR - } - - clif_config.packet_db_ver = -1; // the main packet version of the DB - memset(clif_config.connect_cmd, 0, sizeof(clif_config.connect_cmd)); //The default connect command will be determined after reading the packet_db [Skotlex] - - memset(packet_db,0,sizeof(packet_db)); - //Using the packet_db file is the only way to set up packets now [Skotlex] - packetdb_readdb(); - - set_defaultparse(clif_parse); - if( make_listen_bind(bind_ip,map_port) == -1 ) { - ShowFatalError("can't bind game port\n"); - exit(EXIT_FAILURE); - } - - add_timer_func_list(clif_clearunit_delayed_sub, "clif_clearunit_delayed_sub"); - add_timer_func_list(clif_delayquit, "clif_delayquit"); - - delay_clearunit_ers = ers_new(sizeof(struct block_list),"clif.c::delay_clearunit_ers",ERS_OPT_CLEAR); - - return 0; -} - -void do_final_clif(void) { - ers_destroy(delay_clearunit_ers); -} +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/socket.h" +#include "../common/timer.h" +#include "../common/grfio.h" +#include "../common/malloc.h" +#include "../common/nullpo.h" +#include "../common/random.h" +#include "../common/showmsg.h" +#include "../common/strlib.h" +#include "../common/utils.h" +#include "../common/ers.h" + +#include "map.h" +#include "chrif.h" +#include "pc.h" +#include "status.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 "battleground.h" +#include "mob.h" +#include "party.h" +#include "unit.h" +#include "guild.h" +#include "vending.h" +#include "pet.h" +#include "homunculus.h" +#include "instance.h" +#include "mercenary.h" +#include "elemental.h" +#include "log.h" +#include "clif.h" +#include "mail.h" +#include "quest.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <time.h> + +/* for clif_clearunit_delayed */ +static struct eri *delay_clearunit_ers; + +//#define DUMP_UNKNOWN_PACKET +//#define DUMP_INVALID_PACKET + +struct Clif_Config { + int packet_db_ver; //Preferred packet version. + int connect_cmd[MAX_PACKET_VER + 1]; //Store the connect command for all versions. [Skotlex] +} clif_config; + +struct s_packet_db packet_db[MAX_PACKET_VER + 1][MAX_PACKET_DB + 1]; + +//Converts item type in case of pet eggs. +static inline int itemtype(int type) +{ + return ( type == IT_PETEGG ) ? IT_WEAPON : type; +} + + +static inline void WBUFPOS(uint8* p, unsigned short pos, short x, short y, unsigned char dir) +{ + p += pos; + p[0] = (uint8)(x>>2); + p[1] = (uint8)((x<<6) | ((y>>4)&0x3f)); + p[2] = (uint8)((y<<4) | (dir&0xf)); +} + + +// client-side: x0+=sx0*0.0625-0.5 and y0+=sy0*0.0625-0.5 +static inline void WBUFPOS2(uint8* p, unsigned short pos, short x0, short y0, short x1, short y1, unsigned char sx0, unsigned char sy0) +{ + p += pos; + p[0] = (uint8)(x0>>2); + p[1] = (uint8)((x0<<6) | ((y0>>4)&0x3f)); + p[2] = (uint8)((y0<<4) | ((x1>>6)&0x0f)); + p[3] = (uint8)((x1<<2) | ((y1>>8)&0x03)); + p[4] = (uint8)y1; + p[5] = (uint8)((sx0<<4) | (sy0&0x0f)); +} + + +static inline void WFIFOPOS(int fd, unsigned short pos, short x, short y, unsigned char dir) +{ + WBUFPOS(WFIFOP(fd,pos), 0, x, y, dir); +} + + +static inline void WFIFOPOS2(int fd, unsigned short pos, short x0, short y0, short x1, short y1, unsigned char sx0, unsigned char sy0) +{ + WBUFPOS2(WFIFOP(fd,pos), 0, x0, y0, x1, y1, sx0, sy0); +} + + +static inline void RBUFPOS(const uint8* p, unsigned short pos, short* x, short* y, unsigned char* dir) +{ + p += pos; + + if( x ) { + x[0] = ( ( p[0] & 0xff ) << 2 ) | ( p[1] >> 6 ); + } + + if( y ) { + y[0] = ( ( p[1] & 0x3f ) << 4 ) | ( p[2] >> 4 ); + } + + if( dir ) { + dir[0] = ( p[2] & 0x0f ); + } +} + + +static inline void RBUFPOS2(const uint8* p, unsigned short pos, short* x0, short* y0, short* x1, short* y1, unsigned char* sx0, unsigned char* sy0) +{ + p += pos; + + if( x0 ) { + x0[0] = ( ( p[0] & 0xff ) << 2 ) | ( p[1] >> 6 ); + } + + if( y0 ) { + y0[0] = ( ( p[1] & 0x3f ) << 4 ) | ( p[2] >> 4 ); + } + + if( x1 ) { + x1[0] = ( ( p[2] & 0x0f ) << 6 ) | ( p[3] >> 2 ); + } + + if( y1 ) { + y1[0] = ( ( p[3] & 0x03 ) << 8 ) | ( p[4] >> 0 ); + } + + if( sx0 ) { + sx0[0] = ( p[5] & 0xf0 ) >> 4; + } + + if( sy0 ) { + sy0[0] = ( p[5] & 0x0f ) >> 0; + } +} + + +static inline void RFIFOPOS(int fd, unsigned short pos, short* x, short* y, unsigned char* dir) +{ + RBUFPOS(RFIFOP(fd,pos), 0, x, y, dir); +} + + +static inline void RFIFOPOS2(int fd, unsigned short pos, short* x0, short* y0, short* x1, short* y1, unsigned char* sx0, unsigned char* sy0) +{ + RBUFPOS2(WFIFOP(fd,pos), 0, x0, y0, x1, y1, sx0, sy0); +} + + +//To idenfity disguised characters. +static inline bool disguised(struct block_list* bl) +{ + return (bool)( bl->type == BL_PC && ((TBL_PC*)bl)->disguise ); +} + + +//Guarantees that the given string does not exceeds the allowed size, as well as making sure it's null terminated. [Skotlex] +static inline unsigned int mes_len_check(char* mes, unsigned int len, unsigned int max) +{ + if( len > max ) + len = max; + + mes[len-1] = '\0'; + + return len; +} + + +static char map_ip_str[128]; +static uint32 map_ip; +static uint32 bind_ip = INADDR_ANY; +static uint16 map_port = 5121; +int map_fd; + +static int clif_parse (int fd); + +/*========================================== + * Ip setting of map-server + *------------------------------------------*/ +int clif_setip(const char* ip) +{ + char ip_str[16]; + map_ip = host2ip(ip); + if (!map_ip) { + ShowWarning("Failed to Resolve Map Server Address! (%s)\n", ip); + return 0; + } + + strncpy(map_ip_str, ip, sizeof(map_ip_str)); + ShowInfo("Map Server IP Address : '"CL_WHITE"%s"CL_RESET"' -> '"CL_WHITE"%s"CL_RESET"'.\n", ip, ip2str(map_ip, ip_str)); + return 1; +} + +void clif_setbindip(const char* ip) +{ + char ip_str[16]; + bind_ip = host2ip(ip); + if (bind_ip) { + ShowInfo("Map Server Bind IP Address : '"CL_WHITE"%s"CL_RESET"' -> '"CL_WHITE"%s"CL_RESET"'.\n", ip, ip2str(bind_ip, ip_str)); + } else { + ShowWarning("Failed to Resolve Map Server Address! (%s)\n", ip); + } +} + +/*========================================== + * Sets map port to 'port' + * is run from map.c upon loading map server configuration + *------------------------------------------*/ +void clif_setport(uint16 port) +{ + map_port = port; +} + +/*========================================== + * Returns map server IP + *------------------------------------------*/ +uint32 clif_getip(void) +{ + return map_ip; +} + +//Refreshes map_server ip, returns the new ip if the ip changed, otherwise it returns 0. +uint32 clif_refresh_ip(void) +{ + uint32 new_ip; + + new_ip = host2ip(map_ip_str); + if (new_ip && new_ip != map_ip) { + map_ip = new_ip; + ShowInfo("Updating IP resolution of [%s].\n", map_ip_str); + return map_ip; + } + return 0; +} + +/*========================================== + * Returns map port which is set by clif_setport() + *------------------------------------------*/ +uint16 clif_getport(void) +{ + return map_port; +} + +#if PACKETVER >= 20071106 +static inline unsigned char clif_bl_type(struct block_list *bl) { + switch (bl->type) { + case BL_PC: return disguised(bl)?0x1:0x0; //PC_TYPE + case BL_ITEM: return 0x2; //ITEM_TYPE + case BL_SKILL: return 0x3; //SKILL_TYPE + case BL_CHAT: return 0x4; //UNKNOWN_TYPE + case BL_MOB: return pcdb_checkid(status_get_viewdata(bl)->class_)?0x0:0x5; //NPC_MOB_TYPE + case BL_NPC: return 0x6; //NPC_EVT_TYPE + case BL_PET: return pcdb_checkid(status_get_viewdata(bl)->class_)?0x0:0x7; //NPC_PET_TYPE + case BL_HOM: return 0x8; //NPC_HOM_TYPE + case BL_MER: return 0x9; //NPC_MERSOL_TYPE + case BL_ELEM: return 0xa; //NPC_ELEMENTAL_TYPE + default: return 0x1; //NPC_TYPE + } +} +#endif + +/*========================================== + * sub process of clif_send + * Called from a map_foreachinarea (grabs all players in specific area and subjects them to this function) + * In order to send area-wise packets, such as: + * - AREA : everyone nearby your area + * - AREA_WOSC (AREA WITHOUT SAME CHAT) : Not run for people in the same chat as yours + * - AREA_WOC (AREA WITHOUT CHAT) : Not run for people inside a chat + * - AREA_WOS (AREA WITHOUT SELF) : Not run for self + * - AREA_CHAT_WOC : Everyone in the area of your chat without a chat + *------------------------------------------*/ +static int clif_send_sub(struct block_list *bl, va_list ap) +{ + struct block_list *src_bl; + struct map_session_data *sd; + unsigned char *buf; + int len, type, fd; + + nullpo_ret(bl); + nullpo_ret(sd = (struct map_session_data *)bl); + + fd = sd->fd; + if (!fd) //Don't send to disconnected clients. + return 0; + + buf = va_arg(ap,unsigned char*); + len = va_arg(ap,int); + nullpo_ret(src_bl = va_arg(ap,struct block_list*)); + type = va_arg(ap,int); + + switch(type) + { + case AREA_WOS: + if (bl == src_bl) + return 0; + break; + case AREA_WOC: + if (sd->chatID || bl == src_bl) + return 0; + break; + case AREA_WOSC: + { + if(src_bl->type == BL_PC){ + struct map_session_data *ssd = (struct map_session_data *)src_bl; + if (ssd && sd->chatID && (sd->chatID == ssd->chatID)) + return 0; + } + else if(src_bl->type == BL_NPC) { + struct npc_data *nd = (struct npc_data *)src_bl; + if (nd && sd->chatID && (sd->chatID == nd->chat_id)) + return 0; + } + } + break; + } + + if (session[fd] == NULL) + return 0; + + WFIFOHEAD(fd, len); + if (WFIFOP(fd,0) == buf) { + ShowError("WARNING: Invalid use of clif_send function\n"); + ShowError(" Packet x%4x use a WFIFO of a player instead of to use a buffer.\n", WBUFW(buf,0)); + ShowError(" Please correct your code.\n"); + // don't send to not move the pointer of the packet for next sessions in the loop + //WFIFOSET(fd,0);//## TODO is this ok? + //NO. It is not ok. There is the chance WFIFOSET actually sends the buffer data, and shifts elements around, which will corrupt the buffer. + return 0; + } + + if (packet_db[sd->packet_ver][RBUFW(buf,0)].len) { // packet must exist for the client version + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + } + + return 0; +} + +/*========================================== + * Packet Delegation (called on all packets that require data to be sent to more than one client) + * functions that are sent solely to one use whose ID it posses use WFIFOSET + *------------------------------------------*/ +int clif_send(const uint8* buf, int len, struct block_list* bl, enum send_target type) +{ + int i; + struct map_session_data *sd, *tsd; + struct party_data *p = NULL; + struct guild *g = NULL; + struct battleground_data *bg = NULL; + int x0 = 0, x1 = 0, y0 = 0, y1 = 0, fd; + struct s_mapiterator* iter; + + if( type != ALL_CLIENT && type != CHAT_MAINCHAT ) + nullpo_ret(bl); + + sd = BL_CAST(BL_PC, bl); + + switch(type) { + + case ALL_CLIENT: //All player clients. + iter = mapit_getallusers(); + while( (tsd = (TBL_PC*)mapit_next(iter)) != NULL ) + { + if( packet_db[tsd->packet_ver][RBUFW(buf,0)].len ) + { // packet must exist for the client version + WFIFOHEAD(tsd->fd, len); + memcpy(WFIFOP(tsd->fd,0), buf, len); + WFIFOSET(tsd->fd,len); + } + } + mapit_free(iter); + break; + + case ALL_SAMEMAP: //All players on the same map + iter = mapit_getallusers(); + while( (tsd = (TBL_PC*)mapit_next(iter)) != NULL ) + { + if( bl->m == tsd->bl.m && packet_db[tsd->packet_ver][RBUFW(buf,0)].len ) + { // packet must exist for the client version + WFIFOHEAD(tsd->fd, len); + memcpy(WFIFOP(tsd->fd,0), buf, len); + WFIFOSET(tsd->fd,len); + } + } + mapit_free(iter); + break; + + case AREA: + case AREA_WOSC: + if (sd && bl->prev == NULL) //Otherwise source misses the packet.[Skotlex] + clif_send (buf, len, bl, SELF); + case AREA_WOC: + case AREA_WOS: + 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: + { + struct chat_data *cd; + if (sd) { + cd = (struct chat_data*)map_id2bl(sd->chatID); + } else if (bl->type == BL_CHAT) { + cd = (struct chat_data*)bl; + } else break; + if (cd == NULL) + break; + for(i = 0; i < cd->users; i++) { + if (type == CHAT_WOS && cd->usersd[i] == sd) + continue; + if (packet_db[cd->usersd[i]->packet_ver][RBUFW(buf,0)].len) { // packet must exist for the client version + if ((fd=cd->usersd[i]->fd) >0 && session[fd]) // Added check to see if session exists [PoW] + { + WFIFOHEAD(fd,len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + } + } + } + } + break; + + case CHAT_MAINCHAT: //[LuzZza] + iter = mapit_getallusers(); + while( (tsd = (TBL_PC*)mapit_next(iter)) != NULL ) + { + if( tsd->state.mainchat && tsd->chatID == 0 && packet_db[tsd->packet_ver][RBUFW(buf,0)].len ) + { // packet must exist for the client version + WFIFOHEAD(tsd->fd, len); + memcpy(WFIFOP(tsd->fd,0), buf, len); + WFIFOSET(tsd->fd,len); + } + } + mapit_free(iter); + 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 (sd && sd->status.party_id) + p = party_search(sd->status.party_id); + + if (p) { + for(i=0;i<MAX_PARTY;i++){ + if( (sd = p->data[i].sd) == NULL ) + continue; + + if( !(fd=sd->fd) ) + continue; + + 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_db[sd->packet_ver][RBUFW(buf,0)].len ) + { // packet must exist for the client version + WFIFOHEAD(fd,len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + } + } + if (!enable_spy) //Skip unnecessary parsing. [Skotlex] + break; + + iter = mapit_getallusers(); + while( (tsd = (TBL_PC*)mapit_next(iter)) != NULL ) + { + if( tsd->partyspy == p->party.party_id && packet_db[tsd->packet_ver][RBUFW(buf,0)].len ) + { // packet must exist for the client version + WFIFOHEAD(tsd->fd, len); + memcpy(WFIFOP(tsd->fd,0), buf, len); + WFIFOSET(tsd->fd,len); + } + } + mapit_free(iter); + } + break; + + case DUEL: + case DUEL_WOS: + if (!sd || !sd->duel_group) break; //Invalid usage. + + iter = mapit_getallusers(); + while( (tsd = (TBL_PC*)mapit_next(iter)) != NULL ) + { + if( type == DUEL_WOS && bl->id == tsd->bl.id ) + continue; + if( sd->duel_group == tsd->duel_group && packet_db[tsd->packet_ver][RBUFW(buf,0)].len ) + { // packet must exist for the client version + WFIFOHEAD(tsd->fd, len); + memcpy(WFIFOP(tsd->fd,0), buf, len); + WFIFOSET(tsd->fd,len); + } + } + mapit_free(iter); + break; + + case SELF: + if (sd && (fd=sd->fd) && packet_db[sd->packet_ver][RBUFW(buf,0)].len) { // packet must exist for the client version + WFIFOHEAD(fd,len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + } + break; + + // New definitions for guilds [Valaris] - Cleaned up and reorganized by [Skotlex] + 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_SAMEMAP: + case GUILD_SAMEMAP_WOS: + case GUILD: + case GUILD_WOS: + case GUILD_NOBG: + if (sd && sd->status.guild_id) + 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( !(fd=sd->fd) ) + continue; + + if( type == GUILD_NOBG && sd->bg_id ) + continue; + + if( sd->bl.id == bl->id && (type == GUILD_WOS || type == GUILD_SAMEMAP_WOS || type == GUILD_AREA_WOS) ) + continue; + + if( type != GUILD && type != GUILD_NOBG && type != GUILD_WOS && sd->bl.m != 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_db[sd->packet_ver][RBUFW(buf,0)].len ) + { // packet must exist for the client version + WFIFOHEAD(fd,len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + } + } + } + if (!enable_spy) //Skip unnecessary parsing. [Skotlex] + break; + + iter = mapit_getallusers(); + while( (tsd = (TBL_PC*)mapit_next(iter)) != NULL ) + { + if( tsd->guildspy == g->guild_id && packet_db[tsd->packet_ver][RBUFW(buf,0)].len ) + { // packet must exist for the client version + WFIFOHEAD(tsd->fd, len); + memcpy(WFIFOP(tsd->fd,0), buf, len); + WFIFOSET(tsd->fd,len); + } + } + mapit_free(iter); + } + break; + + case BG_AREA: + case BG_AREA_WOS: + x0 = bl->x - AREA_SIZE; + y0 = bl->y - AREA_SIZE; + x1 = bl->x + AREA_SIZE; + y1 = bl->y + AREA_SIZE; + case BG_SAMEMAP: + case BG_SAMEMAP_WOS: + case BG: + case BG_WOS: + if( sd && sd->bg_id && (bg = bg_team_search(sd->bg_id)) != NULL ) + { + for( i = 0; i < MAX_BG_MEMBERS; i++ ) + { + if( (sd = bg->members[i].sd) == NULL || !(fd = sd->fd) ) + continue; + if( sd->bl.id == bl->id && (type == BG_WOS || type == BG_SAMEMAP_WOS || type == BG_AREA_WOS) ) + continue; + if( type != BG && type != BG_WOS && sd->bl.m != bl->m ) + continue; + if( (type == BG_AREA || type == BG_AREA_WOS) && (sd->bl.x < x0 || sd->bl.y < y0 || sd->bl.x > x1 || sd->bl.y > y1) ) + continue; + if( packet_db[sd->packet_ver][RBUFW(buf,0)].len ) + { // packet must exist for the client version + WFIFOHEAD(fd,len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + } + } + } + break; + + default: + ShowError("clif_send: Unrecognized type %d\n",type); + return -1; + } + + return 0; +} + + +/// Notifies the client, that it's connection attempt was accepted. +/// 0073 <start time>.L <position>.3B <x size>.B <y size>.B (ZC_ACCEPT_ENTER) +/// 02eb <start time>.L <position>.3B <x size>.B <y size>.B <font>.W (ZC_ACCEPT_ENTER2) +void clif_authok(struct map_session_data *sd) +{ +#if PACKETVER < 20080102 + const int cmd = 0x73; +#else + const int cmd = 0x2eb; +#endif + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(cmd)); + WFIFOW(fd, 0) = cmd; + WFIFOL(fd, 2) = gettick(); + WFIFOPOS(fd, 6, sd->bl.x, sd->bl.y, sd->ud.dir); + WFIFOB(fd, 9) = 5; // ignored + WFIFOB(fd,10) = 5; // ignored +#if PACKETVER >= 20080102 + WFIFOW(fd,11) = sd->user_font; // FIXME: Font is currently not saved. +#endif + WFIFOSET(fd,packet_len(cmd)); +} + + +/// Notifies the client, that it's connection attempt was refused (ZC_REFUSE_ENTER). +/// 0074 <error code>.B +/// error code: +/// 0 = client type mismatch +/// 1 = ID mismatch +/// 2 = mobile - out of available time +/// 3 = mobile - already logged in +/// 4 = mobile - waiting state +void clif_authrefuse(int fd, uint8 error_code) +{ + WFIFOHEAD(fd,packet_len(0x74)); + WFIFOW(fd,0) = 0x74; + WFIFOB(fd,2) = error_code; + WFIFOSET(fd,packet_len(0x74)); +} + + +/// Notifies the client of a ban or forced disconnect (SC_NOTIFY_BAN). +/// 0081 <error code>.B +/// error code: +/// 0 = BAN_UNFAIR +/// 1 = server closed -> MsgStringTable[4] +/// 2 = ID already logged in -> MsgStringTable[5] +/// 3 = timeout/too much lag -> MsgStringTable[241] +/// 4 = server full -> MsgStringTable[264] +/// 5 = underaged -> MsgStringTable[305] +/// 8 = Server sill recognizes last connection -> MsgStringTable[441] +/// 9 = too many connections from this ip -> MsgStringTable[529] +/// 10 = out of available time paid for -> MsgStringTable[530] +/// 11 = BAN_PAY_SUSPEND +/// 12 = BAN_PAY_CHANGE +/// 13 = BAN_PAY_WRONGIP +/// 14 = BAN_PAY_PNGAMEROOM +/// 15 = disconnected by a GM -> if( servicetype == taiwan ) MsgStringTable[579] +/// 16 = BAN_JAPAN_REFUSE1 +/// 17 = BAN_JAPAN_REFUSE2 +/// 18 = BAN_INFORMATION_REMAINED_ANOTHER_ACCOUNT +/// 100 = BAN_PC_IP_UNFAIR +/// 101 = BAN_PC_IP_COUNT_ALL +/// 102 = BAN_PC_IP_COUNT +/// 103 = BAN_GRAVITY_MEM_AGREE +/// 104 = BAN_GAME_MEM_AGREE +/// 105 = BAN_HAN_VALID +/// 106 = BAN_PC_IP_LIMIT_ACCESS +/// 107 = BAN_OVER_CHARACTER_LIST +/// 108 = BAN_IP_BLOCK +/// 109 = BAN_INVALID_PWD_CNT +/// 110 = BAN_NOT_ALLOWED_JOBCLASS +/// ? = disconnected -> MsgStringTable[3] +void clif_authfail_fd(int fd, int type) +{ + if (!fd || !session[fd] || session[fd]->func_parse != clif_parse) //clif_authfail should only be invoked on players! + return; + + WFIFOHEAD(fd, packet_len(0x81)); + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = type; + WFIFOSET(fd,packet_len(0x81)); + set_eof(fd); +} + + +/// Notifies the client, whether it can disconnect and change servers (ZC_RESTART_ACK). +/// 00b3 <type>.B +/// type: +/// 1 = disconnect, char-select +/// ? = nothing +void clif_charselectok(int id, uint8 ok) +{ + struct map_session_data* sd; + int fd; + + if ((sd = map_id2sd(id)) == NULL || !sd->fd) + return; + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0xb3)); + WFIFOW(fd,0) = 0xb3; + WFIFOB(fd,2) = ok; + WFIFOSET(fd,packet_len(0xb3)); +} + +/// Makes an item appear on the ground. +/// 009e <id>.L <name id>.W <identified>.B <x>.W <y>.W <subX>.B <subY>.B <amount>.W (ZC_ITEM_FALL_ENTRY) +/// 084b (ZC_ITEM_FALL_ENTRY4) +void clif_dropflooritem(struct flooritem_data* fitem) +{ + uint8 buf[17]; + int view; + + nullpo_retv(fitem); + + if (fitem->item_data.nameid <= 0) + return; + + WBUFW(buf, 0) = 0x9e; + WBUFL(buf, 2) = fitem->bl.id; + WBUFW(buf, 6) = ((view = itemdb_viewid(fitem->item_data.nameid)) > 0) ? view : 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; + + clif_send(buf, packet_len(0x9e), &fitem->bl, AREA); +} + + + +/// Makes an item disappear from the ground. +/// 00a1 <id>.L (ZC_ITEM_DISAPPEAR) +void clif_clearflooritem(struct flooritem_data *fitem, int fd) +{ + unsigned char buf[16]; + + nullpo_retv(fitem); + + WBUFW(buf,0) = 0xa1; + WBUFL(buf,2) = fitem->bl.id; + + if (fd == 0) { + clif_send(buf, packet_len(0xa1), &fitem->bl, AREA); + } else { + WFIFOHEAD(fd,packet_len(0xa1)); + memcpy(WFIFOP(fd,0), buf, packet_len(0xa1)); + WFIFOSET(fd,packet_len(0xa1)); + } +} + + +/// Makes a unit (char, npc, mob, homun) disappear to one client (ZC_NOTIFY_VANISH). +/// 0080 <id>.L <type>.B +/// type: +/// 0 = out of sight +/// 1 = died +/// 2 = logged out +/// 3 = teleport +/// 4 = trickdead +void clif_clearunit_single(int id, clr_type type, int fd) +{ + WFIFOHEAD(fd, packet_len(0x80)); + WFIFOW(fd,0) = 0x80; + WFIFOL(fd,2) = id; + WFIFOB(fd,6) = type; + WFIFOSET(fd, packet_len(0x80)); +} + +/// Makes a unit (char, npc, mob, homun) disappear to all clients in area (ZC_NOTIFY_VANISH). +/// 0080 <id>.L <type>.B +/// type: +/// 0 = out of sight +/// 1 = died +/// 2 = logged out +/// 3 = teleport +/// 4 = trickdead +void clif_clearunit_area(struct block_list* bl, clr_type type) +{ + unsigned char buf[8]; + + nullpo_retv(bl); + + WBUFW(buf,0) = 0x80; + WBUFL(buf,2) = bl->id; + WBUFB(buf,6) = type; + + clif_send(buf, packet_len(0x80), bl, type == CLR_DEAD ? AREA : AREA_WOS); + + if(disguised(bl)) { + WBUFL(buf,2) = -bl->id; + clif_send(buf, packet_len(0x80), bl, SELF); + } +} + + +/// Used to make monsters with player-sprites disappear after dying +/// like normal monsters, because the client does not remove those +/// automatically. +static int clif_clearunit_delayed_sub(int tid, unsigned int tick, int id, intptr_t data) +{ + struct block_list *bl = (struct block_list *)data; + clif_clearunit_area(bl, (clr_type) id); + ers_free(delay_clearunit_ers,bl); + return 0; +} +void clif_clearunit_delayed(struct block_list* bl, clr_type type, unsigned int tick) +{ + struct block_list *tbl = ers_alloc(delay_clearunit_ers, struct block_list); + memcpy (tbl, bl, sizeof (struct block_list)); + add_timer(tick, clif_clearunit_delayed_sub, (int)type, (intptr_t)tbl); +} + +void clif_get_weapon_view(struct map_session_data* sd, unsigned short *rhand, unsigned short *lhand) +{ + if(sd->sc.option&(OPTION_WEDDING|OPTION_XMAS|OPTION_SUMMER)) + { + *rhand = *lhand = 0; + return; + } + +#if PACKETVER < 4 + *rhand = sd->status.weapon; + *lhand = sd->status.shield; +#else + if (sd->equip_index[EQI_HAND_R] >= 0 && + sd->inventory_data[sd->equip_index[EQI_HAND_R]]) + { + struct item_data* id = sd->inventory_data[sd->equip_index[EQI_HAND_R]]; + if (id->view_id > 0) + *rhand = id->view_id; + else + *rhand = id->nameid; + } else + *rhand = 0; + + if (sd->equip_index[EQI_HAND_L] >= 0 && + sd->equip_index[EQI_HAND_L] != sd->equip_index[EQI_HAND_R] && + sd->inventory_data[sd->equip_index[EQI_HAND_L]]) + { + struct item_data* id = sd->inventory_data[sd->equip_index[EQI_HAND_L]]; + if (id->view_id > 0) + *lhand = id->view_id; + else + *lhand = id->nameid; + } else + *lhand = 0; +#endif +} + +//To make the assignation of the level based on limits clearer/easier. [Skotlex] +static int clif_setlevel_sub(int lv) +{ + if( lv < battle_config.max_lv ) + { + ; + } + else if( lv < battle_config.aura_lv ) + { + lv = battle_config.max_lv - 1; + } + else + { + lv = battle_config.max_lv; + } + + return lv; +} + +static int clif_setlevel(struct block_list* bl) +{ + int lv = status_get_lv(bl); + if( battle_config.client_limit_unit_lv&bl->type ) + return clif_setlevel_sub(lv); + switch( bl->type ) + { + case BL_NPC: + case BL_PET: + // npcs and pets do not have level + return 0; + } + return lv; +} + +/*========================================== + * Prepares 'unit standing/spawning' packet + *------------------------------------------*/ +static int clif_set_unit_idle(struct block_list* bl, unsigned char* buffer, bool spawn) +{ + struct map_session_data* sd; + struct status_change* sc = status_get_sc(bl); + struct view_data* vd = status_get_viewdata(bl); + unsigned char *buf = WBUFP(buffer,0); +#if PACKETVER < 20091103 + bool type = !pcdb_checkid(vd->class_); +#endif + unsigned short offset = 0; +#if PACKETVER >= 20091103 + const char *name; +#endif + sd = BL_CAST(BL_PC, bl); + +#if PACKETVER < 20091103 + if(type) + WBUFW(buf,0) = spawn?0x7c:0x78; + else +#endif +#if PACKETVER < 4 + WBUFW(buf,0) = spawn?0x79:0x78; +#elif PACKETVER < 7 + WBUFW(buf,0) = spawn?0x1d9:0x1d8; +#elif PACKETVER < 20080102 + WBUFW(buf,0) = spawn?0x22b:0x22a; +#elif PACKETVER < 20091103 + WBUFW(buf,0) = spawn?0x2ed:0x2ee; +#elif PACKETVER < 20101124 + WBUFW(buf,0) = spawn?0x7f8:0x7f9; +#else + WBUFW(buf,0) = spawn?0x858:0x857; +#endif + +#if PACKETVER >= 20091103 + name = status_get_name(bl); +#if PACKETVER < 20110111 + WBUFW(buf,2) = (spawn?62:63)+strlen(name); +#else + WBUFW(buf,2) = (spawn?64:65)+strlen(name); +#endif + WBUFB(buf,4) = clif_bl_type(bl); + offset+=3; + buf = WBUFP(buffer,offset); +#elif PACKETVER >= 20071106 + if (type) { //Non-player packets + WBUFB(buf,2) = clif_bl_type(bl); + offset++; + buf = WBUFP(buffer,offset); + } +#endif + WBUFL(buf, 2) = bl->id; + WBUFW(buf, 6) = status_get_speed(bl); + WBUFW(buf, 8) = (sc)? sc->opt1 : 0; + WBUFW(buf,10) = (sc)? sc->opt2 : 0; +#if PACKETVER < 20091103 + if (type&&spawn) { //uses an older and different packet structure + WBUFW(buf,12) = (sc)? sc->option : 0; + WBUFW(buf,14) = vd->hair_style; + WBUFW(buf,16) = vd->weapon; + WBUFW(buf,18) = vd->head_bottom; + WBUFW(buf,20) = vd->class_; //Pet armor (ignored by client) + WBUFW(buf,22) = vd->shield; + } else { +#endif +#if PACKETVER >= 20091103 + WBUFL(buf,12) = (sc)? sc->option : 0; + offset+=2; + buf = WBUFP(buffer,offset); +#elif PACKETVER >= 7 + if (!type) { + WBUFL(buf,12) = (sc)? sc->option : 0; + offset+=2; + buf = WBUFP(buffer,offset); + } else + WBUFW(buf,12) = (sc)? sc->option : 0; +#else + WBUFW(buf,12) = (sc)? sc->option : 0; +#endif + WBUFW(buf,14) = vd->class_; + WBUFW(buf,16) = vd->hair_style; + WBUFW(buf,18) = vd->weapon; +#if PACKETVER < 4 + WBUFW(buf,20) = vd->head_bottom; + WBUFW(buf,22) = vd->shield; +#else + WBUFW(buf,20) = vd->shield; + WBUFW(buf,22) = vd->head_bottom; +#endif +#if PACKETVER < 20091103 + } +#endif + WBUFW(buf,24) = vd->head_top; + WBUFW(buf,26) = vd->head_mid; + + if( bl->type == BL_NPC && vd->class_ == FLAG_CLASS ) + { //The hell, why flags work like this? + WBUFW(buf,22) = status_get_emblem_id(bl); + WBUFW(buf,24) = GetWord(status_get_guild_id(bl), 1); + WBUFW(buf,26) = GetWord(status_get_guild_id(bl), 0); + } + + WBUFW(buf,28) = vd->hair_color; + WBUFW(buf,30) = vd->cloth_color; + WBUFW(buf,32) = (sd)? sd->head_dir : 0; +#if PACKETVER < 20091103 + if (type&&spawn) { //End of packet 0x7c + WBUFB(buf,34) = (sd)?sd->status.karma:0; // karma + WBUFB(buf,35) = vd->sex; + WBUFPOS(buf,36,bl->x,bl->y,unit_getdir(bl)); + WBUFB(buf,39) = 0; + WBUFB(buf,40) = 0; + return packet_len(0x7c); + } +#endif +#if PACKETVER >= 20110111 + WBUFW(buf,34) = vd->robe; + offset+= 2; + buf = WBUFP(buffer,offset); +#endif + WBUFL(buf,34) = status_get_guild_id(bl); + WBUFW(buf,38) = status_get_emblem_id(bl); + WBUFW(buf,40) = (sd)? sd->status.manner : 0; +#if PACKETVER >= 20091103 + WBUFL(buf,42) = (sc)? sc->opt3 : 0; + offset+=2; + buf = WBUFP(buffer,offset); +#elif PACKETVER >= 7 + if (!type) { + WBUFL(buf,42) = (sc)? sc->opt3 : 0; + offset+=2; + buf = WBUFP(buffer,offset); + } else + WBUFW(buf,42) = (sc)? sc->opt3 : 0; +#else + WBUFW(buf,42) = (sc)? sc->opt3 : 0; +#endif + WBUFB(buf,44) = (sd)? sd->status.karma : 0; + WBUFB(buf,45) = vd->sex; + WBUFPOS(buf,46,bl->x,bl->y,unit_getdir(bl)); + WBUFB(buf,49) = (sd)? 5 : 0; + WBUFB(buf,50) = (sd)? 5 : 0; + if (!spawn) { + WBUFB(buf,51) = vd->dead_sit; + offset++; + buf = WBUFP(buffer,offset); + } + WBUFW(buf,51) = clif_setlevel(bl); +#if PACKETVER < 20091103 + if (type) //End for non-player packet + return packet_len(WBUFW(buffer,0)); +#endif +#if PACKETVER >= 20080102 + WBUFW(buf,53) = sd?sd->user_font:0; +#endif +#if PACKETVER >= 20091103 + memcpy((char*)WBUFP(buf,55), name, NAME_LENGTH); + return WBUFW(buffer,2); +#else + return packet_len(WBUFW(buffer,0)); +#endif +} + +/*========================================== + * Prepares 'unit walking' packet + *------------------------------------------*/ +static int clif_set_unit_walking(struct block_list* bl, struct unit_data* ud, unsigned char* buffer) +{ + struct map_session_data* sd; + struct status_change* sc = status_get_sc(bl); + struct view_data* vd = status_get_viewdata(bl); + unsigned char* buf = WBUFP(buffer,0); +#if PACKETVER >= 7 + unsigned short offset = 0; +#endif +#if PACKETVER >= 20091103 + const char *name; +#endif + + sd = BL_CAST(BL_PC, bl); + +#if PACKETVER < 4 + WBUFW(buf, 0) = 0x7b; +#elif PACKETVER < 7 + WBUFW(buf, 0) = 0x1da; +#elif PACKETVER < 20080102 + WBUFW(buf, 0) = 0x22c; +#elif PACKETVER < 20091103 + WBUFW(buf, 0) = 0x2ec; +#elif PACKETVER < 20101124 + WBUFW(buf, 0) = 0x7f7; +#else + WBUFW(buf, 0) = 0x856; +#endif + +#if PACKETVER >= 20091103 + name = status_get_name(bl); +#if PACKETVER < 20110111 + WBUFW(buf, 2) = 69+strlen(name); +#else + WBUFW(buf, 2) = 71+strlen(name); +#endif + offset+=2; + buf = WBUFP(buffer,offset); +#endif +#if PACKETVER >= 20071106 + WBUFB(buf, 2) = clif_bl_type(bl); + offset++; + buf = WBUFP(buffer,offset); +#endif + WBUFL(buf, 2) = bl->id; + WBUFW(buf, 6) = status_get_speed(bl); + WBUFW(buf, 8) = (sc)? sc->opt1 : 0; + WBUFW(buf,10) = (sc)? sc->opt2 : 0; +#if PACKETVER < 7 + WBUFW(buf,12) = (sc)? sc->option : 0; +#else + WBUFL(buf,12) = (sc)? sc->option : 0; + offset+=2; //Shift the rest of elements by 2 bytes. + buf = WBUFP(buffer,offset); +#endif + WBUFW(buf,14) = vd->class_; + WBUFW(buf,16) = vd->hair_style; + WBUFW(buf,18) = vd->weapon; +#if PACKETVER < 4 + WBUFW(buf,20) = vd->head_bottom; + WBUFL(buf,22) = gettick(); + WBUFW(buf,26) = vd->shield; +#else + WBUFW(buf,20) = vd->shield; + WBUFW(buf,22) = vd->head_bottom; + WBUFL(buf,24) = gettick(); +#endif + WBUFW(buf,28) = vd->head_top; + WBUFW(buf,30) = vd->head_mid; + WBUFW(buf,32) = vd->hair_color; + WBUFW(buf,34) = vd->cloth_color; + WBUFW(buf,36) = (sd)? sd->head_dir : 0; +#if PACKETVER >= 20110111 + WBUFW(buf,38) = vd->robe; + offset+= 2; + buf = WBUFP(buffer,offset); +#endif + WBUFL(buf,38) = status_get_guild_id(bl); + WBUFW(buf,42) = status_get_emblem_id(bl); + WBUFW(buf,44) = (sd)? sd->status.manner : 0; +#if PACKETVER < 7 + WBUFW(buf,46) = (sc)? sc->opt3 : 0; +#else + WBUFL(buf,46) = (sc)? sc->opt3 : 0; + offset+=2; //Shift the rest of elements by 2 bytes. + buf = WBUFP(buffer,offset); +#endif + WBUFB(buf,48) = (sd)? sd->status.karma : 0; + WBUFB(buf,49) = vd->sex; + WBUFPOS2(buf,50,bl->x,bl->y,ud->to_x,ud->to_y,8,8); + WBUFB(buf,56) = (sd)? 5 : 0; + WBUFB(buf,57) = (sd)? 5 : 0; + WBUFW(buf,58) = clif_setlevel(bl); +#if PACKETVER >= 20080102 + WBUFW(buf,60) = sd?sd->user_font:0; +#endif +#if PACKETVER >= 20091103 + memcpy((char*)WBUFP(buf,62), name, NAME_LENGTH); + return WBUFW(buffer,2); +#else + return packet_len(WBUFW(buffer,0)); +#endif +} + +//Modifies the buffer for disguise characters and sends it to self. +//Used for spawn/walk packets, where the ID offset changes for packetver >=9 +static void clif_setdisguise(struct block_list *bl, unsigned char *buf,int len) +{ +#if PACKETVER >= 20091103 + WBUFB(buf,4)= pcdb_checkid(status_get_viewdata(bl)->class_) ? 0x0 : 0x5; //PC_TYPE : NPC_MOB_TYPE + WBUFL(buf,5)=-bl->id; +#elif PACKETVER >= 20071106 + WBUFB(buf,2)= pcdb_checkid(status_get_viewdata(bl)->class_) ? 0x0 : 0x5; //PC_TYPE : NPC_MOB_TYPE + WBUFL(buf,3)=-bl->id; +#else + WBUFL(buf,2)=-bl->id; +#endif + clif_send(buf, len, bl, SELF); +} + + +/// Changes sprite of an NPC object (ZC_NPCSPRITE_CHANGE). +/// 01b0 <id>.L <type>.B <value>.L +/// type: +/// unused +void clif_class_change(struct block_list *bl,int class_,int type) +{ + unsigned char buf[16]; + + nullpo_retv(bl); + + if(!pcdb_checkid(class_)) + {// player classes yield missing sprites + WBUFW(buf,0)=0x1b0; + WBUFL(buf,2)=bl->id; + WBUFB(buf,6)=type; + WBUFL(buf,7)=class_; + clif_send(buf,packet_len(0x1b0),bl,AREA); + } +} + + +/// Notifies the client of an object's spirits. +/// 01d0 <id>.L <amount>.W (ZC_SPIRITS) +/// 01e1 <id>.L <amount>.W (ZC_SPIRITS2) +static void clif_spiritball_single(int fd, struct map_session_data *sd) +{ + WFIFOHEAD(fd, packet_len(0x1e1)); + WFIFOW(fd,0)=0x1e1; + WFIFOL(fd,2)=sd->bl.id; + WFIFOW(fd,6)=sd->spiritball; + WFIFOSET(fd, packet_len(0x1e1)); +} + +/*========================================== + * Kagerou/Oboro amulet spirit + *------------------------------------------*/ +static void clif_talisman_single(int fd, struct map_session_data *sd, short type) +{ + WFIFOHEAD(fd, packet_len(0x08cf)); + WFIFOW(fd,0)=0x08cf; + WFIFOL(fd,2)=sd->bl.id; + WFIFOW(fd,6)=type; + WFIFOW(fd,8)=sd->talisman[type]; + WFIFOSET(fd, packet_len(0x08cf)); +} + +/*========================================== + * Run when player changes map / refreshes + * Tells its client to display all weather settings being used by this map + *------------------------------------------*/ +static void clif_weather_check(struct map_session_data *sd) +{ + int16 m = sd->bl.m; + int fd = sd->fd; + + if (map[m].flag.snow + || map[m].flag.clouds + || map[m].flag.fog + || map[m].flag.fireworks + || map[m].flag.sakura + || map[m].flag.leaves + /** + * No longer available, keeping here just in case it's back someday. [Ind] + **/ + //|| map[m].flag.rain + || map[m].flag.clouds2) + { + if (map[m].flag.snow) + clif_specialeffect_single(&sd->bl, 162, fd); + if (map[m].flag.clouds) + clif_specialeffect_single(&sd->bl, 233, fd); + if (map[m].flag.clouds2) + clif_specialeffect_single(&sd->bl, 516, fd); + if (map[m].flag.fog) + clif_specialeffect_single(&sd->bl, 515, fd); + if (map[m].flag.fireworks) { + clif_specialeffect_single(&sd->bl, 297, fd); + clif_specialeffect_single(&sd->bl, 299, fd); + clif_specialeffect_single(&sd->bl, 301, fd); + } + if (map[m].flag.sakura) + clif_specialeffect_single(&sd->bl, 163, fd); + if (map[m].flag.leaves) + clif_specialeffect_single(&sd->bl, 333, fd); + /** + * No longer available, keeping here just in case it's back someday. [Ind] + **/ + //if (map[m].flag.rain) + // clif_specialeffect_single(&sd->bl, 161, fd); + } +} +/** + * Run when the weather on a map changes, throws all players in map id 'm' to clif_weather_check function + **/ +void clif_weather(int16 m) +{ + struct s_mapiterator* iter; + struct map_session_data *sd=NULL; + + iter = mapit_getallusers(); + for( sd = (struct map_session_data*)mapit_first(iter); mapit_exists(iter); sd = (struct map_session_data*)mapit_next(iter) ) + { + if( sd->bl.m == m ) + clif_weather_check(sd); + } + mapit_free(iter); +} +/** + * Main function to spawn a unit on the client (player/mob/pet/etc) + **/ +int clif_spawn(struct block_list *bl) +{ + unsigned char buf[128]; + struct view_data *vd; + int len; + + vd = status_get_viewdata(bl); + if( !vd || vd->class_ == INVISIBLE_CLASS ) + return 0; + + /** + * Hide NPC from maya purple card. + **/ + if(bl->type == BL_NPC && !((TBL_NPC*)bl)->chat_id && (((TBL_NPC*)bl)->sc.option&OPTION_INVISIBLE)) + return 0; + + len = clif_set_unit_idle(bl, buf,true); + clif_send(buf, len, bl, AREA_WOS); + if (disguised(bl)) + clif_setdisguise(bl, buf, len); + + if (vd->cloth_color) + clif_refreshlook(bl,bl->id,LOOK_CLOTHES_COLOR,vd->cloth_color,AREA_WOS); + + switch (bl->type) + { + case BL_PC: + { + TBL_PC *sd = ((TBL_PC*)bl); + int i; + if (sd->spiritball > 0) + clif_spiritball(&sd->bl); + if(sd->state.size==SZ_BIG) // tiny/big players [Valaris] + clif_specialeffect(bl,423,AREA); + else if(sd->state.size==SZ_MEDIUM) + clif_specialeffect(bl,421,AREA); + if( sd->bg_id && map[sd->bl.m].flag.battleground ) + clif_sendbgemblem_area(sd); + if( sd->sc.option&OPTION_MOUNTING ) { + //New Mounts are not complaint to the original method, so we gotta tell this guy that he is mounting. + clif_status_load_notick(&sd->bl,SI_ALL_RIDING,2,1,0,0); + } + for(i = 1; i < 5; i++){ + if( sd->talisman[i] > 0 ) + clif_talisman(sd, i); + } + #ifdef NEW_CARTS + if( sd->sc.data[SC_PUSH_CART] ) + clif_status_load_notick(&sd->bl, SI_ON_PUSH_CART, 2, sd->sc.data[SC_PUSH_CART]->val1, 0, 0); + #endif + #if PACKETVER <= 20120207 + if (sd->status.robe) + clif_refreshlook(bl,bl->id,LOOK_ROBE,sd->status.robe,AREA); + #endif + } + break; + case BL_MOB: + { + TBL_MOB *md = ((TBL_MOB*)bl); + if(md->special_state.size==SZ_BIG) // tiny/big mobs [Valaris] + clif_specialeffect(&md->bl,423,AREA); + else if(md->special_state.size==SZ_MEDIUM) + clif_specialeffect(&md->bl,421,AREA); + } + break; + case BL_NPC: + { + TBL_NPC *nd = ((TBL_NPC*)bl); + if( nd->size == SZ_BIG ) + clif_specialeffect(&nd->bl,423,AREA); + else if( nd->size == SZ_MEDIUM ) + clif_specialeffect(&nd->bl,421,AREA); + } + break; + case BL_PET: + if (vd->head_bottom) + clif_pet_equip_area((TBL_PET*)bl); // needed to display pet equip properly + break; + } + return 0; +} + +/// Sends information about owned homunculus to the client (ZC_PROPERTY_HOMUN). [orn] +/// 022e <name>.24B <modified>.B <level>.W <hunger>.W <intimacy>.W <equip id>.W <atk>.W <matk>.W <hit>.W <crit>.W <def>.W <mdef>.W <flee>.W <aspd>.W <hp>.W <max hp>.W <sp>.W <max sp>.W <exp>.L <max exp>.L <skill points>.W <atk range>.W +void clif_hominfo(struct map_session_data *sd, struct homun_data *hd, int flag) +{ + struct status_data *status; + unsigned char buf[128]; + int m_class; + + nullpo_retv(hd); + + status = &hd->battle_status; + m_class = hom_class2mapid(hd->homunculus.class_); + + memset(buf,0,packet_len(0x22e)); + WBUFW(buf,0)=0x22e; + memcpy(WBUFP(buf,2),hd->homunculus.name,NAME_LENGTH); + // Bit field, bit 0 : rename_flag (1 = already renamed), bit 1 : homunc vaporized (1 = true), bit 2 : homunc dead (1 = true) + WBUFB(buf,26)=(battle_config.hom_rename?0:hd->homunculus.rename_flag) | (hd->homunculus.vaporize << 1) | (hd->homunculus.hp?0:4); + WBUFW(buf,27)=hd->homunculus.level; + WBUFW(buf,29)=hd->homunculus.hunger; + WBUFW(buf,31)=(unsigned short) (hd->homunculus.intimacy / 100) ; + WBUFW(buf,33)=0; // equip id + WBUFW(buf,35)=cap_value(status->rhw.atk2+status->batk, 0, INT16_MAX); + WBUFW(buf,37)=cap_value(status->matk_max, 0, INT16_MAX); + WBUFW(buf,39)=status->hit; + if (battle_config.hom_setting&0x10) + WBUFW(buf,41)=status->luk/3 + 1; //crit is a +1 decimal value! Just display purpose.[Vicious] + else + WBUFW(buf,41)=status->cri/10; + WBUFW(buf,43)=status->def + status->vit ; + WBUFW(buf,45)=status->mdef; + WBUFW(buf,47)=status->flee; + WBUFW(buf,49)=(flag)?0:status->amotion; + if (status->max_hp > INT16_MAX) { + WBUFW(buf,51) = status->hp/(status->max_hp/100); + WBUFW(buf,53) = 100; + } else { + WBUFW(buf,51)=status->hp; + WBUFW(buf,53)=status->max_hp; + } + if (status->max_sp > INT16_MAX) { + WBUFW(buf,55) = status->sp/(status->max_sp/100); + WBUFW(buf,57) = 100; + } else { + WBUFW(buf,55)=status->sp; + WBUFW(buf,57)=status->max_sp; + } + WBUFL(buf,59)=hd->homunculus.exp; + if( ((m_class&HOM_REG) && hd->homunculus.level >= battle_config.hom_max_level) || ((m_class&HOM_S) && hd->homunculus.level >= battle_config.hom_S_max_level) ) + WBUFL(buf,63)=0; + else + WBUFL(buf,63)=hd->exp_next; + WBUFW(buf,67)=hd->homunculus.skillpts; + WBUFW(buf,69)=status_get_range(&hd->bl); + clif_send(buf,packet_len(0x22e),&sd->bl,SELF); +} + + +/// Notification about a change in homunuculus' state (ZC_CHANGESTATE_MER). +/// 0230 <type>.B <state>.B <id>.L <data>.L +/// type: +/// unused +/// state: +/// 0 = pre-init +/// 1 = intimacy +/// 2 = hunger +/// 3 = accessory? +/// ? = ignored +void clif_send_homdata(struct map_session_data *sd, int state, int param) +{ //[orn] + int fd = sd->fd; + + if ( (state == SP_INTIMATE) && (param >= 910) && (sd->hd->homunculus.class_ == sd->hd->homunculusDB->evo_class) ) + merc_hom_calc_skilltree(sd->hd, 0); + + WFIFOHEAD(fd, packet_len(0x230)); + WFIFOW(fd,0)=0x230; + WFIFOB(fd,2)=0; + WFIFOB(fd,3)=state; + WFIFOL(fd,4)=sd->hd->bl.id; + WFIFOL(fd,8)=param; + WFIFOSET(fd,packet_len(0x230)); +} + + +int clif_homskillinfoblock(struct map_session_data *sd) +{ //[orn] + struct homun_data *hd; + int fd = sd->fd; + int i,j,len=4,id; + WFIFOHEAD(fd, 4+37*MAX_HOMUNSKILL); + + hd = sd->hd; + if ( !hd ) + return 0 ; + + WFIFOW(fd,0)=0x235; + for ( i = 0; i < MAX_HOMUNSKILL; i++){ + if( (id = hd->homunculus.hskill[i].id) != 0 ){ + j = id - HM_SKILLBASE; + WFIFOW(fd,len ) = id; + WFIFOW(fd,len+2) = skill_get_inf(id); + WFIFOW(fd,len+4) = 0; + WFIFOW(fd,len+6) = hd->homunculus.hskill[j].lv; + WFIFOW(fd,len+8) = skill_get_sp(id,hd->homunculus.hskill[j].lv); + WFIFOW(fd,len+10)= skill_get_range2(&sd->hd->bl, id,hd->homunculus.hskill[j].lv); + safestrncpy((char*)WFIFOP(fd,len+12), skill_get_name(id), NAME_LENGTH); + WFIFOB(fd,len+36) = (hd->homunculus.hskill[j].lv < merc_skill_tree_get_max(id, hd->homunculus.class_))?1:0; + len+=37; + } + } + WFIFOW(fd,2)=len; + WFIFOSET(fd,len); + + return 0; +} + +void clif_homskillup(struct map_session_data *sd, uint16 skill_id) +{ //[orn] + struct homun_data *hd; + int fd, idx; + nullpo_retv(sd); + idx = skill_id - HM_SKILLBASE; + + fd=sd->fd; + hd=sd->hd; + + WFIFOHEAD(fd, packet_len(0x239)); + WFIFOW(fd,0) = 0x239; + WFIFOW(fd,2) = skill_id; + WFIFOW(fd,4) = hd->homunculus.hskill[idx].lv; + WFIFOW(fd,6) = skill_get_sp(skill_id,hd->homunculus.hskill[idx].lv); + WFIFOW(fd,8) = skill_get_range2(&hd->bl, skill_id,hd->homunculus.hskill[idx].lv); + WFIFOB(fd,10) = (hd->homunculus.hskill[idx].lv < skill_get_max(hd->homunculus.hskill[idx].id)) ? 1 : 0; + WFIFOSET(fd,packet_len(0x239)); +} + +int clif_hom_food(struct map_session_data *sd,int foodid,int fail) //[orn] +{ + int fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x22f)); + WFIFOW(fd,0)=0x22f; + WFIFOB(fd,2)=fail; + WFIFOW(fd,3)=foodid; + WFIFOSET(fd,packet_len(0x22f)); + + return 0; +} + + +/// Notifies the client, that it is walking (ZC_NOTIFY_PLAYERMOVE). +/// 0087 <walk start time>.L <walk data>.6B +void clif_walkok(struct map_session_data *sd) +{ + int fd=sd->fd; + + WFIFOHEAD(fd, packet_len(0x87)); + WFIFOW(fd,0)=0x87; + WFIFOL(fd,2)=gettick(); + WFIFOPOS2(fd,6,sd->bl.x,sd->bl.y,sd->ud.to_x,sd->ud.to_y,8,8); + WFIFOSET(fd,packet_len(0x87)); +} + + +static void clif_move2(struct block_list *bl, struct view_data *vd, struct unit_data *ud) +{ + uint8 buf[128]; + int len; + + len = clif_set_unit_walking(bl,ud,buf); + clif_send(buf,len,bl,AREA_WOS); + if (disguised(bl)) + clif_setdisguise(bl, buf, len); + + if(vd->cloth_color) + clif_refreshlook(bl,bl->id,LOOK_CLOTHES_COLOR,vd->cloth_color,AREA_WOS); + + switch(bl->type) + { + case BL_PC: + { + TBL_PC *sd = ((TBL_PC*)bl); +// clif_movepc(sd); + if(sd->state.size==SZ_BIG) // tiny/big players [Valaris] + clif_specialeffect(&sd->bl,423,AREA); + else if(sd->state.size==SZ_MEDIUM) + clif_specialeffect(&sd->bl,421,AREA); + } + break; + case BL_MOB: + { + TBL_MOB *md = ((TBL_MOB*)bl); + if(md->special_state.size==SZ_BIG) // tiny/big mobs [Valaris] + clif_specialeffect(&md->bl,423,AREA); + else if(md->special_state.size==SZ_MEDIUM) + clif_specialeffect(&md->bl,421,AREA); + } + break; + case BL_PET: + if( vd->head_bottom ) + {// needed to display pet equip properly + clif_pet_equip_area((TBL_PET*)bl); + } + break; + } +} + + +/// Notifies clients in an area, that an other visible object is walking (ZC_NOTIFY_PLAYERMOVE). +/// 0086 <id>.L <walk data>.6B <walk start time>.L +/// Note: unit must not be self +void clif_move(struct unit_data *ud) +{ + unsigned char buf[16]; + struct view_data* vd; + struct block_list* bl = ud->bl; + + vd = status_get_viewdata(bl); + if (!vd || vd->class_ == INVISIBLE_CLASS) + return; //This performance check is needed to keep GM-hidden objects from being notified to bots. + + /** + * Hide NPC from maya purple card. + **/ + if(bl->type == BL_NPC && !((TBL_NPC*)bl)->chat_id && (((TBL_NPC*)bl)->sc.option&OPTION_INVISIBLE)) + return; + + if (ud->state.speed_changed) { + // Since we don't know how to update the speed of other objects, + // use the old walk packet to update the data. + ud->state.speed_changed = 0; + clif_move2(bl, vd, ud); + return; + } + + WBUFW(buf,0)=0x86; + WBUFL(buf,2)=bl->id; + WBUFPOS2(buf,6,bl->x,bl->y,ud->to_x,ud->to_y,8,8); + WBUFL(buf,12)=gettick(); + clif_send(buf, packet_len(0x86), bl, AREA_WOS); + if (disguised(bl)) + { + WBUFL(buf,2)=-bl->id; + clif_send(buf, packet_len(0x86), bl, SELF); + } +} + + +/*========================================== + * Delays the map_quit of a player after they are disconnected. [Skotlex] + *------------------------------------------*/ +static int clif_delayquit(int tid, unsigned int tick, int id, intptr_t data) +{ + struct map_session_data *sd = NULL; + + //Remove player from map server + if ((sd = map_id2sd(id)) != NULL && sd->fd == 0) //Should be a disconnected player. + map_quit(sd); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +void clif_quitsave(int fd,struct map_session_data *sd) +{ + if (!battle_config.prevent_logout || + DIFF_TICK(gettick(), sd->canlog_tick) > battle_config.prevent_logout) + map_quit(sd); + else if (sd->fd) + { //Disassociate session from player (session is deleted after this function was called) + //And set a timer to make him quit later. + session[sd->fd]->session_data = NULL; + sd->fd = 0; + add_timer(gettick() + 10000, clif_delayquit, sd->bl.id, 0); + } +} + +/// Notifies the client of a position change to coordinates on given map (ZC_NPCACK_MAPMOVE). +/// 0091 <map name>.16B <x>.W <y>.W +void clif_changemap(struct map_session_data *sd, short map, int x, int y) +{ + int fd; + nullpo_retv(sd); + fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x91)); + WFIFOW(fd,0) = 0x91; + mapindex_getmapname_ext(mapindex_id2name(map), (char*)WFIFOP(fd,2)); + WFIFOW(fd,18) = x; + WFIFOW(fd,20) = y; + WFIFOSET(fd,packet_len(0x91)); +} + + +/// Notifies the client of a position change to coordinates on given map, which is on another map-server (ZC_NPCACK_SERVERMOVE). +/// 0092 <map name>.16B <x>.W <y>.W <ip>.L <port>.W +void clif_changemapserver(struct map_session_data* sd, unsigned short map_index, int x, int y, uint32 ip, uint16 port) +{ + int fd; + nullpo_retv(sd); + fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x92)); + WFIFOW(fd,0) = 0x92; + mapindex_getmapname_ext(mapindex_id2name(map_index), (char*)WFIFOP(fd,2)); + WFIFOW(fd,18) = x; + WFIFOW(fd,20) = y; + WFIFOL(fd,22) = htonl(ip); + WFIFOW(fd,26) = ntows(htons(port)); // [!] LE byte order here [!] + WFIFOSET(fd,packet_len(0x92)); +} + + +void clif_blown(struct block_list *bl) +{ +//Aegis packets says fixpos, but it's unsure whether slide works better or not. +// clif_fixpos(bl); + clif_slide(bl, bl->x, bl->y); +} + + +/// Visually moves(slides) a character to x,y. If the target cell +/// isn't walkable, the char doesn't move at all. If the char is +/// sitting it will stand up (ZC_STOPMOVE). +/// 0088 <id>.L <x>.W <y>.W +void clif_fixpos(struct block_list *bl) +{ + unsigned char buf[10]; + nullpo_retv(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(0x88), bl, AREA); + + if( disguised(bl) ) + { + WBUFL(buf,2) = -bl->id; + clif_send(buf, packet_len(0x88), bl, SELF); + } +} + + +/// Displays the buy/sell dialog of an NPC shop (ZC_SELECT_DEALTYPE). +/// 00c4 <shop id>.L +void clif_npcbuysell(struct map_session_data* sd, int id) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd, packet_len(0xc4)); + WFIFOW(fd,0)=0xc4; + WFIFOL(fd,2)=id; + WFIFOSET(fd,packet_len(0xc4)); +} + + +/// Presents list of items, that can be bought in an NPC shop (ZC_PC_PURCHASE_ITEMLIST). +/// 00c6 <packet len>.W { <price>.L <discount price>.L <item type>.B <name id>.W }* +void clif_buylist(struct map_session_data *sd, struct npc_data *nd) +{ + int fd,i,c; + + nullpo_retv(sd); + nullpo_retv(nd); + + fd = sd->fd; + WFIFOHEAD(fd, 4 + nd->u.shop.count * 11); + WFIFOW(fd,0) = 0xc6; + + c = 0; + for( i = 0; i < nd->u.shop.count; i++ ) + { + struct item_data* id = itemdb_exists(nd->u.shop.shop_item[i].nameid); + int val = nd->u.shop.shop_item[i].value; + if( id == NULL ) + continue; + WFIFOL(fd, 4+c*11) = val; + WFIFOL(fd, 8+c*11) = pc_modifybuyvalue(sd,val); + WFIFOB(fd,12+c*11) = itemtype(id->type); + WFIFOW(fd,13+c*11) = ( id->view_id > 0 ) ? id->view_id : id->nameid; + c++; + } + + WFIFOW(fd,2) = 4 + c*11; + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Presents list of items, that can be sold to an NPC shop (ZC_PC_SELL_ITEMLIST). +/// 00c7 <packet len>.W { <index>.W <price>.L <overcharge price>.L }* +void clif_selllist(struct map_session_data *sd) +{ + int fd,i,c=0,val; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd, MAX_INVENTORY * 10 + 4); + WFIFOW(fd,0)=0xc7; + for( i = 0; i < MAX_INVENTORY; i++ ) + { + if( sd->status.inventory[i].nameid > 0 && sd->inventory_data[i] ) + { + if( !itemdb_cansell(&sd->status.inventory[i], pc_get_group_level(sd)) ) + continue; + + if( sd->status.inventory[i].expire_time ) + continue; // Cannot Sell Rental Items + + val=sd->inventory_data[i]->value_sell; + if( val < 0 ) + continue; + WFIFOW(fd,4+c*10)=i+2; + WFIFOL(fd,6+c*10)=val; + WFIFOL(fd,10+c*10)=pc_modifysellvalue(sd,val); + c++; + } + } + WFIFOW(fd,2)=c*10+4; + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Displays an NPC dialog message (ZC_SAY_DIALOG). +/// 00b4 <packet len>.W <npc id>.L <message>.?B +/// Client behavior (dialog window): +/// - disable mouse targeting +/// - open the dialog window +/// - set npcid of dialog window (0 by default) +/// - if set to clear on next mes, clear contents +/// - append this text +void clif_scriptmes(struct map_session_data *sd, int npcid, const char *mes) +{ + int fd = sd->fd; + int slen = strlen(mes) + 9; + + WFIFOHEAD(fd, slen); + WFIFOW(fd,0)=0xb4; + WFIFOW(fd,2)=slen; + WFIFOL(fd,4)=npcid; + memcpy((char*)WFIFOP(fd,8), mes, slen-8); + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Adds a 'next' button to an NPC dialog (ZC_WAIT_DIALOG). +/// 00b5 <npc id>.L +/// Client behavior (dialog window): +/// - disable mouse targeting +/// - open the dialog window +/// - add 'next' button +/// When 'next' is pressed: +/// - 00B9 <npcid of dialog window>.L +/// - set to clear on next mes +/// - remove 'next' button +void clif_scriptnext(struct map_session_data *sd,int npcid) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd, packet_len(0xb5)); + WFIFOW(fd,0)=0xb5; + WFIFOL(fd,2)=npcid; + WFIFOSET(fd,packet_len(0xb5)); +} + + +/// Adds a 'close' button to an NPC dialog (ZC_CLOSE_DIALOG). +/// 00b6 <npc id>.L +/// Client behavior: +/// - if dialog window is open: +/// - remove 'next' button +/// - add 'close' button +/// - else: +/// - enable mouse targeting +/// - close the dialog window +/// - close the menu window +/// When 'close' is pressed: +/// - enable mouse targeting +/// - close the dialog window +/// - close the menu window +/// - 0146 <npcid of dialog window>.L +void clif_scriptclose(struct map_session_data *sd, int npcid) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd, packet_len(0xb6)); + WFIFOW(fd,0)=0xb6; + WFIFOL(fd,2)=npcid; + WFIFOSET(fd,packet_len(0xb6)); +} + +/*========================================== + * + *------------------------------------------*/ +void clif_sendfakenpc(struct map_session_data *sd, int npcid) +{ + unsigned char *buf; + int fd = sd->fd; + sd->state.using_fake_npc = 1; + + WFIFOHEAD(fd, packet_len(0x78)); + buf = WFIFOP(fd,0); + memset(WBUFP(buf,0), 0, packet_len(0x78)); + WBUFW(buf,0)=0x78; +#if PACKETVER >= 20071106 + WBUFB(buf,2) = 0; // object type + buf = WFIFOP(fd,1); +#endif + WBUFL(buf,2)=npcid; + WBUFW(buf,14)=111; + WBUFPOS(buf,46,sd->bl.x,sd->bl.y,sd->ud.dir); + WBUFB(buf,49)=5; + WBUFB(buf,50)=5; + WFIFOSET(fd, packet_len(0x78)); +} + + +/// Displays an NPC dialog menu (ZC_MENU_LIST). +/// 00b7 <packet len>.W <npc id>.L <menu items>.?B +/// Client behavior: +/// - disable mouse targeting +/// - close the menu window +/// - open the menu window +/// - add options to the menu (separated in the text by ":") +/// - set npcid of menu window +/// - if dialog window is open: +/// - remove 'next' button +/// When 'ok' is pressed: +/// - 00B8 <npcid of menu window>.L <selected option>.B +/// - close the menu window +/// When 'cancel' is pressed: +/// - 00B8 <npcid of menu window>.L <-1>.B +/// - enable mouse targeting +/// - close a bunch of windows... +/// WARNING: the 'cancel' button closes other windows besides the dialog window and the menu window. +/// Which suggests their have intertwined behavior. (probably the mouse targeting) +/// TODO investigate behavior of other windows [FlavioJS] +void clif_scriptmenu(struct map_session_data* sd, int npcid, const char* mes) +{ + int fd = sd->fd; + int slen = strlen(mes) + 9; + struct block_list *bl = NULL; + + if (!sd->state.using_fake_npc && (npcid == fake_nd->bl.id || ((bl = map_id2bl(npcid)) && (bl->m!=sd->bl.m || + bl->x<sd->bl.x-AREA_SIZE-1 || bl->x>sd->bl.x+AREA_SIZE+1 || + bl->y<sd->bl.y-AREA_SIZE-1 || bl->y>sd->bl.y+AREA_SIZE+1)))) + clif_sendfakenpc(sd, npcid); + + WFIFOHEAD(fd, slen); + WFIFOW(fd,0)=0xb7; + WFIFOW(fd,2)=slen; + WFIFOL(fd,4)=npcid; + memcpy((char*)WFIFOP(fd,8), mes, slen-8); + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Displays an NPC dialog input box for numbers (ZC_OPEN_EDITDLG). +/// 0142 <npc id>.L +/// Client behavior (inputnum window): +/// - if npcid exists in the client: +/// - open the inputnum window +/// - set npcid of inputnum window +/// When 'ok' is pressed: +/// - if inputnum window has text: +/// - if npcid exists in the client: +/// - 0143 <npcid of inputnum window>.L <atoi(text)>.L +/// - close inputnum window +void clif_scriptinput(struct map_session_data *sd, int npcid) +{ + int fd; + struct block_list *bl = NULL; + + nullpo_retv(sd); + + if (!sd->state.using_fake_npc && (npcid == fake_nd->bl.id || ((bl = map_id2bl(npcid)) && (bl->m!=sd->bl.m || + bl->x<sd->bl.x-AREA_SIZE-1 || bl->x>sd->bl.x+AREA_SIZE+1 || + bl->y<sd->bl.y-AREA_SIZE-1 || bl->y>sd->bl.y+AREA_SIZE+1)))) + clif_sendfakenpc(sd, npcid); + + fd=sd->fd; + WFIFOHEAD(fd, packet_len(0x142)); + WFIFOW(fd,0)=0x142; + WFIFOL(fd,2)=npcid; + WFIFOSET(fd,packet_len(0x142)); +} + + +/// Displays an NPC dialog input box for numbers (ZC_OPEN_EDITDLGSTR). +/// 01d4 <npc id>.L +/// Client behavior (inputstr window): +/// - if npcid is 0 or npcid exists in the client: +/// - open the inputstr window +/// - set npcid of inputstr window +/// When 'ok' is pressed: +/// - if inputstr window has text and isn't an insult(manner.txt): +/// - if npcid is 0 or npcid exists in the client: +/// - 01d5 <packetlen>.W <npcid of inputstr window>.L <text>.?B +/// - close inputstr window +void clif_scriptinputstr(struct map_session_data *sd, int npcid) +{ + int fd; + struct block_list *bl = NULL; + + nullpo_retv(sd); + + if (!sd->state.using_fake_npc && (npcid == fake_nd->bl.id || ((bl = map_id2bl(npcid)) && (bl->m!=sd->bl.m || + bl->x<sd->bl.x-AREA_SIZE-1 || bl->x>sd->bl.x+AREA_SIZE+1 || + bl->y<sd->bl.y-AREA_SIZE-1 || bl->y>sd->bl.y+AREA_SIZE+1)))) + clif_sendfakenpc(sd, npcid); + + fd=sd->fd; + WFIFOHEAD(fd, packet_len(0x1d4)); + WFIFOW(fd,0)=0x1d4; + WFIFOL(fd,2)=npcid; + WFIFOSET(fd,packet_len(0x1d4)); +} + + +/// Marks a position on client's minimap (ZC_COMPASS). +/// 0144 <npc id>.L <type>.L <x>.L <y>.L <id>.B <color>.L +/// npc id: +/// is ignored in the client +/// type: +/// 0 = display mark for 15 seconds +/// 1 = display mark until dead or teleported +/// 2 = remove mark +/// color: +/// 0x00RRGGBB +void clif_viewpoint(struct map_session_data *sd, int npc_id, int type, int x, int y, int id, int color) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd, packet_len(0x144)); + 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(0x144)); +} + + +/// Displays an illustration image. +/// 0145 <image name>.16B <type>.B (ZC_SHOW_IMAGE) +/// 01b3 <image name>.64B <type>.B (ZC_SHOW_IMAGE2) +/// type: +/// 0 = bottom left corner +/// 1 = bottom middle +/// 2 = bottom right corner +/// 3 = middle of screen, inside a movable window +/// 4 = middle of screen, movable with a close button, chrome-less +void clif_cutin(struct map_session_data* sd, const char* image, int type) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd, packet_len(0x1b3)); + WFIFOW(fd,0)=0x1b3; + strncpy((char*)WFIFOP(fd,2),image,64); + WFIFOB(fd,66)=type; + WFIFOSET(fd,packet_len(0x1b3)); +} + + +/*========================================== + * Fills in card data from the given item and into the buffer. [Skotlex] + *------------------------------------------*/ +static void clif_addcards(unsigned char* buf, struct item* item) +{ + int i=0,j; + if( item == NULL ) { //Blank data + WBUFW(buf,0) = 0; + WBUFW(buf,2) = 0; + WBUFW(buf,4) = 0; + WBUFW(buf,6) = 0; + return; + } + if( item->card[0] == CARD0_PET ) { //pet eggs + WBUFW(buf,0) = 0; + WBUFW(buf,2) = 0; + WBUFW(buf,4) = 0; + WBUFW(buf,6) = item->card[3]; //Pet renamed flag. + return; + } + if( item->card[0] == CARD0_FORGE || item->card[0] == CARD0_CREATE ) { //Forged/created items + WBUFW(buf,0) = item->card[0]; + WBUFW(buf,2) = item->card[1]; + WBUFW(buf,4) = item->card[2]; + WBUFW(buf,6) = item->card[3]; + return; + } + //Client only receives four cards.. so randomly send them a set of cards. [Skotlex] + if( MAX_SLOTS > 4 && (j = itemdb_slot(item->nameid)) > 4 ) + i = rnd()%(j-3); //eg: 6 slots, possible i values: 0->3, 1->4, 2->5 => i = rnd()%3; + + //Normal items. + if( item->card[i] > 0 && (j=itemdb_viewid(item->card[i])) > 0 ) + WBUFW(buf,0) = j; + else + WBUFW(buf,0) = item->card[i]; + + if( item->card[++i] > 0 && (j=itemdb_viewid(item->card[i])) > 0 ) + WBUFW(buf,2) = j; + else + WBUFW(buf,2) = item->card[i]; + + if( item->card[++i] > 0 && (j=itemdb_viewid(item->card[i])) > 0 ) + WBUFW(buf,4) = j; + else + WBUFW(buf,4) = item->card[i]; + + if( item->card[++i] > 0 && (j=itemdb_viewid(item->card[i])) > 0 ) + WBUFW(buf,6) = j; + else + WBUFW(buf,6) = item->card[i]; +} + + +/// Notifies the client, about a received inventory item or the result of a pick-up request. +/// 00a0 <index>.W <amount>.W <name id>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W <equip location>.W <item type>.B <result>.B (ZC_ITEM_PICKUP_ACK) +/// 029a <index>.W <amount>.W <name id>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W <equip location>.W <item type>.B <result>.B <expire time>.L (ZC_ITEM_PICKUP_ACK2) +/// 02d4 <index>.W <amount>.W <name id>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W <equip location>.W <item type>.B <result>.B <expire time>.L <bindOnEquipType>.W (ZC_ITEM_PICKUP_ACK3) +void clif_additem(struct map_session_data *sd, int n, int amount, int fail) +{ + int fd; +#if PACKETVER < 20061218 + const int cmd = 0xa0; +#elif PACKETVER < 20071002 + const int cmd = 0x29a; +#else + const int cmd = 0x2d4; +#endif + nullpo_retv(sd); + + fd = sd->fd; + if( !session_isActive(fd) ) //Sasuke- + return; + + WFIFOHEAD(fd,packet_len(cmd)); + if( fail ) + { + WFIFOW(fd,0)=cmd; + WFIFOW(fd,2)=n+2; + WFIFOW(fd,4)=amount; + WFIFOW(fd,6)=0; + WFIFOB(fd,8)=0; + WFIFOB(fd,9)=0; + WFIFOB(fd,10)=0; + WFIFOW(fd,11)=0; + WFIFOW(fd,13)=0; + WFIFOW(fd,15)=0; + WFIFOW(fd,17)=0; + WFIFOW(fd,19)=0; + WFIFOB(fd,21)=0; + WFIFOB(fd,22)=fail; +#if PACKETVER >= 20061218 + WFIFOL(fd,23)=0; +#endif +#if PACKETVER >= 20071002 + WFIFOW(fd,27)=0; // unknown +#endif + } + else + { + if( n < 0 || n >= MAX_INVENTORY || sd->status.inventory[n].nameid <=0 || sd->inventory_data[n] == NULL ) + return; + + WFIFOW(fd,0)=cmd; + WFIFOW(fd,2)=n+2; + WFIFOW(fd,4)=amount; + if (sd->inventory_data[n]->view_id > 0) + WFIFOW(fd,6)=sd->inventory_data[n]->view_id; + else + WFIFOW(fd,6)=sd->status.inventory[n].nameid; + WFIFOB(fd,8)=sd->status.inventory[n].identify; + WFIFOB(fd,9)=sd->status.inventory[n].attribute; + WFIFOB(fd,10)=sd->status.inventory[n].refine; + clif_addcards(WFIFOP(fd,11), &sd->status.inventory[n]); + WFIFOW(fd,19)=pc_equippoint(sd,n); + WFIFOB(fd,21)=itemtype(sd->inventory_data[n]->type); + WFIFOB(fd,22)=fail; +#if PACKETVER >= 20061218 + WFIFOL(fd,23)=sd->status.inventory[n].expire_time; +#endif +#if PACKETVER >= 20071002 + WFIFOW(fd,27)=0; // unknown +#endif + } + + WFIFOSET(fd,packet_len(cmd)); +} + + +/// Notifies the client, that an inventory item was deleted or dropped (ZC_ITEM_THROW_ACK). +/// 00af <index>.W <amount>.W +void clif_dropitem(struct map_session_data *sd,int n,int amount) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd, packet_len(0xaf)); + WFIFOW(fd,0)=0xaf; + WFIFOW(fd,2)=n+2; + WFIFOW(fd,4)=amount; + WFIFOSET(fd,packet_len(0xaf)); +} + + +/// Notifies the client, that an inventory item was deleted (ZC_DELETE_ITEM_FROM_BODY). +/// 07fa <delete type>.W <index>.W <amount>.W +/// delete type: +/// 0 = Normal +/// 1 = Item used for a skill +/// 2 = Refine failed +/// 3 = Material changed +/// 4 = Moved to storage +/// 5 = Moved to cart +/// 6 = Item sold +/// 7 = Consumed by Four Spirit Analysis (SO_EL_ANALYSIS) skill +void clif_delitem(struct map_session_data *sd,int n,int amount, short reason) +{ +#if PACKETVER < 20091117 + clif_dropitem(sd,n,amount); +#else + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + + WFIFOHEAD(fd, packet_len(0x7fa)); + WFIFOW(fd,0)=0x7fa; + WFIFOW(fd,2)=reason; + WFIFOW(fd,4)=n+2; + WFIFOW(fd,6)=amount; + WFIFOSET(fd,packet_len(0x7fa)); +#endif +} + + +// Simplifies inventory/cart/storage packets by handling the packet section relevant to items. [Skotlex] +// Equip is >= 0 for equippable items (holds the equip-point, is 0 for pet +// armor/egg) -1 for stackable items, -2 for stackable items where arrows must send in the equip-point. +void clif_item_sub(unsigned char *buf, int n, struct item *i, struct item_data *id, int equip) +{ + if (id->view_id > 0) + WBUFW(buf,n)=id->view_id; + else + WBUFW(buf,n)=i->nameid; + WBUFB(buf,n+2)=itemtype(id->type); + WBUFB(buf,n+3)=i->identify; + if (equip >= 0) { //Equippable item + WBUFW(buf,n+4)=equip; + WBUFW(buf,n+6)=i->equip; + WBUFB(buf,n+8)=i->attribute; + WBUFB(buf,n+9)=i->refine; + } else { //Stackable item. + WBUFW(buf,n+4)=i->amount; + if (equip == -2 && id->equip == EQP_AMMO) + WBUFW(buf,n+6)=EQP_AMMO; + else + WBUFW(buf,n+6)=0; + } + +} +void clif_favorite_item(struct map_session_data* sd, unsigned short index); +//Unified inventory function which sends all of the inventory (requires two packets, one for equipable items and one for stackable ones. [Skotlex] +void clif_inventorylist(struct map_session_data *sd) +{ + int i,n,ne,arrow=-1; + unsigned char *buf; + unsigned char *bufe; + +#if PACKETVER < 5 + const int s = 10; //Entry size. +#elif PACKETVER < 20080102 + const int s = 18; +#else + const int s = 22; +#endif +#if PACKETVER < 20071002 + const int se = 20; +#elif PACKETVER < 20100629 + const int se = 26; +#else + const int se = 28; +#endif + + buf = (unsigned char*)aMalloc(MAX_INVENTORY * s + 4); + bufe = (unsigned char*)aMalloc(MAX_INVENTORY * se + 4); + + for( i = 0, n = 0, ne = 0; i < MAX_INVENTORY; i++ ) + { + if( sd->status.inventory[i].nameid <=0 || sd->inventory_data[i] == NULL ) + continue; + + if( !itemdb_isstackable2(sd->inventory_data[i]) ) + { //Non-stackable (Equippable) + WBUFW(bufe,ne*se+4)=i+2; + clif_item_sub(bufe, ne*se+6, &sd->status.inventory[i], sd->inventory_data[i], pc_equippoint(sd,i)); + clif_addcards(WBUFP(bufe, ne*se+16), &sd->status.inventory[i]); +#if PACKETVER >= 20071002 + WBUFL(bufe,ne*se+24)=sd->status.inventory[i].expire_time; + WBUFW(bufe,ne*se+28)=0; //Unknown +#endif +#if PACKETVER >= 20100629 + if (sd->inventory_data[i]->equip&EQP_VISIBLE) + WBUFW(bufe,ne*se+30)= sd->inventory_data[i]->look; + else + WBUFW(bufe,ne*se+30)=0; +#endif + ne++; + } + else + { //Stackable. + WBUFW(buf,n*s+4)=i+2; + clif_item_sub(buf, n*s+6, &sd->status.inventory[i], sd->inventory_data[i], -2); + if( sd->inventory_data[i]->equip == EQP_AMMO && sd->status.inventory[i].equip ) + arrow=i; +#if PACKETVER >= 5 + clif_addcards(WBUFP(buf, n*s+14), &sd->status.inventory[i]); +#endif +#if PACKETVER >= 20080102 + WBUFL(buf,n*s+22)=sd->status.inventory[i].expire_time; +#endif + n++; + } + } + if( n ) + { +#if PACKETVER < 5 + WBUFW(buf,0)=0xa3; +#elif PACKETVER < 20080102 + WBUFW(buf,0)=0x1ee; +#else + WBUFW(buf,0)=0x2e8; +#endif + WBUFW(buf,2)=4+n*s; + clif_send(buf, WBUFW(buf,2), &sd->bl, SELF); + } + if( arrow >= 0 ) + clif_arrowequip(sd,arrow); + + if( ne ) + { +#if PACKETVER < 20071002 + WBUFW(bufe,0)=0xa4; +#else + WBUFW(bufe,0)=0x2d0; +#endif + WBUFW(bufe,2)=4+ne*se; + clif_send(bufe, WBUFW(bufe,2), &sd->bl, SELF); + } +#if PACKETVER >= 20111122 + for( i = 0; i < MAX_INVENTORY; i++ ) { + if( sd->status.inventory[i].nameid <= 0 || sd->inventory_data[i] == NULL ) + continue; + + if ( sd->status.inventory[i].favorite ) + clif_favorite_item(sd, i); + } +#endif + + if( buf ) aFree(buf); + if( bufe ) aFree(bufe); +} + +//Required when items break/get-repaired. Only sends equippable item list. +void clif_equiplist(struct map_session_data *sd) +{ + int i,n,fd = sd->fd; + unsigned char *buf; +#if PACKETVER < 20071002 + const int cmd = 20; +#elif PACKETVER < 20100629 + const int cmd = 26; +#else + const int cmd = 28; +#endif + + WFIFOHEAD(fd, MAX_INVENTORY * cmd + 4); + buf = WFIFOP(fd,0); + + for(i=0,n=0;i<MAX_INVENTORY;i++){ + if (sd->status.inventory[i].nameid <=0 || sd->inventory_data[i] == NULL) + continue; + + if(itemdb_isstackable2(sd->inventory_data[i])) + continue; + //Equippable + WBUFW(buf,n*cmd+4)=i+2; + clif_item_sub(buf, n*cmd+6, &sd->status.inventory[i], sd->inventory_data[i], pc_equippoint(sd,i)); + clif_addcards(WBUFP(buf, n*cmd+16), &sd->status.inventory[i]); +#if PACKETVER >= 20071002 + WBUFL(buf,n*cmd+24)=sd->status.inventory[i].expire_time; + WBUFW(buf,n*cmd+28)=0; //Unknown +#endif +#if PACKETVER >= 20100629 + if (sd->inventory_data[i]->equip&EQP_VISIBLE) + WBUFW(buf,n*cmd+30)= sd->inventory_data[i]->look; + else + WBUFW(buf,n*cmd+30)=0; +#endif + n++; + } + if (n) { +#if PACKETVER < 20071002 + WBUFW(buf,0)=0xa4; +#else + WBUFW(buf,0)=0x2d0; +#endif + WBUFW(buf,2)=4+n*cmd; + WFIFOSET(fd,WFIFOW(fd,2)); + } +} + +void clif_storagelist(struct map_session_data* sd, struct item* items, int items_length) +{ + struct item_data *id; + int i,n,ne; + unsigned char *buf; + unsigned char *bufe; +#if PACKETVER < 5 + const int s = 10; //Entry size. +#elif PACKETVER < 20080102 + const int s = 18; +#else + const int s = 22; +#endif +#if PACKETVER < 20071002 + const int cmd = 20; +#elif PACKETVER < 20100629 + const int cmd = 26; +#else + const int cmd = 28; +#endif + + buf = (unsigned char*)aMalloc(items_length * s + 4); + bufe = (unsigned char*)aMalloc(items_length * cmd + 4); + + for( i = 0, n = 0, ne = 0; i < items_length; i++ ) + { + if( items[i].nameid <= 0 ) + continue; + id = itemdb_search(items[i].nameid); + if( !itemdb_isstackable2(id) ) + { //Equippable + WBUFW(bufe,ne*cmd+4)=i+1; + clif_item_sub(bufe, ne*cmd+6, &items[i], id, id->equip); + clif_addcards(WBUFP(bufe, ne*cmd+16), &items[i]); +#if PACKETVER >= 20071002 + WBUFL(bufe,ne*cmd+24)=items[i].expire_time; + WBUFW(bufe,ne*cmd+28)=0; //Unknown +#endif + ne++; + } + else + { //Stackable + WBUFW(buf,n*s+4)=i+1; + clif_item_sub(buf, n*s+6, &items[i], id,-1); +#if PACKETVER >= 5 + clif_addcards(WBUFP(buf,n*s+14), &items[i]); +#endif +#if PACKETVER >= 20080102 + WBUFL(buf,n*s+22)=items[i].expire_time; +#endif + n++; + } + } + if( n ) + { +#if PACKETVER < 5 + WBUFW(buf,0)=0xa5; +#elif PACKETVER < 20080102 + WBUFW(buf,0)=0x1f0; +#else + WBUFW(buf,0)=0x2ea; +#endif + WBUFW(buf,2)=4+n*s; + clif_send(buf, WBUFW(buf,2), &sd->bl, SELF); + } + if( ne ) + { +#if PACKETVER < 20071002 + WBUFW(bufe,0)=0xa6; +#else + WBUFW(bufe,0)=0x2d1; +#endif + WBUFW(bufe,2)=4+ne*cmd; + clif_send(bufe, WBUFW(bufe,2), &sd->bl, SELF); + } + + if( buf ) aFree(buf); + if( bufe ) aFree(bufe); +} + +void clif_cartlist(struct map_session_data *sd) +{ + struct item_data *id; + int i,n,ne; + unsigned char *buf; + unsigned char *bufe; +#if PACKETVER < 5 + const int s = 10; //Entry size. +#elif PACKETVER < 20080102 + const int s = 18; +#else + const int s = 22; +#endif +#if PACKETVER < 20071002 + const int cmd = 20; +#elif PACKETVER < 20100629 + const int cmd = 26; +#else + const int cmd = 28; +#endif + + buf = (unsigned char*)aMalloc(MAX_CART * s + 4); + bufe = (unsigned char*)aMalloc(MAX_CART * cmd + 4); + + for( i = 0, n = 0, ne = 0; i < MAX_CART; i++ ) + { + if( sd->status.cart[i].nameid <= 0 ) + continue; + id = itemdb_search(sd->status.cart[i].nameid); + if( !itemdb_isstackable2(id) ) + { //Equippable + WBUFW(bufe,ne*cmd+4)=i+2; + clif_item_sub(bufe, ne*cmd+6, &sd->status.cart[i], id, id->equip); + clif_addcards(WBUFP(bufe, ne*cmd+16), &sd->status.cart[i]); +#if PACKETVER >= 20071002 + WBUFL(bufe,ne*cmd+24)=sd->status.cart[i].expire_time; + WBUFW(bufe,ne*cmd+28)=0; //Unknown +#endif + ne++; + } + else + { //Stackable + WBUFW(buf,n*s+4)=i+2; + clif_item_sub(buf, n*s+6, &sd->status.cart[i], id,-1); +#if PACKETVER >= 5 + clif_addcards(WBUFP(buf,n*s+14), &sd->status.cart[i]); +#endif +#if PACKETVER >= 20080102 + WBUFL(buf,n*s+22)=sd->status.cart[i].expire_time; +#endif + n++; + } + } + if( n ) + { +#if PACKETVER < 5 + WBUFW(buf,0)=0x123; +#elif PACKETVER < 20080102 + WBUFW(buf,0)=0x1ef; +#else + WBUFW(buf,0)=0x2e9; +#endif + WBUFW(buf,2)=4+n*s; + clif_send(buf, WBUFW(buf,2), &sd->bl, SELF); + } + if( ne ) + { +#if PACKETVER < 20071002 + WBUFW(bufe,0)=0x122; +#else + WBUFW(bufe,0)=0x2d2; +#endif + WBUFW(bufe,2)=4+ne*cmd; + clif_send(bufe, WBUFW(bufe,2), &sd->bl, SELF); + } + + if( buf ) aFree(buf); + if( bufe ) aFree(bufe); +} + + +/// Removes cart (ZC_CARTOFF). +/// 012b +/// Client behaviour: +/// Closes the cart storage and removes all it's items from memory. +/// The Num & Weight values of the cart are left untouched and the cart is NOT removed. +void clif_clearcart(int fd) +{ + WFIFOHEAD(fd, packet_len(0x12b)); + WFIFOW(fd,0) = 0x12b; + WFIFOSET(fd, packet_len(0x12b)); + +} + + +/// Guild XY locators (ZC_NOTIFY_POSITION_TO_GUILDM) [Valaris] +/// 01eb <account id>.L <x>.W <y>.W +void clif_guild_xy(struct map_session_data *sd) +{ + unsigned char buf[10]; + + nullpo_retv(sd); + + WBUFW(buf,0)=0x1eb; + WBUFL(buf,2)=sd->status.account_id; + WBUFW(buf,6)=sd->bl.x; + WBUFW(buf,8)=sd->bl.y; + clif_send(buf,packet_len(0x1eb),&sd->bl,GUILD_SAMEMAP_WOS); +} + +/*========================================== + * Sends x/y dot to a single fd. [Skotlex] + *------------------------------------------*/ +void clif_guild_xy_single(int fd, struct map_session_data *sd) +{ + if( sd->bg_id ) + return; + + WFIFOHEAD(fd,packet_len(0x1eb)); + WFIFOW(fd,0)=0x1eb; + WFIFOL(fd,2)=sd->status.account_id; + WFIFOW(fd,6)=sd->bl.x; + WFIFOW(fd,8)=sd->bl.y; + WFIFOSET(fd,packet_len(0x1eb)); +} + +// Guild XY locators [Valaris] +void clif_guild_xy_remove(struct map_session_data *sd) +{ + unsigned char buf[10]; + + nullpo_retv(sd); + + WBUFW(buf,0)=0x1eb; + WBUFL(buf,2)=sd->status.account_id; + WBUFW(buf,6)=-1; + WBUFW(buf,8)=-1; + clif_send(buf,packet_len(0x1eb),&sd->bl,GUILD_SAMEMAP_WOS); +} + +/*========================================== + * + *------------------------------------------*/ +static int clif_hpmeter_sub(struct block_list *bl, va_list ap) +{ + struct map_session_data *sd, *tsd; +#if PACKETVER < 20100126 + const int cmd = 0x106; +#else + const int cmd = 0x80e; +#endif + + sd = va_arg(ap, struct map_session_data *); + tsd = (TBL_PC *)bl; + + nullpo_ret(sd); + nullpo_ret(tsd); + + if( !tsd->fd || tsd == sd ) + return 0; + + if( !pc_has_permission(tsd, PC_PERM_VIEW_HPMETER) ) + return 0; + WFIFOHEAD(tsd->fd,packet_len(cmd)); + WFIFOW(tsd->fd,0) = cmd; + WFIFOL(tsd->fd,2) = sd->status.account_id; +#if PACKETVER < 20100126 + if( sd->battle_status.max_hp > INT16_MAX ) + { //To correctly display the %hp bar. [Skotlex] + WFIFOW(tsd->fd,6) = sd->battle_status.hp/(sd->battle_status.max_hp/100); + WFIFOW(tsd->fd,8) = 100; + } else { + WFIFOW(tsd->fd,6) = sd->battle_status.hp; + WFIFOW(tsd->fd,8) = sd->battle_status.max_hp; + } +#else + WFIFOL(tsd->fd,6) = sd->battle_status.hp; + WFIFOL(tsd->fd,10) = sd->battle_status.max_hp; +#endif + WFIFOSET(tsd->fd,packet_len(cmd)); + return 0; +} + +/*========================================== + * Server tells all players that are allowed to view HP bars + * and are nearby 'sd' that 'sd' hp bar was updated. + *------------------------------------------*/ +static int clif_hpmeter(struct map_session_data *sd) +{ + nullpo_ret(sd); + map_foreachinarea(clif_hpmeter_sub, sd->bl.m, sd->bl.x-AREA_SIZE, sd->bl.y-AREA_SIZE, sd->bl.x+AREA_SIZE, sd->bl.y+AREA_SIZE, BL_PC, sd); + return 0; +} + +/// Notifies client of a character parameter change. +/// 00b0 <var id>.W <value>.L (ZC_PAR_CHANGE) +/// 00b1 <var id>.W <value>.L (ZC_LONGPAR_CHANGE) +/// 00be <status id>.W <value>.B (ZC_STATUS_CHANGE) +/// 0121 <current count>.W <max count>.W <current weight>.L <max weight>.L (ZC_NOTIFY_CARTITEM_COUNTINFO) +/// 013a <atk range>.W (ZC_ATTACK_RANGE) +/// 0141 <status id>.L <base status>.L <plus status>.L (ZC_COUPLESTATUS) +/// TODO: Extract individual packets. +/// FIXME: Packet lengths from packet_len(cmd) +void clif_updatestatus(struct map_session_data *sd,int type) +{ + int fd,len=8; + + nullpo_retv(sd); + + fd=sd->fd; + + if ( !session_isActive(fd) ) // Invalid pointer fix, by sasuke [Kevin] + return; + + WFIFOHEAD(fd, 14); + WFIFOW(fd,0)=0xb0; + WFIFOW(fd,2)=type; + switch(type){ + // 00b0 + case SP_WEIGHT: + pc_updateweightstatus(sd); + WFIFOHEAD(fd,14); + WFIFOW(fd,0)=0xb0; //Need to re-set as pc_updateweightstatus can alter the buffer. [Skotlex] + 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->battle_status.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_KARMA: // Adding this back, I wonder if the client intercepts this - [Lance] + WFIFOL(fd,4)=sd->status.karma; + break; + case SP_MANNER: + WFIFOL(fd,4)=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->battle_status.hit; + break; + case SP_FLEE1: + WFIFOL(fd,4)=sd->battle_status.flee; + break; + case SP_FLEE2: + WFIFOL(fd,4)=sd->battle_status.flee2/10; + break; + case SP_MAXHP: + WFIFOL(fd,4)=sd->battle_status.max_hp; + break; + case SP_MAXSP: + WFIFOL(fd,4)=sd->battle_status.max_sp; + break; + case SP_HP: + WFIFOL(fd,4)=sd->battle_status.hp; + // TODO: Won't these overwrite the current packet? + clif_hpmeter(sd); + if( !battle_config.party_hp_mode && sd->status.party_id ) + clif_party_hp(sd); + if( sd->bg_id ) + clif_bg_hp(sd); + break; + case SP_SP: + WFIFOL(fd,4)=sd->battle_status.sp; + break; + case SP_ASPD: + WFIFOL(fd,4)=sd->battle_status.amotion; + break; + case SP_ATK1: + WFIFOL(fd,4)=pc_leftside_atk(sd); + break; + case SP_DEF1: + WFIFOL(fd,4)=pc_leftside_def(sd); + break; + case SP_MDEF1: + WFIFOL(fd,4)=pc_leftside_mdef(sd); + break; + case SP_ATK2: + WFIFOL(fd,4)=pc_rightside_atk(sd); + break; + case SP_DEF2: + WFIFOL(fd,4)=pc_rightside_def(sd); + break; + case SP_MDEF2: { + //negative check (in case you have something like Berserk active) + int mdef2 = pc_rightside_mdef(sd); + + WFIFOL(fd,4)= +#ifndef RENEWAL + ( mdef2 < 0 ) ? 0 : +#endif + mdef2; + + } + break; + case SP_CRITICAL: + WFIFOL(fd,4)=sd->battle_status.cri/10; + break; + case SP_MATK1: + WFIFOL(fd,4)=pc_rightside_matk(sd); + break; + case SP_MATK2: + WFIFOL(fd,4)=pc_leftside_matk(sd); + break; + + + case SP_ZENY: + WFIFOW(fd,0)=0xb1; + 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; + + /** + * SP_U<STAT> are used to update the amount of points necessary to increase that stat + **/ + 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,1); + len=5; + break; + + /** + * Tells the client how far it is allowed to attack (weapon range) + **/ + case SP_ATTACKRANGE: + WFIFOW(fd,0)=0x13a; + WFIFOW(fd,2)=sd->battle_status.rhw.range; + len=4; + break; + + case SP_STR: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.str; + WFIFOL(fd,10)=sd->battle_status.str - sd->status.str; + len=14; + break; + case SP_AGI: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.agi; + WFIFOL(fd,10)=sd->battle_status.agi - sd->status.agi; + len=14; + break; + case SP_VIT: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.vit; + WFIFOL(fd,10)=sd->battle_status.vit - sd->status.vit; + len=14; + break; + case SP_INT: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.int_; + WFIFOL(fd,10)=sd->battle_status.int_ - sd->status.int_; + len=14; + break; + case SP_DEX: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.dex; + WFIFOL(fd,10)=sd->battle_status.dex - sd->status.dex; + len=14; + break; + case SP_LUK: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.luk; + WFIFOL(fd,10)=sd->battle_status.luk - sd->status.luk; + len=14; + break; + + case SP_CARTINFO: + WFIFOW(fd,0)=0x121; + WFIFOW(fd,2)=sd->cart_num; + WFIFOW(fd,4)=MAX_CART; + WFIFOL(fd,6)=sd->cart_weight; + WFIFOL(fd,10)=sd->cart_weight_max; + len=14; + break; + + default: + ShowError("clif_updatestatus : unrecognized type %d\n",type); + return; + } + WFIFOSET(fd,len); +} + + +/// Notifies client of a parameter change of an another player (ZC_PAR_CHANGE_USER). +/// 01ab <account id>.L <var id>.W <value>.L +void clif_changestatus(struct map_session_data* sd,int type,int val) +{ + unsigned char buf[12]; + + nullpo_retv(sd); + + WBUFW(buf,0)=0x1ab; + WBUFL(buf,2)=sd->bl.id; + WBUFW(buf,6)=type; + + switch(type) + { + case SP_MANNER: + WBUFL(buf,8)=val; + break; + default: + ShowError("clif_changestatus : unrecognized type %d.\n",type); + return; + } + + clif_send(buf,packet_len(0x1ab),&sd->bl,AREA_WOS); +} + + +/// Updates sprite/style properties of an object. +/// 00c3 <id>.L <type>.B <value>.B (ZC_SPRITE_CHANGE) +/// 01d7 <id>.L <type>.B <value>.L (ZC_SPRITE_CHANGE2) +void clif_changelook(struct block_list *bl,int type,int val) +{ + unsigned char buf[16]; + struct map_session_data* sd = NULL; + struct status_change* sc; + struct view_data* vd; + enum send_target target = AREA; + nullpo_retv(bl); + + sd = BL_CAST(BL_PC, bl); + sc = status_get_sc(bl); + vd = status_get_viewdata(bl); + //nullpo_ret(vd); + if( vd ) //temp hack to let Warp Portal change appearance + switch(type) + { + case LOOK_WEAPON: + if (sd) + { + clif_get_weapon_view(sd, &vd->weapon, &vd->shield); + val = vd->weapon; + } + else vd->weapon = val; + break; + case LOOK_SHIELD: + if (sd) + { + clif_get_weapon_view(sd, &vd->weapon, &vd->shield); + val = vd->shield; + } + else vd->shield = val; + break; + case LOOK_BASE: + vd->class_ = val; + if (vd->class_ == JOB_WEDDING || vd->class_ == JOB_XMAS || vd->class_ == JOB_SUMMER) + vd->weapon = vd->shield = 0; + if (vd->cloth_color && ( + (vd->class_ == JOB_WEDDING && battle_config.wedding_ignorepalette) || + (vd->class_ == JOB_XMAS && battle_config.xmas_ignorepalette) || + (vd->class_ == JOB_SUMMER && battle_config.summer_ignorepalette) + )) + clif_changelook(bl,LOOK_CLOTHES_COLOR,0); + break; + case LOOK_HAIR: + vd->hair_style = val; + break; + case LOOK_HEAD_BOTTOM: + vd->head_bottom = val; + break; + case LOOK_HEAD_TOP: + vd->head_top = val; + break; + case LOOK_HEAD_MID: + vd->head_mid = val; + break; + case LOOK_HAIR_COLOR: + vd->hair_color = val; + break; + case LOOK_CLOTHES_COLOR: + if (val && ( + (vd->class_ == JOB_WEDDING && battle_config.wedding_ignorepalette) || + (vd->class_ == JOB_XMAS && battle_config.xmas_ignorepalette) || + (vd->class_ == JOB_SUMMER && battle_config.summer_ignorepalette) + )) + val = 0; + vd->cloth_color = val; + break; + case LOOK_SHOES: +#if PACKETVER > 3 + if (sd) { + int n; + if((n = sd->equip_index[2]) >= 0 && sd->inventory_data[n]) { + if(sd->inventory_data[n]->view_id > 0) + val = sd->inventory_data[n]->view_id; + else + val = sd->status.inventory[n].nameid; + } else + val = 0; + } +#endif + //Shoes? No packet uses this.... + break; + case LOOK_BODY: + case LOOK_FLOOR: + // unknown purpose + break; + case LOOK_ROBE: +#if PACKETVER < 20110111 + return; +#else + vd->robe = val; +#endif + break; + } + + // prevent leaking the presence of GM-hidden objects + if( sc && sc->option&OPTION_INVISIBLE ) + target = SELF; + +#if PACKETVER < 4 + WBUFW(buf,0)=0xc3; + WBUFL(buf,2)=bl->id; + WBUFB(buf,6)=type; + WBUFB(buf,7)=val; + clif_send(buf,packet_len(0xc3),bl,target); +#else + WBUFW(buf,0)=0x1d7; + WBUFL(buf,2)=bl->id; + if(type == LOOK_WEAPON || type == LOOK_SHIELD) { + WBUFB(buf,6)=LOOK_WEAPON; + WBUFW(buf,7)=vd->weapon; + WBUFW(buf,9)=vd->shield; + } else { + WBUFB(buf,6)=type; + WBUFL(buf,7)=val; + } + clif_send(buf,packet_len(0x1d7),bl,target); +#endif +} + +//Sends a change-base-look packet required for traps as they are triggered. +void clif_changetraplook(struct block_list *bl,int val) +{ + unsigned char buf[32]; +#if PACKETVER < 4 + WBUFW(buf,0)=0xc3; + WBUFL(buf,2)=bl->id; + WBUFB(buf,6)=LOOK_BASE; + WBUFB(buf,7)=val; + clif_send(buf,packet_len(0xc3),bl,AREA); +#else + WBUFW(buf,0)=0x1d7; + WBUFL(buf,2)=bl->id; + WBUFB(buf,6)=LOOK_BASE; + WBUFW(buf,7)=val; + WBUFW(buf,9)=0; + clif_send(buf,packet_len(0x1d7),bl,AREA); +#endif +} + +//For the stupid cloth-dye bug. Resends the given view data to the area specified by bl. +void clif_refreshlook(struct block_list *bl,int id,int type,int val,enum send_target target) +{ + unsigned char buf[32]; +#if PACKETVER < 4 + WBUFW(buf,0)=0xc3; + WBUFL(buf,2)=id; + WBUFB(buf,6)=type; + WBUFB(buf,7)=val; + clif_send(buf,packet_len(0xc3),bl,target); +#else + WBUFW(buf,0)=0x1d7; + WBUFL(buf,2)=id; + WBUFB(buf,6)=type; + WBUFW(buf,7)=val; + WBUFW(buf,9)=0; + clif_send(buf,packet_len(0x1d7),bl,target); +#endif +} + + +/// Character status (ZC_STATUS). +/// 00bd <stpoint>.W <str>.B <need str>.B <agi>.B <need agi>.B <vit>.B <need vit>.B +/// <int>.B <need int>.B <dex>.B <need dex>.B <luk>.B <need luk>.B <atk>.W <atk2>.W +/// <matk min>.W <matk max>.W <def>.W <def2>.W <mdef>.W <mdef2>.W <hit>.W +/// <flee>.W <flee2>.W <crit>.W <aspd>.W <aspd2>.W +void clif_initialstatus(struct map_session_data *sd) +{ + int fd, mdef2; + unsigned char *buf; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xbd)); + buf=WFIFOP(fd,0); + + WBUFW(buf,0)=0xbd; + WBUFW(buf,2)=min(sd->status.status_point, INT16_MAX); + WBUFB(buf,4)=min(sd->status.str, UINT8_MAX); + WBUFB(buf,5)=pc_need_status_point(sd,SP_STR,1); + WBUFB(buf,6)=min(sd->status.agi, UINT8_MAX); + WBUFB(buf,7)=pc_need_status_point(sd,SP_AGI,1); + WBUFB(buf,8)=min(sd->status.vit, UINT8_MAX); + WBUFB(buf,9)=pc_need_status_point(sd,SP_VIT,1); + WBUFB(buf,10)=min(sd->status.int_, UINT8_MAX); + WBUFB(buf,11)=pc_need_status_point(sd,SP_INT,1); + WBUFB(buf,12)=min(sd->status.dex, UINT8_MAX); + WBUFB(buf,13)=pc_need_status_point(sd,SP_DEX,1); + WBUFB(buf,14)=min(sd->status.luk, UINT8_MAX); + WBUFB(buf,15)=pc_need_status_point(sd,SP_LUK,1); + + WBUFW(buf,16) = pc_leftside_atk(sd); + WBUFW(buf,18) = pc_rightside_atk(sd); + WBUFW(buf,20) = pc_rightside_matk(sd); + WBUFW(buf,22) = pc_leftside_matk(sd); + WBUFW(buf,24) = pc_leftside_def(sd); + WBUFW(buf,26) = pc_rightside_def(sd); + WBUFW(buf,28) = pc_leftside_mdef(sd); + mdef2 = pc_rightside_mdef(sd); + WBUFW(buf,30) = +#ifndef RENEWAL + ( mdef2 < 0 ) ? 0 : //Negative check for Frenzy'ed characters. +#endif + mdef2; + WBUFW(buf,32) = sd->battle_status.hit; + WBUFW(buf,34) = sd->battle_status.flee; + WBUFW(buf,36) = sd->battle_status.flee2/10; + WBUFW(buf,38) = sd->battle_status.cri/10; + WBUFW(buf,40) = sd->battle_status.amotion; // aspd + WBUFW(buf,42) = 0; // always 0 (plusASPD) + + WFIFOSET(fd,packet_len(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); +} + + +/// Marks an ammunition item in inventory as equipped (ZC_EQUIP_ARROW). +/// 013c <index>.W +void clif_arrowequip(struct map_session_data *sd,int val) +{ + int fd; + + nullpo_retv(sd); + + pc_stop_attack(sd); // [Valaris] + + fd=sd->fd; + WFIFOHEAD(fd, packet_len(0x013c)); + WFIFOW(fd,0)=0x013c; + WFIFOW(fd,2)=val+2; //Item ID of the arrow + WFIFOSET(fd,packet_len(0x013c)); +} + + +/// Ammunition action message (ZC_ACTION_FAILURE). +/// 013b <type>.W +/// type: +/// 0 = MsgStringTable[242]="Please equip the proper ammunition first." +/// 1 = MsgStringTable[243]="You can't Attack or use Skills because your Weight Limit has been exceeded." +/// 2 = MsgStringTable[244]="You can't use Skills because Weight Limit has been exceeded." +/// 3 = assassin, baby_assassin, assassin_cross => MsgStringTable[1040]="You have equipped throwing daggers." +/// gunslinger => MsgStringTable[1175]="Bullets have been equipped." +/// NOT ninja => MsgStringTable[245]="Ammunition has been equipped." +void clif_arrow_fail(struct map_session_data *sd,int type) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd, packet_len(0x013b)); + WFIFOW(fd,0)=0x013b; + WFIFOW(fd,2)=type; + WFIFOSET(fd,packet_len(0x013b)); +} + + +/// Presents a list of items, that can be processed by Arrow Crafting (ZC_MAKINGARROW_LIST). +/// 01ad <packet len>.W { <name id>.W }* +void clif_arrow_create_list(struct map_session_data *sd) +{ + int i, c, j; + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd, MAX_SKILL_ARROW_DB*2+4); + WFIFOW(fd,0) = 0x1ad; + + for (i = 0, c = 0; i < MAX_SKILL_ARROW_DB; i++) { + if (skill_arrow_db[i].nameid > 0 && + (j = pc_search_inventory(sd, skill_arrow_db[i].nameid)) >= 0 && + !sd->status.inventory[j].equip && sd->status.inventory[j].identify) + { + if ((j = itemdb_viewid(skill_arrow_db[i].nameid)) > 0) + WFIFOW(fd,c*2+4) = j; + 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->menuskill_id = AC_MAKINGARROW; + sd->menuskill_val = c; + } +} + + +/// Notifies the client, about the result of an status change request (ZC_STATUS_CHANGE_ACK). +/// 00bc <status id>.W <result>.B <value>.B +/// status id: +/// SP_STR ~ SP_LUK +/// result: +/// 0 = failure +/// 1 = success +void clif_statusupack(struct map_session_data *sd,int type,int ok,int val) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xbc)); + WFIFOW(fd,0)=0xbc; + WFIFOW(fd,2)=type; + WFIFOB(fd,4)=ok; + WFIFOB(fd,5)=cap_value(val,0,UINT8_MAX); + WFIFOSET(fd,packet_len(0xbc)); +} + + +/// Notifies the client about the result of a request to equip an item (ZC_REQ_WEAR_EQUIP_ACK). +/// 00aa <index>.W <equip location>.W <result>.B +/// 00aa <index>.W <equip location>.W <view id>.W <result>.B (PACKETVER >= 20100629) +/// result: +/// 0 = failure +/// 1 = success +/// 2 = failure due to low level +void clif_equipitemack(struct map_session_data *sd,int n,int pos,int ok) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xaa)); + WFIFOW(fd,0)=0xaa; + WFIFOW(fd,2)=n+2; + WFIFOW(fd,4)=pos; +#if PACKETVER < 20100629 + WFIFOB(fd,6)=ok; +#else + if (ok && sd->inventory_data[n]->equip&EQP_VISIBLE) + WFIFOW(fd,6)=sd->inventory_data[n]->look; + else + WFIFOW(fd,6)=0; + WFIFOB(fd,8)=ok; +#endif + WFIFOSET(fd,packet_len(0xaa)); +} + + +/// Notifies the client about the result of a request to take off an item (ZC_REQ_TAKEOFF_EQUIP_ACK). +/// 00ac <index>.W <equip location>.W <result>.B +/// result: +/// 0 = failure +/// 1 = success +void clif_unequipitemack(struct map_session_data *sd,int n,int pos,int ok) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xac)); + WFIFOW(fd,0)=0xac; + WFIFOW(fd,2)=n+2; + WFIFOW(fd,4)=pos; + WFIFOB(fd,6)=ok; + WFIFOSET(fd,packet_len(0xac)); +} + + +/// Notifies clients in the area about an special/visual effect (ZC_NOTIFY_EFFECT). +/// 019b <id>.L <effect id>.L +/// effect id: +/// 0 = base level up +/// 1 = job level up +/// 2 = refine failure +/// 3 = refine success +/// 4 = game over +/// 5 = pharmacy success +/// 6 = pharmacy failure +/// 7 = base level up (super novice) +/// 8 = job level up (super novice) +/// 9 = base level up (taekwon) +void clif_misceffect(struct block_list* bl,int type) +{ + unsigned char buf[32]; + + nullpo_retv(bl); + + WBUFW(buf,0) = 0x19b; + WBUFL(buf,2) = bl->id; + WBUFL(buf,6) = type; + + clif_send(buf,packet_len(0x19b),bl,AREA); +} + + +/// Notifies clients in the area of a state change. +/// 0119 <id>.L <body state>.W <health state>.W <effect state>.W <pk mode>.B (ZC_STATE_CHANGE) +/// 0229 <id>.L <body state>.W <health state>.W <effect state>.L <pk mode>.B (ZC_STATE_CHANGE3) +void clif_changeoption(struct block_list* bl) +{ + unsigned char buf[32]; + struct status_change *sc; + struct map_session_data* sd; + + nullpo_retv(bl); + sc = status_get_sc(bl); + if (!sc) return; //How can an option change if there's no sc? + sd = BL_CAST(BL_PC, bl); + +#if PACKETVER >= 7 + WBUFW(buf,0) = 0x229; + WBUFL(buf,2) = bl->id; + WBUFW(buf,6) = sc->opt1; + WBUFW(buf,8) = sc->opt2; + WBUFL(buf,10) = sc->option; + WBUFB(buf,14) = (sd)? sd->status.karma : 0; + if(disguised(bl)) { + clif_send(buf,packet_len(0x229),bl,AREA_WOS); + WBUFL(buf,2) = -bl->id; + clif_send(buf,packet_len(0x229),bl,SELF); + WBUFL(buf,2) = bl->id; + WBUFL(buf,10) = OPTION_INVISIBLE; + clif_send(buf,packet_len(0x229),bl,SELF); + } else + clif_send(buf,packet_len(0x229),bl,AREA); +#else + WBUFW(buf,0) = 0x119; + WBUFL(buf,2) = bl->id; + WBUFW(buf,6) = sc->opt1; + WBUFW(buf,8) = sc->opt2; + WBUFW(buf,10) = sc->option; + WBUFB(buf,12) = (sd)? sd->status.karma : 0; + if(disguised(bl)) { + clif_send(buf,packet_len(0x119),bl,AREA_WOS); + WBUFL(buf,2) = -bl->id; + clif_send(buf,packet_len(0x119),bl,SELF); + WBUFL(buf,2) = bl->id; + WBUFW(buf,10) = OPTION_INVISIBLE; + clif_send(buf,packet_len(0x119),bl,SELF); + } else + clif_send(buf,packet_len(0x119),bl,AREA); +#endif +} + + +/// Displays status change effects on NPCs/monsters (ZC_NPC_SHOWEFST_UPDATE). +/// 028a <id>.L <effect state>.L <level>.L <showEFST>.L +void clif_changeoption2(struct block_list* bl) +{ + unsigned char buf[20]; + struct status_change *sc; + + sc = status_get_sc(bl); + if (!sc) return; //How can an option change if there's no sc? + + WBUFW(buf,0) = 0x28a; + WBUFL(buf,2) = bl->id; + WBUFL(buf,6) = sc->option; + WBUFL(buf,10) = clif_setlevel(bl); + WBUFL(buf,14) = sc->opt3; + if(disguised(bl)) { + clif_send(buf,packet_len(0x28a),bl,AREA_WOS); + WBUFL(buf,2) = -bl->id; + clif_send(buf,packet_len(0x28a),bl,SELF); + WBUFL(buf,2) = bl->id; + WBUFL(buf,6) = OPTION_INVISIBLE; + clif_send(buf,packet_len(0x28a),bl,SELF); + } else + clif_send(buf,packet_len(0x28a),bl,AREA); +} + + +/// Notifies the client about the result of an item use request. +/// 00a8 <index>.W <amount>.W <result>.B (ZC_USE_ITEM_ACK) +/// 01c8 <index>.W <name id>.W <id>.L <amount>.W <result>.B (ZC_USE_ITEM_ACK2) +void clif_useitemack(struct map_session_data *sd,int index,int amount,bool ok) +{ + nullpo_retv(sd); + + if(!ok) { + int fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xa8)); + WFIFOW(fd,0)=0xa8; + WFIFOW(fd,2)=index+2; + WFIFOW(fd,4)=amount; + WFIFOB(fd,6)=ok; + WFIFOSET(fd,packet_len(0xa8)); + } + else { +#if PACKETVER < 3 + int fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xa8)); + WFIFOW(fd,0)=0xa8; + WFIFOW(fd,2)=index+2; + WFIFOW(fd,4)=amount; + WFIFOB(fd,6)=ok; + WFIFOSET(fd,packet_len(0xa8)); +#else + unsigned 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(0x1c8),&sd->bl,AREA); +#endif + } +} + + +/// Inform client whether chatroom creation was successful or not (ZC_ACK_CREATE_CHATROOM). +/// 00d6 <flag>.B +/// flag: +/// 0 = Room has been successfully created (opens chat room) +/// 1 = Room limit exceeded +/// 2 = Same room already exists +void clif_createchat(struct map_session_data* sd, int flag) +{ + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0xd6)); + WFIFOW(fd,0) = 0xd6; + WFIFOB(fd,2) = flag; + WFIFOSET(fd,packet_len(0xd6)); +} + + +/// Display a chat above the owner (ZC_ROOM_NEWENTRY). +/// 00d7 <packet len>.W <owner id>.L <char id>.L <limit>.W <users>.W <type>.B <title>.?B +/// type: +/// 0 = private (password protected) +/// 1 = public +/// 2 = arena (npc waiting room) +/// 3 = PK zone (non-clickable) +void clif_dispchat(struct chat_data* cd, int fd) +{ + unsigned char buf[128]; + uint8 type; + + if( cd == NULL || cd->owner == NULL ) + return; + + type = (cd->owner->type == BL_PC ) ? (cd->pub) ? 1 : 0 + : (cd->owner->type == BL_NPC) ? (cd->limit) ? 2 : 3 + : 1; + + WBUFW(buf, 0) = 0xd7; + WBUFW(buf, 2) = 17 + strlen(cd->title); + WBUFL(buf, 4) = cd->owner->id; + WBUFL(buf, 8) = cd->bl.id; + WBUFW(buf,12) = cd->limit; + WBUFW(buf,14) = (cd->owner->type == BL_NPC) ? cd->users+1 : cd->users; + WBUFB(buf,16) = type; + memcpy((char*)WBUFP(buf,17), cd->title, strlen(cd->title)); // not zero-terminated + + if( fd ) { + WFIFOHEAD(fd,WBUFW(buf,2)); + memcpy(WFIFOP(fd,0),buf,WBUFW(buf,2)); + WFIFOSET(fd,WBUFW(buf,2)); + } else { + clif_send(buf,WBUFW(buf,2),cd->owner,AREA_WOSC); + } +} + + +/// Chatroom properties adjustment (ZC_CHANGE_CHATROOM). +/// 00df <packet len>.W <owner id>.L <chat id>.L <limit>.W <users>.W <type>.B <title>.?B +/// type: +/// 0 = private (password protected) +/// 1 = public +/// 2 = arena (npc waiting room) +/// 3 = PK zone (non-clickable) +void clif_changechatstatus(struct chat_data* cd) +{ + unsigned char buf[128]; + uint8 type; + + if( cd == NULL || cd->usersd[0] == NULL ) + return; + + type = (cd->owner->type == BL_PC ) ? (cd->pub) ? 1 : 0 + : (cd->owner->type == BL_NPC) ? (cd->limit) ? 2 : 3 + : 1; + + WBUFW(buf, 0) = 0xdf; + WBUFW(buf, 2) = 17 + strlen(cd->title); + WBUFL(buf, 4) = cd->owner->id; + WBUFL(buf, 8) = cd->bl.id; + WBUFW(buf,12) = cd->limit; + WBUFW(buf,14) = (cd->owner->type == BL_NPC) ? cd->users+1 : cd->users; + WBUFB(buf,16) = type; + memcpy((char*)WBUFP(buf,17), cd->title, strlen(cd->title)); // not zero-terminated + + clif_send(buf,WBUFW(buf,2),cd->owner,CHAT); +} + + +/// Removes the chatroom (ZC_DESTROY_ROOM). +/// 00d8 <chat id>.L +void clif_clearchat(struct chat_data *cd,int fd) +{ + unsigned char buf[32]; + + nullpo_retv(cd); + + WBUFW(buf,0) = 0xd8; + WBUFL(buf,2) = cd->bl.id; + if( fd ) { + WFIFOHEAD(fd,packet_len(0xd8)); + memcpy(WFIFOP(fd,0),buf,packet_len(0xd8)); + WFIFOSET(fd,packet_len(0xd8)); + } else { + clif_send(buf,packet_len(0xd8),cd->owner,AREA_WOSC); + } +} + + +/// Displays messages regarding join chat failures (ZC_REFUSE_ENTER_ROOM). +/// 00da <result>.B +/// result: +/// 0 = room full +/// 1 = wrong password +/// 2 = kicked +/// 3 = success (no message) +/// 4 = no enough zeny +/// 5 = too low level +/// 6 = too high level +/// 7 = unsuitable job class +void clif_joinchatfail(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0xda)); + WFIFOW(fd,0) = 0xda; + WFIFOB(fd,2) = flag; + WFIFOSET(fd,packet_len(0xda)); +} + + +/// Notifies the client about entering a chatroom (ZC_ENTER_ROOM). +/// 00db <packet len>.W <chat id>.L { <role>.L <name>.24B }* +/// role: +/// 0 = owner (menu) +/// 1 = normal +void clif_joinchatok(struct map_session_data *sd,struct chat_data* cd) +{ + int fd; + int i,t; + + nullpo_retv(sd); + nullpo_retv(cd); + + fd = sd->fd; + if (!session_isActive(fd)) + return; + t = (int)(cd->owner->type == BL_NPC); + WFIFOHEAD(fd, 8 + (28*(cd->users+t))); + WFIFOW(fd, 0) = 0xdb; + WFIFOW(fd, 2) = 8 + (28*(cd->users+t)); + WFIFOL(fd, 4) = cd->bl.id; + + if(cd->owner->type == BL_NPC){ + WFIFOL(fd, 30) = 1; + WFIFOL(fd, 8) = 0; + memcpy(WFIFOP(fd, 12), ((struct npc_data *)cd->owner)->name, NAME_LENGTH); + for (i = 0; i < cd->users; i++) { + WFIFOL(fd, 8+(i+1)*28) = 1; + memcpy(WFIFOP(fd, 8+(i+t)*28+4), cd->usersd[i]->status.name, NAME_LENGTH); + } + } else + for (i = 0; i < cd->users; i++) { + WFIFOL(fd, 8+i*28) = (i != 0 || cd->owner->type == BL_NPC); + memcpy(WFIFOP(fd, 8+(i+t)*28+4), cd->usersd[i]->status.name, NAME_LENGTH); + } + WFIFOSET(fd, WFIFOW(fd, 2)); +} + + +/// Notifies clients in a chat about a new member (ZC_MEMBER_NEWENTRY). +/// 00dc <users>.W <name>.24B +void clif_addchat(struct chat_data* cd,struct map_session_data *sd) +{ + unsigned char buf[32]; + + nullpo_retv(sd); + nullpo_retv(cd); + + WBUFW(buf, 0) = 0xdc; + WBUFW(buf, 2) = cd->users; + memcpy(WBUFP(buf, 4),sd->status.name,NAME_LENGTH); + clif_send(buf,packet_len(0xdc),&sd->bl,CHAT_WOS); +} + + +/// Announce the new owner (ZC_ROLE_CHANGE). +/// 00e1 <role>.L <nick>.24B +/// role: +/// 0 = owner (menu) +/// 1 = normal +void clif_changechatowner(struct chat_data* cd, struct map_session_data* sd) +{ + unsigned char buf[64]; + + nullpo_retv(sd); + nullpo_retv(cd); + + WBUFW(buf, 0) = 0xe1; + WBUFL(buf, 2) = 1; + memcpy(WBUFP(buf,6),cd->usersd[0]->status.name,NAME_LENGTH); + + WBUFW(buf,30) = 0xe1; + WBUFL(buf,32) = 0; + memcpy(WBUFP(buf,36),sd->status.name,NAME_LENGTH); + + clif_send(buf,packet_len(0xe1)*2,&sd->bl,CHAT); +} + + +/// Notify about user leaving the chatroom (ZC_MEMBER_EXIT). +/// 00dd <users>.W <nick>.24B <flag>.B +/// flag: +/// 0 = left +/// 1 = kicked +void clif_leavechat(struct chat_data* cd, struct map_session_data* sd, bool flag) +{ + unsigned char buf[32]; + + nullpo_retv(sd); + nullpo_retv(cd); + + WBUFW(buf, 0) = 0xdd; + WBUFW(buf, 2) = cd->users-1; + memcpy(WBUFP(buf,4),sd->status.name,NAME_LENGTH); + WBUFB(buf,28) = flag; + + clif_send(buf,packet_len(0xdd),&sd->bl,CHAT); +} + + +/// Opens a trade request window from char 'name'. +/// 00e5 <nick>.24B (ZC_REQ_EXCHANGE_ITEM) +/// 01f4 <nick>.24B <charid>.L <baselvl>.W (ZC_REQ_EXCHANGE_ITEM2) +void clif_traderequest(struct map_session_data* sd, const char* name) +{ + int fd = sd->fd; + +#if PACKETVER < 6 + WFIFOHEAD(fd,packet_len(0xe5)); + WFIFOW(fd,0) = 0xe5; + safestrncpy((char*)WFIFOP(fd,2), name, NAME_LENGTH); + WFIFOSET(fd,packet_len(0xe5)); +#else + struct map_session_data* tsd = map_id2sd(sd->trade_partner); + if( !tsd ) return; + + WFIFOHEAD(fd,packet_len(0x1f4)); + WFIFOW(fd,0) = 0x1f4; + safestrncpy((char*)WFIFOP(fd,2), name, NAME_LENGTH); + WFIFOL(fd,26) = tsd->status.char_id; + WFIFOW(fd,30) = tsd->status.base_level; + WFIFOSET(fd,packet_len(0x1f4)); +#endif +} + + +/// Reply to a trade-request. +/// 00e7 <result>.B (ZC_ACK_EXCHANGE_ITEM) +/// 01f5 <result>.B <charid>.L <baselvl>.W (ZC_ACK_EXCHANGE_ITEM2) +/// result: +/// 0 = Char is too far +/// 1 = Character does not exist +/// 2 = Trade failed +/// 3 = Accept +/// 4 = Cancel +/// 5 = Busy +void clif_tradestart(struct map_session_data* sd, uint8 type) +{ + int fd = sd->fd; + struct map_session_data* tsd = map_id2sd(sd->trade_partner); + if( PACKETVER < 6 || !tsd ) { + WFIFOHEAD(fd,packet_len(0xe7)); + WFIFOW(fd,0) = 0xe7; + WFIFOB(fd,2) = type; + WFIFOSET(fd,packet_len(0xe7)); + } else { + WFIFOHEAD(fd,packet_len(0x1f5)); + WFIFOW(fd,0) = 0x1f5; + WFIFOB(fd,2) = type; + WFIFOL(fd,3) = tsd->status.char_id; + WFIFOW(fd,7) = tsd->status.base_level; + WFIFOSET(fd,packet_len(0x1f5)); + } +} + + +/// Notifies the client about an item from other player in current trade. +/// 00e9 <amount>.L <nameid>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W (ZC_ADD_EXCHANGE_ITEM) +/// 080f <nameid>.W <item type>.B <amount>.L <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W (ZC_ADD_EXCHANGE_ITEM2) +void clif_tradeadditem(struct map_session_data* sd, struct map_session_data* tsd, int index, int amount) +{ + int fd; + unsigned char *buf; +#if PACKETVER < 20100223 + const int cmd = 0xe9; +#else + const int cmd = 0x80f; +#endif + nullpo_retv(sd); + nullpo_retv(tsd); + + fd = tsd->fd; + buf = WFIFOP(fd,0); + WFIFOHEAD(fd,packet_len(cmd)); + WBUFW(buf,0) = cmd; + if( index == 0 ) + { +#if PACKETVER < 20100223 + WBUFL(buf,2) = amount; //amount + WBUFW(buf,6) = 0; // type id +#else + WBUFW(buf,2) = 0; // type id + WBUFB(buf,4) = 0; // item type + WBUFL(buf,5) = amount; // amount + buf = WBUFP(buf,1); //Advance 1B +#endif + WBUFB(buf,8) = 0; //identify flag + WBUFB(buf,9) = 0; // attribute + WBUFB(buf,10)= 0; //refine + WBUFW(buf,11)= 0; //card (4w) + WBUFW(buf,13)= 0; //card (4w) + WBUFW(buf,15)= 0; //card (4w) + WBUFW(buf,17)= 0; //card (4w) + } + else + { + index -= 2; //index fix +#if PACKETVER < 20100223 + WBUFL(buf,2) = amount; //amount + if(sd->inventory_data[index] && sd->inventory_data[index]->view_id > 0) + WBUFW(buf,6) = sd->inventory_data[index]->view_id; + else + WBUFW(buf,6) = sd->status.inventory[index].nameid; // type id +#else + if(sd->inventory_data[index] && sd->inventory_data[index]->view_id > 0) + WBUFW(buf,2) = sd->inventory_data[index]->view_id; + else + WBUFW(buf,2) = sd->status.inventory[index].nameid; // type id + WBUFB(buf,4) = sd->inventory_data[index]->type; // item type + WBUFL(buf,5) = amount; // amount + buf = WBUFP(buf,1); //Advance 1B +#endif + WBUFB(buf,8) = sd->status.inventory[index].identify; //identify flag + WBUFB(buf,9) = sd->status.inventory[index].attribute; // attribute + WBUFB(buf,10)= sd->status.inventory[index].refine; //refine + clif_addcards(WBUFP(buf, 11), &sd->status.inventory[index]); + } + WFIFOSET(fd,packet_len(cmd)); +} + + +/// Notifies the client about the result of request to add an item to the current trade (ZC_ACK_ADD_EXCHANGE_ITEM). +/// 00ea <index>.W <result>.B +/// result: +/// 0 = success +/// 1 = overweight +/// 2 = trade canceled +void clif_tradeitemok(struct map_session_data* sd, int index, int fail) +{ + int fd; + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0xea)); + WFIFOW(fd,0) = 0xea; + WFIFOW(fd,2) = index; + WFIFOB(fd,4) = fail; + WFIFOSET(fd,packet_len(0xea)); +} + + +/// Notifies the client about finishing one side of the current trade (ZC_CONCLUDE_EXCHANGE_ITEM). +/// 00ec <who>.B +/// who: +/// 0 = self +/// 1 = other player +void clif_tradedeal_lock(struct map_session_data* sd, int fail) +{ + int fd; + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0xec)); + WFIFOW(fd,0) = 0xec; + WFIFOB(fd,2) = fail; + WFIFOSET(fd,packet_len(0xec)); +} + + +/// Notifies the client about the trade being canceled (ZC_CANCEL_EXCHANGE_ITEM). +/// 00ee +void clif_tradecancelled(struct map_session_data* sd) +{ + int fd; + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0xee)); + WFIFOW(fd,0) = 0xee; + WFIFOSET(fd,packet_len(0xee)); +} + + +/// Result of a trade (ZC_EXEC_EXCHANGE_ITEM). +/// 00f0 <result>.B +/// result: +/// 0 = success +/// 1 = failure +void clif_tradecompleted(struct map_session_data* sd, int fail) +{ + int fd; + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0xf0)); + WFIFOW(fd,0) = 0xf0; + WFIFOB(fd,2) = fail; + WFIFOSET(fd,packet_len(0xf0)); +} + + +/// Resets the trade window on the send side (ZC_EXCHANGEITEM_UNDO). +/// 00f1 +/// NOTE: Unknown purpose. Items are not removed until the window is +/// refreshed (ex. by putting another item in there). +void clif_tradeundo(struct map_session_data* sd) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0xf1)); + WFIFOW(fd,0) = 0xf1; + WFIFOSET(fd,packet_len(0xf1)); +} + + +/// Updates storage total amount (ZC_NOTIFY_STOREITEM_COUNTINFO). +/// 00f2 <current count>.W <max count>.W +void clif_updatestorageamount(struct map_session_data* sd, int amount, int max_amount) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xf2)); + WFIFOW(fd,0) = 0xf2; + WFIFOW(fd,2) = amount; + WFIFOW(fd,4) = max_amount; + WFIFOSET(fd,packet_len(0xf2)); +} + + +/// Notifies the client of an item being added to the storage. +/// 00f4 <index>.W <amount>.L <nameid>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W (ZC_ADD_ITEM_TO_STORE) +/// 01c4 <index>.W <amount>.L <nameid>.W <type>.B <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W (ZC_ADD_ITEM_TO_STORE2) +void clif_storageitemadded(struct map_session_data* sd, struct item* i, int index, int amount) +{ + int view,fd; + + nullpo_retv(sd); + nullpo_retv(i); + fd=sd->fd; + view = itemdb_viewid(i->nameid); + +#if PACKETVER < 5 + WFIFOHEAD(fd,packet_len(0xf4)); + WFIFOW(fd, 0) = 0xf4; // Storage item added + WFIFOW(fd, 2) = index+1; // index + WFIFOL(fd, 4) = amount; // amount + WFIFOW(fd, 8) = ( view > 0 ) ? view : i->nameid; // id + WFIFOB(fd,10) = i->identify; //identify flag + WFIFOB(fd,11) = i->attribute; // attribute + WFIFOB(fd,12) = i->refine; //refine + clif_addcards(WFIFOP(fd,13), i); + WFIFOSET(fd,packet_len(0xf4)); +#else + WFIFOHEAD(fd,packet_len(0x1c4)); + WFIFOW(fd, 0) = 0x1c4; // Storage item added + WFIFOW(fd, 2) = index+1; // index + WFIFOL(fd, 4) = amount; // amount + WFIFOW(fd, 8) = ( view > 0 ) ? view : i->nameid; // id + WFIFOB(fd,10) = itemdb_type(i->nameid); //type + WFIFOB(fd,11) = i->identify; //identify flag + WFIFOB(fd,12) = i->attribute; // attribute + WFIFOB(fd,13) = i->refine; //refine + clif_addcards(WFIFOP(fd,14), i); + WFIFOSET(fd,packet_len(0x1c4)); +#endif +} + + +/// Notifies the client of an item being deleted from the storage (ZC_DELETE_ITEM_FROM_STORE). +/// 00f6 <index>.W <amount>.L +void clif_storageitemremoved(struct map_session_data* sd, int index, int amount) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xf6)); + WFIFOW(fd,0)=0xf6; // Storage item removed + WFIFOW(fd,2)=index+1; + WFIFOL(fd,4)=amount; + WFIFOSET(fd,packet_len(0xf6)); +} + + +/// Closes storage (ZC_CLOSE_STORE). +/// 00f8 +void clif_storageclose(struct map_session_data* sd) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xf8)); + WFIFOW(fd,0) = 0xf8; // Storage Closed + WFIFOSET(fd,packet_len(0xf8)); +} + +/*========================================== + * Server tells 'sd' player client the abouts of 'dstsd' player + *------------------------------------------*/ +static void clif_getareachar_pc(struct map_session_data* sd,struct map_session_data* dstsd) +{ + struct block_list *d_bl; + int i; + + if( dstsd->chatID ) { + struct chat_data *cd = NULL; + if( (cd = (struct chat_data*)map_id2bl(dstsd->chatID)) && cd->usersd[0]==dstsd) + clif_dispchat(cd,sd->fd); + } else if( dstsd->state.vending ) + clif_showvendingboard(&dstsd->bl,dstsd->message,sd->fd); + else if( dstsd->state.buyingstore ) + clif_buyingstore_entry_single(sd, dstsd); + + if(dstsd->spiritball > 0) + clif_spiritball_single(sd->fd, dstsd); + for(i = 1; i < 5; i++){ + if( dstsd->talisman[i] > 0 ) + clif_talisman_single(sd->fd, dstsd, i); + } + if( dstsd->sc.option&OPTION_MOUNTING ) { + //New Mounts are not complaint to the original method, so we gotta tell this guy that I'm mounting. + clif_status_load_single(sd->fd,dstsd->bl.id,SI_ALL_RIDING,2,1,0,0); + } +#ifdef NEW_CARTS + if( dstsd->sc.data[SC_PUSH_CART] ) + clif_status_load_single(sd->fd, dstsd->bl.id, SI_ON_PUSH_CART, 2, dstsd->sc.data[SC_PUSH_CART]->val1, 0, 0); +#endif + if( (sd->status.party_id && dstsd->status.party_id == sd->status.party_id) || //Party-mate, or hpdisp setting. + (sd->bg_id && sd->bg_id == dstsd->bg_id) || //BattleGround + pc_has_permission(sd, PC_PERM_VIEW_HPMETER) + ) + clif_hpmeter_single(sd->fd, dstsd->bl.id, dstsd->battle_status.hp, dstsd->battle_status.max_hp); + + // display link (sd - dstsd) to sd + ARR_FIND( 0, 5, i, sd->devotion[i] == dstsd->bl.id ); + if( i < 5 ) clif_devotion(&sd->bl, sd); + // display links (dstsd - devotees) to sd + ARR_FIND( 0, 5, i, dstsd->devotion[i] > 0 ); + if( i < 5 ) clif_devotion(&dstsd->bl, sd); + // display link (dstsd - crusader) to sd + if( dstsd->sc.data[SC_DEVOTION] && (d_bl = map_id2bl(dstsd->sc.data[SC_DEVOTION]->val1)) != NULL ) + clif_devotion(d_bl, sd); +} + +void clif_getareachar_unit(struct map_session_data* sd,struct block_list *bl) +{ + uint8 buf[128]; + struct unit_data *ud; + struct view_data *vd; + int len; + + vd = status_get_viewdata(bl); + if (!vd || vd->class_ == INVISIBLE_CLASS) + return; + + /** + * Hide NPC from maya purple card. + **/ + if(bl->type == BL_NPC && !((TBL_NPC*)bl)->chat_id && (((TBL_NPC*)bl)->sc.option&OPTION_INVISIBLE)) + return; + + ud = unit_bl2ud(bl); + len = ( ud && ud->walktimer != INVALID_TIMER ) ? clif_set_unit_walking(bl,ud,buf) : clif_set_unit_idle(bl,buf,false); + clif_send(buf,len,&sd->bl,SELF); + + if (vd->cloth_color) + clif_refreshlook(&sd->bl,bl->id,LOOK_CLOTHES_COLOR,vd->cloth_color,SELF); + + switch (bl->type) + { + case BL_PC: + { + TBL_PC* tsd = (TBL_PC*)bl; + clif_getareachar_pc(sd, tsd); + if(tsd->state.size==SZ_BIG) // tiny/big players [Valaris] + clif_specialeffect_single(bl,423,sd->fd); + else if(tsd->state.size==SZ_MEDIUM) + clif_specialeffect_single(bl,421,sd->fd); + if( tsd->bg_id && map[tsd->bl.m].flag.battleground ) + clif_sendbgemblem_single(sd->fd,tsd); + if( tsd->sc.data[SC_CAMOUFLAGE] ) + clif_status_load(bl,SI_CAMOUFLAGE,1); + } + break; + case BL_MER: // Devotion Effects + if( ((TBL_MER*)bl)->devotion_flag ) + clif_devotion(bl, sd); + break; + case BL_NPC: + { + TBL_NPC* nd = (TBL_NPC*)bl; + if( nd->chat_id ) + clif_dispchat((struct chat_data*)map_id2bl(nd->chat_id),sd->fd); + if( nd->size == SZ_BIG ) + clif_specialeffect_single(bl,423,sd->fd); + else if( nd->size == SZ_MEDIUM ) + clif_specialeffect_single(bl,421,sd->fd); + } + break; + case BL_MOB: + { + TBL_MOB* md = (TBL_MOB*)bl; + if(md->special_state.size==SZ_BIG) // tiny/big mobs [Valaris] + clif_specialeffect_single(bl,423,sd->fd); + else if(md->special_state.size==SZ_MEDIUM) + clif_specialeffect_single(bl,421,sd->fd); +#if PACKETVER >= 20120404 + if( !(md->status.mode&MD_BOSS) ){ + int i; + for(i = 0; i < DAMAGELOG_SIZE; i++)// must show hp bar to all char who already hit the mob. + if( md->dmglog[i].id == sd->status.char_id ) + clif_monster_hp_bar(md, sd->fd); + } +#endif + } + break; + case BL_PET: + if (vd->head_bottom) + clif_pet_equip(sd, (TBL_PET*)bl); // needed to display pet equip properly + break; + } +} + +//Modifies the type of damage according to status changes [Skotlex] +//Aegis data specifies that: 4 endure against single hit sources, 9 against multi-hit. +static inline int clif_calc_delay(int type, int div, int damage, int delay) +{ + return ( delay == 0 && damage > 0 ) ? ( div > 1 ? 9 : 4 ) : type; +} + +/*========================================== + * Estimates walk delay based on the damage criteria. [Skotlex] + *------------------------------------------*/ +static int clif_calc_walkdelay(struct block_list *bl,int delay, int type, int damage, int div_) +{ + if (type == 4 || type == 9 || damage <=0) + return 0; + + if (bl->type == BL_PC) { + if (battle_config.pc_walk_delay_rate != 100) + delay = delay*battle_config.pc_walk_delay_rate/100; + } else + if (battle_config.walk_delay_rate != 100) + delay = delay*battle_config.walk_delay_rate/100; + + if (div_ > 1) //Multi-hit skills mean higher delays. + delay += battle_config.multihit_delay*(div_-1); + + return delay>0?delay:1; //Return 1 to specify there should be no noticeable delay, but you should stop walking. +} + + +/// Sends a 'damage' packet (src performs action on dst) +/// 008a <src ID>.L <dst ID>.L <server tick>.L <src speed>.L <dst speed>.L <damage>.W <div>.W <type>.B <damage2>.W (ZC_NOTIFY_ACT) +/// 02e1 <src ID>.L <dst ID>.L <server tick>.L <src speed>.L <dst speed>.L <damage>.L <div>.W <type>.B <damage2>.L (ZC_NOTIFY_ACT2) +/// type: +/// 0 = damage [ damage: total damage, div: amount of hits, damage2: assassin dual-wield damage ] +/// 1 = pick up item +/// 2 = sit down +/// 3 = stand up +/// 4 = damage (endure) +/// 5 = (splash?) +/// 6 = (skill?) +/// 7 = (repeat damage?) +/// 8 = multi-hit damage +/// 9 = multi-hit damage (endure) +/// 10 = critical hit +/// 11 = lucky dodge +/// 12 = (touch skill?) +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[33]; + struct status_change *sc; +#if PACKETVER < 20071113 + const int cmd = 0x8a; +#else + const int cmd = 0x2e1; +#endif + + nullpo_ret(src); + nullpo_ret(dst); + + type = clif_calc_delay(type,div,damage+damage2,ddelay); + sc = status_get_sc(dst); + if(sc && sc->count) { + if(sc->data[SC_HALLUCINATION]) { + if(damage) damage = damage*(sc->data[SC_HALLUCINATION]->val2) + rnd()%100; + if(damage2) damage2 = damage2*(sc->data[SC_HALLUCINATION]->val2) + rnd()%100; + } + } + + WBUFW(buf,0)=cmd; + WBUFL(buf,2)=src->id; + WBUFL(buf,6)=dst->id; + WBUFL(buf,10)=tick; + WBUFL(buf,14)=sdelay; + WBUFL(buf,18)=ddelay; +#if PACKETVER < 20071113 + if (battle_config.hide_woe_damage && map_flag_gvg(src->m)) { + WBUFW(buf,22)=damage?div:0; + WBUFW(buf,27)=damage2?div:0; + } else { + WBUFW(buf,22)=min(damage, INT16_MAX); + WBUFW(buf,27)=damage2; + } + WBUFW(buf,24)=div; + WBUFB(buf,26)=type; +#else + if (battle_config.hide_woe_damage && map_flag_gvg(src->m)) { + WBUFL(buf,22)=damage?div:0; + WBUFL(buf,29)=damage2?div:0; + } else { + WBUFL(buf,22)=damage; + WBUFL(buf,29)=damage2; + } + WBUFW(buf,26)=div; + WBUFB(buf,28)=type; +#endif + if(disguised(dst)) { + clif_send(buf,packet_len(cmd),dst,AREA_WOS); + WBUFL(buf,6) = -dst->id; + clif_send(buf,packet_len(cmd),dst,SELF); + } else + clif_send(buf,packet_len(cmd),dst,AREA); + + if(disguised(src)) { + WBUFL(buf,2) = -src->id; + if (disguised(dst)) + WBUFL(buf,6) = dst->id; +#if PACKETVER < 20071113 + if(damage > 0) WBUFW(buf,22) = -1; + if(damage2 > 0) WBUFW(buf,27) = -1; +#else + if(damage > 0) WBUFL(buf,22) = -1; + if(damage2 > 0) WBUFL(buf,29) = -1; +#endif + clif_send(buf,packet_len(cmd),src,SELF); + } + + if(src == dst) { + unit_setdir(src,unit_getdir(src)); + } + //Return adjusted can't walk delay for further processing. + return clif_calc_walkdelay(dst,ddelay,type,damage+damage2,div); +} + +/*========================================== + * src picks up dst + *------------------------------------------*/ +void clif_takeitem(struct block_list* src, struct block_list* dst) +{ + //clif_damage(src,dst,0,0,0,0,0,1,0); + unsigned char buf[32]; + + nullpo_retv(src); + nullpo_retv(dst); + + WBUFW(buf, 0) = 0x8a; + WBUFL(buf, 2) = src->id; + WBUFL(buf, 6) = dst->id; + WBUFB(buf,26) = 1; + clif_send(buf, packet_len(0x8a), src, AREA); + +} + +/*========================================== + * inform clients in area that `bl` is sitting + *------------------------------------------*/ +void clif_sitting(struct block_list* bl) +{ + unsigned char buf[32]; + nullpo_retv(bl); + + WBUFW(buf, 0) = 0x8a; + WBUFL(buf, 2) = bl->id; + WBUFB(buf,26) = 2; + clif_send(buf, packet_len(0x8a), bl, AREA); + + if(disguised(bl)) { + WBUFL(buf, 2) = - bl->id; + clif_send(buf, packet_len(0x8a), bl, SELF); + } +} + +/*========================================== + * inform clients in area that `bl` is standing + *------------------------------------------*/ +void clif_standing(struct block_list* bl) +{ + unsigned char buf[32]; + nullpo_retv(bl); + + WBUFW(buf, 0) = 0x8a; + WBUFL(buf, 2) = bl->id; + WBUFB(buf,26) = 3; + clif_send(buf, packet_len(0x8a), bl, AREA); + + if(disguised(bl)) { + WBUFL(buf, 2) = - bl->id; + clif_send(buf, packet_len(0x8a), bl, SELF); + } +} + + +/// Inform client(s) about a map-cell change (ZC_UPDATE_MAPINFO). +/// 0192 <x>.W <y>.W <type>.W <map name>.16B +void clif_changemapcell(int fd, int16 m, int x, int y, int type, enum send_target target) +{ + unsigned char buf[32]; + + WBUFW(buf,0) = 0x192; + WBUFW(buf,2) = x; + WBUFW(buf,4) = y; + WBUFW(buf,6) = type; + mapindex_getmapname_ext(map[m].name,(char*)WBUFP(buf,8)); + + if( fd ) + { + WFIFOHEAD(fd,packet_len(0x192)); + memcpy(WFIFOP(fd,0), buf, packet_len(0x192)); + WFIFOSET(fd,packet_len(0x192)); + } + else + { + struct block_list dummy_bl; + dummy_bl.type = BL_NUL; + dummy_bl.x = x; + dummy_bl.y = y; + dummy_bl.m = m; + clif_send(buf,packet_len(0x192),&dummy_bl,target); + } +} + + +/// Notifies the client about an item on floor (ZC_ITEM_ENTRY). +/// 009d <id>.L <name id>.W <identified>.B <x>.W <y>.W <amount>.W <subX>.B <subY>.B +void clif_getareachar_item(struct map_session_data* sd,struct flooritem_data* fitem) +{ + int view,fd; + fd=sd->fd; + + WFIFOHEAD(fd,packet_len(0x9d)); + 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(0x9d)); +} + + +/// Notifies the client of a skill unit. +/// 011f <id>.L <creator id>.L <x>.W <y>.W <unit id>.B <visible>.B (ZC_SKILL_ENTRY) +/// 01c9 <id>.L <creator id>.L <x>.W <y>.W <unit id>.B <visible>.B <has msg>.B <msg>.80B (ZC_SKILL_ENTRY2) +static void clif_getareachar_skillunit(struct map_session_data *sd, struct skill_unit *unit) +{ + int fd = sd->fd; + + if( unit->group->state.guildaura ) + return; + +#if PACKETVER >= 3 + if(unit->group->unit_id==UNT_GRAFFITI) { // Graffiti [Valaris] + WFIFOHEAD(fd,packet_len(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; + WFIFOB(fd,16)=1; + safestrncpy((char*)WFIFOP(fd,17),unit->group->valstr,MESSAGE_SIZE); + WFIFOSET(fd,packet_len(0x1c9)); + return; + } +#endif + WFIFOHEAD(fd,packet_len(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; + if (battle_config.traps_setting&1 && skill_get_inf2(unit->group->skill_id)&INF2_TRAP) + WFIFOB(fd,14)=UNT_DUMMYSKILL; //Use invisible unit id for traps. + else if (skill_get_unit_flag(unit->group->skill_id) & UF_RANGEDSINGLEUNIT && !(unit->val2 & UF_RANGEDSINGLEUNIT)) + WFIFOB(fd,14)=UNT_DUMMYSKILL; //Use invisible unit id for traps. + else + WFIFOB(fd,14)=unit->group->unit_id; + WFIFOB(fd,15)=1; // ignored by client (always gets set to 1) + WFIFOSET(fd,packet_len(0x11f)); + + if(unit->group->skill_id == WZ_ICEWALL) + clif_changemapcell(fd,unit->bl.m,unit->bl.x,unit->bl.y,5,SELF); +} + + +/*========================================== + * Server tells client to remove unit of id 'unit->bl.id' + *------------------------------------------*/ +static void clif_clearchar_skillunit(struct skill_unit *unit, int fd) +{ + nullpo_retv(unit); + + WFIFOHEAD(fd,packet_len(0x120)); + WFIFOW(fd, 0)=0x120; + WFIFOL(fd, 2)=unit->bl.id; + WFIFOSET(fd,packet_len(0x120)); + + if(unit->group && unit->group->skill_id == WZ_ICEWALL) + clif_changemapcell(fd,unit->bl.m,unit->bl.x,unit->bl.y,unit->val2,SELF); +} + + +/// Removes a skill unit (ZC_SKILL_DISAPPEAR). +/// 0120 <id>.L +void clif_skill_delunit(struct skill_unit *unit) +{ + unsigned char buf[16]; + + nullpo_retv(unit); + + WBUFW(buf, 0)=0x120; + WBUFL(buf, 2)=unit->bl.id; + clif_send(buf,packet_len(0x120),&unit->bl,AREA); +} + + +/// Sent when an object gets ankle-snared (ZC_SKILL_UPDATE). +/// 01ac <id>.L +/// Only affects units with class [139,153] client-side. +void clif_skillunit_update(struct block_list* bl) +{ + unsigned char buf[6]; + nullpo_retv(bl); + + WBUFW(buf,0) = 0x1ac; + WBUFL(buf,2) = bl->id; + + clif_send(buf,packet_len(0x1ac),bl,AREA); +} + + +/*========================================== + * + *------------------------------------------*/ +static int clif_getareachar(struct block_list* bl,va_list ap) +{ + struct map_session_data *sd; + + nullpo_ret(bl); + + sd=va_arg(ap,struct map_session_data*); + + if (sd == NULL || !sd->fd) + return 0; + + switch(bl->type){ + case BL_ITEM: + clif_getareachar_item(sd,(struct flooritem_data*) bl); + break; + case BL_SKILL: + clif_getareachar_skillunit(sd,(TBL_SKILL*)bl); + break; + default: + if(&sd->bl == bl) + break; + clif_getareachar_unit(sd,bl); + break; + } + return 0; +} + +/*========================================== + * tbl has gone out of view-size of bl + *------------------------------------------*/ +int clif_outsight(struct block_list *bl,va_list ap) +{ + struct block_list *tbl; + struct view_data *vd; + TBL_PC *sd, *tsd; + tbl=va_arg(ap,struct block_list*); + if(bl == tbl) return 0; + sd = BL_CAST(BL_PC, bl); + tsd = BL_CAST(BL_PC, tbl); + + if (tsd && tsd->fd) + { //tsd has lost sight of the bl object. + switch(bl->type){ + case BL_PC: + if (sd->vd.class_ != INVISIBLE_CLASS) + clif_clearunit_single(bl->id,CLR_OUTSIGHT,tsd->fd); + if(sd->chatID){ + struct chat_data *cd; + cd=(struct chat_data*)map_id2bl(sd->chatID); + if(cd->usersd[0]==sd) + clif_dispchat(cd,tsd->fd); + } + if( sd->state.vending ) + clif_closevendingboard(bl,tsd->fd); + if( sd->state.buyingstore ) + clif_buyingstore_disappear_entry_single(tsd, sd); + break; + case BL_ITEM: + clif_clearflooritem((struct flooritem_data*)bl,tsd->fd); + break; + case BL_SKILL: + clif_clearchar_skillunit((struct skill_unit *)bl,tsd->fd); + break; + case BL_NPC: + if( !(((TBL_NPC*)bl)->sc.option&OPTION_INVISIBLE) ) + clif_clearunit_single(bl->id,CLR_OUTSIGHT,tsd->fd); + break; + default: + if ((vd=status_get_viewdata(bl)) && vd->class_ != INVISIBLE_CLASS) + clif_clearunit_single(bl->id,CLR_OUTSIGHT,tsd->fd); + break; + } + } + if (sd && sd->fd) + { //sd is watching tbl go out of view. + if (((vd=status_get_viewdata(tbl)) && vd->class_ != INVISIBLE_CLASS) && + !(tbl->type == BL_NPC && (((TBL_NPC*)tbl)->sc.option&OPTION_INVISIBLE))) + clif_clearunit_single(tbl->id,CLR_OUTSIGHT,sd->fd); + } + return 0; +} + +/*========================================== + * tbl has come into view of bl + *------------------------------------------*/ +int clif_insight(struct block_list *bl,va_list ap) +{ + struct block_list *tbl; + TBL_PC *sd, *tsd; + tbl=va_arg(ap,struct block_list*); + + if (bl == tbl) return 0; + + sd = BL_CAST(BL_PC, bl); + tsd = BL_CAST(BL_PC, tbl); + + if (tsd && tsd->fd) + { //Tell tsd that bl entered into his view + switch(bl->type){ + case BL_ITEM: + clif_getareachar_item(tsd,(struct flooritem_data*)bl); + break; + case BL_SKILL: + clif_getareachar_skillunit(tsd,(TBL_SKILL*)bl); + break; + default: + clif_getareachar_unit(tsd,bl); + break; + } + } + if (sd && sd->fd) + { //Tell sd that tbl walked into his view + clif_getareachar_unit(sd,tbl); + } + return 0; +} + + +/// Updates whole skill tree (ZC_SKILLINFO_LIST). +/// 010f <packet len>.W { <skill id>.W <type>.L <level>.W <sp cost>.W <attack range>.W <skill name>.24B <upgradable>.B }* +void clif_skillinfoblock(struct map_session_data *sd) +{ + int fd; + int i,len,id; + + nullpo_retv(sd); + + fd=sd->fd; + if (!fd) return; + + WFIFOHEAD(fd, MAX_SKILL * 37 + 4); + WFIFOW(fd,0) = 0x10f; + for ( i = 0, len = 4; i < MAX_SKILL; i++) + { + if( (id = sd->status.skill[i].id) != 0 ) + { + // workaround for bugreport:5348 + if (len + 37 > 8192) + break; + + WFIFOW(fd,len) = id; + WFIFOL(fd,len+2) = skill_get_inf(id); + WFIFOW(fd,len+6) = sd->status.skill[i].lv; + WFIFOW(fd,len+8) = skill_get_sp(id,sd->status.skill[i].lv); + WFIFOW(fd,len+10)= skill_get_range2(&sd->bl, id,sd->status.skill[i].lv); + safestrncpy((char*)WFIFOP(fd,len+12), skill_get_name(id), NAME_LENGTH); + if(sd->status.skill[i].flag == SKILL_FLAG_PERMANENT) + WFIFOB(fd,len+36) = (sd->status.skill[i].lv < skill_tree_get_max(id, sd->status.class_))? 1:0; + else + WFIFOB(fd,len+36) = 0; + len += 37; + } + } + WFIFOW(fd,2)=len; + WFIFOSET(fd,len); + + // workaround for bugreport:5348; send the remaining skills one by one to bypass packet size limit + for ( ; i < MAX_SKILL; i++) + { + if( (id = sd->status.skill[i].id) != 0 ) + { + clif_addskill(sd, id); + clif_skillinfo(sd, id, 0); + } + } +} +/** + * Server tells client 'sd' to add skill of id 'id' to it's skill tree (e.g. with Ice Falcion item) + **/ + +/// Adds new skill to the skill tree (ZC_ADD_SKILL). +/// 0111 <skill id>.W <type>.L <level>.W <sp cost>.W <attack range>.W <skill name>.24B <upgradable>.B +void clif_addskill(struct map_session_data *sd, int id) +{ + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + if (!fd) return; + + if( sd->status.skill[id].id <= 0 ) + return; + + WFIFOHEAD(fd, packet_len(0x111)); + WFIFOW(fd,0) = 0x111; + WFIFOW(fd,2) = id; + WFIFOL(fd,4) = skill_get_inf(id); + WFIFOW(fd,8) = sd->status.skill[id].lv; + WFIFOW(fd,10) = skill_get_sp(id,sd->status.skill[id].lv); + WFIFOW(fd,12)= skill_get_range2(&sd->bl, id,sd->status.skill[id].lv); + safestrncpy((char*)WFIFOP(fd,14), skill_get_name(id), NAME_LENGTH); + if( sd->status.skill[id].flag == SKILL_FLAG_PERMANENT ) + WFIFOB(fd,38) = (sd->status.skill[id].lv < skill_tree_get_max(id, sd->status.class_))? 1:0; + else + WFIFOB(fd,38) = 0; + WFIFOSET(fd,packet_len(0x111)); +} + + +/// Deletes a skill from the skill tree (ZC_SKILLINFO_DELETE). +/// 0441 <skill id>.W +void clif_deleteskill(struct map_session_data *sd, int id) +{ +#if PACKETVER >= 20081217 + int fd; + + nullpo_retv(sd); + fd = sd->fd; + if( !fd ) return; + + WFIFOHEAD(fd,packet_len(0x441)); + WFIFOW(fd,0) = 0x441; + WFIFOW(fd,2) = id; + WFIFOSET(fd,packet_len(0x441)); +#endif + clif_skillinfoblock(sd); +} + + +/// Updates a skill in the skill tree (ZC_SKILLINFO_UPDATE). +/// 010e <skill id>.W <level>.W <sp cost>.W <attack range>.W <upgradable>.B +void clif_skillup(struct map_session_data *sd,uint16 skill_id) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x10e)); + WFIFOW(fd,0) = 0x10e; + WFIFOW(fd,2) = skill_id; + WFIFOW(fd,4) = sd->status.skill[skill_id].lv; + WFIFOW(fd,6) = skill_get_sp(skill_id,sd->status.skill[skill_id].lv); + WFIFOW(fd,8) = skill_get_range2(&sd->bl,skill_id,sd->status.skill[skill_id].lv); + WFIFOB(fd,10) = (sd->status.skill[skill_id].lv < skill_tree_get_max(sd->status.skill[skill_id].id, sd->status.class_)) ? 1 : 0; + WFIFOSET(fd,packet_len(0x10e)); +} + + +/// Updates a skill in the skill tree (ZC_SKILLINFO_UPDATE2). +/// 07e1 <skill id>.W <type>.L <level>.W <sp cost>.W <attack range>.W <upgradable>.B +void clif_skillinfo(struct map_session_data *sd,int skill, int inf) +{ + const int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x7e1)); + WFIFOW(fd,0) = 0x7e1; + WFIFOW(fd,2) = skill; + WFIFOL(fd,4) = inf?inf:skill_get_inf(skill); + WFIFOW(fd,8) = sd->status.skill[skill].lv; + WFIFOW(fd,10) = skill_get_sp(skill,sd->status.skill[skill].lv); + WFIFOW(fd,12) = skill_get_range2(&sd->bl,skill,sd->status.skill[skill].lv); + if( sd->status.skill[skill].flag == SKILL_FLAG_PERMANENT ) + WFIFOB(fd,14) = (sd->status.skill[skill].lv < skill_tree_get_max(skill, sd->status.class_))? 1:0; + else + WFIFOB(fd,14) = 0; + WFIFOSET(fd,packet_len(0x7e1)); +} + + +/// Notifies clients in area, that an object is about to use a skill. +/// 013e <src id>.L <dst id>.L <x>.W <y>.W <skill id>.W <property>.L <delaytime>.L (ZC_USESKILL_ACK) +/// 07fb <src id>.L <dst id>.L <x>.W <y>.W <skill id>.W <property>.L <delaytime>.L <is disposable>.B (ZC_USESKILL_ACK2) +/// property: +/// 0 = Yellow cast aura +/// 1 = Water elemental cast aura +/// 2 = Earth elemental cast aura +/// 3 = Fire elemental cast aura +/// 4 = Wind elemental cast aura +/// 5 = Poison elemental cast aura +/// 6 = Holy elemental cast aura +/// ? = like 0 +/// is disposable: +/// 0 = yellow chat text "[src name] will use skill [skill name]." +/// 1 = no text +void clif_skillcasting(struct block_list* bl, int src_id, int dst_id, int dst_x, int dst_y, uint16 skill_id, int property, int casttime) +{ +#if PACKETVER < 20091124 + const int cmd = 0x13e; +#else + const int cmd = 0x7fb; +#endif + unsigned char buf[32]; + + WBUFW(buf,0) = cmd; + WBUFL(buf,2) = src_id; + WBUFL(buf,6) = dst_id; + WBUFW(buf,10) = dst_x; + WBUFW(buf,12) = dst_y; + WBUFW(buf,14) = skill_id; + WBUFL(buf,16) = property<0?0:property; //Avoid sending negatives as element [Skotlex] + WBUFL(buf,20) = casttime; +#if PACKETVER >= 20091124 + WBUFB(buf,24) = 0; // isDisposable +#endif + + if (disguised(bl)) { + clif_send(buf,packet_len(cmd), bl, AREA_WOS); + WBUFL(buf,2) = -src_id; + clif_send(buf,packet_len(cmd), bl, SELF); + } else + clif_send(buf,packet_len(cmd), bl, AREA); +} + + +/// Notifies clients in area, that an object canceled casting (ZC_DISPEL). +/// 01b9 <id>.L +void clif_skillcastcancel(struct block_list* bl) +{ + unsigned char buf[16]; + + nullpo_retv(bl); + + WBUFW(buf,0) = 0x1b9; + WBUFL(buf,2) = bl->id; + clif_send(buf,packet_len(0x1b9), bl, AREA); +} + + +/// Notifies the client about the result of a skill use request (ZC_ACK_TOUSESKILL). +/// 0110 <skill id>.W <num>.L <result>.B <cause>.B +/// num (only used when skill id = NV_BASIC and cause = 0): +/// 0 = "skill failed" MsgStringTable[159] +/// 1 = "no emotions" MsgStringTable[160] +/// 2 = "no sit" MsgStringTable[161] +/// 3 = "no chat" MsgStringTable[162] +/// 4 = "no party" MsgStringTable[163] +/// 5 = "no shout" MsgStringTable[164] +/// 6 = "no PKing" MsgStringTable[165] +/// 7 = "no alligning" MsgStringTable[383] +/// ? = ignored +/// cause: +/// 0 = "not enough skill level" MsgStringTable[214] (AL_WARP) +/// "steal failed" MsgStringTable[205] (TF_STEAL) +/// "envenom failed" MsgStringTable[207] (TF_POISON) +/// "skill failed" MsgStringTable[204] (otherwise) +/// ... = @see enum useskill_fail_cause +/// ? = ignored +/// +/// if(result!=0) doesn't display any of the previous messages +/// Note: when this packet is received an unknown flag is always set to 0, +/// suggesting this is an ACK packet for the UseSkill packets and should be sent on success too [FlavioJS] +void clif_skill_fail(struct map_session_data *sd,uint16 skill_id,enum useskill_fail_cause cause,int btype) +{ + int fd; + + if (!sd) { //Since this is the most common nullpo.... + ShowDebug("clif_skill_fail: Error, received NULL sd for skill %d\n", skill_id); + return; + } + + fd=sd->fd; + if (!fd) return; + + if(battle_config.display_skill_fail&1) + return; //Disable all skill failed messages + + if(cause==USESKILL_FAIL_SKILLINTERVAL && !sd->state.showdelay) + return; //Disable delay failed messages + + if(skill_id == RG_SNATCHER && battle_config.display_skill_fail&4) + return; + + if(skill_id == TF_POISON && battle_config.display_skill_fail&8) + return; + + WFIFOHEAD(fd,packet_len(0x110)); + WFIFOW(fd,0) = 0x110; + WFIFOW(fd,2) = skill_id; + WFIFOL(fd,4) = btype; + WFIFOB(fd,8) = 0;// success + WFIFOB(fd,9) = cause; + WFIFOSET(fd,packet_len(0x110)); +} + + +/// Skill cooldown display icon (ZC_SKILL_POSTDELAY). +/// 043d <skill ID>.W <tick>.L +void clif_skill_cooldown(struct map_session_data *sd, uint16 skill_id, unsigned int tick) +{ +#if PACKETVER>=20081112 + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x43d)); + WFIFOW(fd,0) = 0x43d; + WFIFOW(fd,2) = skill_id; + WFIFOL(fd,4) = tick; + WFIFOSET(fd,packet_len(0x43d)); +#endif +} + + +/// Skill attack effect and damage. +/// 0114 <skill id>.W <src id>.L <dst id>.L <tick>.L <src delay>.L <dst delay>.L <damage>.W <level>.W <div>.W <type>.B (ZC_NOTIFY_SKILL) +/// 01de <skill id>.W <src id>.L <dst id>.L <tick>.L <src delay>.L <dst delay>.L <damage>.L <level>.W <div>.W <type>.B (ZC_NOTIFY_SKILL2) +int clif_skill_damage(struct block_list *src,struct block_list *dst,unsigned int tick,int sdelay,int ddelay,int damage,int div,uint16 skill_id,uint16 skill_lv,int type) +{ + unsigned char buf[64]; + struct status_change *sc; + + nullpo_ret(src); + nullpo_ret(dst); + + type = clif_calc_delay(type,div,damage,ddelay); + sc = status_get_sc(dst); + if(sc && sc->count) { + if(sc->data[SC_HALLUCINATION] && damage) + damage = damage*(sc->data[SC_HALLUCINATION]->val2) + rnd()%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; + if (battle_config.hide_woe_damage && map_flag_gvg(src->m)) { + WBUFW(buf,24)=damage?div:0; + } else { + WBUFW(buf,24)=damage; + } + WBUFW(buf,26)=skill_lv; + WBUFW(buf,28)=div; + WBUFB(buf,30)=type; + if (disguised(dst)) { + clif_send(buf,packet_len(0x114),dst,AREA_WOS); + WBUFL(buf,8)=-dst->id; + clif_send(buf,packet_len(0x114),dst,SELF); + } else + clif_send(buf,packet_len(0x114),dst,AREA); + + if(disguised(src)) { + WBUFL(buf,4)=-src->id; + if (disguised(dst)) + WBUFL(buf,8)=dst->id; + if(damage > 0) + WBUFW(buf,24)=-1; + clif_send(buf,packet_len(0x114),src,SELF); + } +#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; + if (battle_config.hide_woe_damage && map_flag_gvg(src->m)) { + WBUFL(buf,24)=damage?div:0; + } else { + WBUFL(buf,24)=damage; + } + WBUFW(buf,28)=skill_lv; + WBUFW(buf,30)=div; + WBUFB(buf,32)=type; + if (disguised(dst)) { + clif_send(buf,packet_len(0x1de),dst,AREA_WOS); + WBUFL(buf,8)=-dst->id; + clif_send(buf,packet_len(0x1de),dst,SELF); + } else + clif_send(buf,packet_len(0x1de),dst,AREA); + + if(disguised(src)) { + WBUFL(buf,4)=-src->id; + if (disguised(dst)) + WBUFL(buf,8)=dst->id; + if(damage > 0) + WBUFL(buf,24)=-1; + clif_send(buf,packet_len(0x1de),src,SELF); + } +#endif + + //Because the damage delay must be synced with the client, here is where the can-walk tick must be updated. [Skotlex] + return clif_calc_walkdelay(dst,ddelay,type,damage,div); +} + + +/// Ground skill attack effect and damage (ZC_NOTIFY_SKILL_POSITION). +/// 0115 <skill id>.W <src id>.L <dst id>.L <tick>.L <src delay>.L <dst delay>.L <x>.W <y>.W <damage>.W <level>.W <div>.W <type>.B +/* +int clif_skill_damage2(struct block_list *src,struct block_list *dst,unsigned int tick,int sdelay,int ddelay,int damage,int div,uint16 skill_id,uint16 skill_lv,int type) +{ + unsigned char buf[64]; + struct status_change *sc; + + nullpo_ret(src); + nullpo_ret(dst); + + type = (type>0)?type:skill_get_hit(skill_id); + type = clif_calc_delay(type,div,damage,ddelay); + sc = status_get_sc(dst); + + if(sc && sc->count) { + if(sc->data[SC_HALLUCINATION] && damage) + damage = damage*(sc->data[SC_HALLUCINATION]->val2) + rnd()%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; + if (battle_config.hide_woe_damage && map_flag_gvg(src->m)) { + WBUFW(buf,28)=damage?div:0; + } else { + WBUFW(buf,28)=damage; + } + WBUFW(buf,30)=skill_lv; + WBUFW(buf,32)=div; + WBUFB(buf,34)=type; + clif_send(buf,packet_len(0x115),src,AREA); + if(disguised(src)) { + WBUFL(buf,4)=-src->id; + if(damage > 0) + WBUFW(buf,28)=-1; + clif_send(buf,packet_len(0x115),src,SELF); + } + if (disguised(dst)) { + WBUFL(buf,8)=-dst->id; + if (disguised(src)) + WBUFL(buf,4)=src->id; + else if(damage > 0) + WBUFW(buf,28)=-1; + clif_send(buf,packet_len(0x115),dst,SELF); + } + + //Because the damage delay must be synced with the client, here is where the can-walk tick must be updated. [Skotlex] + return clif_calc_walkdelay(dst,ddelay,type,damage,div); +} +*/ + + +/// Non-damaging skill effect (ZC_USE_SKILL). +/// 011a <skill id>.W <skill lv>.W <dst id>.L <src id>.L <result>.B +int clif_skill_nodamage(struct block_list *src,struct block_list *dst,uint16 skill_id,int heal,int fail) +{ + unsigned char buf[32]; + + nullpo_ret(dst); + + WBUFW(buf,0)=0x11a; + WBUFW(buf,2)=skill_id; + WBUFW(buf,4)=min(heal, INT16_MAX); + WBUFL(buf,6)=dst->id; + WBUFL(buf,10)=src?src->id:0; + WBUFB(buf,14)=fail; + + if (disguised(dst)) { + clif_send(buf,packet_len(0x11a),dst,AREA_WOS); + WBUFL(buf,6)=-dst->id; + clif_send(buf,packet_len(0x11a),dst,SELF); + } else + clif_send(buf,packet_len(0x11a),dst,AREA); + + if(src && disguised(src)) { + WBUFL(buf,10)=-src->id; + if (disguised(dst)) + WBUFL(buf,6)=dst->id; + clif_send(buf,packet_len(0x11a),src,SELF); + } + + return fail; +} + + +/// Non-damaging ground skill effect (ZC_NOTIFY_GROUNDSKILL). +/// 0117 <skill id>.W <src id>.L <level>.W <x>.W <y>.W <tick>.L +void clif_skill_poseffect(struct block_list *src,uint16 skill_id,int val,int x,int y,int tick) +{ + unsigned char buf[32]; + + nullpo_retv(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; + if(disguised(src)) { + clif_send(buf,packet_len(0x117),src,AREA_WOS); + WBUFL(buf,4)=-src->id; + clif_send(buf,packet_len(0x117),src,SELF); + } else + clif_send(buf,packet_len(0x117),src,AREA); +} + + +/*========================================== + * Tells all client's nearby 'unit' sight range that it spawned + *------------------------------------------*/ +//FIXME: this is just an AREA version of clif_getareachar_skillunit() +void clif_skill_setunit(struct skill_unit *unit) +{ + unsigned char buf[128]; + + nullpo_retv(unit); + + if( unit->group->state.guildaura ) + return; + +#if PACKETVER >= 3 + if(unit->group->unit_id==UNT_GRAFFITI) { // Graffiti [Valaris] + 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; + WBUFB(buf,16)=1; + safestrncpy((char*)WBUFP(buf,17),unit->group->valstr,MESSAGE_SIZE); + clif_send(buf,packet_len(0x1c9),&unit->bl,AREA); + return; + } +#endif + 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; + if (unit->group->state.song_dance&0x1 && unit->val2&UF_ENSEMBLE) + WBUFB(buf,14)=unit->val2&UF_SONG?UNT_DISSONANCE:UNT_UGLYDANCE; + else if (skill_get_unit_flag(unit->group->skill_id) & UF_RANGEDSINGLEUNIT && !(unit->val2 & UF_RANGEDSINGLEUNIT)) + WBUFB(buf, 14) = UNT_DUMMYSKILL; // Only display the unit at center. + else + WBUFB(buf,14)=unit->group->unit_id; + WBUFB(buf,15)=1; // ignored by client (always gets set to 1) + clif_send(buf,packet_len(0x11f),&unit->bl,AREA); +} + + +/// Presents a list of available warp destinations (ZC_WARPLIST). +/// 011c <skill id>.W { <map name>.16B }*4 +void clif_skill_warppoint(struct map_session_data* sd, uint16 skill_id, uint16 skill_lv, unsigned short map1, unsigned short map2, unsigned short map3, unsigned short map4) +{ + int fd; + nullpo_retv(sd); + fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x11c)); + WFIFOW(fd,0) = 0x11c; + WFIFOW(fd,2) = skill_id; + memset(WFIFOP(fd,4), 0x00, 4*MAP_NAME_LENGTH_EXT); + if (map1 == (unsigned short)-1) strcpy((char*)WFIFOP(fd,4), "Random"); + else // normal map name + if (map1 > 0) mapindex_getmapname_ext(mapindex_id2name(map1), (char*)WFIFOP(fd,4)); + if (map2 > 0) mapindex_getmapname_ext(mapindex_id2name(map2), (char*)WFIFOP(fd,20)); + if (map3 > 0) mapindex_getmapname_ext(mapindex_id2name(map3), (char*)WFIFOP(fd,36)); + if (map4 > 0) mapindex_getmapname_ext(mapindex_id2name(map4), (char*)WFIFOP(fd,52)); + WFIFOSET(fd,packet_len(0x11c)); + + sd->menuskill_id = skill_id; + if (skill_id == AL_WARP) + sd->menuskill_val = (sd->ud.skillx<<16)|sd->ud.skilly; //Store warp position here. + else + sd->menuskill_val = skill_lv; +} + + +/// Memo message (ZC_ACK_REMEMBER_WARPPOINT). +/// 011e <type>.B +/// type: +/// 0 = "Saved location as a Memo Point for Warp skill." in color 0xFFFF00 (cyan) +/// 1 = "Skill Level is not high enough." in color 0x0000FF (red) +/// 2 = "You haven't learned Warp." in color 0x0000FF (red) +/// +/// @param sd Who receives the message +/// @param type What message +void clif_skill_memomessage(struct map_session_data* sd, int type) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x11e)); + WFIFOW(fd,0)=0x11e; + WFIFOB(fd,2)=type; + WFIFOSET(fd,packet_len(0x11e)); +} + + +/// Teleport message (ZC_NOTIFY_MAPINFO). +/// 0189 <type>.W +/// type: +/// 0 = "Unable to Teleport in this area" in color 0xFFFF00 (cyan) +/// 1 = "Saved point cannot be memorized." in color 0x0000FF (red) +/// +/// @param sd Who receives the message +/// @param type What message +void clif_skill_teleportmessage(struct map_session_data *sd, int type) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x189)); + WFIFOW(fd,0)=0x189; + WFIFOW(fd,2)=type; + WFIFOSET(fd,packet_len(0x189)); +} + + +/// Displays Sense (WZ_ESTIMATION) information window (ZC_MONSTER_INFO). +/// 018c <class>.W <level>.W <size>.W <hp>.L <def>.W <race>.W <mdef>.W <element>.W +/// <water%>.B <earth%>.B <fire%>.B <wind%>.B <poison%>.B <holy%>.B <shadow%>.B <ghost%>.B <undead%>.B +void clif_skill_estimation(struct map_session_data *sd,struct block_list *dst) +{ + struct status_data *status; + unsigned char buf[64]; + int i;//, fix; + + nullpo_retv(sd); + nullpo_retv(dst); + + if( dst->type != BL_MOB ) + return; + + status = status_get_status_data(dst); + + WBUFW(buf, 0)=0x18c; + WBUFW(buf, 2)=status_get_class(dst); + WBUFW(buf, 4)=status_get_lv(dst); + WBUFW(buf, 6)=status->size; + WBUFL(buf, 8)=status->hp; + WBUFW(buf,12)= (battle_config.estimation_type&1?status->def:0) + +(battle_config.estimation_type&2?status->def2:0); + WBUFW(buf,14)=status->race; + WBUFW(buf,16)= (battle_config.estimation_type&1?status->mdef:0) + +(battle_config.estimation_type&2?status->mdef2:0); + WBUFW(buf,18)= status->def_ele; + for(i=0;i<9;i++) + WBUFB(buf,20+i)= (unsigned char)battle_attr_ratio(i+1,status->def_ele, status->ele_lv); +// The following caps negative attributes to 0 since the client displays them as 255-fix. [Skotlex] +// WBUFB(buf,20+i)= (unsigned char)((fix=battle_attr_ratio(i+1,status->def_ele, status->ele_lv))<0?0:fix); + + clif_send(buf,packet_len(0x18c),&sd->bl,sd->status.party_id>0?PARTY_SAMEMAP:SELF); +} + + +/// Presents a textual list of producable items (ZC_MAKABLEITEMLIST). +/// 018d <packet len>.W { <name id>.W { <material id>.W }*3 }* +/// material id: +/// unused by the client +void clif_skill_produce_mix_list(struct map_session_data *sd, int skill_id , int trigger) +{ + int i,c,view,fd; + nullpo_retv(sd); + + if(sd->menuskill_id == skill_id) + return; //Avoid resending the menu twice or more times... + if( skill_id == GC_CREATENEWPOISON ) + skill_id = GC_RESEARCHNEWPOISON; + + fd=sd->fd; + WFIFOHEAD(fd, MAX_SKILL_PRODUCE_DB * 8 + 8); + 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, 1) && + ( skill_id > 0 && skill_produce_db[i].req_skill == skill_id || skill_id < 0 ) + ){ + 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)= 0; + WFIFOW(fd,c*8+ 8)= 0; + WFIFOW(fd,c*8+10)= 0; + c++; + } + } + WFIFOW(fd, 2)=c*8+8; + WFIFOSET(fd,WFIFOW(fd,2)); + if(c > 0) { + sd->menuskill_id = skill_id; + sd->menuskill_val = trigger; + return; + } +} + + +/// Present a list of producable items (ZC_MAKINGITEM_LIST). +/// 025a <packet len>.W <mk type>.W { <name id>.W }* +/// mk type: +/// 1 = cooking +/// 2 = arrow +/// 3 = elemental +/// 4 = GN_MIX_COOKING +/// 5 = GN_MAKEBOMB +/// 6 = GN_S_PHARMACY +void clif_cooking_list(struct map_session_data *sd, int trigger, uint16 skill_id, int qty, int list_type) +{ + int fd; + int i, c; + int view; + + nullpo_retv(sd); + fd = sd->fd; + + WFIFOHEAD(fd, 6 + 2 * MAX_SKILL_PRODUCE_DB); + WFIFOW(fd,0) = 0x25a; + WFIFOW(fd,4) = list_type; // list type + + c = 0; + for( i = 0; i < MAX_SKILL_PRODUCE_DB; i++ ) { + if( !skill_can_produce_mix(sd,skill_produce_db[i].nameid,trigger, qty) ) + continue; + + if( (view = itemdb_viewid(skill_produce_db[i].nameid)) > 0 ) + WFIFOW(fd, 6 + 2 * c) = view; + else + WFIFOW(fd, 6 + 2 * c) = skill_produce_db[i].nameid; + + c++; + } + + if( skill_id == AM_PHARMACY ) { // Only send it while Cooking else check for c. + WFIFOW(fd,2) = 6 + 2 * c; + WFIFOSET(fd,WFIFOW(fd,2)); + } + + if( c > 0 ) { + sd->menuskill_id = skill_id; + sd->menuskill_val = trigger; + if( skill_id != AM_PHARMACY ) { + sd->menuskill_val2 = qty; // amount. + WFIFOW(fd,2) = 6 + 2 * c; + WFIFOSET(fd,WFIFOW(fd,2)); + } + } else { + clif_menuskill_clear(sd); + if( skill_id != AM_PHARMACY ) { // AM_PHARMACY is used to Cooking. + // It fails. +#if PACKETVER >= 20090922 + clif_msg_skill(sd,skill_id,0x625); +#else + WFIFOW(fd,2) = 6 + 2 * c; + WFIFOSET(fd,WFIFOW(fd,2)); +#endif + } + } +} + + +/// Notifies clients of a status change. +/// 0196 <index>.W <id>.L <state>.B (ZC_MSG_STATE_CHANGE) [used for ending status changes and starting them on non-pc units (when needed)] +/// 043f <index>.W <id>.L <state>.B <remain msec>.L { <val>.L }*3 (ZC_MSG_STATE_CHANGE2) [used exclusively for starting statuses on pcs] +void clif_status_change(struct block_list *bl,int type,int flag,int tick,int val1, int val2, int val3) +{ + unsigned char buf[32]; + struct map_session_data *sd; + + if (type == SI_BLANK) //It shows nothing on the client... + return; + + nullpo_retv(bl); + + sd = BL_CAST(BL_PC, bl); + + if (!(status_type2relevant_bl_types(type)&bl->type)) // only send status changes that actually matter to the client + return; + +#if PACKETVER >= 20090121 + if(flag && battle_config.display_status_timers && sd) + WBUFW(buf,0)=0x43f; + else +#endif + WBUFW(buf,0)=0x196; + WBUFW(buf,2)=type; + WBUFL(buf,4)=bl->id; + WBUFB(buf,8)=flag; +#if PACKETVER >= 20090121 + if(flag && battle_config.display_status_timers && sd) + { + if (tick <= 0) + tick = 9999; // this is indeed what official servers do + + WBUFL(buf,9) = tick; + WBUFL(buf,13) = val1; + WBUFL(buf,17) = val2; + WBUFL(buf,21) = val3; + } +#endif + clif_send(buf,packet_len(WBUFW(buf,0)),bl, (sd && sd->status.option&OPTION_INVISIBLE) ? SELF : AREA); +} + +/// Send message (modified by [Yor]) (ZC_NOTIFY_PLAYERCHAT). +/// 008e <packet len>.W <message>.?B +void clif_displaymessage(const int fd, const char* mes) +{ + nullpo_retv(mes); + + //Scrapped, as these are shared by disconnected players =X [Skotlex] + if (fd == 0) + ; + else { + char *message, *line; + + message = aStrdup(mes); + line = strtok(message, "\n"); + while(line != NULL) { + // Limit message to 255+1 characters (otherwise it causes a buffer overflow in the client) + int len = strnlen(line, 255); + + if (len > 0) { // don't send a void message (it's not displaying on the client chat). @help can send void line. + WFIFOHEAD(fd, 5 + len); + WFIFOW(fd,0) = 0x8e; + WFIFOW(fd,2) = 5 + len; // 4 + len + NULL teminate + safestrncpy((char *)WFIFOP(fd,4), line, len + 1); + WFIFOSET(fd, 5 + len); + } + line = strtok(NULL, "\n"); + } + aFree(message); + } +} + +/// Send broadcast message in yellow or blue without font formatting (ZC_BROADCAST). +/// 009a <packet len>.W <message>.?B +void clif_broadcast(struct block_list* bl, const char* mes, int len, int type, enum send_target target) +{ + int lp = type ? 4 : 0; + unsigned char *buf = (unsigned char*)aMalloc((4 + lp + len)*sizeof(unsigned char)); + + WBUFW(buf,0) = 0x9a; + WBUFW(buf,2) = 4 + lp + len; + if (type == 0x10) // bc_blue + WBUFL(buf,4) = 0x65756c62; //If there's "blue" at the beginning of the message, game client will display it in blue instead of yellow. + else if (type == 0x20) // bc_woe + WBUFL(buf,4) = 0x73737373; //If there's "ssss", game client will recognize message as 'WoE broadcast'. + memcpy(WBUFP(buf, 4 + lp), mes, len); + clif_send(buf, WBUFW(buf,2), bl, target); + + if (buf) + aFree(buf); +} + +/*========================================== + * Displays a message on a 'bl' to all it's nearby clients + * Used by npc_globalmessage + *------------------------------------------*/ +void clif_GlobalMessage(struct block_list* bl, const char* message) { + char buf[100]; + int len; + nullpo_retv(bl); + + if(!message) + return; + + len = strlen(message)+1; + + if( len > sizeof(buf)-8 ) { + ShowWarning("clif_GlobalMessage: Truncating too long message '%s' (len=%d).\n", message, len); + len = sizeof(buf)-8; + } + + WBUFW(buf,0)=0x8d; + WBUFW(buf,2)=len+8; + WBUFL(buf,4)=bl->id; + safestrncpy((char *) WBUFP(buf,8),message,len); + clif_send((unsigned char *) buf,WBUFW(buf,2),bl,ALL_CLIENT); + +} + +/*========================================== + * Send main chat message [LuzZza] + *------------------------------------------*/ +void clif_MainChatMessage(const char* message) { + uint8 buf[200]; + int len; + + if(!message) + return; + + len = strlen(message)+1; + if (len+8 > sizeof(buf)) { + ShowDebug("clif_MainChatMessage: Received message too long (len %d): %s\n", len, message); + len = sizeof(buf)-8; + } + WBUFW(buf,0)=0x8d; + WBUFW(buf,2)=len+8; + WBUFL(buf,4)=0; + safestrncpy((char *) WBUFP(buf,8),message,len); + clif_send(buf,WBUFW(buf,2),NULL,CHAT_MAINCHAT); +} + +/// Send broadcast message with font formatting (ZC_BROADCAST2). +/// 01c3 <packet len>.W <fontColor>.L <fontType>.W <fontSize>.W <fontAlign>.W <fontY>.W <message>.?B +void clif_broadcast2(struct block_list* bl, const char* mes, int len, unsigned long fontColor, short fontType, short fontSize, short fontAlign, short fontY, enum send_target target) +{ + unsigned char *buf = (unsigned char*)aMalloc((16 + len)*sizeof(unsigned char)); + + WBUFW(buf,0) = 0x1c3; + WBUFW(buf,2) = len + 16; + WBUFL(buf,4) = fontColor; + WBUFW(buf,8) = fontType; + WBUFW(buf,10) = fontSize; + WBUFW(buf,12) = fontAlign; + WBUFW(buf,14) = fontY; + memcpy(WBUFP(buf,16), mes, len); + clif_send(buf, WBUFW(buf,2), bl, target); + + if (buf) + aFree(buf); +} + + +/// Displays heal effect (ZC_RECOVERY). +/// 013d <var id>.W <amount>.W +/// var id: +/// 5 = HP (SP_HP) +/// 7 = SP (SP_SP) +/// ? = ignored +void clif_heal(int fd,int type,int val) +{ + WFIFOHEAD(fd,packet_len(0x13d)); + WFIFOW(fd,0)=0x13d; + WFIFOW(fd,2)=type; + WFIFOW(fd,4)=cap_value(val,0,INT16_MAX); + WFIFOSET(fd,packet_len(0x13d)); +} + + +/// Displays resurrection effect (ZC_RESURRECTION). +/// 0148 <id>.L <type>.W +/// type: +/// ignored +void clif_resurrection(struct block_list *bl,int type) +{ + unsigned char buf[16]; + + nullpo_retv(bl); + + WBUFW(buf,0)=0x148; + WBUFL(buf,2)=bl->id; + WBUFW(buf,6)=0; + + clif_send(buf,packet_len(0x148),bl,type==1 ? AREA : AREA_WOS); + if (disguised(bl)) + clif_spawn(bl); +} + + +/// Sets the map property (ZC_NOTIFY_MAPPROPERTY). +/// 0199 <type>.W +void clif_map_property(struct map_session_data* sd, enum map_property property) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x199)); + WFIFOW(fd,0)=0x199; + WFIFOW(fd,2)=property; + WFIFOSET(fd,packet_len(0x199)); +} + + +/// Set the map type (ZC_NOTIFY_MAPPROPERTY2). +/// 01d6 <type>.W +void clif_map_type(struct map_session_data* sd, enum map_type type) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x1D6)); + WFIFOW(fd,0)=0x1D6; + WFIFOW(fd,2)=type; + WFIFOSET(fd,packet_len(0x1D6)); +} + + +/// Updates PvP ranking (ZC_NOTIFY_RANKING). +/// 019a <id>.L <ranking>.L <total>.L +void clif_pvpset(struct map_session_data *sd,int pvprank,int pvpnum,int type) +{ + if(type == 2) { + int fd = sd->fd; + WFIFOHEAD(fd,packet_len(0x19a)); + WFIFOW(fd,0) = 0x19a; + WFIFOL(fd,2) = sd->bl.id; + WFIFOL(fd,6) = pvprank; + WFIFOL(fd,10) = pvpnum; + WFIFOSET(fd,packet_len(0x19a)); + } else { + unsigned char buf[32]; + WBUFW(buf,0) = 0x19a; + WBUFL(buf,2) = sd->bl.id; + if(sd->sc.option&(OPTION_HIDE|OPTION_CLOAK)) + WBUFL(buf,6) = UINT32_MAX; //On client displays as -- + else + WBUFL(buf,6) = pvprank; + WBUFL(buf,10) = pvpnum; + if(sd->sc.option&OPTION_INVISIBLE || sd->disguise) //Causes crashes when a 'mob' with pvp info dies. + clif_send(buf,packet_len(0x19a),&sd->bl,SELF); + else if(!type) + clif_send(buf,packet_len(0x19a),&sd->bl,AREA); + else + clif_send(buf,packet_len(0x19a),&sd->bl,ALL_SAMEMAP); + } +} + + +/*========================================== + * + *------------------------------------------*/ +void clif_map_property_mapall(int map, enum map_property property) +{ + struct block_list bl; + unsigned char buf[16]; + + bl.id = 0; + bl.type = BL_NUL; + bl.m = map; + WBUFW(buf,0)=0x199; + WBUFW(buf,2)=property; + clif_send(buf,packet_len(0x199),&bl,ALL_SAMEMAP); +} + + +/// Notifies the client about the result of a refine attempt (ZC_ACK_ITEMREFINING). +/// 0188 <result>.W <index>.W <refine>.W +/// result: +/// 0 = success +/// 1 = failure +/// 2 = downgrade +void clif_refine(int fd, int fail, int index, int val) +{ + WFIFOHEAD(fd,packet_len(0x188)); + WFIFOW(fd,0)=0x188; + WFIFOW(fd,2)=fail; + WFIFOW(fd,4)=index+2; + WFIFOW(fd,6)=val; + WFIFOSET(fd,packet_len(0x188)); +} + + +/// Notifies the client about the result of a weapon refine attempt (ZC_ACK_WEAPONREFINE). +/// 0223 <result>.L <nameid>.W +/// result: +/// 0 = "weapon upgraded: %s" MsgStringTable[911] in rgb(0,255,255) +/// 1 = "weapon upgraded: %s" MsgStringTable[912] in rgb(0,205,205) +/// 2 = "cannot upgrade %s until you level up the upgrade weapon skill" MsgStringTable[913] in rgb(255,200,200) +/// 3 = "you lack the item %s to upgrade the weapon" MsgStringTable[914] in rgb(255,200,200) +void clif_upgrademessage(int fd, int result, int item_id) +{ + WFIFOHEAD(fd,packet_len(0x223)); + WFIFOW(fd,0)=0x223; + WFIFOL(fd,2)=result; + WFIFOW(fd,6)=item_id; + WFIFOSET(fd,packet_len(0x223)); +} + + +/// Whisper is transmitted to the destination player (ZC_WHISPER). +/// 0097 <packet len>.W <nick>.24B <message>.?B +/// 0097 <packet len>.W <nick>.24B <isAdmin>.L <message>.?B (PACKETVER >= 20091104) +void clif_wis_message(int fd, const char* nick, const char* mes, int mes_len) +{ +#if PACKETVER < 20091104 + WFIFOHEAD(fd, mes_len + NAME_LENGTH + 4); + WFIFOW(fd,0) = 0x97; + WFIFOW(fd,2) = mes_len + NAME_LENGTH + 4; + safestrncpy((char*)WFIFOP(fd,4), nick, NAME_LENGTH); + safestrncpy((char*)WFIFOP(fd,28), mes, mes_len); + WFIFOSET(fd,WFIFOW(fd,2)); +#else + WFIFOHEAD(fd, mes_len + NAME_LENGTH + 8); + WFIFOW(fd,0) = 0x97; + WFIFOW(fd,2) = mes_len + NAME_LENGTH + 8; + safestrncpy((char*)WFIFOP(fd,4), nick, NAME_LENGTH); + WFIFOL(fd,28) = 0; // isAdmin; if nonzero, also displays text above char + // TODO: WFIFOL(fd,28) = pc_get_group_level(ssd); + safestrncpy((char*)WFIFOP(fd,32), mes, mes_len); + WFIFOSET(fd,WFIFOW(fd,2)); +#endif +} + + +/// Inform the player about the result of his whisper action (ZC_ACK_WHISPER). +/// 0098 <result>.B +/// result: +/// 0 = success to send wisper +/// 1 = target character is not loged in +/// 2 = ignored by target +/// 3 = everyone ignored by target +void clif_wis_end(int fd, int flag) +{ + WFIFOHEAD(fd,packet_len(0x98)); + WFIFOW(fd,0) = 0x98; + WFIFOW(fd,2) = flag; + WFIFOSET(fd,packet_len(0x98)); +} + + +/// Returns character name requested by char_id (ZC_ACK_REQNAME_BYGID). +/// 0194 <char id>.L <name>.24B +void clif_solved_charname(int fd, int charid, const char* name) +{ + WFIFOHEAD(fd,packet_len(0x194)); + WFIFOW(fd,0)=0x194; + WFIFOL(fd,2)=charid; + safestrncpy((char*)WFIFOP(fd,6), name, NAME_LENGTH); + WFIFOSET(fd,packet_len(0x194)); +} + + +/// Presents a list of items that can be carded/composed (ZC_ITEMCOMPOSITION_LIST). +/// 017b <packet len>.W { <name id>.W }* +void clif_use_card(struct map_session_data *sd,int idx) +{ + int i,c,ep; + int fd=sd->fd; + + nullpo_retv(sd); + if (idx < 0 || idx >= MAX_INVENTORY) //Crash-fix from bad packets. + return; + + if (!sd->inventory_data[idx] || sd->inventory_data[idx]->type != IT_CARD) + return; //Avoid parsing invalid item indexes (no card/no item) + + ep=sd->inventory_data[idx]->equip; + WFIFOHEAD(fd,MAX_INVENTORY * 2 + 4); + WFIFOW(fd,0)=0x17b; + + for(i=c=0;i<MAX_INVENTORY;i++){ + int j; + + if(sd->inventory_data[i] == NULL) + continue; + if(sd->inventory_data[i]->type!=IT_WEAPON && sd->inventory_data[i]->type!=IT_ARMOR) + continue; + if(itemdb_isspecial(sd->status.inventory[i].card[0])) //Can't slot it + continue; + + if(sd->status.inventory[i].identify==0 ) //Not identified + continue; + + if((sd->inventory_data[i]->equip&ep)==0) //Not equippable on this part. + continue; + + if(sd->inventory_data[i]->type==IT_WEAPON && ep==EQP_SHIELD) //Shield card won't go on left weapon. + continue; + + ARR_FIND( 0, sd->inventory_data[i]->slot, j, sd->status.inventory[i].card[j] == 0 ); + if( j == sd->inventory_data[i]->slot ) // No room + continue; + + WFIFOW(fd,4+c*2)=i+2; + c++; + } + WFIFOW(fd,2)=4+c*2; + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Notifies the client about the result of item carding/composition (ZC_ACK_ITEMCOMPOSITION). +/// 017d <equip index>.W <card index>.W <result>.B +/// result: +/// 0 = success +/// 1 = failure +void clif_insert_card(struct map_session_data *sd,int idx_equip,int idx_card,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x17d)); + WFIFOW(fd,0)=0x17d; + WFIFOW(fd,2)=idx_equip+2; + WFIFOW(fd,4)=idx_card+2; + WFIFOB(fd,6)=flag; + WFIFOSET(fd,packet_len(0x17d)); +} + + +/// Presents a list of items that can be identified (ZC_ITEMIDENTIFY_LIST). +/// 0177 <packet len>.W { <name id>.W }* +void clif_item_identify_list(struct map_session_data *sd) +{ + int i,c; + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + + WFIFOHEAD(fd,MAX_INVENTORY * 2 + 4); + WFIFOW(fd,0)=0x177; + for(i=c=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid > 0 && !sd->status.inventory[i].identify){ + WFIFOW(fd,c*2+4)=i+2; + c++; + } + } + if(c > 0) { + WFIFOW(fd,2)=c*2+4; + WFIFOSET(fd,WFIFOW(fd,2)); + sd->menuskill_id = MC_IDENTIFY; + sd->menuskill_val = c; + } +} + + +/// Notifies the client about the result of a item identify request (ZC_ACK_ITEMIDENTIFY). +/// 0179 <index>.W <result>.B +void clif_item_identified(struct map_session_data *sd,int idx,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x179)); + WFIFOW(fd, 0)=0x179; + WFIFOW(fd, 2)=idx+2; + WFIFOB(fd, 4)=flag; + WFIFOSET(fd,packet_len(0x179)); +} + + +/// Presents a list of items that can be repaired (ZC_REPAIRITEMLIST). +/// 01fc <packet len>.W { <index>.W <name id>.W <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W }* +void clif_item_repair_list(struct map_session_data *sd,struct map_session_data *dstsd, int lv) +{ + int i,c; + int fd; + int nameid; + + nullpo_retv(sd); + nullpo_retv(dstsd); + + fd=sd->fd; + + WFIFOHEAD(fd, MAX_INVENTORY * 13 + 4); + WFIFOW(fd,0)=0x1fc; + for(i=c=0;i<MAX_INVENTORY;i++){ + if((nameid=dstsd->status.inventory[i].nameid) > 0 && dstsd->status.inventory[i].attribute!=0){// && skill_can_repair(sd,nameid)){ + WFIFOW(fd,c*13+4) = i; + WFIFOW(fd,c*13+6) = nameid; + WFIFOB(fd,c*13+8) = dstsd->status.inventory[i].refine; + clif_addcards(WFIFOP(fd,c*13+9), &dstsd->status.inventory[i]); + c++; + } + } + if(c > 0) { + WFIFOW(fd,2)=c*13+4; + WFIFOSET(fd,WFIFOW(fd,2)); + sd->menuskill_id = BS_REPAIRWEAPON; + sd->menuskill_val = dstsd->bl.id; + sd->menuskill_val2 = lv; + }else + clif_skill_fail(sd,sd->ud.skill_id,USESKILL_FAIL_LEVEL,0); +} + + +/// Notifies the client about the result of a item repair request (ZC_ACK_ITEMREPAIR). +/// 01fe <index>.W <result>.B +/// index: +/// ignored (inventory index) +/// result: +/// 0 = Item repair success. +/// 1 = Item repair failure. +void clif_item_repaireffect(struct map_session_data *sd,int idx,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x1fe)); + WFIFOW(fd, 0)=0x1fe; + WFIFOW(fd, 2)=idx+2; + WFIFOB(fd, 4)=flag; + WFIFOSET(fd,packet_len(0x1fe)); + +} + + +/// Displays a message, that an equipment got damaged (ZC_EQUIPITEM_DAMAGED). +/// 02bb <equip location>.W <account id>.L +void clif_item_damaged(struct map_session_data* sd, unsigned short position) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x2bb)); + WFIFOW(fd,0) = 0x2bb; + WFIFOW(fd,2) = position; + WFIFOL(fd,4) = sd->bl.id; // TODO: the packet seems to be sent to other people as well, probably party and/or guild. + WFIFOSET(fd,packet_len(0x2bb)); +} + + +/// Presents a list of weapon items that can be refined [Taken from jAthena] (ZC_NOTIFY_WEAPONITEMLIST). +/// 0221 <packet len>.W { <index>.W <name id>.W <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W }* +void clif_item_refine_list(struct map_session_data *sd) +{ + int i,c; + int fd; + uint16 skill_lv; + int wlv; + int refine_item[5]; + + nullpo_retv(sd); + + skill_lv = pc_checkskill(sd,WS_WEAPONREFINE); + + fd=sd->fd; + + refine_item[0] = -1; + refine_item[1] = pc_search_inventory(sd,1010); + refine_item[2] = pc_search_inventory(sd,1011); + refine_item[3] = refine_item[4] = pc_search_inventory(sd,984); + + WFIFOHEAD(fd, MAX_INVENTORY * 13 + 4); + WFIFOW(fd,0)=0x221; + for(i=c=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid > 0 && sd->status.inventory[i].refine < skill_lv && + sd->status.inventory[i].identify && (wlv=itemdb_wlv(sd->status.inventory[i].nameid)) >=1 && + refine_item[wlv]!=-1 && !(sd->status.inventory[i].equip&EQP_ARMS)){ + WFIFOW(fd,c*13+ 4)=i+2; + WFIFOW(fd,c*13+ 6)=sd->status.inventory[i].nameid; + WFIFOB(fd,c*13+ 8)=sd->status.inventory[i].refine; + clif_addcards(WFIFOP(fd,c*13+9), &sd->status.inventory[i]); + c++; + } + } + WFIFOW(fd,2)=c*13+4; + WFIFOSET(fd,WFIFOW(fd,2)); + if (c > 0) { + sd->menuskill_id = WS_WEAPONREFINE; + sd->menuskill_val = skill_lv; + } +} + + +/// Notification of an auto-casted skill (ZC_AUTORUN_SKILL). +/// 0147 <skill id>.W <type>.L <level>.W <sp cost>.W <atk range>.W <skill name>.24B <upgradable>.B +void clif_item_skill(struct map_session_data *sd,uint16 skill_id,uint16 skill_lv) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x147)); + WFIFOW(fd, 0)=0x147; + WFIFOW(fd, 2)=skill_id; + WFIFOW(fd, 4)=skill_get_inf(skill_id); + WFIFOW(fd, 6)=0; + WFIFOW(fd, 8)=skill_lv; + WFIFOW(fd,10)=skill_get_sp(skill_id,skill_lv); + WFIFOW(fd,12)=skill_get_range2(&sd->bl, skill_id,skill_lv); + safestrncpy((char*)WFIFOP(fd,14),skill_get_name(skill_id),NAME_LENGTH); + WFIFOB(fd,38)=0; + WFIFOSET(fd,packet_len(0x147)); +} + + +/// Adds an item to character's cart. +/// 0124 <index>.W <amount>.L <name id>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W (ZC_ADD_ITEM_TO_CART) +/// 01c5 <index>.W <amount>.L <name id>.W <type>.B <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W (ZC_ADD_ITEM_TO_CART2) +void clif_cart_additem(struct map_session_data *sd,int n,int amount,int fail) +{ + int view,fd; + unsigned char *buf; + + nullpo_retv(sd); + + fd=sd->fd; + if(n<0 || n>=MAX_CART || sd->status.cart[n].nameid<=0) + return; + +#if PACKETVER < 5 + WFIFOHEAD(fd,packet_len(0x124)); + buf=WFIFOP(fd,0); + 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; + WBUFB(buf,11)=sd->status.cart[n].attribute; + WBUFB(buf,12)=sd->status.cart[n].refine; + clif_addcards(WBUFP(buf,13), &sd->status.cart[n]); + WFIFOSET(fd,packet_len(0x124)); +#else + WFIFOHEAD(fd,packet_len(0x1c5)); + buf=WFIFOP(fd,0); + WBUFW(buf,0)=0x1c5; + 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)=itemdb_type(sd->status.cart[n].nameid); + WBUFB(buf,11)=sd->status.cart[n].identify; + WBUFB(buf,12)=sd->status.cart[n].attribute; + WBUFB(buf,13)=sd->status.cart[n].refine; + clif_addcards(WBUFP(buf,14), &sd->status.cart[n]); + WFIFOSET(fd,packet_len(0x1c5)); +#endif +} + + +/// Deletes an item from character's cart (ZC_DELETE_ITEM_FROM_CART). +/// 0125 <index>.W <amount>.L +void clif_cart_delitem(struct map_session_data *sd,int n,int amount) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + + WFIFOHEAD(fd,packet_len(0x125)); + WFIFOW(fd,0)=0x125; + WFIFOW(fd,2)=n+2; + WFIFOL(fd,4)=amount; + WFIFOSET(fd,packet_len(0x125)); +} + + +/// Opens the shop creation menu (ZC_OPENSTORE). +/// 012d <num>.W +/// num: +/// number of allowed item slots +void clif_openvendingreq(struct map_session_data* sd, int num) +{ + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0x12d)); + WFIFOW(fd,0) = 0x12d; + WFIFOW(fd,2) = num; + WFIFOSET(fd,packet_len(0x12d)); +} + + +/// Displays a vending board to target/area (ZC_STORE_ENTRY). +/// 0131 <owner id>.L <message>.80B +void clif_showvendingboard(struct block_list* bl, const char* message, int fd) +{ + unsigned char buf[128]; + + nullpo_retv(bl); + + WBUFW(buf,0) = 0x131; + WBUFL(buf,2) = bl->id; + safestrncpy((char*)WBUFP(buf,6), message, 80); + + if( fd ) { + WFIFOHEAD(fd,packet_len(0x131)); + memcpy(WFIFOP(fd,0),buf,packet_len(0x131)); + WFIFOSET(fd,packet_len(0x131)); + } else { + clif_send(buf,packet_len(0x131),bl,AREA_WOS); + } +} + + +/// Removes a vending board from screen (ZC_DISAPPEAR_ENTRY). +/// 0132 <owner id>.L +void clif_closevendingboard(struct block_list* bl, int fd) +{ + unsigned char buf[16]; + + nullpo_retv(bl); + + WBUFW(buf,0) = 0x132; + WBUFL(buf,2) = bl->id; + if( fd ) { + WFIFOHEAD(fd,packet_len(0x132)); + memcpy(WFIFOP(fd,0),buf,packet_len(0x132)); + WFIFOSET(fd,packet_len(0x132)); + } else { + clif_send(buf,packet_len(0x132),bl,AREA_WOS); + } +} + + +/// Sends a list of items in a shop. +/// R 0133 <packet len>.W <owner id>.L { <price>.L <amount>.W <index>.W <type>.B <name id>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W }* (ZC_PC_PURCHASE_ITEMLIST_FROMMC) +/// R 0800 <packet len>.W <owner id>.L <unique id>.L { <price>.L <amount>.W <index>.W <type>.B <name id>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W }* (ZC_PC_PURCHASE_ITEMLIST_FROMMC2) +void clif_vendinglist(struct map_session_data* sd, int id, struct s_vending* vending) +{ + int i,fd; + int count; + struct map_session_data* vsd; +#if PACKETVER < 20100105 + const int cmd = 0x133; + const int offset = 8; +#else + const int cmd = 0x800; + const int offset = 12; +#endif + + nullpo_retv(sd); + nullpo_retv(vending); + nullpo_retv(vsd=map_id2sd(id)); + + fd = sd->fd; + count = vsd->vend_num; + + WFIFOHEAD(fd, offset+count*22); + WFIFOW(fd,0) = cmd; + WFIFOW(fd,2) = offset+count*22; + WFIFOL(fd,4) = id; +#if PACKETVER >= 20100105 + WFIFOL(fd,8) = vsd->vender_id; +#endif + + for( i = 0; i < count; i++ ) + { + int index = vending[i].index; + struct item_data* data = itemdb_search(vsd->status.cart[index].nameid); + WFIFOL(fd,offset+ 0+i*22) = vending[i].value; + WFIFOW(fd,offset+ 4+i*22) = vending[i].amount; + WFIFOW(fd,offset+ 6+i*22) = vending[i].index + 2; + WFIFOB(fd,offset+ 8+i*22) = itemtype(data->type); + WFIFOW(fd,offset+ 9+i*22) = ( data->view_id > 0 ) ? data->view_id : vsd->status.cart[index].nameid; + WFIFOB(fd,offset+11+i*22) = vsd->status.cart[index].identify; + WFIFOB(fd,offset+12+i*22) = vsd->status.cart[index].attribute; + WFIFOB(fd,offset+13+i*22) = vsd->status.cart[index].refine; + clif_addcards(WFIFOP(fd,offset+14+i*22), &vsd->status.cart[index]); + } + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Shop purchase failure (ZC_PC_PURCHASE_RESULT_FROMMC). +/// 0135 <index>.W <amount>.W <result>.B +/// result: +/// 0 = success +/// 1 = not enough zeny +/// 2 = overweight +/// 4 = out of stock +/// 5 = "cannot use an npc shop while in a trade" +/// 6 = Because the store information was incorrect the item was not purchased. +/// 7 = No sales information. +void clif_buyvending(struct map_session_data* sd, int index, int amount, int fail) +{ + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0x135)); + WFIFOW(fd,0) = 0x135; + WFIFOW(fd,2) = index+2; + WFIFOW(fd,4) = amount; + WFIFOB(fd,6) = fail; + WFIFOSET(fd,packet_len(0x135)); +} + + +/// Shop creation success (ZC_PC_PURCHASE_MYITEMLIST). +/// 0136 <packet len>.W <owner id>.L { <price>.L <index>.W <amount>.W <type>.B <name id>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W }* +void clif_openvending(struct map_session_data* sd, int id, struct s_vending* vending) +{ + int i,fd; + int count; + + nullpo_retv(sd); + + fd = sd->fd; + count = sd->vend_num; + + WFIFOHEAD(fd, 8+count*22); + WFIFOW(fd,0) = 0x136; + WFIFOW(fd,2) = 8+count*22; + WFIFOL(fd,4) = id; + for( i = 0; i < count; i++ ) + { + int index = vending[i].index; + struct item_data* data = itemdb_search(sd->status.cart[index].nameid); + WFIFOL(fd, 8+i*22) = vending[i].value; + WFIFOW(fd,12+i*22) = vending[i].index + 2; + WFIFOW(fd,14+i*22) = vending[i].amount; + WFIFOB(fd,16+i*22) = itemtype(data->type); + WFIFOW(fd,17+i*22) = ( data->view_id > 0 ) ? data->view_id : sd->status.cart[index].nameid; + WFIFOB(fd,19+i*22) = sd->status.cart[index].identify; + WFIFOB(fd,20+i*22) = sd->status.cart[index].attribute; + WFIFOB(fd,21+i*22) = sd->status.cart[index].refine; + clif_addcards(WFIFOP(fd,22+i*22), &sd->status.cart[index]); + } + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Inform merchant that someone has bought an item (ZC_DELETEITEM_FROM_MCSTORE). +/// 0137 <index>.W <amount>.W +void clif_vendingreport(struct map_session_data* sd, int index, int amount) +{ + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0x137)); + WFIFOW(fd,0) = 0x137; + WFIFOW(fd,2) = index+2; + WFIFOW(fd,4) = amount; + WFIFOSET(fd,packet_len(0x137)); +} + + +/// Result of organizing a party (ZC_ACK_MAKE_GROUP). +/// 00fa <result>.B +/// result: +/// 0 = opens party window and shows MsgStringTable[77]="party successfully organized" +/// 1 = MsgStringTable[78]="party name already exists" +/// 2 = MsgStringTable[79]="already in a party" +/// 3 = cannot organize parties on this map +/// ? = nothing +void clif_party_created(struct map_session_data *sd,int result) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xfa)); + WFIFOW(fd,0)=0xfa; + WFIFOB(fd,2)=result; + WFIFOSET(fd,packet_len(0xfa)); +} + + +/// Adds new member to a party. +/// 0104 <account id>.L <role>.L <x>.W <y>.W <state>.B <party name>.24B <char name>.24B <map name>.16B (ZC_ADD_MEMBER_TO_GROUP) +/// 01e9 <account id>.L <role>.L <x>.W <y>.W <state>.B <party name>.24B <char name>.24B <map name>.16B <item pickup rule>.B <item share rule>.B (ZC_ADD_MEMBER_TO_GROUP2) +/// role: +/// 0 = leader +/// 1 = normal +/// state: +/// 0 = connected +/// 1 = disconnected +void clif_party_member_info(struct party_data *p, struct map_session_data *sd) +{ + unsigned char buf[81]; + int i; + + if (!sd) { //Pick any party member (this call is used when changing item share rules) + ARR_FIND( 0, MAX_PARTY, i, p->data[i].sd != 0 ); + } else { + ARR_FIND( 0, MAX_PARTY, i, p->data[i].sd == sd ); + } + if (i >= MAX_PARTY) return; //Should never happen... + sd = p->data[i].sd; + + WBUFW(buf, 0) = 0x1e9; + WBUFL(buf, 2) = sd->status.account_id; + WBUFL(buf, 6) = (p->party.member[i].leader)?0:1; + WBUFW(buf,10) = sd->bl.x; + WBUFW(buf,12) = sd->bl.y; + WBUFB(buf,14) = (p->party.member[i].online)?0:1; + memcpy(WBUFP(buf,15), p->party.name, NAME_LENGTH); + memcpy(WBUFP(buf,39), sd->status.name, NAME_LENGTH); + mapindex_getmapname_ext(map[sd->bl.m].name, (char*)WBUFP(buf,63)); + WBUFB(buf,79) = (p->party.item&1)?1:0; + WBUFB(buf,80) = (p->party.item&2)?1:0; + clif_send(buf,packet_len(0x1e9),&sd->bl,PARTY); +} + + +/// Sends party information (ZC_GROUP_LIST). +/// 00fb <packet len>.W <party name>.24B { <account id>.L <nick>.24B <map name>.16B <role>.B <state>.B }* +/// role: +/// 0 = leader +/// 1 = normal +/// state: +/// 0 = connected +/// 1 = disconnected +void clif_party_info(struct party_data* p, struct map_session_data *sd) +{ + unsigned char buf[2+2+NAME_LENGTH+(4+NAME_LENGTH+MAP_NAME_LENGTH_EXT+1+1)*MAX_PARTY]; + struct map_session_data* party_sd = NULL; + int i, c; + + nullpo_retv(p); + + WBUFW(buf,0) = 0xfb; + memcpy(WBUFP(buf,4), p->party.name, NAME_LENGTH); + for(i = 0, c = 0; i < MAX_PARTY; i++) + { + struct party_member* m = &p->party.member[i]; + if(!m->account_id) continue; + + if(party_sd == NULL) party_sd = p->data[i].sd; + + WBUFL(buf,28+c*46) = m->account_id; + memcpy(WBUFP(buf,28+c*46+4), m->name, NAME_LENGTH); + mapindex_getmapname_ext(mapindex_id2name(m->map), (char*)WBUFP(buf,28+c*46+28)); + 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(sd) { // send only to self + clif_send(buf, WBUFW(buf,2), &sd->bl, SELF); + } else if (party_sd) { // send to whole party + clif_send(buf, WBUFW(buf,2), &party_sd->bl, PARTY); + } +} + + +/// The player's 'party invite' state, sent during login (ZC_PARTY_CONFIG). +/// 02c9 <flag>.B +/// flag: +/// 0 = allow party invites +/// 1 = auto-deny party invites +void clif_partyinvitationstate(struct map_session_data* sd) +{ + int fd; + nullpo_retv(sd); + fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0x2c9)); + WFIFOW(fd, 0) = 0x2c9; + WFIFOB(fd, 2) = 0; // not implemented + WFIFOSET(fd, packet_len(0x2c9)); +} + + +/// Party invitation request. +/// 00fe <party id>.L <party name>.24B (ZC_REQ_JOIN_GROUP) +/// 02c6 <party id>.L <party name>.24B (ZC_PARTY_JOIN_REQ) +void clif_party_invite(struct map_session_data *sd,struct map_session_data *tsd) +{ +#if PACKETVER < 20070821 + const int cmd = 0xfe; +#else + const int cmd = 0x2c6; +#endif + int fd; + struct party_data *p; + + nullpo_retv(sd); + nullpo_retv(tsd); + + fd=tsd->fd; + + if( (p=party_search(sd->status.party_id))==NULL ) + return; + + WFIFOHEAD(fd,packet_len(cmd)); + WFIFOW(fd,0)=cmd; + WFIFOL(fd,2)=sd->status.party_id; + memcpy(WFIFOP(fd,6),p->party.name,NAME_LENGTH); + WFIFOSET(fd,packet_len(cmd)); +} + + +/// Party invite result. +/// 00fd <nick>.24S <result>.B (ZC_ACK_REQ_JOIN_GROUP) +/// 02c5 <nick>.24S <result>.L (ZC_PARTY_JOIN_REQ_ACK) +/// result=0 : char is already in a party -> MsgStringTable[80] +/// result=1 : party invite was rejected -> MsgStringTable[81] +/// result=2 : party invite was accepted -> MsgStringTable[82] +/// result=3 : party is full -> MsgStringTable[83] +/// result=4 : char of the same account already joined the party -> MsgStringTable[608] +/// result=5 : char blocked party invite -> MsgStringTable[1324] (since 20070904) +/// result=7 : char is not online or doesn't exist -> MsgStringTable[71] (since 20070904) +/// result=8 : (%s) TODO instance related? -> MsgStringTable[1388] (since 20080527) +/// return=9 : TODO map prohibits party joining? -> MsgStringTable[1871] (since 20110205) +void clif_party_inviteack(struct map_session_data* sd, const char* nick, int result) +{ + int fd; + nullpo_retv(sd); + fd=sd->fd; + +#if PACKETVER < 20070904 + if( result == 7 ) { + clif_displaymessage(fd, msg_txt(3)); + return; + } +#endif + +#if PACKETVER < 20070821 + WFIFOHEAD(fd,packet_len(0xfd)); + WFIFOW(fd,0) = 0xfd; + safestrncpy((char*)WFIFOP(fd,2),nick,NAME_LENGTH); + WFIFOB(fd,26) = result; + WFIFOSET(fd,packet_len(0xfd)); +#else + WFIFOHEAD(fd,packet_len(0x2c5)); + WFIFOW(fd,0) = 0x2c5; + safestrncpy((char*)WFIFOP(fd,2),nick,NAME_LENGTH); + WFIFOL(fd,26) = result; + WFIFOSET(fd,packet_len(0x2c5)); +#endif +} + + +/// Updates party settings. +/// 0101 <exp option>.L (ZC_GROUPINFO_CHANGE) +/// 07d8 <exp option>.L <item pick rule>.B <item share rule>.B (ZC_REQ_GROUPINFO_CHANGE_V2) +/// exp option: +/// 0 = exp sharing disabled +/// 1 = exp sharing enabled +/// 2 = cannot change exp sharing +/// +/// flag: +/// 0 = send to party +/// 1 = send to sd +void clif_party_option(struct party_data *p,struct map_session_data *sd,int flag) +{ + unsigned char buf[16]; +#if PACKETVER < 20090603 + const int cmd = 0x101; +#else + const int cmd = 0x7d8; +#endif + + nullpo_retv(p); + + if(!sd && flag==0){ + int i; + for(i=0;i<MAX_PARTY && !p->data[i].sd;i++); + if (i < MAX_PARTY) + sd = p->data[i].sd; + } + if(!sd) return; + WBUFW(buf,0)=cmd; + WBUFL(buf,2)=((flag&0x01)?2:p->party.exp); +#if PACKETVER >= 20090603 + WBUFB(buf,6)=(p->party.item&1)?1:0; + WBUFB(buf,7)=(p->party.item&2)?1:0; +#endif + if(flag==0) + clif_send(buf,packet_len(cmd),&sd->bl,PARTY); + else + clif_send(buf,packet_len(cmd),&sd->bl,SELF); +} + + +/// 0105 <account id>.L <char name>.24B <result>.B (ZC_DELETE_MEMBER_FROM_GROUP). +/// result: +/// 0 = leave +/// 1 = expel +/// 2 = cannot leave party on this map +/// 3 = cannot expel from party on this map +void clif_party_withdraw(struct party_data* p, struct map_session_data* sd, int account_id, const char* name, int flag) +{ + unsigned char buf[64]; + int i; + + nullpo_retv(p); + + if(!sd && (flag&0xf0)==0) + { + for(i=0;i<MAX_PARTY && !p->data[i].sd;i++); + if (i < MAX_PARTY) + sd = p->data[i].sd; + } + + if(!sd) return; + + WBUFW(buf,0)=0x105; + WBUFL(buf,2)=account_id; + memcpy(WBUFP(buf,6),name,NAME_LENGTH); + WBUFB(buf,30)=flag&0x0f; + if((flag&0xf0)==0) + clif_send(buf,packet_len(0x105),&sd->bl,PARTY); + else + clif_send(buf,packet_len(0x105),&sd->bl,SELF); +} + + +/// Party chat message (ZC_NOTIFY_CHAT_PARTY). +/// 0109 <packet len>.W <account id>.L <message>.?B +void clif_party_message(struct party_data* p, int account_id, const char* mes, int len) +{ + struct map_session_data *sd; + int i; + + nullpo_retv(p); + + for(i=0; i < MAX_PARTY && !p->data[i].sd;i++); + if(i < MAX_PARTY){ + unsigned char buf[1024]; + + if( len > sizeof(buf)-8 ) + { + ShowWarning("clif_party_message: Truncated message '%s' (len=%d, max=%d, party_id=%d).\n", mes, len, sizeof(buf)-8, p->party.party_id); + len = sizeof(buf)-8; + } + + sd = p->data[i].sd; + WBUFW(buf,0)=0x109; + WBUFW(buf,2)=len+8; + WBUFL(buf,4)=account_id; + safestrncpy((char *)WBUFP(buf,8), mes, len); + clif_send(buf,len+8,&sd->bl,PARTY); + } +} + + +/// Updates the position of a party member on the minimap (ZC_NOTIFY_POSITION_TO_GROUPM). +/// 0107 <account id>.L <x>.W <y>.W +void clif_party_xy(struct map_session_data *sd) +{ + unsigned char buf[16]; + + nullpo_retv(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(0x107),&sd->bl,PARTY_SAMEMAP_WOS); +} + + +/*========================================== + * Sends x/y dot to a single fd. [Skotlex] + *------------------------------------------*/ +void clif_party_xy_single(int fd, struct map_session_data *sd) +{ + WFIFOHEAD(fd,packet_len(0x107)); + WFIFOW(fd,0)=0x107; + WFIFOL(fd,2)=sd->status.account_id; + WFIFOW(fd,6)=sd->bl.x; + WFIFOW(fd,8)=sd->bl.y; + WFIFOSET(fd,packet_len(0x107)); +} + + +/// Updates HP bar of a party member. +/// 0106 <account id>.L <hp>.W <max hp>.W (ZC_NOTIFY_HP_TO_GROUPM) +/// 080e <account id>.L <hp>.L <max hp>.L (ZC_NOTIFY_HP_TO_GROUPM_R2) +void clif_party_hp(struct map_session_data *sd) +{ + unsigned char buf[16]; +#if PACKETVER < 20100126 + const int cmd = 0x106; +#else + const int cmd = 0x80e; +#endif + + nullpo_retv(sd); + + WBUFW(buf,0)=cmd; + WBUFL(buf,2)=sd->status.account_id; +#if PACKETVER < 20100126 + if (sd->battle_status.max_hp > INT16_MAX) { //To correctly display the %hp bar. [Skotlex] + WBUFW(buf,6) = sd->battle_status.hp/(sd->battle_status.max_hp/100); + WBUFW(buf,8) = 100; + } else { + WBUFW(buf,6) = sd->battle_status.hp; + WBUFW(buf,8) = sd->battle_status.max_hp; + } +#else + WBUFL(buf,6) = sd->battle_status.hp; + WBUFL(buf,10) = sd->battle_status.max_hp; +#endif + clif_send(buf,packet_len(cmd),&sd->bl,PARTY_AREA_WOS); +} + + +/*========================================== + * Sends HP bar to a single fd. [Skotlex] + *------------------------------------------*/ +void clif_hpmeter_single(int fd, int id, unsigned int hp, unsigned int maxhp) +{ +#if PACKETVER < 20100126 + const int cmd = 0x106; +#else + const int cmd = 0x80e; +#endif + WFIFOHEAD(fd,packet_len(cmd)); + WFIFOW(fd,0) = cmd; + WFIFOL(fd,2) = id; +#if PACKETVER < 20100126 + if( maxhp > INT16_MAX ) + {// To correctly display the %hp bar. [Skotlex] + WFIFOW(fd,6) = hp/(maxhp/100); + WFIFOW(fd,8) = 100; + } else { + WFIFOW(fd,6) = hp; + WFIFOW(fd,8) = maxhp; + } +#else + WFIFOL(fd,6) = hp; + WFIFOL(fd,10) = maxhp; +#endif + WFIFOSET(fd, packet_len(cmd)); +} + +/// Notifies the client, that it's attack target is too far (ZC_ATTACK_FAILURE_FOR_DISTANCE). +/// 0139 <target id>.L <target x>.W <target y>.W <x>.W <y>.W <atk range>.W +void clif_movetoattack(struct map_session_data *sd,struct block_list *bl) +{ + int fd; + + nullpo_retv(sd); + nullpo_retv(bl); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x139)); + 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->battle_status.rhw.range; + WFIFOSET(fd,packet_len(0x139)); +} + + +/// Notifies the client about the result of an item produce request (ZC_ACK_REQMAKINGITEM). +/// 018f <result>.W <name id>.W +/// result: +/// 0 = success +/// 1 = failure +/// 2 = success (alchemist) +/// 3 = failure (alchemist) +void clif_produceeffect(struct map_session_data* sd,int flag,int nameid) +{ + int view,fd; + + nullpo_retv(sd); + + fd = sd->fd; + clif_solved_charname(fd, sd->status.char_id, sd->status.name); + WFIFOHEAD(fd,packet_len(0x18f)); + 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(0x18f)); +} + + +/// Initiates the pet taming process (ZC_START_CAPTURE). +/// 019e +void clif_catch_process(struct map_session_data *sd) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x19e)); + WFIFOW(fd,0)=0x19e; + WFIFOSET(fd,packet_len(0x19e)); +} + + +/// Displays the result of a pet taming attempt (ZC_TRYCAPTURE_MONSTER). +/// 01a0 <result>.B +/// 0 = failure +/// 1 = success +void clif_pet_roulette(struct map_session_data *sd,int data) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x1a0)); + WFIFOW(fd,0)=0x1a0; + WFIFOB(fd,2)=data; + WFIFOSET(fd,packet_len(0x1a0)); +} + + +/// Presents a list of pet eggs that can be hatched (ZC_PETEGG_LIST). +/// 01a6 <packet len>.W { <index>.W }* +void clif_sendegg(struct map_session_data *sd) +{ + int i,n=0,fd; + + nullpo_retv(sd); + + fd=sd->fd; + if (battle_config.pet_no_gvg && map_flag_gvg(sd->bl.m)) + { //Disable pet hatching in GvG grounds during Guild Wars [Skotlex] + clif_displaymessage(fd, msg_txt(666)); + return; + } + WFIFOHEAD(fd, MAX_INVENTORY * 2 + 4); + WFIFOW(fd,0)=0x1a6; + 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!=IT_PETEGG || + 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)); + + sd->menuskill_id = SA_TAMINGMONSTER; + sd->menuskill_val = -1; +} + + +/// Sends a specific pet data update (ZC_CHANGESTATE_PET). +/// 01a4 <type>.B <id>.L <data>.L +/// type: +/// 0 = pre-init (data = 0) +/// 1 = intimacy (data = 0~4) +/// 2 = hunger (data = 0~4) +/// 3 = accessory +/// 4 = performance (data = 1~3: normal, 4: special) +/// 5 = hairstyle +/// +/// If sd is null, the update is sent to nearby objects, otherwise it is sent only to that player. +void clif_send_petdata(struct map_session_data* sd, struct pet_data* pd, int type, int param) +{ + uint8 buf[16]; + nullpo_retv(pd); + + WBUFW(buf,0) = 0x1a4; + WBUFB(buf,2) = type; + WBUFL(buf,3) = pd->bl.id; + WBUFL(buf,7) = param; + if (sd) + clif_send(buf, packet_len(0x1a4), &sd->bl, SELF); + else + clif_send(buf, packet_len(0x1a4), &pd->bl, AREA); +} + + +/// Pet's base data (ZC_PROPERTY_PET). +/// 01a2 <name>.24B <renamed>.B <level>.W <hunger>.W <intimacy>.W <accessory id>.W <class>.W +void clif_send_petstatus(struct map_session_data *sd) +{ + int fd; + struct s_pet *pet; + + nullpo_retv(sd); + nullpo_retv(sd->pd); + + fd=sd->fd; + pet = &sd->pd->pet; + WFIFOHEAD(fd,packet_len(0x1a2)); + WFIFOW(fd,0)=0x1a2; + memcpy(WFIFOP(fd,2),pet->name,NAME_LENGTH); + WFIFOB(fd,26)=battle_config.pet_rename?0:pet->rename_flag; + WFIFOW(fd,27)=pet->level; + WFIFOW(fd,29)=pet->hungry; + WFIFOW(fd,31)=pet->intimate; + WFIFOW(fd,33)=pet->equip; +#if PACKETVER >= 20081126 + WFIFOW(fd,35)=pet->class_; +#endif + WFIFOSET(fd,packet_len(0x1a2)); +} + + +/// Notification about a pet's emotion/talk (ZC_PET_ACT). +/// 01aa <id>.L <data>.L +/// data: +/// @see CZ_PET_ACT. +void clif_pet_emotion(struct pet_data *pd,int param) +{ + unsigned char buf[16]; + + nullpo_retv(pd); + + memset(buf,0,packet_len(0x1aa)); + + WBUFW(buf,0)=0x1aa; + WBUFL(buf,2)=pd->bl.id; + if(param >= 100 && pd->petDB->talk_convert_class) { + if(pd->petDB->talk_convert_class < 0) + return; + else if(pd->petDB->talk_convert_class > 0) { + // replace mob_id component of talk/act data + param -= (pd->pet.class_ - 100)*100; + param += (pd->petDB->talk_convert_class - 100)*100; + } + } + WBUFL(buf,6)=param; + + clif_send(buf,packet_len(0x1aa),&pd->bl,AREA); +} + + +/// Result of request to feed a pet (ZC_FEED_PET). +/// 01a3 <result>.B <name id>.W +/// result: +/// 0 = failure +/// 1 = success +void clif_pet_food(struct map_session_data *sd,int foodid,int fail) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x1a3)); + WFIFOW(fd,0)=0x1a3; + WFIFOB(fd,2)=fail; + WFIFOW(fd,3)=foodid; + WFIFOSET(fd,packet_len(0x1a3)); +} + + +/// Presents a list of skills that can be auto-spelled (ZC_AUTOSPELLLIST). +/// 01cd { <skill id>.L }*7 +void clif_autospell(struct map_session_data *sd,uint16 skill_lv) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x1cd)); + WFIFOW(fd, 0)=0x1cd; + + if(skill_lv>0 && pc_checkskill(sd,MG_NAPALMBEAT)>0) + WFIFOL(fd,2)= MG_NAPALMBEAT; + else + WFIFOL(fd,2)= 0x00000000; + if(skill_lv>1 && pc_checkskill(sd,MG_COLDBOLT)>0) + WFIFOL(fd,6)= MG_COLDBOLT; + else + WFIFOL(fd,6)= 0x00000000; + if(skill_lv>1 && pc_checkskill(sd,MG_FIREBOLT)>0) + WFIFOL(fd,10)= MG_FIREBOLT; + else + WFIFOL(fd,10)= 0x00000000; + if(skill_lv>1 && pc_checkskill(sd,MG_LIGHTNINGBOLT)>0) + WFIFOL(fd,14)= MG_LIGHTNINGBOLT; + else + WFIFOL(fd,14)= 0x00000000; + if(skill_lv>4 && pc_checkskill(sd,MG_SOULSTRIKE)>0) + WFIFOL(fd,18)= MG_SOULSTRIKE; + else + WFIFOL(fd,18)= 0x00000000; + if(skill_lv>7 && pc_checkskill(sd,MG_FIREBALL)>0) + WFIFOL(fd,22)= MG_FIREBALL; + else + WFIFOL(fd,22)= 0x00000000; + if(skill_lv>9 && pc_checkskill(sd,MG_FROSTDIVER)>0) + WFIFOL(fd,26)= MG_FROSTDIVER; + else + WFIFOL(fd,26)= 0x00000000; + + WFIFOSET(fd,packet_len(0x1cd)); + sd->menuskill_id = SA_AUTOSPELL; + sd->menuskill_val = skill_lv; +} + + +/// Devotion's visual effect (ZC_DEVOTIONLIST). +/// 01cf <devoter id>.L { <devotee id>.L }*5 <max distance>.W +void clif_devotion(struct block_list *src, struct map_session_data *tsd) +{ + unsigned char buf[56]; + int i; + + nullpo_retv(src); + memset(buf,0,packet_len(0x1cf)); + + WBUFW(buf,0) = 0x1cf; + WBUFL(buf,2) = src->id; + if( src->type == BL_MER ) + { + struct mercenary_data *md = BL_CAST(BL_MER,src); + if( md && md->master && md->devotion_flag ) + WBUFL(buf,6) = md->master->bl.id; + + WBUFW(buf,26) = skill_get_range2(src, ML_DEVOTION, mercenary_checkskill(md, ML_DEVOTION)); + } + else + { + struct map_session_data *sd = BL_CAST(BL_PC,src); + if( sd == NULL ) + return; + + for( i = 0; i < 5; i++ ) + WBUFL(buf,6+4*i) = sd->devotion[i]; + WBUFW(buf,26) = skill_get_range2(src, CR_DEVOTION, pc_checkskill(sd, CR_DEVOTION)); + } + + if( tsd ) + clif_send(buf, packet_len(0x1cf), &tsd->bl, SELF); + else + clif_send(buf, packet_len(0x1cf), src, AREA); +} + +/*========================================== + * Server tells clients nearby 'sd' (and himself) to display 'sd->spiritball' number of spiritballs on 'sd' + * Notifies clients in an area of an object's spirits. + * 01d0 <id>.L <amount>.W (ZC_SPIRITS) + * 01e1 <id>.L <amount>.W (ZC_SPIRITS2) + *------------------------------------------*/ +void clif_spiritball(struct block_list *bl) { + unsigned char buf[16]; + TBL_PC *sd = BL_CAST(BL_PC,bl); + TBL_HOM *hd = BL_CAST(BL_HOM,bl); + + nullpo_retv(bl); + + WBUFW(buf, 0) = 0x1d0; + WBUFL(buf, 2) = bl->id; + WBUFW(buf, 6) = 0; //init to 0 + switch(bl->type){ + case BL_PC: WBUFW(buf, 6) = sd->spiritball; break; + case BL_HOM: WBUFW(buf, 6) = hd->homunculus.spiritball; break; + } + clif_send(buf, packet_len(0x1d0), bl, AREA); +} + + +/// Notifies clients in area of a character's combo delay (ZC_COMBODELAY). +/// 01d2 <account id>.L <delay>.L +void clif_combo_delay(struct block_list *bl,int wait) +{ + unsigned char buf[32]; + + nullpo_retv(bl); + + WBUFW(buf,0)=0x1d2; + WBUFL(buf,2)=bl->id; + WBUFL(buf,6)=wait; + clif_send(buf,packet_len(0x1d2),bl,AREA); +} + + +/// Notifies clients in area that a character has blade-stopped another (ZC_BLADESTOP). +/// 01d1 <src id>.L <dst id>.L <flag>.L +/// flag: +/// 0 = inactive +/// 1 = active +void clif_bladestop(struct block_list *src, int dst_id, int active) +{ + unsigned char buf[32]; + + nullpo_retv(src); + + WBUFW(buf,0)=0x1d1; + WBUFL(buf,2)=src->id; + WBUFL(buf,6)=dst_id; + WBUFL(buf,10)=active; + + clif_send(buf,packet_len(0x1d1),src,AREA); +} + + +/// MVP effect (ZC_MVP). +/// 010c <account id>.L +void clif_mvp_effect(struct map_session_data *sd) +{ + unsigned char buf[16]; + + nullpo_retv(sd); + + WBUFW(buf,0)=0x10c; + WBUFL(buf,2)=sd->bl.id; + clif_send(buf,packet_len(0x10c),&sd->bl,AREA); +} + + +/// MVP item reward message (ZC_MVP_GETTING_ITEM). +/// 010a <name id>.W +void clif_mvp_item(struct map_session_data *sd,int nameid) +{ + int view,fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x10a)); + WFIFOW(fd,0)=0x10a; + if((view = itemdb_viewid(nameid)) > 0) + WFIFOW(fd,2)=view; + else + WFIFOW(fd,2)=nameid; + WFIFOSET(fd,packet_len(0x10a)); +} + + +/// MVP EXP reward message (ZC_MVP_GETTING_SPECIAL_EXP). +/// 010b <exp>.L +void clif_mvp_exp(struct map_session_data *sd, unsigned int exp) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x10b)); + WFIFOW(fd,0)=0x10b; + WFIFOL(fd,2)=cap_value(exp,0,INT32_MAX); + WFIFOSET(fd,packet_len(0x10b)); +} + + +/// Dropped MVP item reward message (ZC_THROW_MVPITEM). +/// 010d +/// +/// "You are the MVP, but cannot obtain the reward because +/// you are overweight." +void clif_mvp_noitem(struct map_session_data* sd) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x10d)); + WFIFOW(fd,0) = 0x10d; + WFIFOSET(fd,packet_len(0x10d)); +} + + +/// Guild creation result (ZC_RESULT_MAKE_GUILD). +/// 0167 <result>.B +/// result: +/// 0 = "Guild has been created." +/// 1 = "You are already in a Guild." +/// 2 = "That Guild Name already exists." +/// 3 = "You need the neccessary item to create a Guild." +void clif_guild_created(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x167)); + WFIFOW(fd,0)=0x167; + WFIFOB(fd,2)=flag; + WFIFOSET(fd,packet_len(0x167)); +} + + +/// Notifies the client that it is belonging to a guild (ZC_UPDATE_GDID). +/// 016c <guild id>.L <emblem id>.L <mode>.L <ismaster>.B <inter sid>.L <guild name>.24B +/// mode: +/// &0x01 = allow invite +/// &0x10 = allow expel +void clif_guild_belonginfo(struct map_session_data *sd, struct guild *g) +{ + int ps,fd; + nullpo_retv(sd); + nullpo_retv(g); + + fd=sd->fd; + ps=guild_getposition(g,sd); + WFIFOHEAD(fd,packet_len(0x16c)); + WFIFOW(fd,0)=0x16c; + WFIFOL(fd,2)=g->guild_id; + WFIFOL(fd,6)=g->emblem_id; + WFIFOL(fd,10)=g->position[ps].mode; + WFIFOB(fd,14)=(bool)(sd->state.gmaster_flag==g); + WFIFOL(fd,15)=0; // InterSID (unknown purpose) + memcpy(WFIFOP(fd,19),g->name,NAME_LENGTH); + WFIFOSET(fd,packet_len(0x16c)); +} + + +/// Guild member login notice. +/// 016d <account id>.L <char id>.L <status>.L (ZC_UPDATE_CHARSTAT) +/// 01f2 <account id>.L <char id>.L <status>.L <gender>.W <hair style>.W <hair color>.W (ZC_UPDATE_CHARSTAT2) +/// status: +/// 0 = offline +/// 1 = online +void clif_guild_memberlogin_notice(struct guild *g,int idx,int flag) +{ + unsigned char buf[64]; + struct map_session_data* sd; + + nullpo_retv(g); + + WBUFW(buf, 0)=0x1f2; + WBUFL(buf, 2)=g->member[idx].account_id; + WBUFL(buf, 6)=g->member[idx].char_id; + WBUFL(buf,10)=flag; + + if( ( sd = g->member[idx].sd ) != NULL ) + { + WBUFW(buf,14) = sd->status.sex; + WBUFW(buf,16) = sd->status.hair; + WBUFW(buf,18) = sd->status.hair_color; + clif_send(buf,packet_len(0x1f2),&sd->bl,GUILD_WOS); + } + else if( ( sd = guild_getavailablesd(g) ) != NULL ) + { + WBUFW(buf,14) = 0; + WBUFW(buf,16) = 0; + WBUFW(buf,18) = 0; + clif_send(buf,packet_len(0x1f2),&sd->bl,GUILD); + } +} + +// Function `clif_guild_memberlogin_notice` sends info about +// logins and logouts of a guild member to the rest members. +// But at the 1st time (after a player login or map changing) +// the client won't show the message. +// So I suggest use this function for sending "first-time-info" +// to some player on entering the game or changing location. +// At next time the client would always show the message. +// The function sends all the statuses in the single packet +// to economize traffic. [LuzZza] +void clif_guild_send_onlineinfo(struct map_session_data *sd) +{ + struct guild *g; + unsigned char buf[14*128]; + int i, count=0, p_len; + + nullpo_retv(sd); + + p_len = packet_len(0x16d); + + if(!(g = guild_search(sd->status.guild_id))) + return; + + for(i=0; i<g->max_member; i++) { + + if(g->member[i].account_id > 0 && + g->member[i].account_id != sd->status.account_id) { + + WBUFW(buf,count*p_len) = 0x16d; + WBUFL(buf,count*p_len+2) = g->member[i].account_id; + WBUFL(buf,count*p_len+6) = g->member[i].char_id; + WBUFL(buf,count*p_len+10) = g->member[i].online; + count++; + } + } + + clif_send(buf, p_len*count, &sd->bl, SELF); +} + + +/// Bitmask of enabled guild window tabs (ZC_ACK_GUILD_MENUINTERFACE). +/// 014e <menu flag>.L +/// menu flag: +/// 0x00 = Basic Info (always on) +/// &0x01 = Member manager +/// &0x02 = Positions +/// &0x04 = Skills +/// &0x10 = Expulsion list +/// &0x40 = Unknown (GMENUFLAG_ALLGUILDLIST) +/// &0x80 = Notice +void clif_guild_masterormember(struct map_session_data *sd) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x14e)); + WFIFOW(fd,0) = 0x14e; + WFIFOL(fd,2) = (sd->state.gmaster_flag) ? 0xd7 : 0x57; + WFIFOSET(fd,packet_len(0x14e)); +} + + +/// Guild basic information (Territories [Valaris]) +/// 0150 <guild id>.L <level>.L <member num>.L <member max>.L <exp>.L <max exp>.L <points>.L <honor>.L <virtue>.L <emblem id>.L <name>.24B <master name>.24B <manage land>.16B (ZC_GUILD_INFO) +/// 01b6 <guild id>.L <level>.L <member num>.L <member max>.L <exp>.L <max exp>.L <points>.L <honor>.L <virtue>.L <emblem id>.L <name>.24B <master name>.24B <manage land>.16B <zeny>.L (ZC_GUILD_INFO2) +void clif_guild_basicinfo(struct map_session_data *sd) { + int fd; + struct guild *g; + + nullpo_retv(sd); + fd = sd->fd; + + if( (g = guild_search(sd->status.guild_id)) == NULL ) + return; + + WFIFOHEAD(fd,packet_len(0x1b6)); + 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)=(uint32)cap_value(g->exp,0,INT32_MAX); + WFIFOL(fd,26)=g->next_exp; + WFIFOL(fd,30)=0; // Tax Points + WFIFOL(fd,34)=0; // Honor: (left) Vulgar [-100,100] Famed (right) + WFIFOL(fd,38)=0; // Virtue: (down) Wicked [-100,100] Righteous (up) + WFIFOL(fd,42)=g->emblem_id; + memcpy(WFIFOP(fd,46),g->name, NAME_LENGTH); + memcpy(WFIFOP(fd,70),g->master, NAME_LENGTH); + + safestrncpy((char*)WFIFOP(fd,94),msg_txt(300+guild_checkcastles(g)),16); // "'N' castles" + WFIFOL(fd,110) = 0; // zeny + + WFIFOSET(fd,packet_len(0x1b6)); +} + + +/// Guild alliance and opposition list (ZC_MYGUILD_BASIC_INFO). +/// 014c <packet len>.W { <relation>.L <guild id>.L <guild name>.24B }* +void clif_guild_allianceinfo(struct map_session_data *sd) +{ + int fd,i,c; + struct guild *g; + + nullpo_retv(sd); + if( (g = guild_search(sd->status.guild_id)) == NULL ) + return; + + fd = sd->fd; + WFIFOHEAD(fd, MAX_GUILDALLIANCE * 32 + 4); + 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,NAME_LENGTH); + c++; + } + } + WFIFOW(fd, 2)=c*32+4; + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Guild member manager information (ZC_MEMBERMGR_INFO). +/// 0154 <packet len>.W { <account>.L <char id>.L <hair style>.W <hair color>.W <gender>.W <class>.W <level>.W <contrib exp>.L <state>.L <position>.L <memo>.50B <name>.24B }* +/// state: +/// 0 = offline +/// 1 = online +/// memo: +/// probably member's self-introduction (unused, no client UI/packets for editing it) +void clif_guild_memberlist(struct map_session_data *sd) +{ + int fd; + int i,c; + struct guild *g; + nullpo_retv(sd); + + if( (fd = sd->fd) == 0 ) + return; + if( (g = guild_search(sd->status.guild_id)) == NULL ) + return; + + WFIFOHEAD(fd, g->max_member * 104 + 4); + 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)=(int)cap_value(m->exp,0,INT32_MAX); + WFIFOL(fd,c*104+26)=m->online; + WFIFOL(fd,c*104+30)=m->position; + memset(WFIFOP(fd,c*104+34),0,50); //[Ind] - This is displayed in the 'note' column but being you can't edit it it's sent empty. + memcpy(WFIFOP(fd,c*104+84),m->name,NAME_LENGTH); + c++; + } + WFIFOW(fd, 2)=c*104+4; + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Guild position name information (ZC_POSITION_ID_NAME_INFO). +/// 0166 <packet len>.W { <position id>.L <position name>.24B }* +void clif_guild_positionnamelist(struct map_session_data *sd) +{ + int i,fd; + struct guild *g; + + nullpo_retv(sd); + if( (g = guild_search(sd->status.guild_id)) == NULL ) + return; + + fd = sd->fd; + WFIFOHEAD(fd, MAX_GUILDPOSITION * 28 + 4); + 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,NAME_LENGTH); + } + WFIFOW(fd,2)=i*28+4; + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Guild position information (ZC_POSITION_INFO). +/// 0160 <packet len>.W { <position id>.L <mode>.L <ranking>.L <pay rate>.L }* +/// mode: +/// &0x01 = allow invite +/// &0x10 = allow expel +/// ranking: +/// TODO +void clif_guild_positioninfolist(struct map_session_data *sd) +{ + int i,fd; + struct guild *g; + + nullpo_retv(sd); + if( (g = guild_search(sd->status.guild_id)) == NULL ) + return; + + fd = sd->fd; + WFIFOHEAD(fd, MAX_GUILDPOSITION * 16 + 4); + 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)); +} + + +/// Notifies clients in a guild about updated position information (ZC_ACK_CHANGE_GUILD_POSITIONINFO). +/// 0174 <packet len>.W { <position id>.L <mode>.L <ranking>.L <pay rate>.L <position name>.24B }* +/// mode: +/// &0x01 = allow invite +/// &0x10 = allow expel +/// ranking: +/// TODO +void clif_guild_positionchanged(struct guild *g,int idx) +{ + // FIXME: This packet is intended to update the clients after a + // commit of position info changes, not sending one packet per + // position. + struct map_session_data *sd; + unsigned char buf[128]; + + nullpo_retv(g); + + WBUFW(buf, 0)=0x174; + WBUFW(buf, 2)=44; // packet len + // GUILD_REG_POSITION_INFO{ + 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,NAME_LENGTH); + // }* + if( (sd=guild_getavailablesd(g))!=NULL ) + clif_send(buf,WBUFW(buf,2),&sd->bl,GUILD); +} + + +/// Notifies clients in a guild about updated member position assignments (ZC_ACK_REQ_CHANGE_MEMBERS). +/// 0156 <packet len>.W { <account id>.L <char id>.L <position id>.L }* +void clif_guild_memberpositionchanged(struct guild *g,int idx) +{ + // FIXME: This packet is intended to update the clients after a + // commit of member position assignment changes, not sending one + // packet per position. + struct map_session_data *sd; + unsigned char buf[64]; + + nullpo_retv(g); + + WBUFW(buf, 0)=0x156; + WBUFW(buf, 2)=16; // packet len + // MEMBER_POSITION_INFO{ + 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); +} + + +/// Sends emblems bitmap data to the client that requested it (ZC_GUILD_EMBLEM_IMG). +/// 0152 <packet len>.W <guild id>.L <emblem id>.L <emblem data>.?B +void clif_guild_emblem(struct map_session_data *sd,struct guild *g) +{ + int fd; + nullpo_retv(sd); + nullpo_retv(g); + + fd = sd->fd; + if( g->emblem_len <= 0 ) + return; + + WFIFOHEAD(fd,g->emblem_len+12); + 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)); +} + + +/// Sends update of the guild id/emblem id to everyone in the area (ZC_CHANGE_GUILD). +/// 01b4 <id>.L <guild id>.L <emblem id>.W +void clif_guild_emblem_area(struct block_list* bl) +{ + uint8 buf[12]; + + nullpo_retv(bl); + + // TODO this packet doesn't force the update of ui components that have the emblem visible + // (emblem in the flag npcs and emblem over the head in agit maps) [FlavioJS] + WBUFW(buf,0) = 0x1b4; + WBUFL(buf,2) = bl->id; + WBUFL(buf,6) = status_get_guild_id(bl); + WBUFW(buf,10) = status_get_emblem_id(bl); + clif_send(buf, 12, bl, AREA_WOS); +} + + +/// Sends guild skills (ZC_GUILD_SKILLINFO). +/// 0162 <packet len>.W <skill points>.W { <skill id>.W <type>.L <level>.W <sp cost>.W <atk range>.W <skill name>.24B <upgradable>.B }* +void clif_guild_skillinfo(struct map_session_data* sd) +{ + int fd; + struct guild* g; + int i,c; + + nullpo_retv(sd); + if( (g = guild_search(sd->status.guild_id)) == NULL ) + return; + + fd = sd->fd; + WFIFOHEAD(fd, 6 + MAX_GUILDSKILL*37); + WFIFOW(fd,0) = 0x0162; + WFIFOW(fd,4) = g->skill_point; + for(i = 0, c = 0; i < MAX_GUILDSKILL; i++) + { + if(g->skill[i].id > 0 && guild_check_skill_require(g, g->skill[i].id)) + { + int id = g->skill[i].id; + int p = 6 + c*37; + WFIFOW(fd,p+0) = id; + WFIFOL(fd,p+2) = skill_get_inf(id); + WFIFOW(fd,p+6) = g->skill[i].lv; + WFIFOW(fd,p+8) = skill_get_sp(id, g->skill[i].lv); + WFIFOW(fd,p+10) = skill_get_range(id, g->skill[i].lv); + safestrncpy((char*)WFIFOP(fd,p+12), skill_get_name(id), NAME_LENGTH); + WFIFOB(fd,p+36)= (g->skill[i].lv < guild_skill_get_max(id) && sd == g->member[0].sd) ? 1 : 0; + c++; + } + } + WFIFOW(fd,2) = 6 + c*37; + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Sends guild notice to client (ZC_GUILD_NOTICE). +/// 016f <subject>.60B <notice>.120B +void clif_guild_notice(struct map_session_data* sd, struct guild* g) +{ + int fd; + + nullpo_retv(sd); + nullpo_retv(g); + + fd = sd->fd; + + if ( !session_isActive(fd) ) + return; + + if(g->mes1[0] == '\0' && g->mes2[0] == '\0') + return; + + WFIFOHEAD(fd,packet_len(0x16f)); + WFIFOW(fd,0) = 0x16f; + memcpy(WFIFOP(fd,2), g->mes1, MAX_GUILDMES1); + memcpy(WFIFOP(fd,62), g->mes2, MAX_GUILDMES2); + WFIFOSET(fd,packet_len(0x16f)); +} + + +/// Guild invite (ZC_REQ_JOIN_GUILD). +/// 016a <guild id>.L <guild name>.24B +void clif_guild_invite(struct map_session_data *sd,struct guild *g) +{ + int fd; + + nullpo_retv(sd); + nullpo_retv(g); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x16a)); + WFIFOW(fd,0)=0x16a; + WFIFOL(fd,2)=g->guild_id; + memcpy(WFIFOP(fd,6),g->name,NAME_LENGTH); + WFIFOSET(fd,packet_len(0x16a)); +} + + +/// Reply to invite request (ZC_ACK_REQ_JOIN_GUILD). +/// 0169 <answer>.B +/// answer: +/// 0 = Already in guild. +/// 1 = Offer rejected. +/// 2 = Offer accepted. +/// 3 = Guild full. +void clif_guild_inviteack(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x169)); + WFIFOW(fd,0)=0x169; + WFIFOB(fd,2)=flag; + WFIFOSET(fd,packet_len(0x169)); +} + + +/// Notifies clients of a guild of a leaving member (ZC_ACK_LEAVE_GUILD). +/// 015a <char name>.24B <reason>.40B +void clif_guild_leave(struct map_session_data *sd,const char *name,const char *mes) +{ + unsigned char buf[128]; + + nullpo_retv(sd); + + WBUFW(buf, 0)=0x15a; + memcpy(WBUFP(buf, 2),name,NAME_LENGTH); + memcpy(WBUFP(buf,26),mes,40); + clif_send(buf,packet_len(0x15a),&sd->bl,GUILD_NOBG); +} + + +/// Notifies clients of a guild of an expelled member. +/// 015c <char name>.24B <reason>.40B <account name>.24B (ZC_ACK_BAN_GUILD) +/// 0839 <char name>.24B <reason>.40B (ZC_ACK_BAN_GUILD_SSO) +void clif_guild_expulsion(struct map_session_data* sd, const char* name, const char* mes, int account_id) +{ + unsigned char buf[128]; +#if PACKETVER < 20100803 + const unsigned short cmd = 0x15c; +#else + const unsigned short cmd = 0x839; +#endif + + nullpo_retv(sd); + + WBUFW(buf,0) = cmd; + safestrncpy((char*)WBUFP(buf,2), name, NAME_LENGTH); + safestrncpy((char*)WBUFP(buf,26), mes, 40); +#if PACKETVER < 20100803 + memset(WBUFP(buf,66), 0, NAME_LENGTH); // account name (not used for security reasons) +#endif + clif_send(buf, packet_len(cmd), &sd->bl, GUILD_NOBG); +} + + +/// Guild expulsion list (ZC_BAN_LIST). +/// 0163 <packet len>.W { <char name>.24B <account name>.24B <reason>.40B }* +/// 0163 <packet len>.W { <char name>.24B <reason>.40B }* (PACKETVER >= 20100803) +void clif_guild_expulsionlist(struct map_session_data* sd) +{ +#if PACKETVER < 20100803 + const int offset = NAME_LENGTH*2+40; +#else + const int offset = NAME_LENGTH+40; +#endif + int fd, i, c = 0; + struct guild* g; + + nullpo_retv(sd); + + if( (g = guild_search(sd->status.guild_id)) == NULL ) + return; + + fd = sd->fd; + + WFIFOHEAD(fd,4 + MAX_GUILDEXPULSION * offset); + WFIFOW(fd,0) = 0x163; + + for( i = 0; i < MAX_GUILDEXPULSION; i++ ) + { + struct guild_expulsion* e = &g->expulsion[i]; + + if( e->account_id > 0 ) + { + memcpy(WFIFOP(fd,4 + c*offset), e->name, NAME_LENGTH); +#if PACKETVER < 20100803 + memset(WFIFOP(fd,4 + c*offset+24), 0, NAME_LENGTH); // account name (not used for security reasons) + memcpy(WFIFOP(fd,4 + c*offset+48), e->mes, 40); +#else + memcpy(WFIFOP(fd,4 + c*offset+24), e->mes, 40); +#endif + c++; + } + } + WFIFOW(fd,2) = 4 + c*offset; + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Guild chat message (ZC_GUILD_CHAT). +/// 017f <packet len>.W <message>.?B +void clif_guild_message(struct guild *g,int account_id,const char *mes,int len) +{// TODO: account_id is not used, candidate for deletion? [Ai4rei] + struct map_session_data *sd; + uint8 buf[256]; + + if( len == 0 ) + { + return; + } + else if( len > sizeof(buf)-5 ) + { + ShowWarning("clif_guild_message: Truncated message '%s' (len=%d, max=%d, guild_id=%d).\n", mes, len, sizeof(buf)-5, g->guild_id); + len = sizeof(buf)-5; + } + + WBUFW(buf, 0) = 0x17f; + WBUFW(buf, 2) = len + 5; + safestrncpy((char*)WBUFP(buf,4), mes, len+1); + + if ((sd = guild_getavailablesd(g)) != NULL) + clif_send(buf, WBUFW(buf,2), &sd->bl, GUILD_NOBG); +} + + +/*========================================== + * Server tells client 'sd' that his guild skill 'skill_id' gone to level 'lv' + *------------------------------------------*/ +int clif_guild_skillup(struct map_session_data *sd,uint16 skill_id,int lv) +{// TODO: Merge with clif_skillup (same packet). + int fd; + + nullpo_ret(sd); + + fd=sd->fd; + WFIFOHEAD(fd,11); + WFIFOW(fd,0) = 0x10e; + WFIFOW(fd,2) = skill_id; + WFIFOW(fd,4) = lv; + WFIFOW(fd,6) = skill_get_sp(skill_id,lv); + WFIFOW(fd,8) = skill_get_range(skill_id,lv); + WFIFOB(fd,10) = 1; + WFIFOSET(fd,11); + return 0; +} + + +/// Request for guild alliance (ZC_REQ_ALLY_GUILD). +/// 0171 <inviter account id>.L <guild name>.24B +void clif_guild_reqalliance(struct map_session_data *sd,int account_id,const char *name) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x171)); + WFIFOW(fd,0)=0x171; + WFIFOL(fd,2)=account_id; + memcpy(WFIFOP(fd,6),name,NAME_LENGTH); + WFIFOSET(fd,packet_len(0x171)); +} + + +/// Notifies the client about the result of a alliance request (ZC_ACK_REQ_ALLY_GUILD). +/// 0173 <answer>.B +/// answer: +/// 0 = Already allied. +/// 1 = You rejected the offer. +/// 2 = You accepted the offer. +/// 3 = They have too any alliances. +/// 4 = You have too many alliances. +/// 5 = Alliances are disabled. +void clif_guild_allianceack(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x173)); + WFIFOW(fd,0)=0x173; + WFIFOL(fd,2)=flag; + WFIFOSET(fd,packet_len(0x173)); +} + + +/// Notifies the client that a alliance or opposition has been removed (ZC_DELETE_RELATED_GUILD). +/// 0184 <other guild id>.L <relation>.L +/// relation: +/// 0 = Ally +/// 1 = Enemy +void clif_guild_delalliance(struct map_session_data *sd,int guild_id,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + if (fd <= 0) + return; + WFIFOHEAD(fd,packet_len(0x184)); + WFIFOW(fd,0)=0x184; + WFIFOL(fd,2)=guild_id; + WFIFOL(fd,6)=flag; + WFIFOSET(fd,packet_len(0x184)); +} + + +/// Notifies the client about the result of a opposition request (ZC_ACK_REQ_HOSTILE_GUILD). +/// 0181 <result>.B +/// result: +/// 0 = Antagonist has been set. +/// 1 = Guild has too many Antagonists. +/// 2 = Already set as an Antagonist. +/// 3 = Antagonists are disabled. +void clif_guild_oppositionack(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x181)); + WFIFOW(fd,0)=0x181; + WFIFOB(fd,2)=flag; + WFIFOSET(fd,packet_len(0x181)); +} + + +/// Adds alliance or opposition (ZC_ADD_RELATED_GUILD). +/// 0185 <relation>.L <guild id>.L <guild name>.24B +/* +void clif_guild_allianceadded(struct guild *g,int idx) +{ + unsigned char buf[64]; + WBUFW(buf,0)=0x185; + WBUFL(buf,2)=g->alliance[idx].opposition; + WBUFL(buf,6)=g->alliance[idx].guild_id; + memcpy(WBUFP(buf,10),g->alliance[idx].name,NAME_LENGTH); + clif_send(buf,packet_len(0x185),guild_getavailablesd(g),GUILD); +} +*/ + + +/// Notifies the client about the result of a guild break (ZC_ACK_DISORGANIZE_GUILD_RESULT). +/// 015e <reason>.L +/// 0 = success +/// 1 = invalid key (guild name, @see clif_parse_GuildBreak) +/// 2 = there are still members in the guild +void clif_guild_broken(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x15e)); + WFIFOW(fd,0)=0x15e; + WFIFOL(fd,2)=flag; + WFIFOSET(fd,packet_len(0x15e)); +} + + +/// Displays emotion on an object (ZC_EMOTION). +/// 00c0 <id>.L <type>.B +/// type: +/// enum emotion_type +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(0xc0),bl,AREA); +} + + +/// Displays the contents of a talkiebox trap (ZC_TALKBOX_CHATCONTENTS). +/// 0191 <id>.L <contents>.80B +void clif_talkiebox(struct block_list* bl, const char* talkie) +{ + unsigned char buf[MESSAGE_SIZE+6]; + nullpo_retv(bl); + + WBUFW(buf,0) = 0x191; + WBUFL(buf,2) = bl->id; + safestrncpy((char*)WBUFP(buf,6),talkie,MESSAGE_SIZE); + clif_send(buf,packet_len(0x191),bl,AREA); +} + + +/// Displays wedding effect centered on an object (ZC_CONGRATULATION). +/// 01ea <id>.L +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(0x1ea), bl, AREA); +} + + +/// Notifies the client of the name of the partner character (ZC_COUPLENAME). +/// 01e6 <partner name>.24B +void clif_callpartner(struct map_session_data *sd) +{ + unsigned char buf[26]; + const char *p; + + nullpo_retv(sd); + + WBUFW(buf,0) = 0x1e6; + + if( sd->status.partner_id ) + { + if( ( p = map_charid2nick(sd->status.partner_id) ) != NULL ) + { + memcpy(WBUFP(buf,2), p, NAME_LENGTH); + } + else + { + WBUFB(buf,2) = 0; + } + } + else + {// Send zero-length name if no partner, to initialize the client buffer. + WBUFB(buf,2) = 0; + } + + clif_send(buf, packet_len(0x1e6), &sd->bl, AREA); +} + + +/// Initiates the partner "taming" process [DracoRPG] (ZC_START_COUPLE). +/// 01e4 +/// This packet while still implemented by the client is no longer being officially used. +/* +void clif_marriage_process(struct map_session_data *sd) +{ + int fd; + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x1e4)); + WFIFOW(fd,0)=0x1e4; + WFIFOSET(fd,packet_len(0x1e4)); +} +*/ + + +/// Notice of divorce (ZC_DIVORCE). +/// 0205 <partner name>.24B +void clif_divorced(struct map_session_data* sd, const char* name) +{ + int fd; + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x205)); + WFIFOW(fd,0)=0x205; + memcpy(WFIFOP(fd,2), name, NAME_LENGTH); + WFIFOSET(fd, packet_len(0x205)); +} + + +/// Marriage proposal (ZC_REQ_COUPLE). +/// 01e2 <account id>.L <char id>.L <char name>.24B +/// This packet while still implemented by the client is no longer being officially used. +/* +void clif_marriage_proposal(int fd, struct map_session_data *sd, struct map_session_data* ssd) +{ + nullpo_retv(sd); + + WFIFOHEAD(fd,packet_len(0x1e2)); + WFIFOW(fd,0) = 0x1e2; + WFIFOL(fd,2) = ssd->status.account_id; + WFIFOL(fd,6) = ssd->status.char_id; + safestrncpy((char*)WFIFOP(fd,10), ssd->status.name, NAME_LENGTH); + WFIFOSET(fd, packet_len(0x1e2)); +} +*/ + + +/*========================================== + * + *------------------------------------------*/ +void clif_disp_onlyself(struct map_session_data *sd, const char *mes, int len) +{ + clif_disp_message(&sd->bl, mes, len, SELF); +} + +/*========================================== + * Displays a message using the guild-chat colors to the specified targets. [Skotlex] + *------------------------------------------*/ +void clif_disp_message(struct block_list* src, const char* mes, int len, enum send_target target) +{ + unsigned char buf[256]; + + if( len == 0 ) + { + return; + } + else if( len > sizeof(buf)-5 ) + { + ShowWarning("clif_disp_message: Truncated message '%s' (len=%d, max=%d, aid=%d).\n", mes, len, sizeof(buf)-5, src->id); + len = sizeof(buf)-5; + } + + WBUFW(buf, 0) = 0x17f; + WBUFW(buf, 2) = len + 5; + safestrncpy((char*)WBUFP(buf,4), mes, len+1); + clif_send(buf, WBUFW(buf,2), src, target); +} + + +/// Notifies the client about the result of a request to disconnect another player (ZC_ACK_DISCONNECT_CHARACTER). +/// 00cd <result>.L (unknown packet version or invalid information at packet_len_table) +/// 00cd <result>.B +/// result: +/// 0 = failure +/// 1 = success +void clif_GM_kickack(struct map_session_data *sd, int id) +{ + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0xcd)); + WFIFOW(fd,0) = 0xcd; + WFIFOB(fd,2) = id; // FIXME: this is not account id + WFIFOSET(fd, packet_len(0xcd)); +} + + +void clif_GM_kick(struct map_session_data *sd,struct map_session_data *tsd) +{ + int fd = tsd->fd; + + if( fd > 0 ) + clif_authfail_fd(fd, 15); + else + map_quit(tsd); + + if( sd ) + clif_GM_kickack(sd,tsd->status.account_id); +} + + +/// Displays various manner-related status messages (ZC_ACK_GIVE_MANNER_POINT). +/// 014a <result>.L +/// result: +/// 0 = "A manner point has been successfully aligned." +/// 1 = MP_FAILURE_EXHAUST +/// 2 = MP_FAILURE_ALREADY_GIVING +/// 3 = "Chat Block has been applied by GM due to your ill-mannerous action." +/// 4 = "Automated Chat Block has been applied due to Anti-Spam System." +/// 5 = "You got a good point from %s." +void clif_manner_message(struct map_session_data* sd, uint32 type) +{ + int fd; + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0x14a)); + WFIFOW(fd,0) = 0x14a; + WFIFOL(fd,2) = type; + WFIFOSET(fd, packet_len(0x14a)); +} + + +/// Followup to 0x14a type 3/5, informs who did the manner adjustment action (ZC_NOTIFY_MANNER_POINT_GIVEN). +/// 014b <type>.B <GM name>.24B +/// type: +/// 0 = positive (unmute) +/// 1 = negative (mute) +void clif_GM_silence(struct map_session_data* sd, struct map_session_data* tsd, uint8 type) +{ + int fd; + nullpo_retv(sd); + nullpo_retv(tsd); + + fd = tsd->fd; + WFIFOHEAD(fd,packet_len(0x14b)); + WFIFOW(fd,0) = 0x14b; + WFIFOB(fd,2) = type; + safestrncpy((char*)WFIFOP(fd,3), sd->status.name, NAME_LENGTH); + WFIFOSET(fd, packet_len(0x14b)); +} + + +/// Notifies the client about the result of a request to allow/deny whispers from a player (ZC_SETTING_WHISPER_PC). +/// 00d1 <type>.B <result>.B +/// type: +/// 0 = /ex (deny) +/// 1 = /in (allow) +/// result: +/// 0 = success +/// 1 = failure +/// 2 = too many blocks +void clif_wisexin(struct map_session_data *sd,int type,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xd1)); + WFIFOW(fd,0)=0xd1; + WFIFOB(fd,2)=type; + WFIFOB(fd,3)=flag; + WFIFOSET(fd,packet_len(0xd1)); +} + +/// Notifies the client about the result of a request to allow/deny whispers from anyone (ZC_SETTING_WHISPER_STATE). +/// 00d2 <type>.B <result>.B +/// type: +/// 0 = /exall (deny) +/// 1 = /inall (allow) +/// result: +/// 0 = success +/// 1 = failure +void clif_wisall(struct map_session_data *sd,int type,int flag) +{ + int fd; + + nullpo_retv(sd); + + fd=sd->fd; + WFIFOHEAD(fd,packet_len(0xd2)); + WFIFOW(fd,0)=0xd2; + WFIFOB(fd,2)=type; + WFIFOB(fd,3)=flag; + WFIFOSET(fd,packet_len(0xd2)); +} + + +/// Play a BGM! [Rikter/Yommy] (ZC_PLAY_NPC_BGM). +/// 07fe <bgm>.24B +void clif_playBGM(struct map_session_data* sd, const char* name) +{ + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0x7fe)); + WFIFOW(fd,0) = 0x7fe; + safestrncpy((char*)WFIFOP(fd,2), name, NAME_LENGTH); + WFIFOSET(fd,packet_len(0x7fe)); +} + + +/// Plays/stops a wave sound (ZC_SOUND). +/// 01d3 <file name>.24B <act>.B <term>.L <npc id>.L +/// file name: +/// relative to data\wav +/// act: +/// 0 = play (once) +/// 1 = play (repeat, does not work) +/// 2 = stops all sound instances of file name (does not work) +/// term: +/// unknown purpose, only relevant to act = 1 +/// npc id: +/// The accustic direction of the sound is determined by the +/// relative position of the NPC to the player (3D sound). +void clif_soundeffect(struct map_session_data* sd, struct block_list* bl, const char* name, int type) +{ + int fd; + + nullpo_retv(sd); + nullpo_retv(bl); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0x1d3)); + WFIFOW(fd,0) = 0x1d3; + safestrncpy((char*)WFIFOP(fd,2), name, NAME_LENGTH); + WFIFOB(fd,26) = type; + WFIFOL(fd,27) = 0; + WFIFOL(fd,31) = bl->id; + WFIFOSET(fd,packet_len(0x1d3)); +} + +void clif_soundeffectall(struct block_list* bl, const char* name, int type, enum send_target coverage) +{ + unsigned char buf[40]; + + nullpo_retv(bl); + + WBUFW(buf,0) = 0x1d3; + safestrncpy((char*)WBUFP(buf,2), name, NAME_LENGTH); + WBUFB(buf,26) = type; + WBUFL(buf,27) = 0; + WBUFL(buf,31) = bl->id; + clif_send(buf, packet_len(0x1d3), bl, coverage); +} + + +/// Displays special effects (npcs, weather, etc) [Valaris] (ZC_NOTIFY_EFFECT2). +/// 01f3 <id>.L <effect id>.L +/// effect id: +/// @see doc/effect_list.txt +void clif_specialeffect(struct block_list* bl, int type, enum send_target target) +{ + unsigned char buf[24]; + + nullpo_retv(bl); + + memset(buf, 0, packet_len(0x1f3)); + + WBUFW(buf,0) = 0x1f3; + WBUFL(buf,2) = bl->id; + WBUFL(buf,6) = type; + + clif_send(buf, packet_len(0x1f3), bl, target); + + if (disguised(bl)) { + WBUFL(buf,2) = -bl->id; + clif_send(buf, packet_len(0x1f3), bl, SELF); + } +} + +void clif_specialeffect_single(struct block_list* bl, int type, int fd) +{ + WFIFOHEAD(fd,10); + WFIFOW(fd,0) = 0x1f3; + WFIFOL(fd,2) = bl->id; + WFIFOL(fd,6) = type; + WFIFOSET(fd,10); +} + + +/// Notifies clients of an special/visual effect that accepts an value (ZC_NOTIFY_EFFECT3). +/// 0284 <id>.L <effect id>.L <num data>.L +/// effect id: +/// @see doc/effect_list.txt +/// num data: +/// effect-dependent value +void clif_specialeffect_value(struct block_list* bl, int effect_id, int num, send_target target) +{ + uint8 buf[14]; + + WBUFW(buf,0) = 0x284; + WBUFL(buf,2) = bl->id; + WBUFL(buf,6) = effect_id; + WBUFL(buf,10) = num; + + clif_send(buf, packet_len(0x284), bl, target); + + if( disguised(bl) ) + { + WBUFL(buf,2) = -bl->id; + clif_send(buf, packet_len(0x284), bl, SELF); + } +} +// Modification of clif_messagecolor to send colored messages to players to chat log only (doesn't display overhead) +/// 02c1 <packet len>.W <id>.L <color>.L <message>.?B +int clif_colormes(struct map_session_data * sd, enum clif_colors color, const char* msg) { + unsigned short msg_len = strlen(msg) + 1; + + WFIFOHEAD(sd->fd,msg_len + 12); + WFIFOW(sd->fd,0) = 0x2C1; + WFIFOW(sd->fd,2) = msg_len + 12; + WFIFOL(sd->fd,4) = 0; + WFIFOL(sd->fd,8) = color_table[color]; + safestrncpy((char*)WFIFOP(sd->fd,12), msg, msg_len); + clif_send(WFIFOP(sd->fd,0), WFIFOW(sd->fd,2), &sd->bl, SELF); + + return 0; +} + +/// Monster/NPC color chat [SnakeDrak] (ZC_NPC_CHAT). +/// 02c1 <packet len>.W <id>.L <color>.L <message>.?B +void clif_messagecolor(struct block_list* bl, unsigned long color, const char* msg) { + unsigned short msg_len = strlen(msg) + 1; + uint8 buf[256]; + color = (color & 0x0000FF) << 16 | (color & 0x00FF00) | (color & 0xFF0000) >> 16; // RGB to BGR + + nullpo_retv(bl); + + if( msg_len > sizeof(buf)-12 ) + { + ShowWarning("clif_messagecolor: Truncating too long message '%s' (len=%u).\n", msg, msg_len); + msg_len = sizeof(buf)-12; + } + + WBUFW(buf,0) = 0x2C1; + WBUFW(buf,2) = msg_len + 12; + WBUFL(buf,4) = bl->id; + WBUFL(buf,8) = color; + memcpy(WBUFP(buf,12), msg, msg_len); + + clif_send(buf, WBUFW(buf,2), bl, AREA_CHAT_WOC); +} + +/// Public chat message [Valaris] (ZC_NOTIFY_CHAT). +/// 008d <packet len>.W <id>.L <message>.?B +void clif_message(struct block_list* bl, const char* msg) { + unsigned short msg_len = strlen(msg) + 1; + uint8 buf[256]; + nullpo_retv(bl); + + if( msg_len > sizeof(buf)-8 ) { + ShowWarning("clif_message: Truncating too long message '%s' (len=%u).\n", msg, msg_len); + msg_len = sizeof(buf)-8; + } + + WBUFW(buf,0) = 0x8d; + WBUFW(buf,2) = msg_len + 8; + WBUFL(buf,4) = bl->id; + safestrncpy((char*)WBUFP(buf,8), msg, msg_len); + + clif_send(buf, WBUFW(buf,2), bl, AREA_CHAT_WOC); +} + +// refresh the client's screen, getting rid of any effects +void clif_refresh(struct map_session_data *sd) +{ + int i; + nullpo_retv(sd); + + clif_changemap(sd,sd->mapindex,sd->bl.x,sd->bl.y); + clif_inventorylist(sd); + if(pc_iscarton(sd)) { + clif_cartlist(sd); + clif_updatestatus(sd,SP_CARTINFO); + } + clif_updatestatus(sd,SP_WEIGHT); + clif_updatestatus(sd,SP_MAXWEIGHT); + 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); + if (sd->spiritball) + clif_spiritball_single(sd->fd, sd); + for(i = 1; i < 5; i++){ + if( sd->talisman[i] > 0 ) + clif_talisman_single(sd->fd, sd, i); + } + if (sd->vd.cloth_color) + clif_refreshlook(&sd->bl,sd->bl.id,LOOK_CLOTHES_COLOR,sd->vd.cloth_color,SELF); + if(merc_is_hom_active(sd->hd)) + clif_send_homdata(sd,SP_ACK,0); + if( sd->md ) { + clif_mercenary_info(sd); + clif_mercenary_skillblock(sd); + } + if( sd->ed ) + clif_elemental_info(sd); + map_foreachinrange(clif_getareachar,&sd->bl,AREA_SIZE,BL_ALL,sd); + clif_weather_check(sd); + if( sd->chatID ) + chat_leavechat(sd,0); + if( sd->state.vending ) + clif_openvending(sd, sd->bl.id, sd->vending); + if( pc_issit(sd) ) + clif_sitting(&sd->bl); // FIXME: just send to self, not area + if( pc_isdead(sd) ) // When you refresh, resend the death packet. + clif_clearunit_single(sd->bl.id,CLR_DEAD,sd->fd); + else + clif_changed_dir(&sd->bl, SELF); + + // unlike vending, resuming buyingstore crashes the client. + buyingstore_close(sd); + + mail_clear(sd); +} + + +/// Updates the object's (bl) name on client. +/// 0095 <id>.L <char name>.24B (ZC_ACK_REQNAME) +/// 0195 <id>.L <char name>.24B <party name>.24B <guild name>.24B <position name>.24B (ZC_ACK_REQNAMEALL) +void clif_charnameack (int fd, struct block_list *bl) +{ + unsigned char buf[103]; + int cmd = 0x95, i, ps = -1; + + nullpo_retv(bl); + + WBUFW(buf,0) = cmd; + WBUFL(buf,2) = bl->id; + + switch( bl->type ) + { + case BL_PC: + { + struct map_session_data *ssd = (struct map_session_data *)bl; + struct party_data *p = NULL; + struct guild *g = NULL; + + //Requesting your own "shadow" name. [Skotlex] + if (ssd->fd == fd && ssd->disguise) + WBUFL(buf,2) = -bl->id; + + if( ssd->fakename[0] ) + { + WBUFW(buf, 0) = cmd = 0x195; + memcpy(WBUFP(buf,6), ssd->fakename, NAME_LENGTH); + WBUFB(buf,30) = WBUFB(buf,54) = WBUFB(buf,78) = 0; + break; + } + memcpy(WBUFP(buf,6), ssd->status.name, NAME_LENGTH); + + if( ssd->status.party_id ) + { + p = party_search(ssd->status.party_id); + } + if( ssd->status.guild_id ) + { + if( ( g = guild_search(ssd->status.guild_id) ) != NULL ) + { + ARR_FIND(0, g->max_member, i, g->member[i].account_id == ssd->status.account_id && g->member[i].char_id == ssd->status.char_id); + if( i < g->max_member ) ps = g->member[i].position; + } + } + + if( !battle_config.display_party_name && g == NULL ) + {// do not display party unless the player is also in a guild + p = NULL; + } + + if (p == NULL && g == NULL) + break; + + WBUFW(buf, 0) = cmd = 0x195; + if (p) + memcpy(WBUFP(buf,30), p->party.name, NAME_LENGTH); + else + WBUFB(buf,30) = 0; + + if (g && ps >= 0 && ps < MAX_GUILDPOSITION) + { + memcpy(WBUFP(buf,54), g->name,NAME_LENGTH); + memcpy(WBUFP(buf,78), g->position[ps].name, NAME_LENGTH); + } else { //Assume no guild. + WBUFB(buf,54) = 0; + WBUFB(buf,78) = 0; + } + } + break; + //[blackhole89] + case BL_HOM: + memcpy(WBUFP(buf,6), ((TBL_HOM*)bl)->homunculus.name, NAME_LENGTH); + break; + case BL_MER: + memcpy(WBUFP(buf,6), ((TBL_MER*)bl)->db->name, NAME_LENGTH); + break; + case BL_PET: + memcpy(WBUFP(buf,6), ((TBL_PET*)bl)->pet.name, NAME_LENGTH); + break; + case BL_NPC: + memcpy(WBUFP(buf,6), ((TBL_NPC*)bl)->name, NAME_LENGTH); + break; + case BL_MOB: + { + struct mob_data *md = (struct mob_data *)bl; + nullpo_retv(md); + + memcpy(WBUFP(buf,6), md->name, NAME_LENGTH); + if( md->guardian_data && md->guardian_data->guild_id ) + { + WBUFW(buf, 0) = cmd = 0x195; + WBUFB(buf,30) = 0; + memcpy(WBUFP(buf,54), md->guardian_data->guild_name, NAME_LENGTH); + memcpy(WBUFP(buf,78), md->guardian_data->castle->castle_name, NAME_LENGTH); + } + else if( battle_config.show_mob_info ) + { + char mobhp[50], *str_p = mobhp; + WBUFW(buf, 0) = cmd = 0x195; + if( battle_config.show_mob_info&4 ) + str_p += sprintf(str_p, "Lv. %d | ", md->level); + if( battle_config.show_mob_info&1 ) + str_p += sprintf(str_p, "HP: %u/%u | ", md->status.hp, md->status.max_hp); + if( battle_config.show_mob_info&2 ) + str_p += sprintf(str_p, "HP: %d%% | ", get_percentage(md->status.hp, md->status.max_hp)); + //Even thought mobhp ain't a name, we send it as one so the client + //can parse it. [Skotlex] + if( str_p != mobhp ) + { + *(str_p-3) = '\0'; //Remove trailing space + pipe. + memcpy(WBUFP(buf,30), mobhp, NAME_LENGTH); + WBUFB(buf,54) = 0; + WBUFB(buf,78) = 0; + } + } + } + break; + case BL_CHAT: //FIXME: Clients DO request this... what should be done about it? The chat's title may not fit... [Skotlex] +// memcpy(WBUFP(buf,6), (struct chat*)->title, NAME_LENGTH); +// break; + return; + case BL_ELEM: + memcpy(WBUFP(buf,6), ((TBL_ELEM*)bl)->db->name, NAME_LENGTH); + break; + default: + ShowError("clif_charnameack: bad type %d(%d)\n", bl->type, bl->id); + return; + } + + // if no receipient specified just update nearby clients + if (fd == 0) + clif_send(buf, packet_len(cmd), bl, AREA); + else { + WFIFOHEAD(fd, packet_len(cmd)); + memcpy(WFIFOP(fd, 0), buf, packet_len(cmd)); + WFIFOSET(fd, packet_len(cmd)); + } +} + + +//Used to update when a char leaves a party/guild. [Skotlex] +//Needed because when you send a 0x95 packet, the client will not remove the cached party/guild info that is not sent. +void clif_charnameupdate (struct map_session_data *ssd) +{ + unsigned char buf[103]; + int cmd = 0x195, ps = -1, i; + struct party_data *p = NULL; + struct guild *g = NULL; + + nullpo_retv(ssd); + + if( ssd->fakename[0] ) + return; //No need to update as the party/guild was not displayed anyway. + + WBUFW(buf,0) = cmd; + WBUFL(buf,2) = ssd->bl.id; + + memcpy(WBUFP(buf,6), ssd->status.name, NAME_LENGTH); + + if (!battle_config.display_party_name) { + if (ssd->status.party_id > 0 && ssd->status.guild_id > 0 && (g = guild_search(ssd->status.guild_id)) != NULL) + p = party_search(ssd->status.party_id); + }else{ + if (ssd->status.party_id > 0) + p = party_search(ssd->status.party_id); + } + + if( ssd->status.guild_id > 0 && (g = guild_search(ssd->status.guild_id)) != NULL ) + { + ARR_FIND(0, g->max_member, i, g->member[i].account_id == ssd->status.account_id && g->member[i].char_id == ssd->status.char_id); + if( i < g->max_member ) ps = g->member[i].position; + } + + if( p ) + memcpy(WBUFP(buf,30), p->party.name, NAME_LENGTH); + else + WBUFB(buf,30) = 0; + + if( g && ps >= 0 && ps < MAX_GUILDPOSITION ) + { + memcpy(WBUFP(buf,54), g->name,NAME_LENGTH); + memcpy(WBUFP(buf,78), g->position[ps].name, NAME_LENGTH); + } + else + { + WBUFB(buf,54) = 0; + WBUFB(buf,78) = 0; + } + + // Update nearby clients + clif_send(buf, packet_len(cmd), &ssd->bl, AREA); +} + + +/// Taekwon Jump (TK_HIGHJUMP) effect (ZC_HIGHJUMP). +/// 01ff <id>.L <x>.W <y>.W +/// +/// Visually moves(instant) a character to x,y. The char moves even +/// when the target cell isn't walkable. If the char is sitting it +/// stays that way. +void clif_slide(struct block_list *bl, int x, int y) +{ + unsigned char buf[10]; + nullpo_retv(bl); + + WBUFW(buf, 0) = 0x01ff; + WBUFL(buf, 2) = bl->id; + WBUFW(buf, 6) = x; + WBUFW(buf, 8) = y; + clif_send(buf, packet_len(0x1ff), bl, AREA); + + if( disguised(bl) ) + { + WBUFL(buf,2) = -bl->id; + clif_send(buf, packet_len(0x1ff), bl, SELF); + } +} + + +/*------------------------------------------ + * @me command by lordalfa, rewritten implementation by Skotlex + *------------------------------------------*/ +void clif_disp_overhead(struct map_session_data *sd, const char* mes) +{ + unsigned char buf[256]; //This should be more than sufficient, the theorical max is CHAT_SIZE + 8 (pads and extra inserted crap) + int len_mes = strlen(mes)+1; //Account for \0 + + if (len_mes > sizeof(buf)-8) { + ShowError("clif_disp_overhead: Message too long (length %d)\n", len_mes); + len_mes = sizeof(buf)-8; //Trunk it to avoid problems. + } + // send message to others + WBUFW(buf,0) = 0x8d; + WBUFW(buf,2) = len_mes + 8; // len of message + 8 (command+len+id) + WBUFL(buf,4) = sd->bl.id; + safestrncpy((char*)WBUFP(buf,8), mes, len_mes); + clif_send(buf, WBUFW(buf,2), &sd->bl, AREA_CHAT_WOC); + + // send back message to the speaker + WBUFW(buf,0) = 0x8e; + WBUFW(buf, 2) = len_mes + 4; + safestrncpy((char*)WBUFP(buf,4), mes, len_mes); + clif_send(buf, WBUFW(buf,2), &sd->bl, SELF); +} + +/*========================== + * Minimap fix [Kevin] + * Remove dot from minimap + *--------------------------*/ +void clif_party_xy_remove(struct map_session_data *sd) +{ + unsigned char buf[16]; + nullpo_retv(sd); + WBUFW(buf,0)=0x107; + WBUFL(buf,2)=sd->status.account_id; + WBUFW(buf,6)=-1; + WBUFW(buf,8)=-1; + clif_send(buf,packet_len(0x107),&sd->bl,PARTY_SAMEMAP_WOS); +} + + +/// Displays a skill message (thanks to Rayce) (ZC_SKILLMSG). +/// 0215 <msg id>.L +/// msg id: +/// 0x15 = End all negative status (PA_GOSPEL) +/// 0x16 = Immunity to all status (PA_GOSPEL) +/// 0x17 = MaxHP +100% (PA_GOSPEL) +/// 0x18 = MaxSP +100% (PA_GOSPEL) +/// 0x19 = All stats +20 (PA_GOSPEL) +/// 0x1c = Enchant weapon with Holy element (PA_GOSPEL) +/// 0x1d = Enchant armor with Holy element (PA_GOSPEL) +/// 0x1e = DEF +25% (PA_GOSPEL) +/// 0x1f = ATK +100% (PA_GOSPEL) +/// 0x20 = HIT/Flee +50 (PA_GOSPEL) +/// 0x28 = Full strip failed because of coating (ST_FULLSTRIP) +/// ? = nothing +void clif_gospel_info(struct map_session_data *sd, int type) +{ + int fd=sd->fd; + WFIFOHEAD(fd,packet_len(0x215)); + WFIFOW(fd,0)=0x215; + WFIFOL(fd,2)=type; + WFIFOSET(fd, packet_len(0x215)); + +} + + +/// Multi-purpose mission information packet (ZC_STARSKILL). +/// 020e <mapname>.24B <monster_id>.L <star>.B <result>.B +/// result: +/// 0 = Star Gladiator %s has designed <mapname>'s as the %s. +/// star: +/// 0 = Place of the Sun +/// 1 = Place of the Moon +/// 2 = Place of the Stars +/// 1 = Star Gladiator %s's %s: <mapname> +/// star: +/// 0 = Place of the Sun +/// 1 = Place of the Moon +/// 2 = Place of the Stars +/// 10 = Star Gladiator %s has designed <mapname>'s as the %s. +/// star: +/// 0 = Target of the Sun +/// 1 = Target of the Moon +/// 2 = Target of the Stars +/// 11 = Star Gladiator %s's %s: <mapname used as monster name> +/// star: +/// 0 = Monster of the Sun +/// 1 = Monster of the Moon +/// 2 = Monster of the Stars +/// 20 = [TaeKwon Mission] Target Monster : <mapname used as monster name> (<star>%) +/// 21 = [Taming Mission] Target Monster : <mapname used as monster name> +/// 22 = [Collector Rank] Target Item : <monster_id used as item id> +/// 30 = [Sun, Moon and Stars Angel] Designed places and monsters have been reset. +/// 40 = Target HP : <monster_id used as HP> +void clif_starskill(struct map_session_data* sd, const char* mapname, int monster_id, unsigned char star, unsigned char result) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x20e)); + WFIFOW(fd,0) = 0x20e; + safestrncpy((char*)WFIFOP(fd,2), mapname, NAME_LENGTH); + WFIFOL(fd,26) = monster_id; + WFIFOB(fd,30) = star; + WFIFOB(fd,31) = result; + WFIFOSET(fd,packet_len(0x20e)); +} + +/*========================================== + * Info about Star Glaldiator save map [Komurka] + * type: 1: Information, 0: Map registered + *------------------------------------------*/ +void clif_feel_info(struct map_session_data* sd, unsigned char feel_level, unsigned char type) +{ + char mapname[MAP_NAME_LENGTH_EXT]; + + mapindex_getmapname_ext(mapindex_id2name(sd->feel_map[feel_level].index), mapname); + clif_starskill(sd, mapname, 0, feel_level, type ? 1 : 0); +} + +/*========================================== + * Info about Star Glaldiator hate mob [Komurka] + * type: 1: Register mob, 0: Information. + *------------------------------------------*/ +void clif_hate_info(struct map_session_data *sd, unsigned char hate_level,int class_, unsigned char type) +{ + if( pcdb_checkid(class_) ) + { + clif_starskill(sd, job_name(class_), class_, hate_level, type ? 10 : 11); + } + else if( mobdb_checkid(class_) ) + { + clif_starskill(sd, mob_db(class_)->jname, class_, hate_level, type ? 10 : 11); + } + else + { + ShowWarning("clif_hate_info: Received invalid class %d for this packet (char_id=%d, hate_level=%u, type=%u).\n", class_, sd->status.char_id, (unsigned int)hate_level, (unsigned int)type); + } +} + +/*========================================== + * Info about TaeKwon Do TK_MISSION mob [Skotlex] + *------------------------------------------*/ +void clif_mission_info(struct map_session_data *sd, int mob_id, unsigned char progress) +{ + clif_starskill(sd, mob_db(mob_id)->jname, mob_id, progress, 20); +} + +/*========================================== + * Feel/Hate reset (thanks to Rayce) [Skotlex] + *------------------------------------------*/ +void clif_feel_hate_reset(struct map_session_data *sd) +{ + clif_starskill(sd, "", 0, 0, 30); +} + + +/// Equip window (un)tick ack (ZC_CONFIG). +/// 02d9 <type>.L <value>.L +/// type: +/// 0 = open equip window +/// value: +/// 0 = disabled +/// 1 = enabled +void clif_equiptickack(struct map_session_data* sd, int flag) +{ + int fd; + nullpo_retv(sd); + fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0x2d9)); + WFIFOW(fd, 0) = 0x2d9; + WFIFOL(fd, 2) = 0; + WFIFOL(fd, 6) = flag; + WFIFOSET(fd, packet_len(0x2d9)); +} + + +/// The player's 'view equip' state, sent during login (ZC_CONFIG_NOTIFY). +/// 02da <open equip window>.B +/// open equip window: +/// 0 = disabled +/// 1 = enabled +void clif_equipcheckbox(struct map_session_data* sd) +{ + int fd; + nullpo_retv(sd); + fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0x2da)); + WFIFOW(fd, 0) = 0x2da; + WFIFOB(fd, 2) = (sd->status.show_equip ? 1 : 0); + WFIFOSET(fd, packet_len(0x2da)); +} + + +/// Sends info about a player's equipped items. +/// 02d7 <packet len>.W <name>.24B <class>.W <hairstyle>.W <up-viewid>.W <mid-viewid>.W <low-viewid>.W <haircolor>.W <cloth-dye>.W <gender>.B {equip item}.26B* (ZC_EQUIPWIN_MICROSCOPE) +/// 02d7 <packet len>.W <name>.24B <class>.W <hairstyle>.W <bottom-viewid>.W <mid-viewid>.W <up-viewid>.W <haircolor>.W <cloth-dye>.W <gender>.B {equip item}.28B* (ZC_EQUIPWIN_MICROSCOPE, PACKETVER >= 20100629) +/// 0859 <packet len>.W <name>.24B <class>.W <hairstyle>.W <bottom-viewid>.W <mid-viewid>.W <up-viewid>.W <haircolor>.W <cloth-dye>.W <gender>.B {equip item}.28B* (ZC_EQUIPWIN_MICROSCOPE2, PACKETVER >= 20101124) +/// 0859 <packet len>.W <name>.24B <class>.W <hairstyle>.W <bottom-viewid>.W <mid-viewid>.W <up-viewid>.W <robe>.W <haircolor>.W <cloth-dye>.W <gender>.B {equip item}.28B* (ZC_EQUIPWIN_MICROSCOPE2, PACKETVER >= 20110111) +void clif_viewequip_ack(struct map_session_data* sd, struct map_session_data* tsd) +{ + uint8* buf; + int i, n, fd, offset = 0; +#if PACKETVER < 20100629 + const int s = 26; +#else + const int s = 28; +#endif + nullpo_retv(sd); + nullpo_retv(tsd); + fd = sd->fd; + + WFIFOHEAD(fd, MAX_INVENTORY * s + 43); + buf = WFIFOP(fd,0); + +#if PACKETVER < 20101124 + WBUFW(buf, 0) = 0x2d7; +#else + WBUFW(buf, 0) = 0x859; +#endif + safestrncpy((char*)WBUFP(buf, 4), tsd->status.name, NAME_LENGTH); + WBUFW(buf,28) = tsd->status.class_; + WBUFW(buf,30) = tsd->vd.hair_style; + WBUFW(buf,32) = tsd->vd.head_bottom; + WBUFW(buf,34) = tsd->vd.head_mid; + WBUFW(buf,36) = tsd->vd.head_top; +#if PACKETVER >= 20110111 + WBUFW(buf,38) = tsd->vd.robe; + offset+= 2; + buf = WBUFP(buf,2); +#endif + WBUFW(buf,38) = tsd->vd.hair_color; + WBUFW(buf,40) = tsd->vd.cloth_color; + WBUFB(buf,42) = tsd->vd.sex; + + for(i=0,n=0; i < MAX_INVENTORY; i++) + { + if (tsd->status.inventory[i].nameid <= 0 || tsd->inventory_data[i] == NULL) // Item doesn't exist + continue; + if (!itemdb_isequip2(tsd->inventory_data[i])) // Is not equippable + continue; + + // Inventory position + WBUFW(buf, n*s+43) = i + 2; + // Add refine, identify flag, element, etc. + clif_item_sub(WBUFP(buf,0), n*s+45, &tsd->status.inventory[i], tsd->inventory_data[i], pc_equippoint(tsd, i)); + // Add cards + clif_addcards(WBUFP(buf, n*s+55), &tsd->status.inventory[i]); + // Expiration date stuff, if all of those are set to 0 then the client doesn't show anything related (6 bytes) + WBUFL(buf, n*s+63) = tsd->status.inventory[i].expire_time; + WBUFW(buf, n*s+67) = 0; +#if PACKETVER >= 20100629 + if (tsd->inventory_data[i]->equip&EQP_VISIBLE) + WBUFW(buf, n*s+69) = tsd->inventory_data[i]->look; + else + WBUFW(buf, n*s+69) = 0; +#endif + n++; + } + + WFIFOW(fd, 2) = 43+offset+n*s; // Set length + WFIFOSET(fd, WFIFOW(fd, 2)); +} + + +/// Display msgstringtable.txt string (ZC_MSG). +/// 0291 <message>.W +void clif_msg(struct map_session_data* sd, unsigned short id) +{ + int fd; + nullpo_retv(sd); + fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0x291)); + WFIFOW(fd, 0) = 0x291; + WFIFOW(fd, 2) = id; // zero-based msgstringtable.txt index + WFIFOSET(fd, packet_len(0x291)); +} + + +/// Display msgstringtable.txt string and fill in a valid for %d format (ZC_MSG_VALUE). +/// 0x7e2 <message>.W <value>.L +void clif_msg_value(struct map_session_data* sd, unsigned short id, int value) +{ + int fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0x7e2)); + WFIFOW(fd,0) = 0x7e2; + WFIFOW(fd,2) = id; + WFIFOL(fd,4) = value; + WFIFOSET(fd, packet_len(0x7e2)); +} + + +/// Displays msgstringtable.txt string, prefixed with a skill name. (ZC_MSG_SKILL). +/// 07e6 <skill id>.W <msg id>.L +/// +/// NOTE: Message has following format and is printed in color 0xCDCDFF (purple): +/// "[SkillName] Message" +void clif_msg_skill(struct map_session_data* sd, uint16 skill_id, int msg_id) +{ + int fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0x7e6)); + WFIFOW(fd,0) = 0x7e6; + WFIFOW(fd,2) = skill_id; + WFIFOL(fd,4) = msg_id; + WFIFOSET(fd, packet_len(0x7e6)); +} + + +/// View player equip request denied +void clif_viewequip_fail(struct map_session_data* sd) +{ + clif_msg(sd, 0x54d); +} + + +/// Validates one global/guild/party/whisper message packet and tries to recognize its components. +/// Returns true if the packet was parsed successfully. +/// Formats: 0 - <packet id>.w <packet len>.w (<name> : <message>).?B 00 +/// 1 - <packet id>.w <packet len>.w <name>.24B <message>.?B 00 +static bool clif_process_message(struct map_session_data* sd, int format, char** name_, int* namelen_, char** message_, int* messagelen_) +{ + char *text, *name, *message; + unsigned int packetlen, textlen, namelen, messagelen; + int fd = sd->fd; + + *name_ = NULL; + *namelen_ = 0; + *message_ = NULL; + *messagelen_ = 0; + + packetlen = RFIFOW(fd,2); + // basic structure checks + if( packetlen < 4 + 1 ) + { // 4-byte header and at least an empty string is expected + ShowWarning("clif_process_message: Received malformed packet from player '%s' (no message data)!\n", sd->status.name); + return false; + } + + text = (char*)RFIFOP(fd,4); + textlen = packetlen - 4; + + // process <name> part of the packet + if( format == 0 ) + {// name and message are separated by ' : ' + // validate name + name = text; + namelen = strnlen(sd->status.name, NAME_LENGTH-1); // name length (w/o zero byte) + + if( strncmp(name, sd->status.name, namelen) || // the text must start with the speaker's name + name[namelen] != ' ' || name[namelen+1] != ':' || name[namelen+2] != ' ' ) // followed by ' : ' + { + //Hacked message, or infamous "client desynch" issue where they pick one char while loading another. + ShowWarning("clif_process_message: Player '%s' sent a message using an incorrect name! Forcing a relog...\n", sd->status.name); + set_eof(fd); // Just kick them out to correct it. + return false; + } + + message = name + namelen + 3; + messagelen = textlen - namelen - 3; // this should be the message length (w/ zero byte included) + } + else + {// name has fixed width + if( textlen < NAME_LENGTH + 1 ) + { + ShowWarning("clif_process_message: Received malformed packet from player '%s' (packet length is incorrect)!\n", sd->status.name); + return false; + } + + // validate name + name = text; + namelen = strnlen(name, NAME_LENGTH-1); // name length (w/o zero byte) + + if( name[namelen] != '\0' ) + { // only restriction is that the name must be zero-terminated + ShowWarning("clif_process_message: Player '%s' sent an unterminated name!\n", sd->status.name); + return false; + } + + message = name + NAME_LENGTH; + messagelen = textlen - NAME_LENGTH; // this should be the message length (w/ zero byte included) + } + + if( messagelen != strnlen(message, messagelen)+1 ) + { // the declared length must match real length + ShowWarning("clif_process_message: Received malformed packet from player '%s' (length is incorrect)!\n", sd->status.name); + return false; + } + // verify <message> part of the packet + if( message[messagelen-1] != '\0' ) + { // message must be zero-terminated + ShowWarning("clif_process_message: Player '%s' sent an unterminated message string!\n", sd->status.name); + return false; + } + if( messagelen > CHAT_SIZE_MAX-1 ) + { // messages mustn't be too long + // Normally you can only enter CHATBOX_SIZE-1 letters into the chat box, but Frost Joke / Dazzler's text can be longer. + // Also, the physical size of strings that use multibyte encoding can go multiple times over the chatbox capacity. + // Neither the official client nor server place any restriction on the length of the data in the packet, + // but we'll only allow reasonably long strings here. This also makes sure that they fit into the `chatlog` table. + ShowWarning("clif_process_message: Player '%s' sent a message too long ('%.*s')!\n", sd->status.name, CHAT_SIZE_MAX-1, message); + return false; + } + + *name_ = name; + *namelen_ = namelen; + *message_ = message; + *messagelen_ = messagelen; + return true; +} + +// --------------------- +// clif_guess_PacketVer +// --------------------- +// Parses a WantToConnection packet to try to identify which is the packet version used. [Skotlex] +// error codes: +// 0 - Success +// 1 - Unknown packet_ver +// 2 - Invalid account_id +// 3 - Invalid char_id +// 4 - Invalid login_id1 (reserved) +// 5 - Invalid client_tick (reserved) +// 6 - Invalid sex +// Only the first 'invalid' error that appears is used. +static int clif_guess_PacketVer(int fd, int get_previous, int *error) +{ + static int err = 1; + static int packet_ver = -1; + int cmd, packet_len, value; //Value is used to temporarily store account/char_id/sex + + if (get_previous) + {//For quick reruns, since the normal code flow is to fetch this once to identify the packet version, then again in the wanttoconnect function. [Skotlex] + if( error ) + *error = err; + return packet_ver; + } + + //By default, start searching on the default one. + err = 1; + packet_ver = clif_config.packet_db_ver; + cmd = RFIFOW(fd,0); + packet_len = RFIFOREST(fd); + +#define SET_ERROR(n) \ + if( err == 1 )\ + err = n;\ +//define SET_ERROR + + // FIXME: If the packet is not received at once, this will FAIL. + // Figure out, when it happens, that only part of the packet is + // received, or fix the function to be able to deal with that + // case. +#define CHECK_PACKET_VER() \ + if( cmd != clif_config.connect_cmd[packet_ver] || packet_len != packet_db[packet_ver][cmd].len )\ + ;/* not wanttoconnection or wrong length */\ + else if( (value=(int)RFIFOL(fd, packet_db[packet_ver][cmd].pos[0])) < START_ACCOUNT_NUM || value > END_ACCOUNT_NUM )\ + { SET_ERROR(2); }/* invalid account_id */\ + else if( (value=(int)RFIFOL(fd, packet_db[packet_ver][cmd].pos[1])) <= 0 )\ + { SET_ERROR(3); }/* invalid char_id */\ + /* RFIFOL(fd, packet_db[packet_ver][cmd].pos[2]) - don't care about login_id1 */\ + /* RFIFOL(fd, packet_db[packet_ver][cmd].pos[3]) - don't care about client_tick */\ + else if( (value=(int)RFIFOB(fd, packet_db[packet_ver][cmd].pos[4])) != 0 && value != 1 )\ + { SET_ERROR(6); }/* invalid sex */\ + else\ + {\ + err = 0;\ + if( error )\ + *error = 0;\ + return packet_ver;\ + }\ +//define CHECK_PACKET_VER + + CHECK_PACKET_VER();//Default packet version found. + + for (packet_ver = MAX_PACKET_VER; packet_ver > 0; packet_ver--) + { //Start guessing the version, giving priority to the newer ones. [Skotlex] + CHECK_PACKET_VER(); + } + if( error ) + *error = err; + packet_ver = -1; + return -1; +#undef SET_ERROR +#undef CHECK_PACKET_VER +} + +// ------------ +// clif_parse_* +// ------------ +// Parses incoming (player) connection + + +/// Request to connect to map-server. +/// 0072 <account id>.L <char id>.L <auth code>.L <client time>.L <gender>.B (CZ_ENTER) +/// 0436 <account id>.L <char id>.L <auth code>.L <client time>.L <gender>.B (CZ_ENTER2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_WantToConnection(int fd, TBL_PC* sd) +{ + struct block_list* bl; + struct auth_node* node; + int cmd, account_id, char_id, login_id1, sex; + unsigned int client_tick; //The client tick is a tick, therefore it needs be unsigned. [Skotlex] + int packet_ver; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04, 11: 21sept04, 12: 18oct04, 13: 25oct04 (by [Yor]) + + if (sd) { + ShowError("clif_parse_WantToConnection : invalid request (character already logged in)\n"); + return; + } + + // Only valid packet version get here + packet_ver = clif_guess_PacketVer(fd, 1, NULL); + + cmd = RFIFOW(fd,0); + account_id = RFIFOL(fd, packet_db[packet_ver][cmd].pos[0]); + char_id = RFIFOL(fd, packet_db[packet_ver][cmd].pos[1]); + login_id1 = RFIFOL(fd, packet_db[packet_ver][cmd].pos[2]); + client_tick = RFIFOL(fd, packet_db[packet_ver][cmd].pos[3]); + sex = RFIFOB(fd, packet_db[packet_ver][cmd].pos[4]); + + if( packet_ver < 5 || // reject really old client versions + (packet_ver <= 9 && (battle_config.packet_ver_flag & 1) == 0) || // older than 6sept04 + (packet_ver > 9 && (battle_config.packet_ver_flag & 1<<(packet_ver-9)) == 0)) // version not allowed + {// packet version rejected + ShowInfo("Rejected connection attempt, forbidden packet version (AID/CID: '"CL_WHITE"%d/%d"CL_RESET"', Packet Ver: '"CL_WHITE"%d"CL_RESET"', IP: '"CL_WHITE"%s"CL_RESET"').\n", account_id, char_id, packet_ver, ip2str(session[fd]->client_addr, NULL)); + WFIFOHEAD(fd,packet_len(0x6a)); + WFIFOW(fd,0) = 0x6a; + WFIFOB(fd,2) = 5; // Your Game's EXE file is not the latest version + WFIFOSET(fd,packet_len(0x6a)); + set_eof(fd); + return; + } + + if( runflag != MAPSERVER_ST_RUNNING ) + {// not allowed + clif_authfail_fd(fd,1);// server closed + return; + } + + //Check for double login. + bl = map_id2bl(account_id); + if(bl && bl->type != BL_PC) { + ShowError("clif_parse_WantToConnection: a non-player object already has id %d, please increase the starting account number\n", account_id); + WFIFOHEAD(fd,packet_len(0x6a)); + WFIFOW(fd,0) = 0x6a; + WFIFOB(fd,2) = 3; // Rejected by server + WFIFOSET(fd,packet_len(0x6a)); + set_eof(fd); + return; + } + + if (bl || + ((node=chrif_search(account_id)) && //An already existing node is valid only if it is for this login. + !(node->account_id == account_id && node->char_id == char_id && node->state == ST_LOGIN))) + { + clif_authfail_fd(fd, 8); //Still recognizes last connection + return; + } + + CREATE(sd, TBL_PC, 1); + sd->fd = fd; + sd->packet_ver = packet_ver; + session[fd]->session_data = sd; + + pc_setnewpc(sd, account_id, char_id, login_id1, client_tick, sex, fd); + +#if PACKETVER < 20070521 + WFIFOHEAD(fd,4); + WFIFOL(fd,0) = sd->bl.id; + WFIFOSET(fd,4); +#else + WFIFOHEAD(fd,packet_len(0x283)); + WFIFOW(fd,0) = 0x283; + WFIFOL(fd,2) = sd->bl.id; + WFIFOSET(fd,packet_len(0x283)); +#endif + + chrif_authreq(sd); +} + + +/// Notification from the client, that it has finished map loading and is about to display player's character (CZ_NOTIFY_ACTORINIT). +/// 007d +void clif_parse_LoadEndAck(int fd,struct map_session_data *sd) +{ + if(sd->bl.prev != NULL) + return; + + if (!sd->state.active) + { //Character loading is not complete yet! + //Let pc_reg_received reinvoke this when ready. + sd->state.connect_new = 0; + return; + } + + if (sd->state.rewarp) + { //Rewarp player. + sd->state.rewarp = 0; + clif_changemap(sd, sd->mapindex, sd->bl.x, sd->bl.y); + return; + } + + sd->state.warping = 0; + + // look +#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(sd->vd.cloth_color) + clif_refreshlook(&sd->bl,sd->bl.id,LOOK_CLOTHES_COLOR,sd->vd.cloth_color,SELF); + + // item + clif_inventorylist(sd); // inventory list first, otherwise deleted items in pc_checkitem show up as 'unknown item' + pc_checkitem(sd); + + // cart + if(pc_iscarton(sd)) { + clif_cartlist(sd); + clif_updatestatus(sd,SP_CARTINFO); + } + + // weight + clif_updatestatus(sd,SP_WEIGHT); + clif_updatestatus(sd,SP_MAXWEIGHT); + + // guild + // (needs to go before clif_spawn() to show guild emblems correctly) + if(sd->status.guild_id) + guild_send_memberinfoshort(sd,1); + + if(battle_config.pc_invincible_time > 0) { + if(map_flag_gvg(sd->bl.m)) + pc_setinvincibletimer(sd,battle_config.pc_invincible_time<<1); + else + pc_setinvincibletimer(sd,battle_config.pc_invincible_time); + } + + if( map[sd->bl.m].users++ == 0 && battle_config.dynamic_mobs ) + map_spawnmobs(sd->bl.m); + if( !(sd->sc.option&OPTION_INVISIBLE) ) + {// increment the number of pvp players on the map + map[sd->bl.m].users_pvp++; + } + if( map[sd->bl.m].instance_id ) + { + instance[map[sd->bl.m].instance_id].users++; + instance_check_idle(map[sd->bl.m].instance_id); + } + sd->state.debug_remove_map = 0; // temporary state to track double remove_map's [FlavioJS] + + // reset the callshop flag if the player changes map + sd->state.callshop = 0; + + map_addblock(&sd->bl); + clif_spawn(&sd->bl); + + // Party + // (needs to go after clif_spawn() to show hp bars correctly) + if(sd->status.party_id) { + party_send_movemap(sd); + clif_party_hp(sd); // Show hp after displacement [LuzZza] + } + + if( sd->bg_id ) clif_bg_hp(sd); // BattleGround System + + if(map[sd->bl.m].flag.pvp && !(sd->sc.option&OPTION_INVISIBLE)) { + if(!battle_config.pk_mode) { // remove pvp stuff for pk_mode [Valaris] + if (!map[sd->bl.m].flag.pvp_nocalcrank) + 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; + sd->pvp_won = 0; + sd->pvp_lost = 0; + } + clif_map_property(sd, MAPPROPERTY_FREEPVPZONE); + } else + // set flag, if it's a duel [LuzZza] + if(sd->duel_group) + clif_map_property(sd, MAPPROPERTY_FREEPVPZONE); + + if (map[sd->bl.m].flag.gvg_dungeon) + clif_map_property(sd, MAPPROPERTY_FREEPVPZONE); //TODO: Figure out the real packet to send here. + + if( map_flag_gvg(sd->bl.m) ) + clif_map_property(sd, MAPPROPERTY_AGITZONE); + + // info about nearby objects + // must use foreachinarea (CIRCULAR_AREA interferes with foreachinrange) + 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, BL_ALL, sd); + + // pet + if( sd->pd ) + { + if( battle_config.pet_no_gvg && map_flag_gvg(sd->bl.m) ) + { //Return the pet to egg. [Skotlex] + clif_displaymessage(sd->fd, msg_txt(666)); + pet_menu(sd, 3); //Option 3 is return to egg. + } + else + { + map_addblock(&sd->pd->bl); + clif_spawn(&sd->pd->bl); + clif_send_petdata(sd,sd->pd,0,0); + clif_send_petstatus(sd); +// skill_unit_move(&sd->pd->bl,gettick(),1); + } + } + + //homunculus [blackhole89] + if( merc_is_hom_active(sd->hd) ) + { + map_addblock(&sd->hd->bl); + clif_spawn(&sd->hd->bl); + clif_send_homdata(sd,SP_ACK,0); + clif_hominfo(sd,sd->hd,1); + clif_hominfo(sd,sd->hd,0); //for some reason, at least older clients want this sent twice + clif_homskillinfoblock(sd); + if( battle_config.hom_setting&0x8 ) + status_calc_bl(&sd->hd->bl, SCB_SPEED); //Homunc mimic their master's speed on each map change + if( !(battle_config.hom_setting&0x2) ) + skill_unit_move(&sd->hd->bl,gettick(),1); // apply land skills immediately + } + + if( sd->md ) { + map_addblock(&sd->md->bl); + clif_spawn(&sd->md->bl); + clif_mercenary_info(sd); + clif_mercenary_skillblock(sd); + status_calc_bl(&sd->md->bl, SCB_SPEED); // Mercenary mimic their master's speed on each map change + } + + if( sd->ed ) { + map_addblock(&sd->ed->bl); + clif_spawn(&sd->ed->bl); + clif_elemental_info(sd); + clif_elemental_updatestatus(sd,SP_HP); + clif_hpmeter_single(sd->fd,sd->ed->bl.id,sd->ed->battle_status.hp,sd->ed->battle_status.max_hp); + clif_elemental_updatestatus(sd,SP_SP); + status_calc_bl(&sd->ed->bl, SCB_SPEED); //Elemental mimic their master's speed on each map change + } + + if(sd->state.connect_new) { + int lv; + sd->state.connect_new = 0; + clif_skillinfoblock(sd); + clif_hotkeys_send(sd); + clif_updatestatus(sd,SP_BASEEXP); + clif_updatestatus(sd,SP_NEXTBASEEXP); + clif_updatestatus(sd,SP_JOBEXP); + clif_updatestatus(sd,SP_NEXTJOBEXP); + clif_updatestatus(sd,SP_SKILLPOINT); + clif_initialstatus(sd); + + if (sd->sc.option&OPTION_FALCON) + clif_status_load(&sd->bl, SI_FALCON, 1); + + if (sd->sc.option&OPTION_RIDING) + clif_status_load(&sd->bl, SI_RIDING, 1); + else if (sd->sc.option&OPTION_WUGRIDER) + clif_status_load(&sd->bl, SI_WUGRIDER, 1); + + if(sd->status.manner < 0) + sc_start(&sd->bl,SC_NOCHAT,100,0,0); + + //Auron reported that This skill only triggers when you logon on the map o.O [Skotlex] + if ((lv = pc_checkskill(sd,SG_KNOWLEDGE)) > 0) { + if(sd->bl.m == sd->feel_map[0].m + || sd->bl.m == sd->feel_map[1].m + || sd->bl.m == sd->feel_map[2].m) + sc_start(&sd->bl, SC_KNOWLEDGE, 100, lv, skill_get_time(SG_KNOWLEDGE, lv)); + } + + if(sd->pd && sd->pd->pet.intimate > 900) + clif_pet_emotion(sd->pd,(sd->pd->pet.class_ - 100)*100 + 50 + pet_hungry_val(sd->pd)); + + if(merc_is_hom_active(sd->hd)) + merc_hom_init_timers(sd->hd); + + if (night_flag && map[sd->bl.m].flag.nightenabled) { + sd->state.night = 1; + clif_status_load(&sd->bl, SI_NIGHT, 1); + } + + // Notify everyone that this char logged in [Skotlex]. + map_foreachpc(clif_friendslist_toggle_sub, sd->status.account_id, sd->status.char_id, 1); + + //Login Event + npc_script_event(sd, NPCE_LOGIN); + } else { + //For some reason the client "loses" these on warp/map-change. + 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); + + // abort currently running script + sd->state.using_fake_npc = 0; + sd->state.menu_or_input = 0; + sd->npc_menu = 0; + + if(sd->npc_id) + npc_event_dequeue(sd); + } + + if( sd->state.changemap ) + {// restore information that gets lost on map-change +#if PACKETVER >= 20070918 + clif_partyinvitationstate(sd); + clif_equipcheckbox(sd); +#endif + if( (battle_config.bg_flee_penalty != 100 || battle_config.gvg_flee_penalty != 100) && + (map_flag_gvg(sd->state.pmap) || map_flag_gvg(sd->bl.m) || map[sd->state.pmap].flag.battleground || map[sd->bl.m].flag.battleground) ) + status_calc_bl(&sd->bl, SCB_FLEE); //Refresh flee penalty + + if( night_flag && map[sd->bl.m].flag.nightenabled ) + { //Display night. + if( !sd->state.night ) + { + sd->state.night = 1; + clif_status_load(&sd->bl, SI_NIGHT, 1); + } + } + else if( sd->state.night ) + { //Clear night display. + sd->state.night = 0; + clif_status_load(&sd->bl, SI_NIGHT, 0); + } + + if( map[sd->bl.m].flag.battleground ) + { + clif_map_type(sd, MAPTYPE_BATTLEFIELD); // Battleground Mode + if( map[sd->bl.m].flag.battleground == 2 ) + clif_bg_updatescore_single(sd); + } + + if( map[sd->bl.m].flag.allowks && !map_flag_ks(sd->bl.m) ) + { + char output[128]; + sprintf(output, "[ Kill Steal Protection Disable. KS is allowed in this map ]"); + clif_broadcast(&sd->bl, output, strlen(output) + 1, 0x10, SELF); + } + + map_iwall_get(sd); // Updates Walls Info on this Map to Client + sd->state.changemap = false; + } + + mail_clear(sd); + + /* Guild Aura Init */ + if( sd->state.gmaster_flag ) { + guild_guildaura_refresh(sd,GD_LEADERSHIP,guild_checkskill(sd->state.gmaster_flag,GD_LEADERSHIP)); + guild_guildaura_refresh(sd,GD_GLORYWOUNDS,guild_checkskill(sd->state.gmaster_flag,GD_GLORYWOUNDS)); + guild_guildaura_refresh(sd,GD_SOULCOLD,guild_checkskill(sd->state.gmaster_flag,GD_SOULCOLD)); + guild_guildaura_refresh(sd,GD_HAWKEYES,guild_checkskill(sd->state.gmaster_flag,GD_HAWKEYES)); + } + + if( sd->state.vending ) { /* show we have a vending */ + clif_openvending(sd,sd->bl.id,sd->vending); + clif_showvendingboard(&sd->bl,sd->message,0); + } + + if(map[sd->bl.m].flag.loadevent) // Lance + npc_script_event(sd, NPCE_LOADMAP); + + if (pc_checkskill(sd, SG_DEVIL) && !pc_nextjobexp(sd)) + clif_status_load(&sd->bl, SI_DEVIL, 1); //blindness [Komurka] + + if (sd->sc.opt2) //Client loses these on warp. + clif_changeoption(&sd->bl); + + clif_weather_check(sd); + + // For automatic triggering of NPCs after map loading (so you don't need to walk 1 step first) + if (map_getcell(sd->bl.m,sd->bl.x,sd->bl.y,CELL_CHKNPC)) + npc_touch_areanpc(sd,sd->bl.m,sd->bl.x,sd->bl.y); + else + sd->areanpc_id = 0; + + /* it broke at some point (e.g. during a crash), so we make it visibly dead again. */ + if( !sd->status.hp && !pc_isdead(sd) && status_isdead(&sd->bl) ) + pc_setdead(sd); + + // If player is dead, and is spawned (such as @refresh) send death packet. [Valaris] + if(pc_isdead(sd)) + clif_clearunit_area(&sd->bl, CLR_DEAD); + else { + skill_usave_trigger(sd); + clif_changed_dir(&sd->bl, SELF); + } + +// Trigger skill effects if you appear standing on them + if(!battle_config.pc_invincible_time) + skill_unit_move(&sd->bl,gettick(),1); +} + + +/// Server's tick (ZC_NOTIFY_TIME). +/// 007f <time>.L +void clif_notify_time(struct map_session_data* sd, unsigned long time) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x7f)); + WFIFOW(fd,0) = 0x7f; + WFIFOL(fd,2) = time; + WFIFOSET(fd,packet_len(0x7f)); +} + + +/// Request for server's tick. +/// 007e <client tick>.L (CZ_REQUEST_TIME) +/// 0360 <client tick>.L (CZ_REQUEST_TIME2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_TickSend(int fd, struct map_session_data *sd) +{ + sd->client_tick = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + + clif_notify_time(sd, gettick()); +} + + +/// Sends hotkey bar. +/// 02b9 { <is skill>.B <id>.L <count>.W }*27 (ZC_SHORTCUT_KEY_LIST) +/// 07d9 { <is skill>.B <id>.L <count>.W }*36 (ZC_SHORTCUT_KEY_LIST_V2, PACKETVER >= 20090603) +/// 07d9 { <is skill>.B <id>.L <count>.W }*38 (ZC_SHORTCUT_KEY_LIST_V2, PACKETVER >= 20090617) +void clif_hotkeys_send(struct map_session_data *sd) { +#ifdef HOTKEY_SAVING + const int fd = sd->fd; + int i; +#if PACKETVER < 20090603 + const int cmd = 0x2b9; +#else + const int cmd = 0x7d9; +#endif + if (!fd) return; + WFIFOHEAD(fd, 2+MAX_HOTKEYS*7); + WFIFOW(fd, 0) = cmd; + for(i = 0; i < MAX_HOTKEYS; i++) { + WFIFOB(fd, 2 + 0 + i * 7) = sd->status.hotkeys[i].type; // type: 0: item, 1: skill + WFIFOL(fd, 2 + 1 + i * 7) = sd->status.hotkeys[i].id; // item or skill ID + WFIFOW(fd, 2 + 5 + i * 7) = sd->status.hotkeys[i].lv; // skill level + } + WFIFOSET(fd, packet_len(cmd)); +#endif +} + + +/// Request to update a position on the hotkey bar (CZ_SHORTCUT_KEY_CHANGE). +/// 02ba <index>.W <is skill>.B <id>.L <count>.W +void clif_parse_Hotkey(int fd, struct map_session_data *sd) { +#ifdef HOTKEY_SAVING + unsigned short idx; + int cmd; + + cmd = RFIFOW(fd, 0); + idx = RFIFOW(fd, packet_db[sd->packet_ver][cmd].pos[0]); + if (idx >= MAX_HOTKEYS) return; + + sd->status.hotkeys[idx].type = RFIFOB(fd, packet_db[sd->packet_ver][cmd].pos[1]); + sd->status.hotkeys[idx].id = RFIFOL(fd, packet_db[sd->packet_ver][cmd].pos[2]); + sd->status.hotkeys[idx].lv = RFIFOW(fd, packet_db[sd->packet_ver][cmd].pos[3]); +#endif +} + + +/// Displays cast-like progress bar (ZC_PROGRESS). +/// 02f0 <color>.L <time>.L +void clif_progressbar(struct map_session_data * sd, unsigned long color, unsigned int second) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x2f0)); + WFIFOW(fd,0) = 0x2f0; + WFIFOL(fd,2) = color; + WFIFOL(fd,6) = second; + WFIFOSET(fd,packet_len(0x2f0)); +} + + +/// Removes an ongoing progress bar (ZC_PROGRESS_CANCEL). +/// 02f2 +void clif_progressbar_abort(struct map_session_data * sd) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x2f2)); + WFIFOW(fd,0) = 0x2f2; + WFIFOSET(fd,packet_len(0x2f2)); +} + + +/// Notification from the client, that the progress bar has reached 100% (CZ_PROGRESS). +/// 02f1 +void clif_parse_progressbar(int fd, struct map_session_data * sd) +{ + int npc_id = sd->progressbar.npc_id; + + if( gettick() < sd->progressbar.timeout && sd->st ) + sd->st->state = END; + + sd->progressbar.npc_id = sd->progressbar.timeout = 0; + npc_scriptcont(sd, npc_id); +} + + +/// Request to walk to a certain position on the current map. +/// 0085 <dest>.3B (CZ_REQUEST_MOVE) +/// 035f <dest>.3B (CZ_REQUEST_MOVE2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_WalkToXY(int fd, struct map_session_data *sd) +{ + short x, y; + + if (pc_isdead(sd)) { + clif_clearunit_area(&sd->bl, CLR_DEAD); + return; + } + + if (sd->sc.opt1 && ( sd->sc.opt1 == OPT1_STONEWAIT || sd->sc.opt1 == OPT1_BURNING )) + ; //You CAN walk on this OPT1 value. + else if( sd->progressbar.npc_id ) + clif_progressbar_abort(sd); + else if (pc_cant_act(sd)) + return; + + if(sd->sc.data[SC_RUN] || sd->sc.data[SC_WUGDASH]) + return; + + pc_delinvincibletimer(sd); + + RFIFOPOS(fd, packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0], &x, &y, NULL); + + //Set last idle time... [Skotlex] + sd->idletime = last_tick; + + unit_walktoxy(&sd->bl, x, y, 4); +} + + +/// Notification about the result of a disconnect request (ZC_ACK_REQ_DISCONNECT). +/// 018b <result>.W +/// result: +/// 0 = disconnect (quit) +/// 1 = cannot disconnect (wait 10 seconds) +/// ? = ignored +void clif_disconnect_ack(struct map_session_data* sd, short result) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x18b)); + WFIFOW(fd,0) = 0x18b; + WFIFOW(fd,2) = result; + WFIFOSET(fd,packet_len(0x18b)); +} + + +/// Request to disconnect from server (CZ_REQ_DISCONNECT). +/// 018a <type>.W +/// type: +/// 0 = quit +void clif_parse_QuitGame(int fd, struct map_session_data *sd) +{ + /* Rovert's prevent logout option fixed [Valaris] */ + if( !sd->sc.data[SC_CLOAKING] && !sd->sc.data[SC_HIDING] && !sd->sc.data[SC_CHASEWALK] && !sd->sc.data[SC_CLOAKINGEXCEED] && + (!battle_config.prevent_logout || DIFF_TICK(gettick(), sd->canlog_tick) > battle_config.prevent_logout) ) + { + set_eof(fd); + clif_disconnect_ack(sd, 0); + } else { + clif_disconnect_ack(sd, 1); + } +} + + +/// Requesting unit's name. +/// 0094 <id>.L (CZ_REQNAME) +/// 0368 <id>.L (CZ_REQNAME2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_GetCharNameRequest(int fd, struct map_session_data *sd) +{ + int id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + struct block_list* bl; + //struct status_change *sc; + + if( id < 0 && -id == sd->bl.id ) // for disguises [Valaris] + id = sd->bl.id; + + bl = map_id2bl(id); + if( bl == NULL ) + return; // Lagged clients could request names of already gone mobs/players. [Skotlex] + + if( sd->bl.m != bl->m || !check_distance_bl(&sd->bl, bl, AREA_SIZE) ) + return; // Block namerequests past view range + + // 'see people in GM hide' cheat detection + /* disabled due to false positives (network lag + request name of char that's about to hide = race condition) + sc = status_get_sc(bl); + if (sc && sc->option&OPTION_INVISIBLE && !disguised(bl) && + bl->type != BL_NPC && //Skip hidden NPCs which can be seen using Maya Purple + pc_get_group_level(sd) < battle_config.hack_info_GM_level + ) { + char gm_msg[256]; + sprintf(gm_msg, "Hack on NameRequest: character '%s' (account: %d) requested the name of an invisible target (id: %d).\n", sd->status.name, sd->status.account_id, id); + ShowWarning(gm_msg); + // information is sent to all online GMs + intif_wis_message_to_gm(wisp_server_name, battle_config.hack_info_GM_level, gm_msg); + return; + } + */ + + clif_charnameack(fd, bl); +} + + +/// Validates and processes global messages +/// 008c <packet len>.W <text>.?B (<name> : <message>) 00 (CZ_REQUEST_CHAT) +/// There are various variants of this packet. +void clif_parse_GlobalMessage(int fd, struct map_session_data* sd) +{ + const char* text = (char*)RFIFOP(fd,4); + int textlen = RFIFOW(fd,2) - 4; + + char *name, *message, *fakename = NULL; + int namelen, messagelen; + + bool is_fake; + + // validate packet and retrieve name and message + if( !clif_process_message(sd, 0, &name, &namelen, &message, &messagelen) ) + return; + + if( is_atcommand(fd, sd, message, 1) ) + return; + + if( sd->sc.data[SC_BERSERK] || sd->sc.data[SC__BLOODYLUST] || (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOCHAT) ) + return; + + if( battle_config.min_chat_delay ) + { //[Skotlex] + if (DIFF_TICK(sd->cantalk_tick, gettick()) > 0) + return; + sd->cantalk_tick = gettick() + battle_config.min_chat_delay; + } + /** + * Fake Name Design by FatalEror (bug report #9) + **/ + if( ( is_fake = ( sd->fakename[0] ) ) ) { + fakename = (char*) aMalloc(strlen(sd->fakename)+messagelen+3); + strcpy(fakename, sd->fakename); + strcat(fakename, " : "); + strcat(fakename, message); + textlen = strlen(fakename) + 1; + } + // send message to others (using the send buffer for temp. storage) + WFIFOHEAD(fd, 8 + textlen); + WFIFOW(fd,0) = 0x8d; + WFIFOW(fd,2) = 8 + textlen; + WFIFOL(fd,4) = sd->bl.id; + safestrncpy((char*)WFIFOP(fd,8), is_fake ? fakename : text, textlen); + //FIXME: chat has range of 9 only + clif_send(WFIFOP(fd,0), WFIFOW(fd,2), &sd->bl, sd->chatID ? CHAT_WOS : AREA_CHAT_WOC); + + // send back message to the speaker + if( is_fake ) { + WFIFOW(fd,0) = 0x8e; + WFIFOW(fd,2) = textlen + 4; + safestrncpy((char*)WFIFOP(fd,4), fakename, textlen); + aFree(fakename); + } else { + memcpy(WFIFOP(fd,0), RFIFOP(fd,0), RFIFOW(fd,2)); + WFIFOW(fd,0) = 0x8e; + } + WFIFOSET(fd, WFIFOW(fd,2)); +#ifdef PCRE_SUPPORT + // trigger listening npcs + map_foreachinrange(npc_chat_sub, &sd->bl, AREA_SIZE, BL_NPC, text, textlen, &sd->bl); +#endif + + // Chat logging type 'O' / Global Chat + log_chat(LOG_CHAT_GLOBAL, 0, sd->status.char_id, sd->status.account_id, mapindex_id2name(sd->mapindex), sd->bl.x, sd->bl.y, NULL, message); +} + + +/// /mm /mapmove (as @rura GM command) (CZ_MOVETO_MAP). +/// Request to warp to a map on given coordinates. +/// 0140 <map name>.16B <x>.W <y>.W +void clif_parse_MapMove(int fd, struct map_session_data *sd) +{ + char command[MAP_NAME_LENGTH_EXT+25]; + char* map_name; + + map_name = (char*)RFIFOP(fd,2); + map_name[MAP_NAME_LENGTH_EXT-1]='\0'; + sprintf(command, "%cmapmove %s %d %d", atcommand_symbol, map_name, RFIFOW(fd,18), RFIFOW(fd,20)); + is_atcommand(fd, sd, command, 1); +} + + +/// Updates body and head direction of an object (ZC_CHANGE_DIRECTION). +/// 009c <id>.L <head dir>.W <dir>.B +/// head dir: +/// 0 = straight +/// 1 = turned CW +/// 2 = turned CCW +/// dir: +/// 0 = north +/// 1 = northwest +/// 2 = west +/// 3 = southwest +/// 4 = south +/// 5 = southeast +/// 6 = east +/// 7 = northeast +void clif_changed_dir(struct block_list *bl, enum send_target target) +{ + unsigned char buf[64]; + + WBUFW(buf,0) = 0x9c; + WBUFL(buf,2) = bl->id; + WBUFW(buf,6) = bl->type==BL_PC?((TBL_PC*)bl)->head_dir:0; + WBUFB(buf,8) = unit_getdir(bl); + + clif_send(buf, packet_len(0x9c), bl, target); + + if (disguised(bl)) { + WBUFL(buf,2) = -bl->id; + WBUFW(buf,6) = 0; + clif_send(buf, packet_len(0x9c), bl, SELF); + } +} + + +/// Request to change own body and head direction. +/// 009b <head dir>.W <dir>.B (CZ_CHANGE_DIRECTION) +/// 0361 <head dir>.W <dir>.B (CZ_CHANGE_DIRECTION2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_ChangeDir(int fd, struct map_session_data *sd) +{ + unsigned char headdir, dir; + + headdir = RFIFOB(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + dir = RFIFOB(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]); + pc_setdir(sd, dir, headdir); + + clif_changed_dir(&sd->bl, AREA_WOS); +} + + +/// Request to show an emotion (CZ_REQ_EMOTION). +/// 00bf <type>.B +/// type: +/// @see enum emotion_type +void clif_parse_Emotion(int fd, struct map_session_data *sd) +{ + int emoticon = RFIFOB(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + + if (battle_config.basic_skill_check == 0 || pc_checkskill(sd, NV_BASIC) >= 2) { + if (emoticon == E_MUTE) {// prevent use of the mute emote [Valaris] + clif_skill_fail(sd, 1, USESKILL_FAIL_LEVEL, 1); + return; + } + // fix flood of emotion icon (ro-proxy): flood only the hacker player + if (sd->emotionlasttime + 1 >= time(NULL)) { // not more than 1 per second + sd->emotionlasttime = time(NULL); + clif_skill_fail(sd, 1, USESKILL_FAIL_LEVEL, 1); + return; + } + sd->emotionlasttime = time(NULL); + + if(battle_config.client_reshuffle_dice && emoticon>=E_DICE1 && emoticon<=E_DICE6) + {// re-roll dice + emoticon = rnd()%6+E_DICE1; + } + + clif_emotion(&sd->bl, emoticon); + } else + clif_skill_fail(sd, 1, USESKILL_FAIL_LEVEL, 1); +} + + +/// Amount of currently online players, reply to /w /who (ZC_USER_COUNT). +/// 00c2 <count>.L +void clif_user_count(struct map_session_data* sd, int count) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0xc2)); + WFIFOW(fd,0) = 0xc2; + WFIFOL(fd,2) = count; + WFIFOSET(fd,packet_len(0xc2)); +} + + +/// /w /who (CZ_REQ_USER_COUNT). +/// Request to display amount of currently connected players. +/// 00c1 +void clif_parse_HowManyConnections(int fd, struct map_session_data *sd) +{ + clif_user_count(sd, map_getusers()); +} + + +void clif_parse_ActionRequest_sub(struct map_session_data *sd, int action_type, int target_id, unsigned int tick) +{ + if (pc_isdead(sd)) { + clif_clearunit_area(&sd->bl, CLR_DEAD); + return; + } + + if (sd->sc.count && + (sd->sc.data[SC_TRICKDEAD] || + sd->sc.data[SC_AUTOCOUNTER] || + sd->sc.data[SC_BLADESTOP] || + sd->sc.data[SC__MANHOLE] || + sd->sc.data[SC_CURSEDCIRCLE_ATKER] || + sd->sc.data[SC_CURSEDCIRCLE_TARGET] )) + return; + + pc_stop_walking(sd, 1); + pc_stop_attack(sd); + + if(target_id<0 && -target_id == sd->bl.id) // for disguises [Valaris] + target_id = sd->bl.id; + + switch(action_type) + { + case 0x00: // once attack + case 0x07: // continuous attack + + if( pc_cant_act(sd) || sd->sc.option&OPTION_HIDE ) + return; + + if( sd->sc.option&(OPTION_WEDDING|OPTION_XMAS|OPTION_SUMMER) ) + return; + + if( sd->sc.data[SC_BASILICA] || sd->sc.data[SC__SHADOWFORM] ) + return; + + if (!battle_config.sdelay_attack_enable && pc_checkskill(sd, SA_FREECAST) <= 0) { + if (DIFF_TICK(tick, sd->ud.canact_tick) < 0) { + clif_skill_fail(sd, 1, USESKILL_FAIL_SKILLINTERVAL, 0); + return; + } + } + + pc_delinvincibletimer(sd); + sd->idletime = last_tick; + unit_attack(&sd->bl, target_id, action_type != 0); + break; + case 0x02: // sitdown + if (battle_config.basic_skill_check && pc_checkskill(sd, NV_BASIC) < 3) { + clif_skill_fail(sd, 1, USESKILL_FAIL_LEVEL, 2); + break; + } + + if(pc_issit(sd)) { + //Bugged client? Just refresh them. + clif_sitting(&sd->bl); + return; + } + + if (sd->ud.skilltimer != INVALID_TIMER || (sd->sc.opt1 && sd->sc.opt1 != OPT1_BURNING )) + break; + + if (sd->sc.count && ( + sd->sc.data[SC_DANCING] || + (sd->sc.data[SC_GRAVITATION] && sd->sc.data[SC_GRAVITATION]->val3 == BCT_SELF) + )) //No sitting during these states either. + break; + + pc_setsit(sd); + skill_sit(sd,1); + clif_sitting(&sd->bl); + break; + case 0x03: // standup + if (!pc_issit(sd)) { + //Bugged client? Just refresh them. + clif_standing(&sd->bl); + return; + } + pc_setstand(sd); + skill_sit(sd,0); + clif_standing(&sd->bl); + break; + } +} + + +/// Request for an action. +/// 0089 <target id>.L <action>.B (CZ_REQUEST_ACT) +/// 0437 <target id>.L <action>.B (CZ_REQUEST_ACT2) +/// action: +/// 0 = attack +/// 1 = pick up item +/// 2 = sit down +/// 3 = stand up +/// 7 = continous attack +/// 12 = (touch skill?) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_ActionRequest(int fd, struct map_session_data *sd) +{ + clif_parse_ActionRequest_sub(sd, + RFIFOB(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]), + RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]), + gettick() + ); +} + + +/// Response to the death/system menu (CZ_RESTART). +/// 00b2 <type>.B +/// type: +/// 0 = restart (respawn) +/// 1 = char-select (disconnect) +void clif_parse_Restart(int fd, struct map_session_data *sd) +{ + switch(RFIFOB(fd,2)) { + case 0x00: + pc_respawn(sd,CLR_RESPAWN); + break; + case 0x01: + /* Rovert's Prevent logout option - Fixed [Valaris] */ + if( !sd->sc.data[SC_CLOAKING] && !sd->sc.data[SC_HIDING] && !sd->sc.data[SC_CHASEWALK] && !sd->sc.data[SC_CLOAKINGEXCEED] && + (!battle_config.prevent_logout || DIFF_TICK(gettick(), sd->canlog_tick) > battle_config.prevent_logout) ) + { //Send to char-server for character selection. + chrif_charselectreq(sd, session[fd]->client_addr); + } else { + clif_disconnect_ack(sd, 1); + } + break; + } +} + + +/// Validates and processes whispered messages (CZ_WHISPER). +/// 0096 <packet len>.W <nick>.24B <message>.?B +void clif_parse_WisMessage(int fd, struct map_session_data* sd) +{ + struct map_session_data* dstsd; + int i; + + char *target, *message; + int namelen, messagelen; + + // validate packet and retrieve name and message + if( !clif_process_message(sd, 1, &target, &namelen, &message, &messagelen) ) + return; + + if ( is_atcommand(fd, sd, message, 1) ) + return; + + if (sd->sc.data[SC_BERSERK] || sd->sc.data[SC__BLOODYLUST] || (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOCHAT)) + return; + + if (battle_config.min_chat_delay) { //[Skotlex] + if (DIFF_TICK(sd->cantalk_tick, gettick()) > 0) { + return; + } + sd->cantalk_tick = gettick() + battle_config.min_chat_delay; + } + + // Chat logging type 'W' / Whisper + log_chat(LOG_CHAT_WHISPER, 0, sd->status.char_id, sd->status.account_id, mapindex_id2name(sd->mapindex), sd->bl.x, sd->bl.y, target, message); + + //-------------------------------------------------------// + // Lordalfa - Paperboy - To whisper NPC commands // + //-------------------------------------------------------// + if (target[0] && (strncasecmp(target,"NPC:",4) == 0) && (strlen(target) > 4)) + { + char* str = target+4; //Skip the NPC: string part. + struct npc_data* npc; + if ((npc = npc_name2id(str))) { + char split_data[NUM_WHISPER_VAR][CHAT_SIZE_MAX]; + char *split; + char output[256]; + + str = message; + // skip codepage indicator, if detected + if( str[0] == '|' && strlen(str) >= 4 ) + str += 3; + for( i = 0; i < NUM_WHISPER_VAR; ++i ) {// Splits the message using '#' as separators + split = strchr(str,'#'); + if( split == NULL ) { // use the remaining string + safestrncpy(split_data[i], str, ARRAYLENGTH(split_data[i])); + for( ++i; i < NUM_WHISPER_VAR; ++i ) + split_data[i][0] = '\0'; + break; + } + *split = '\0'; + safestrncpy(split_data[i], str, ARRAYLENGTH(split_data[i])); + str = split+1; + } + + for( i = 0; i < NUM_WHISPER_VAR; ++i ) { + sprintf(output, "@whispervar%d$", i); + set_var(sd,output,(char *) split_data[i]); + } + + sprintf(output, "%s::OnWhisperGlobal", npc->exname); + npc_event(sd,output,0); // Calls the NPC label + + return; + } + } else if(strcmpi(target, main_chat_nick) == 0) { // Main chat [LuzZza] + if(!sd->state.mainchat) + clif_displaymessage(fd, msg_txt(388)); // You should enable main chat with "@main on" command. + else { + // send the main message using inter-server system + intif_main_message( sd, message ); + } + + return; + } + + // searching destination character + dstsd = map_nick2sd(target); + + if (dstsd == NULL || strcmp(dstsd->status.name, target) != 0) { + // player is not on this map-server + // 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). + intif_wis_message(sd, target, message, messagelen); + return; + } + + // if player ignores everyone + if (dstsd->state.ignoreAll) { + if (dstsd->sc.option & OPTION_INVISIBLE && pc_get_group_level(sd) < pc_get_group_level(dstsd)) + clif_wis_end(fd, 1); // 1: target character is not loged in + else + clif_wis_end(fd, 3); // 3: everyone ignored by target + return; + } + + // if player is autotrading + if( dstsd->state.autotrade == 1 ) { + char output[256]; + sprintf(output, "%s is in autotrade mode and cannot receive whispered messages.", dstsd->status.name); + clif_wis_message(fd, wisp_server_name, output, strlen(output) + 1); + return; + } + + // if player ignores the source character + ARR_FIND(0, MAX_IGNORE_LIST, i, dstsd->ignore[i].name[0] == '\0' || strcmp(dstsd->ignore[i].name, sd->status.name) == 0); + if(i < MAX_IGNORE_LIST && dstsd->ignore[i].name[0] != '\0') { // source char present in ignore list + clif_wis_end(fd, 2); // 2: ignored by target + return; + } + + // notify sender of success + clif_wis_end(fd, 0); // 0: success to send wisper + + // Normal message + clif_wis_message(dstsd->fd, sd->status.name, message, messagelen); +} + + +/// /b /nb (CZ_BROADCAST). +/// Request to broadcast a message on whole server. +/// 0099 <packet len>.W <text>.?B 00 +void clif_parse_Broadcast(int fd, struct map_session_data* sd) { + char command[CHAT_SIZE_MAX+11]; + char* msg = (char*)RFIFOP(fd,4); + unsigned int len = RFIFOW(fd,2)-4; + + // as the length varies depending on the command used, just block unreasonably long strings + mes_len_check(msg, len, CHAT_SIZE_MAX); + + sprintf(command, "%ckami %s", atcommand_symbol, msg); + is_atcommand(fd, sd, command, 1); +} + + +/// Request to pick up an item. +/// 009f <id>.L (CZ_ITEM_PICKUP) +/// 0362 <id>.L (CZ_ITEM_PICKUP2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_TakeItem(int fd, struct map_session_data *sd) +{ + struct flooritem_data *fitem; + int map_object_id; + + map_object_id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + + fitem = (struct flooritem_data*)map_id2bl(map_object_id); + + do { + if (pc_isdead(sd)) { + clif_clearunit_area(&sd->bl, CLR_DEAD); + break; + } + + if (fitem == NULL || fitem->bl.type != BL_ITEM || fitem->bl.m != sd->bl.m) + break; + + if( sd->sc.cant.pickup ) + break; + + if (pc_cant_act(sd)) + break; + + if (!pc_takeitem(sd, fitem)) + break; + + return; + } while (0); + // Client REQUIRES a fail packet or you can no longer pick items. + clif_additem(sd,0,0,6); +} + + +/// Request to drop an item. +/// 00a2 <index>.W <amount>.W (CZ_ITEM_THROW) +/// 0363 <index>.W <amount>.W (CZ_ITEM_THROW2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_DropItem(int fd, struct map_session_data *sd) +{ + int item_index = RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0])-2; + int item_amount = RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]); + + for(;;) { + if (pc_isdead(sd)) + break; + + if (pc_cant_act(sd)) + break; + + if (sd->sc.count && ( + sd->sc.data[SC_AUTOCOUNTER] || + sd->sc.data[SC_BLADESTOP] || + (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOITEM) + )) + break; + + if (!pc_dropitem(sd, item_index, item_amount)) + break; + + return; + } + + //Because the client does not like being ignored. + clif_dropitem(sd, item_index,0); +} + + +/// Request to use an item. +/// 00a7 <index>.W <account id>.L (CZ_USE_ITEM) +/// 0439 <index>.W <account id>.L (CZ_USE_ITEM2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_UseItem(int fd, struct map_session_data *sd) +{ + int n; + + if (pc_isdead(sd)) { + clif_clearunit_area(&sd->bl, CLR_DEAD); + return; + } + + //This flag enables you to use items while in an NPC. [Skotlex] + if (sd->npc_id) { + if (sd->npc_id != sd->npc_item_flag) + return; + } + else if (pc_istrading(sd)) + return; + + //Whether the item is used or not is irrelevant, the char ain't idle. [Skotlex] + sd->idletime = last_tick; + n = RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0])-2; + + if(n <0 || n >= MAX_INVENTORY) + return; + if (!pc_useitem(sd,n)) + clif_useitemack(sd,n,0,false); //Send an empty ack packet or the client gets stuck. +} + + +/// Request to equip an item (CZ_REQ_WEAR_EQUIP). +/// 00a9 <index>.W <position>.W +void clif_parse_EquipItem(int fd,struct map_session_data *sd) +{ + int index; + + if(pc_isdead(sd)) { + clif_clearunit_area(&sd->bl,CLR_DEAD); + return; + } + index = RFIFOW(fd,2)-2; + if (index < 0 || index >= MAX_INVENTORY) + return; //Out of bounds check. + + if(sd->npc_id) { + if (sd->npc_id != sd->npc_item_flag) + return; + } else if (sd->state.storage_flag || sd->sc.opt1) + ; //You can equip/unequip stuff while storage is open/under status changes + else if (pc_cant_act(sd)) + return; + + if(!sd->status.inventory[index].identify) { + clif_equipitemack(sd,index,0,0); // fail + return; + } + + if(!sd->inventory_data[index]) + return; + + if(sd->inventory_data[index]->type == IT_PETARMOR){ + pet_equipitem(sd,index); + return; + } + + //Client doesn't send the position for ammo. + if(sd->inventory_data[index]->type == IT_AMMO) + pc_equipitem(sd,index,EQP_AMMO); + else + pc_equipitem(sd,index,RFIFOW(fd,4)); +} + + +/// Request to take off an equip (CZ_REQ_TAKEOFF_EQUIP). +/// 00ab <index>.W +void clif_parse_UnequipItem(int fd,struct map_session_data *sd) +{ + int index; + + if(pc_isdead(sd)) { + clif_clearunit_area(&sd->bl,CLR_DEAD); + return; + } + + if (sd->state.storage_flag || sd->sc.opt1) + ; //You can equip/unequip stuff while storage is open/under status changes + else if (pc_cant_act(sd)) + return; + + index = RFIFOW(fd,2)-2; + + pc_unequipitem(sd,index,1); +} + + +/// Request to start a conversation with an NPC (CZ_CONTACTNPC). +/// 0090 <id>.L <type>.B +/// type: +/// 1 = click +void clif_parse_NpcClicked(int fd,struct map_session_data *sd) +{ + struct block_list *bl; + + if(pc_isdead(sd)) { + clif_clearunit_area(&sd->bl,CLR_DEAD); + return; + } + + if (pc_cant_act(sd)) + return; + + bl = map_id2bl(RFIFOL(fd,2)); + if (!bl) return; + switch (bl->type) { + case BL_MOB: + case BL_PC: + clif_parse_ActionRequest_sub(sd, 0x07, bl->id, gettick()); + break; + case BL_NPC: + if( bl->m != -1 )// the user can't click floating npcs directly (hack attempt) + npc_click(sd,(TBL_NPC*)bl); + break; + } +} + + +/// Selection between buy/sell was made (CZ_ACK_SELECT_DEALTYPE). +/// 00c5 <id>.L <type>.B +/// type: +/// 0 = buy +/// 1 = sell +void clif_parse_NpcBuySellSelected(int fd,struct map_session_data *sd) +{ + if (sd->state.trading) + return; + npc_buysellsel(sd,RFIFOL(fd,2),RFIFOB(fd,6)); +} + + +/// Notification about the result of a purchase attempt from an NPC shop (ZC_PC_PURCHASE_RESULT). +/// 00ca <result>.B +/// result: +/// 0 = "The deal has successfully completed." +/// 1 = "You do not have enough zeny." +/// 2 = "You are over your Weight Limit." +/// 3 = "Out of the maximum capacity, you have too many items." +void clif_npc_buy_result(struct map_session_data* sd, unsigned char result) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0xca)); + WFIFOW(fd,0) = 0xca; + WFIFOB(fd,2) = result; + WFIFOSET(fd,packet_len(0xca)); +} + + +/// Request to buy chosen items from npc shop (CZ_PC_PURCHASE_ITEMLIST). +/// 00c8 <packet len>.W { <amount>.W <name id>.W }* +void clif_parse_NpcBuyListSend(int fd, struct map_session_data* sd) +{ + int n = (RFIFOW(fd,2)-4) /4; + unsigned short* item_list = (unsigned short*)RFIFOP(fd,4); + int result; + + if( sd->state.trading || !sd->npc_shopid ) + result = 1; + else + result = npc_buylist(sd,n,item_list); + + sd->npc_shopid = 0; //Clear shop data. + + clif_npc_buy_result(sd, result); +} + + +/// Notification about the result of a sell attempt to an NPC shop (ZC_PC_SELL_RESULT). +/// 00cb <result>.B +/// result: +/// 0 = "The deal has successfully completed." +/// 1 = "The deal has failed." +void clif_npc_sell_result(struct map_session_data* sd, unsigned char result) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0xcb)); + WFIFOW(fd,0) = 0xcb; + WFIFOB(fd,2) = result; + WFIFOSET(fd,packet_len(0xcb)); +} + + +/// Request to sell chosen items to npc shop (CZ_PC_SELL_ITEMLIST). +/// 00c9 <packet len>.W { <index>.W <amount>.W }* +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); + + if (sd->state.trading || !sd->npc_shopid) + fail = 1; + else + fail = npc_selllist(sd,n,item_list); + + sd->npc_shopid = 0; //Clear shop data. + + clif_npc_sell_result(sd, fail); +} + + +/// Chatroom creation request (CZ_CREATE_CHATROOM). +/// 00d5 <packet len>.W <limit>.W <type>.B <passwd>.8B <title>.?B +/// type: +/// 0 = private +/// 1 = public +void clif_parse_CreateChatRoom(int fd, struct map_session_data* sd) +{ + int len = RFIFOW(fd,2)-15; + int limit = RFIFOW(fd,4); + bool pub = (RFIFOB(fd,6) != 0); + const char* password = (char*)RFIFOP(fd,7); //not zero-terminated + const char* title = (char*)RFIFOP(fd,15); // not zero-terminated + char s_password[CHATROOM_PASS_SIZE]; + char s_title[CHATROOM_TITLE_SIZE]; + + if (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOROOM) + return; + if(battle_config.basic_skill_check && pc_checkskill(sd,NV_BASIC) < 4) { + clif_skill_fail(sd,1,USESKILL_FAIL_LEVEL,3); + return; + } + + if( len <= 0 ) + return; // invalid input + + safestrncpy(s_password, password, CHATROOM_PASS_SIZE); + safestrncpy(s_title, title, min(len+1,CHATROOM_TITLE_SIZE)); //NOTE: assumes that safestrncpy will not access the len+1'th byte + + chat_createpcchat(sd, s_title, s_password, limit, pub); +} + + +/// Chatroom join request (CZ_REQ_ENTER_ROOM). +/// 00d9 <chat ID>.L <passwd>.8B +void clif_parse_ChatAddMember(int fd, struct map_session_data* sd) +{ + int chatid = RFIFOL(fd,2); + const char* password = (char*)RFIFOP(fd,6); // not zero-terminated + + chat_joinchat(sd,chatid,password); +} + + +/// Chatroom properties adjustment request (CZ_CHANGE_CHATROOM). +/// 00de <packet len>.W <limit>.W <type>.B <passwd>.8B <title>.?B +/// type: +/// 0 = private +/// 1 = public +void clif_parse_ChatRoomStatusChange(int fd, struct map_session_data* sd) +{ + int len = RFIFOW(fd,2)-15; + int limit = RFIFOW(fd,4); + bool pub = (RFIFOB(fd,6) != 0); + const char* password = (char*)RFIFOP(fd,7); // not zero-terminated + const char* title = (char*)RFIFOP(fd,15); // not zero-terminated + char s_password[CHATROOM_PASS_SIZE]; + char s_title[CHATROOM_TITLE_SIZE]; + + if( len <= 0 ) + return; // invalid input + + safestrncpy(s_password, password, CHATROOM_PASS_SIZE); + safestrncpy(s_title, title, min(len+1,CHATROOM_TITLE_SIZE)); //NOTE: assumes that safestrncpy will not access the len+1'th byte + + chat_changechatstatus(sd, s_title, s_password, limit, pub); +} + + +/// Request to change the chat room ownership (CZ_REQ_ROLE_CHANGE). +/// 00e0 <role>.L <nick>.24B +/// role: +/// 0 = owner +/// 1 = normal +void clif_parse_ChangeChatOwner(int fd, struct map_session_data* sd) +{ + chat_changechatowner(sd,(char*)RFIFOP(fd,6)); +} + + +/// Request to expel a player from chat room (CZ_REQ_EXPEL_MEMBER). +/// 00e2 <name>.24B +void clif_parse_KickFromChat(int fd,struct map_session_data *sd) +{ + chat_kickchat(sd,(char*)RFIFOP(fd,2)); +} + + +/// Request to leave the current chatroom (CZ_EXIT_ROOM). +/// 00e3 +void clif_parse_ChatLeave(int fd, struct map_session_data* sd) +{ + chat_leavechat(sd,0); +} + + +//Handles notifying asker and rejecter of what has just ocurred. +//Type is used to determine the correct msg_txt to use: +//0: +static void clif_noask_sub(struct map_session_data *src, struct map_session_data *target, int type) +{ + const char* msg; + char output[256]; + // Your request has been rejected by autoreject option. + msg = msg_txt(392); + clif_disp_onlyself(src, msg, strlen(msg)); + //Notice that a request was rejected. + snprintf(output, 256, msg_txt(393+type), src->status.name, 256); + clif_disp_onlyself(target, output, strlen(output)); +} + + +/// Request to begin a trade (CZ_REQ_EXCHANGE_ITEM). +/// 00e4 <account id>.L +void clif_parse_TradeRequest(int fd,struct map_session_data *sd) +{ + struct map_session_data *t_sd; + + t_sd = map_id2sd(RFIFOL(fd,2)); + + if(!sd->chatID && pc_cant_act(sd)) + return; //You can trade while in a chatroom. + + // @noask [LuzZza] + if(t_sd && t_sd->state.noask) { + clif_noask_sub(sd, t_sd, 0); + return; + } + + if( battle_config.basic_skill_check && pc_checkskill(sd,NV_BASIC) < 1) + { + clif_skill_fail(sd,1,USESKILL_FAIL_LEVEL,0); + return; + } + + trade_traderequest(sd,t_sd); +} + + +/// Answer to a trade request (CZ_ACK_EXCHANGE_ITEM). +/// 00e6 <result>.B +/// result: +/// 3 = accepted +/// 4 = rejected +void clif_parse_TradeAck(int fd,struct map_session_data *sd) +{ + trade_tradeack(sd,RFIFOB(fd,2)); +} + + +/// Request to add an item to current trade (CZ_ADD_EXCHANGE_ITEM). +/// 00e8 <index>.W <amount>.L +void clif_parse_TradeAddItem(int fd,struct map_session_data *sd) +{ + short index = RFIFOW(fd,2); + int amount = RFIFOL(fd,4); + + if( index == 0 ) + trade_tradeaddzeny(sd, amount); + else + trade_tradeadditem(sd, index, (short)amount); +} + + +/// Request to lock items in current trade (CZ_CONCLUDE_EXCHANGE_ITEM). +/// 00eb +void clif_parse_TradeOk(int fd,struct map_session_data *sd) +{ + trade_tradeok(sd); +} + + +/// Request to cancel current trade (CZ_CANCEL_EXCHANGE_ITEM). +/// 00ed +void clif_parse_TradeCancel(int fd,struct map_session_data *sd) +{ + trade_tradecancel(sd); +} + + +/// Request to commit current trade (CZ_EXEC_EXCHANGE_ITEM). +/// 00ef +void clif_parse_TradeCommit(int fd,struct map_session_data *sd) +{ + trade_tradecommit(sd); +} + + +/// Request to stop chasing/attacking an unit (CZ_CANCEL_LOCKON). +/// 0118 +void clif_parse_StopAttack(int fd,struct map_session_data *sd) +{ + pc_stop_attack(sd); +} + + +/// Request to move an item from inventory to cart (CZ_MOVE_ITEM_FROM_BODY_TO_CART). +/// 0126 <index>.W <amount>.L +void clif_parse_PutItemToCart(int fd,struct map_session_data *sd) +{ + if (pc_istrading(sd)) + return; + if (!pc_iscarton(sd)) + return; + pc_putitemtocart(sd,RFIFOW(fd,2)-2,RFIFOL(fd,4)); +} + + +/// Request to move an item from cart to inventory (CZ_MOVE_ITEM_FROM_CART_TO_BODY). +/// 0127 <index>.W <amount>.L +void clif_parse_GetItemFromCart(int fd,struct map_session_data *sd) +{ + if (!pc_iscarton(sd)) + return; + pc_getitemfromcart(sd,RFIFOW(fd,2)-2,RFIFOL(fd,4)); +} + + +/// Request to remove cart/falcon/peco/dragon (CZ_REQ_CARTOFF). +/// 012a +void clif_parse_RemoveOption(int fd,struct map_session_data *sd) +{ + /** + * Attempts to remove these options when this function is called (will remove all available) + **/ +#ifdef NEW_CARTS + pc_setoption(sd,sd->sc.option&~(OPTION_RIDING|OPTION_FALCON|OPTION_DRAGON|OPTION_MADOGEAR)); + if( sd->sc.data[SC_PUSH_CART] ) + pc_setcart(sd,0); +#else + pc_setoption(sd,sd->sc.option&~(OPTION_CART|OPTION_RIDING|OPTION_FALCON|OPTION_DRAGON|OPTION_MADOGEAR)); +#endif +} + + +/// Request to change cart's visual look (CZ_REQ_CHANGECART). +/// 01af <num>.W +void clif_parse_ChangeCart(int fd,struct map_session_data *sd) +{// TODO: State tracking? + int type; + + if( sd && pc_checkskill(sd, MC_CHANGECART) < 1 ) + return; + + type = (int)RFIFOW(fd,2); +#ifdef NEW_CARTS + if( (type == 9 && sd->status.base_level > 131) || + (type == 8 && sd->status.base_level > 121) || + (type == 7 && sd->status.base_level > 111) || + (type == 6 && sd->status.base_level > 101) || + (type == 5 && sd->status.base_level > 90) || + (type == 4 && sd->status.base_level > 80) || + (type == 3 && sd->status.base_level > 65) || + (type == 2 && sd->status.base_level > 40) || + (type == 1)) +#else + if( (type == 5 && sd->status.base_level > 90) || + (type == 4 && sd->status.base_level > 80) || + (type == 3 && sd->status.base_level > 65) || + (type == 2 && sd->status.base_level > 40) || + (type == 1)) +#endif + pc_setcart(sd,type); +} + + +/// Request to increase status (CZ_STATUS_CHANGE). +/// 00bb <status id>.W <amount>.B +/// status id: +/// SP_STR ~ SP_LUK +/// amount: +/// client sends always 1 for this, even when using /str+ and +/// the like +void clif_parse_StatusUp(int fd,struct map_session_data *sd) +{ + pc_statusup(sd,RFIFOW(fd,2)); +} + + +/// Request to increase level of a skill (CZ_UPGRADE_SKILLLEVEL). +/// 0112 <skill id>.W +void clif_parse_SkillUp(int fd,struct map_session_data *sd) +{ + pc_skillup(sd,RFIFOW(fd,2)); +} + +static void clif_parse_UseSkillToId_homun(struct homun_data *hd, struct map_session_data *sd, unsigned int tick, uint16 skill_id, uint16 skill_lv, int target_id) +{ + int lv; + + if( !hd ) + return; + if( skillnotok_hom(skill_id, hd) ) + return; + if( hd->bl.id != target_id && skill_get_inf(skill_id)&INF_SELF_SKILL ) + target_id = hd->bl.id; + if( hd->ud.skilltimer != INVALID_TIMER ) + { + if( skill_id != SA_CASTCANCEL && skill_id != SO_SPELLFIST ) return; + } + else if( DIFF_TICK(tick, hd->ud.canact_tick) < 0 ) + return; + + lv = merc_hom_checkskill(hd, skill_id); + if( skill_lv > lv ) + skill_lv = lv; + if( skill_lv ) + unit_skilluse_id(&hd->bl, target_id, skill_id, skill_lv); +} + +static void clif_parse_UseSkillToPos_homun(struct homun_data *hd, struct map_session_data *sd, unsigned int tick, uint16 skill_id, uint16 skill_lv, short x, short y, int skillmoreinfo) +{ + int lv; + if( !hd ) + return; + if( skillnotok_hom(skill_id, hd) ) + return; + if( hd->ud.skilltimer != INVALID_TIMER ) { + if( skill_id != SA_CASTCANCEL && skill_id != SO_SPELLFIST ) return; + } else if( DIFF_TICK(tick, hd->ud.canact_tick) < 0 ) + return; + + if( hd->sc.data[SC_BASILICA] ) + return; + lv = merc_hom_checkskill(hd, skill_id); + if( skill_lv > lv ) + skill_lv = lv; + if( skill_lv ) + unit_skilluse_pos(&hd->bl, x, y, skill_id, skill_lv); +} + +static void clif_parse_UseSkillToId_mercenary(struct mercenary_data *md, struct map_session_data *sd, unsigned int tick, uint16 skill_id, uint16 skill_lv, int target_id) +{ + int lv; + + if( !md ) + return; + if( skillnotok_mercenary(skill_id, md) ) + return; + if( md->bl.id != target_id && skill_get_inf(skill_id)&INF_SELF_SKILL ) + target_id = md->bl.id; + if( md->ud.skilltimer != INVALID_TIMER ) + { + if( skill_id != SA_CASTCANCEL && skill_id != SO_SPELLFIST ) return; + } + else if( DIFF_TICK(tick, md->ud.canact_tick) < 0 ) + return; + + lv = mercenary_checkskill(md, skill_id); + if( skill_lv > lv ) + skill_lv = lv; + if( skill_lv ) + unit_skilluse_id(&md->bl, target_id, skill_id, skill_lv); +} + +static void clif_parse_UseSkillToPos_mercenary(struct mercenary_data *md, struct map_session_data *sd, unsigned int tick, uint16 skill_id, uint16 skill_lv, short x, short y, int skillmoreinfo) +{ + int lv; + if( !md ) + return; + if( skillnotok_mercenary(skill_id, md) ) + return; + if( md->ud.skilltimer != INVALID_TIMER ) + return; + if( DIFF_TICK(tick, md->ud.canact_tick) < 0 ) + { + clif_skill_fail(md->master, skill_id, USESKILL_FAIL_SKILLINTERVAL, 0); + return; + } + + if( md->sc.data[SC_BASILICA] ) + return; + lv = mercenary_checkskill(md, skill_id); + if( skill_lv > lv ) + skill_lv = lv; + if( skill_lv ) + unit_skilluse_pos(&md->bl, x, y, skill_id, skill_lv); +} + + +/// Request to use a targeted skill. +/// 0113 <skill lv>.W <skill id>.W <target id>.L (CZ_USE_SKILL) +/// 0438 <skill lv>.W <skill id>.W <target id>.L (CZ_USE_SKILL2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_UseSkillToId(int fd, struct map_session_data *sd) +{ + uint16 skill_id, skill_lv; + int tmp, target_id; + unsigned int tick = gettick(); + + skill_lv = RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + skill_id = RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]); + target_id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[2]); + + if( skill_lv < 1 ) skill_lv = 1; //No clue, I have seen the client do this with guild skills :/ [Skotlex] + + tmp = skill_get_inf(skill_id); + if (tmp&INF_GROUND_SKILL || !tmp) + return; //Using a ground/passive skill on a target? WRONG. + + if( skill_id >= HM_SKILLBASE && skill_id < HM_SKILLBASE + MAX_HOMUNSKILL ) + { + clif_parse_UseSkillToId_homun(sd->hd, sd, tick, skill_id, skill_lv, target_id); + return; + } + + if( skill_id >= MC_SKILLBASE && skill_id < MC_SKILLBASE + MAX_MERCSKILL ) + { + clif_parse_UseSkillToId_mercenary(sd->md, sd, tick, skill_id, skill_lv, target_id); + return; + } + + // Whether skill fails or not is irrelevant, the char ain't idle. [Skotlex] + sd->idletime = last_tick; + + if( pc_cant_act(sd) && skill_id != RK_REFRESH && !(skill_id == SR_GENTLETOUCH_CURE && (sd->sc.opt1 == OPT1_STONE || sd->sc.opt1 == OPT1_FREEZE || sd->sc.opt1 == OPT1_STUN)) ) + return; + if( pc_issit(sd) ) + return; + + if( skillnotok(skill_id, sd) ) + return; + + if( sd->bl.id != target_id && tmp&INF_SELF_SKILL ) + target_id = sd->bl.id; // never trust the client + + if( target_id < 0 && -target_id == sd->bl.id ) // for disguises [Valaris] + target_id = sd->bl.id; + + if( sd->ud.skilltimer != INVALID_TIMER ) + { + if( skill_id != SA_CASTCANCEL && skill_id != SO_SPELLFIST ) + return; + } + else if( DIFF_TICK(tick, sd->ud.canact_tick) < 0 ) + { + if( sd->skillitem != skill_id ) + { + clif_skill_fail(sd, skill_id, USESKILL_FAIL_SKILLINTERVAL, 0); + return; + } + } + + if( sd->sc.option&(OPTION_WEDDING|OPTION_XMAS|OPTION_SUMMER) ) + return; + + if( sd->sc.data[SC_BASILICA] && (skill_id != HP_BASILICA || sd->sc.data[SC_BASILICA]->val4 != sd->bl.id) ) + return; // On basilica only caster can use Basilica again to stop it. + + if( sd->menuskill_id ) { + if( sd->menuskill_id == SA_TAMINGMONSTER ) { + clif_menuskill_clear(sd); //Cancel pet capture. + } else if( sd->menuskill_id != SA_AUTOSPELL ) + return; //Can't use skills while a menu is open. + } + if( sd->skillitem == skill_id ) { + if( skill_lv != sd->skillitemlv ) + skill_lv = sd->skillitemlv; + if( !(tmp&INF_SELF_SKILL) ) + pc_delinvincibletimer(sd); // Target skills thru items cancel invincibility. [Inkfish] + unit_skilluse_id(&sd->bl, target_id, skill_id, skill_lv); + return; + } + + sd->skillitem = sd->skillitemlv = 0; + + if( skill_id >= GD_SKILLBASE ) { + if( sd->state.gmaster_flag ) + skill_lv = guild_checkskill(sd->state.gmaster_flag, skill_id); + else + skill_lv = 0; + } else { + tmp = pc_checkskill(sd, skill_id); + if( skill_lv > tmp ) + skill_lv = tmp; + } + + pc_delinvincibletimer(sd); + + if( skill_lv ) + unit_skilluse_id(&sd->bl, target_id, skill_id, skill_lv); +} + +/*========================================== + * Client tells server he'd like to use AoE skill id 'skill_id' of level 'skill_lv' on 'x','y' location + *------------------------------------------*/ +static void clif_parse_UseSkillToPosSub(int fd, struct map_session_data *sd, uint16 skill_lv, uint16 skill_id, short x, short y, int skillmoreinfo) +{ + unsigned int tick = gettick(); + + if( !(skill_get_inf(skill_id)&INF_GROUND_SKILL) ) + return; //Using a target skill on the ground? WRONG. + + if( skill_id >= HM_SKILLBASE && skill_id < HM_SKILLBASE + MAX_HOMUNSKILL ) { + clif_parse_UseSkillToPos_homun(sd->hd, sd, tick, skill_id, skill_lv, x, y, skillmoreinfo); + return; + } + + if( skill_id >= MC_SKILLBASE && skill_id < MC_SKILLBASE + MAX_MERCSKILL ) + { + clif_parse_UseSkillToPos_mercenary(sd->md, sd, tick, skill_id, skill_lv, x, y, skillmoreinfo); + return; + } + + //Whether skill fails or not is irrelevant, the char ain't idle. [Skotlex] + sd->idletime = last_tick; + + if( skillnotok(skill_id, sd) ) + return; + if( skillmoreinfo != -1 ) + { + if( pc_issit(sd) ) + { + clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); + return; + } + //You can't use Graffiti/TalkieBox AND have a vending open, so this is safe. + safestrncpy(sd->message, (char*)RFIFOP(fd,skillmoreinfo), MESSAGE_SIZE); + } + + if( sd->ud.skilltimer != INVALID_TIMER ) + return; + + if( DIFF_TICK(tick, sd->ud.canact_tick) < 0 ) { + if( sd->skillitem != skill_id ) { + clif_skill_fail(sd, skill_id, USESKILL_FAIL_SKILLINTERVAL, 0); + return; + } + } + + if( sd->sc.option&(OPTION_WEDDING|OPTION_XMAS|OPTION_SUMMER) ) + return; + + if( sd->sc.data[SC_BASILICA] && (skill_id != HP_BASILICA || sd->sc.data[SC_BASILICA]->val4 != sd->bl.id) ) + return; // On basilica only caster can use Basilica again to stop it. + + if( sd->menuskill_id ) { + if( sd->menuskill_id == SA_TAMINGMONSTER ) { + clif_menuskill_clear(sd); //Cancel pet capture. + } else if( sd->menuskill_id != SA_AUTOSPELL ) + return; //Can't use skills while a menu is open. + } + + pc_delinvincibletimer(sd); + + if( sd->skillitem == skill_id ) { + if( skill_lv != sd->skillitemlv ) + skill_lv = sd->skillitemlv; + unit_skilluse_pos(&sd->bl, x, y, skill_id, skill_lv); + } else { + int lv; + sd->skillitem = sd->skillitemlv = 0; + if( (lv = pc_checkskill(sd, skill_id)) > 0 ) { + if( skill_lv > lv ) + skill_lv = lv; + unit_skilluse_pos(&sd->bl, x, y, skill_id,skill_lv); + } + } +} + + +/// Request to use a ground skill. +/// 0116 <skill lv>.W <skill id>.W <x>.W <y>.W (CZ_USE_SKILL_TOGROUND) +/// 0366 <skill lv>.W <skill id>.W <x>.W <y>.W (CZ_USE_SKILL_TOGROUND2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_UseSkillToPos(int fd, struct map_session_data *sd) +{ + if (pc_cant_act(sd)) + return; + if (pc_issit(sd)) + return; + + clif_parse_UseSkillToPosSub(fd, sd, + RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]), //skill lv + RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]), //skill num + RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[2]), //pos x + RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[3]), //pos y + -1 //Skill more info. + ); +} + + +/// Request to use a ground skill with text. +/// 0190 <skill lv>.W <skill id>.W <x>.W <y>.W <contents>.80B (CZ_USE_SKILL_TOGROUND_WITHTALKBOX) +/// 0367 <skill lv>.W <skill id>.W <x>.W <y>.W <contents>.80B (CZ_USE_SKILL_TOGROUND_WITHTALKBOX2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_UseSkillToPosMoreInfo(int fd, struct map_session_data *sd) +{ + if (pc_cant_act(sd)) + return; + if (pc_issit(sd)) + return; + + clif_parse_UseSkillToPosSub(fd, sd, + RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]), //Skill lv + RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]), //Skill num + RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[2]), //pos x + RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[3]), //pos y + packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[4] //skill more info + ); +} + + +/// Answer to map selection dialog (CZ_SELECT_WARPPOINT). +/// 011b <skill id>.W <map name>.16B +void clif_parse_UseSkillMap(int fd, struct map_session_data* sd) +{ + uint16 skill_id = RFIFOW(fd,2); + char map_name[MAP_NAME_LENGTH]; + mapindex_getmapname((char*)RFIFOP(fd,4), map_name); + + if(skill_id != sd->menuskill_id) + return; + + if( pc_cant_act(sd) ) { + clif_menuskill_clear(sd); + return; + } + + pc_delinvincibletimer(sd); + skill_castend_map(sd,skill_id,map_name); +} + + +/// Request to set a memo on current map (CZ_REMEMBER_WARPPOINT). +/// 011d +void clif_parse_RequestMemo(int fd,struct map_session_data *sd) +{ + if (!pc_isdead(sd)) + pc_memo(sd,-1); +} + + +/// Answer to pharmacy item selection dialog (CZ_REQMAKINGITEM). +/// 018e <name id>.W { <material id>.W }*3 +void clif_parse_ProduceMix(int fd,struct map_session_data *sd) +{ + switch( sd->menuskill_id ) { + case -1: + case AM_PHARMACY: + case RK_RUNEMASTERY: + case GC_RESEARCHNEWPOISON: + break; + default: + return; + } + if (pc_istrading(sd)) { + //Make it fail to avoid shop exploits where you sell something different than you see. + clif_skill_fail(sd,sd->ud.skill_id,USESKILL_FAIL_LEVEL,0); + clif_menuskill_clear(sd); + return; + } + if( skill_can_produce_mix(sd,RFIFOW(fd,2),sd->menuskill_val, 1) ) + skill_produce_mix(sd,0,RFIFOW(fd,2),RFIFOW(fd,4),RFIFOW(fd,6),RFIFOW(fd,8), 1); + clif_menuskill_clear(sd); +} + + +/// Answer to mixing item selection dialog (CZ_REQ_MAKINGITEM). +/// 025b <mk type>.W <name id>.W +/// mk type: +/// 1 = cooking +/// 2 = arrow +/// 3 = elemental +/// 4 = GN_MIX_COOKING +/// 5 = GN_MAKEBOMB +/// 6 = GN_S_PHARMACY +void clif_parse_Cooking(int fd,struct map_session_data *sd) { + int type = RFIFOW(fd,2); + int nameid = RFIFOW(fd,4); + int amount = sd->menuskill_val2?sd->menuskill_val2:1; + if( type == 6 && sd->menuskill_id != GN_MIX_COOKING && sd->menuskill_id != GN_S_PHARMACY ) + return; + + if (pc_istrading(sd)) { + //Make it fail to avoid shop exploits where you sell something different than you see. + clif_skill_fail(sd,sd->ud.skill_id,USESKILL_FAIL_LEVEL,0); + clif_menuskill_clear(sd); + return; + } + if( skill_can_produce_mix(sd,nameid,sd->menuskill_val, amount) ) + skill_produce_mix(sd,0,nameid,0,0,0,amount); + clif_menuskill_clear(sd); +} + + +/// Answer to repair weapon item selection dialog (CZ_REQ_ITEMREPAIR). +/// 01fd <index>.W <name id>.W <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W +void clif_parse_RepairItem(int fd, struct map_session_data *sd) +{ + if (sd->menuskill_id != BS_REPAIRWEAPON) + return; + if (pc_istrading(sd)) { + //Make it fail to avoid shop exploits where you sell something different than you see. + clif_skill_fail(sd,sd->ud.skill_id,USESKILL_FAIL_LEVEL,0); + clif_menuskill_clear(sd); + return; + } + skill_repairweapon(sd,RFIFOW(fd,2)); + clif_menuskill_clear(sd); +} + + +/// Answer to refine weapon item selection dialog (CZ_REQ_WEAPONREFINE). +/// 0222 <index>.L +void clif_parse_WeaponRefine(int fd, struct map_session_data *sd) +{ + int idx; + + if (sd->menuskill_id != WS_WEAPONREFINE) //Packet exploit? + return; + if (pc_istrading(sd)) { + //Make it fail to avoid shop exploits where you sell something different than you see. + clif_skill_fail(sd,sd->ud.skill_id,USESKILL_FAIL_LEVEL,0); + clif_menuskill_clear(sd); + return; + } + idx = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + skill_weaponrefine(sd, idx-2); + clif_menuskill_clear(sd); +} + + +/// Answer to script menu dialog (CZ_CHOOSE_MENU). +/// 00b8 <npc id>.L <choice>.B +/// choice: +/// 1~254 = menu item +/// 255 = cancel +/// NOTE: If there were more than 254 items in the list, choice +/// overflows to choice%256. +void clif_parse_NpcSelectMenu(int fd,struct map_session_data *sd) +{ + int npc_id = RFIFOL(fd,2); + uint8 select = RFIFOB(fd,6); + + if( (select > sd->npc_menu && select != 0xff) || select == 0 ) + { + TBL_NPC* nd = map_id2nd(npc_id); + ShowWarning("Invalid menu selection on npc %d:'%s' - got %d, valid range is [%d..%d] (player AID:%d, CID:%d, name:'%s')!\n", npc_id, (nd)?nd->name:"invalid npc id", select, 1, sd->npc_menu, sd->bl.id, sd->status.char_id, sd->status.name); + clif_GM_kick(NULL,sd); + return; + } + + sd->npc_menu = select; + npc_scriptcont(sd,npc_id); +} + + +/// NPC dialog 'next' click (CZ_REQ_NEXT_SCRIPT). +/// 00b9 <npc id>.L +void clif_parse_NpcNextClicked(int fd,struct map_session_data *sd) +{ + npc_scriptcont(sd,RFIFOL(fd,2)); +} + + +/// NPC numeric input dialog value (CZ_INPUT_EDITDLG). +/// 0143 <npc id>.L <value>.L +void clif_parse_NpcAmountInput(int fd,struct map_session_data *sd) +{ + int npcid = RFIFOL(fd,2); + int amount = (int)RFIFOL(fd,6); + + sd->npc_amount = amount; + npc_scriptcont(sd, npcid); +} + + +/// NPC text input dialog value (CZ_INPUT_EDITDLGSTR). +/// 01d5 <packet len>.W <npc id>.L <string>.?B +void clif_parse_NpcStringInput(int fd, struct map_session_data* sd) +{ + int message_len = RFIFOW(fd,2)-8; + int npcid = RFIFOL(fd,4); + const char* message = (char*)RFIFOP(fd,8); + + if( message_len <= 0 ) + return; // invalid input + + safestrncpy(sd->npc_str, message, min(message_len,CHATBOX_SIZE)); + npc_scriptcont(sd, npcid); +} + + +/// NPC dialog 'close' click (CZ_CLOSE_DIALOG). +/// 0146 <npc id>.L +void clif_parse_NpcCloseClicked(int fd,struct map_session_data *sd) +{ + if (!sd->npc_id) //Avoid parsing anything when the script was done with. [Skotlex] + return; + npc_scriptcont(sd,RFIFOL(fd,2)); +} + + +/// Answer to identify item selection dialog (CZ_REQ_ITEMIDENTIFY). +/// 0178 <index>.W +/// index: +/// -1 = cancel +void clif_parse_ItemIdentify(int fd,struct map_session_data *sd) +{ + short idx = RFIFOW(fd,2); + + if (sd->menuskill_id != MC_IDENTIFY) + return; + if( idx == -1 ) {// cancel pressed + clif_menuskill_clear(sd); + return; + } + skill_identify(sd,idx-2); + clif_menuskill_clear(sd); +} + + +/// Answer to arrow crafting item selection dialog (CZ_REQ_MAKINGARROW). +/// 01ae <name id>.W +void clif_parse_SelectArrow(int fd,struct map_session_data *sd) +{ + if (pc_istrading(sd)) { + //Make it fail to avoid shop exploits where you sell something different than you see. + clif_skill_fail(sd,sd->ud.skill_id,USESKILL_FAIL_LEVEL,0); + clif_menuskill_clear(sd); + return; + } + switch( sd->menuskill_id ) { + case AC_MAKINGARROW: + skill_arrow_create(sd,RFIFOW(fd,2)); + break; + case SA_CREATECON: + skill_produce_mix(sd,SA_CREATECON,RFIFOW(fd,2),0,0,0, 1); + break; + case WL_READING_SB: + skill_spellbook(sd,RFIFOW(fd,2)); + break; + case GC_POISONINGWEAPON: + skill_poisoningweapon(sd,RFIFOW(fd,2)); + break; + case NC_MAGICDECOY: + skill_magicdecoy(sd,RFIFOW(fd,2)); + break; + } + + clif_menuskill_clear(sd); +} + + +/// Answer to SA_AUTOSPELL skill selection dialog (CZ_SELECTAUTOSPELL). +/// 01ce <skill id>.L +void clif_parse_AutoSpell(int fd,struct map_session_data *sd) +{ + if (sd->menuskill_id != SA_AUTOSPELL) + return; + skill_autospell(sd,RFIFOL(fd,2)); + clif_menuskill_clear(sd); +} + + +/// Request to display item carding/composition list (CZ_REQ_ITEMCOMPOSITION_LIST). +/// 017a <card index>.W +void clif_parse_UseCard(int fd,struct map_session_data *sd) +{ + if (sd->state.trading != 0) + return; + clif_use_card(sd,RFIFOW(fd,2)-2); +} + + +/// Answer to carding/composing item selection dialog (CZ_REQ_ITEMCOMPOSITION). +/// 017c <card index>.W <equip index>.W +void clif_parse_InsertCard(int fd,struct map_session_data *sd) +{ + if (sd->state.trading != 0) + return; + pc_insert_card(sd,RFIFOW(fd,2)-2,RFIFOW(fd,4)-2); +} + + +/// Request of character's name by char ID. +/// 0193 <char id>.L (CZ_REQNAME_BYGID) +/// 0369 <char id>.L (CZ_REQNAME_BYGID2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_SolveCharName(int fd, struct map_session_data *sd) +{ + int charid; + + charid = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + map_reqnickdb(sd, charid); +} + + +/// /resetskill /resetstate (CZ_RESET). +/// Request to reset stats or skills. +/// 0197 <type>.W +/// type: +/// 0 = state +/// 1 = skill +void clif_parse_ResetChar(int fd, struct map_session_data *sd) { + char cmd[15]; + + if( RFIFOW(fd,2) ) + sprintf(cmd,"%cresetskill",atcommand_symbol); + else + sprintf(cmd,"%cresetstat",atcommand_symbol); + + is_atcommand(fd, sd, cmd, 1); +} + + +/// /lb /nlb (CZ_LOCALBROADCAST). +/// Request to broadcast a message on current map. +/// 019c <packet len>.W <text>.?B +void clif_parse_LocalBroadcast(int fd, struct map_session_data* sd) +{ + char command[CHAT_SIZE_MAX+16]; + char* msg = (char*)RFIFOP(fd,4); + unsigned int len = RFIFOW(fd,2)-4; + + // as the length varies depending on the command used, just block unreasonably long strings + mes_len_check(msg, len, CHAT_SIZE_MAX); + + sprintf(command, "%clkami %s", atcommand_symbol, msg); + is_atcommand(fd, sd, command, 1); +} + + +/// Request to move an item from inventory to storage. +/// 00f3 <index>.W <amount>.L (CZ_MOVE_ITEM_FROM_BODY_TO_STORE) +/// 0364 <index>.W <amount>.L (CZ_MOVE_ITEM_FROM_BODY_TO_STORE2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_MoveToKafra(int fd, struct map_session_data *sd) +{ + int item_index, item_amount; + + if (pc_istrading(sd)) + return; + + item_index = RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0])-2; + item_amount = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]); + if (item_index < 0 || item_index >= MAX_INVENTORY || item_amount < 1) + return; + + if (sd->state.storage_flag == 1) + storage_storageadd(sd, item_index, item_amount); + else + if (sd->state.storage_flag == 2) + storage_guild_storageadd(sd, item_index, item_amount); +} + + +/// Request to move an item from storage to inventory. +/// 00f5 <index>.W <amount>.L (CZ_MOVE_ITEM_FROM_STORE_TO_BODY) +/// 0365 <index>.W <amount>.L (CZ_MOVE_ITEM_FROM_STORE_TO_BODY2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_MoveFromKafra(int fd,struct map_session_data *sd) +{ + int item_index, item_amount; + + item_index = RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0])-1; + item_amount = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1]); + + if (sd->state.storage_flag == 1) + storage_storageget(sd, item_index, item_amount); + else + if(sd->state.storage_flag == 2) + storage_guild_storageget(sd, item_index, item_amount); +} + + +/// Request to move an item from cart to storage (CZ_MOVE_ITEM_FROM_CART_TO_STORE). +/// 0129 <index>.W <amount>.L +void clif_parse_MoveToKafraFromCart(int fd, struct map_session_data *sd) +{ + if( sd->state.vending ) + return; + if (!pc_iscarton(sd)) + return; + + if (sd->state.storage_flag == 1) + storage_storageaddfromcart(sd, RFIFOW(fd,2) - 2, RFIFOL(fd,4)); + else + if (sd->state.storage_flag == 2) + storage_guild_storageaddfromcart(sd, RFIFOW(fd,2) - 2, RFIFOL(fd,4)); +} + + +/// Request to move an item from storage to cart (CZ_MOVE_ITEM_FROM_STORE_TO_CART). +/// 0128 <index>.W <amount>.L +void clif_parse_MoveFromKafraToCart(int fd, struct map_session_data *sd) +{ + if( sd->state.vending ) + return; + if (!pc_iscarton(sd)) + return; + + if (sd->state.storage_flag == 1) + storage_storagegettocart(sd, RFIFOW(fd,2)-1, RFIFOL(fd,4)); + else + if (sd->state.storage_flag == 2) + storage_guild_storagegettocart(sd, RFIFOW(fd,2)-1, RFIFOL(fd,4)); +} + + +/// Request to close storage (CZ_CLOSE_STORE). +/// 00f7 +void clif_parse_CloseKafra(int fd, struct map_session_data *sd) +{ + if( sd->state.storage_flag == 1 ) + storage_storageclose(sd); + else + if( sd->state.storage_flag == 2 ) + storage_guild_storageclose(sd); +} + + +/// Displays kafra storage password dialog (ZC_REQ_STORE_PASSWORD). +/// 023a <info>.W +/// info: +/// 0 = password has not been set yet +/// 1 = storage is password-protected +/// 8 = too many wrong passwords +/// ? = ignored +/// NOTE: This packet is only available on certain non-kRO clients. +void clif_storagepassword(struct map_session_data* sd, short info) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x23a)); + WFIFOW(fd,0) = 0x23a; + WFIFOW(fd,2) = info; + WFIFOSET(fd,packet_len(0x23a)); +} + + +/// Answer to the kafra storage password dialog (CZ_ACK_STORE_PASSWORD). +/// 023b <type>.W <password>.16B <new password>.16B +/// type: +/// 2 = change password +/// 3 = check password +/// NOTE: This packet is only available on certain non-kRO clients. +void clif_parse_StoragePassword(int fd, struct map_session_data *sd) +{ + //TODO +} + + +/// Result of kafra storage password validation (ZC_RESULT_STORE_PASSWORD). +/// 023c <result>.W <error count>.W +/// result: +/// 4 = password change success +/// 5 = password change failure +/// 6 = password check success +/// 7 = password check failure +/// 8 = too many wrong passwords +/// ? = ignored +/// NOTE: This packet is only available on certain non-kRO clients. +void clif_storagepassword_result(struct map_session_data* sd, short result, short error_count) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x23c)); + WFIFOW(fd,0) = 0x23c; + WFIFOW(fd,2) = result; + WFIFOW(fd,4) = error_count; + WFIFOSET(fd,packet_len(0x23c)); +} + + +/// Party creation request +/// 00f9 <party name>.24B (CZ_MAKE_GROUP) +/// 01e8 <party name>.24B <item pickup rule>.B <item share rule>.B (CZ_MAKE_GROUP2) +void clif_parse_CreateParty(int fd, struct map_session_data *sd) +{ + char* name = (char*)RFIFOP(fd,2); + name[NAME_LENGTH-1] = '\0'; + + if( map[sd->bl.m].flag.partylock ) + {// Party locked. + clif_displaymessage(fd, msg_txt(227)); + return; + } + if( battle_config.basic_skill_check && pc_checkskill(sd,NV_BASIC) < 7 ) + { + clif_skill_fail(sd,1,USESKILL_FAIL_LEVEL,4); + return; + } + + party_create(sd,name,0,0); +} + +void clif_parse_CreateParty2(int fd, struct map_session_data *sd) +{ + char* name = (char*)RFIFOP(fd,2); + int item1 = RFIFOB(fd,26); + int item2 = RFIFOB(fd,27); + name[NAME_LENGTH-1] = '\0'; + + if( map[sd->bl.m].flag.partylock ) + {// Party locked. + clif_displaymessage(fd, msg_txt(227)); + return; + } + if( battle_config.basic_skill_check && pc_checkskill(sd,NV_BASIC) < 7 ) + { + clif_skill_fail(sd,1,USESKILL_FAIL_LEVEL,4); + return; + } + + party_create(sd,name,item1,item2); +} + + +/// Party invitation request +/// 00fc <account id>.L (CZ_REQ_JOIN_GROUP) +/// 02c4 <char name>.24B (CZ_PARTY_JOIN_REQ) +void clif_parse_PartyInvite(int fd, struct map_session_data *sd) +{ + struct map_session_data *t_sd; + + if(map[sd->bl.m].flag.partylock) + {// Party locked. + clif_displaymessage(fd, msg_txt(227)); + return; + } + + t_sd = map_id2sd(RFIFOL(fd,2)); + + if(t_sd && t_sd->state.noask) + {// @noask [LuzZza] + clif_noask_sub(sd, t_sd, 1); + return; + } + + party_invite(sd, t_sd); +} + +void clif_parse_PartyInvite2(int fd, struct map_session_data *sd) +{ + struct map_session_data *t_sd; + char *name = (char*)RFIFOP(fd,2); + name[NAME_LENGTH-1] = '\0'; + + if(map[sd->bl.m].flag.partylock) + {// Party locked. + clif_displaymessage(fd, msg_txt(227)); + return; + } + + t_sd = map_nick2sd(name); + + if(t_sd && t_sd->state.noask) + {// @noask [LuzZza] + clif_noask_sub(sd, t_sd, 1); + return; + } + + party_invite(sd, t_sd); +} + + +/// Party invitation reply +/// 00ff <party id>.L <flag>.L (CZ_JOIN_GROUP) +/// 02c7 <party id>.L <flag>.B (CZ_PARTY_JOIN_REQ_ACK) +/// flag: +/// 0 = reject +/// 1 = accept +void clif_parse_ReplyPartyInvite(int fd,struct map_session_data *sd) +{ + party_reply_invite(sd,RFIFOL(fd,2),RFIFOL(fd,6)); +} + +void clif_parse_ReplyPartyInvite2(int fd,struct map_session_data *sd) +{ + party_reply_invite(sd,RFIFOL(fd,2),RFIFOB(fd,6)); +} + + +/// Request to leave party (CZ_REQ_LEAVE_GROUP). +/// 0100 +void clif_parse_LeaveParty(int fd, struct map_session_data *sd) +{ + if(map[sd->bl.m].flag.partylock) + { //Guild locked. + clif_displaymessage(fd, msg_txt(227)); + return; + } + party_leave(sd); +} + + +/// Request to expel a party member (CZ_REQ_EXPEL_GROUP_MEMBER). +/// 0103 <account id>.L <char name>.24B +void clif_parse_RemovePartyMember(int fd, struct map_session_data *sd) +{ + if(map[sd->bl.m].flag.partylock) + { //Guild locked. + clif_displaymessage(fd, msg_txt(227)); + return; + } + party_removemember(sd,RFIFOL(fd,2),(char*)RFIFOP(fd,6)); +} + + +/// Request to change party options. +/// 0102 <exp share rule>.L (CZ_CHANGE_GROUPEXPOPTION) +/// 07d7 <exp share rule>.L <item pickup rule>.B <item share rule>.B (CZ_GROUPINFO_CHANGE_V2) +void clif_parse_PartyChangeOption(int fd, struct map_session_data *sd) +{ + struct party_data *p; + int i; + + if( !sd->status.party_id ) + return; + + p = party_search(sd->status.party_id); + if( p == NULL ) + return; + + ARR_FIND( 0, MAX_PARTY, i, p->data[i].sd == sd ); + if( i == MAX_PARTY ) + return; //Shouldn't happen + + if( !p->party.member[i].leader ) + return; + +#if PACKETVER < 20090603 + //Client can't change the item-field + party_changeoption(sd, RFIFOL(fd,2), p->party.item); +#else + party_changeoption(sd, RFIFOL(fd,2), ((RFIFOB(fd,6)?1:0)|(RFIFOB(fd,7)?2:0))); +#endif +} + + +/// Validates and processes party messages (CZ_REQUEST_CHAT_PARTY). +/// 0108 <packet len>.W <text>.?B (<name> : <message>) 00 +void clif_parse_PartyMessage(int fd, struct map_session_data* sd) +{ + const char* text = (char*)RFIFOP(fd,4); + int textlen = RFIFOW(fd,2) - 4; + + char *name, *message; + int namelen, messagelen; + + // validate packet and retrieve name and message + if( !clif_process_message(sd, 0, &name, &namelen, &message, &messagelen) ) + return; + + if( is_atcommand(fd, sd, message, 1) ) + return; + + if( sd->sc.data[SC_BERSERK] || sd->sc.data[SC__BLOODYLUST] || (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOCHAT) ) + return; + + if( battle_config.min_chat_delay ) + { //[Skotlex] + if (DIFF_TICK(sd->cantalk_tick, gettick()) > 0) + return; + sd->cantalk_tick = gettick() + battle_config.min_chat_delay; + } + + party_send_message(sd, text, textlen); +} + + +/// Changes Party Leader (CZ_CHANGE_GROUP_MASTER). +/// 07da <account id>.L +void clif_parse_PartyChangeLeader(int fd, struct map_session_data* sd) +{ + party_changeleader(sd, map_id2sd(RFIFOL(fd,2))); +} + + +/// Party Booking in KRO [Spiria] +/// + +/// Request to register a party booking advertisment (CZ_PARTY_BOOKING_REQ_REGISTER). +/// 0802 <level>.W <map id>.W { <job>.W }*6 +void clif_parse_PartyBookingRegisterReq(int fd, struct map_session_data* sd) +{ + short level = RFIFOW(fd,2); + short mapid = RFIFOW(fd,4); + short job[PARTY_BOOKING_JOBS]; + int i; + + for(i=0; i<PARTY_BOOKING_JOBS; i++) + job[i] = RFIFOB(fd,6+i*2); + + party_booking_register(sd, level, mapid, job); +} + + +/// Result of request to register a party booking advertisment (ZC_PARTY_BOOKING_ACK_REGISTER). +/// 0803 <result>.W +/// result: +/// 0 = success +/// 1 = failure +/// 2 = already registered +void clif_PartyBookingRegisterAck(struct map_session_data *sd, int flag) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x803)); + WFIFOW(fd,0) = 0x803; + WFIFOW(fd,2) = flag; + WFIFOSET(fd,packet_len(0x803)); +} + + +/// Request to search for party booking advertisments (CZ_PARTY_BOOKING_REQ_SEARCH). +/// 0804 <level>.W <map id>.W <job>.W <last index>.L <result count>.W +void clif_parse_PartyBookingSearchReq(int fd, struct map_session_data* sd) +{ + short level = RFIFOW(fd,2); + short mapid = RFIFOW(fd,4); + short job = RFIFOW(fd,6); + unsigned long lastindex = RFIFOL(fd,8); + short resultcount = RFIFOW(fd,12); + + party_booking_search(sd, level, mapid, job, lastindex, resultcount); +} + + +/// Party booking search results (ZC_PARTY_BOOKING_ACK_SEARCH). +/// 0805 <packet len>.W <more results>.B { <index>.L <char name>.24B <expire time>.L <level>.W <map id>.W { <job>.W }*6 }* +/// more results: +/// 0 = no +/// 1 = yes +void clif_PartyBookingSearchAck(int fd, struct party_booking_ad_info** results, int count, bool more_result) +{ + int i, j; + int size = sizeof(struct party_booking_ad_info); // structure size (48) + struct party_booking_ad_info *pb_ad; + WFIFOHEAD(fd,size*count + 5); + WFIFOW(fd,0) = 0x805; + WFIFOW(fd,2) = size*count + 5; + WFIFOB(fd,4) = more_result; + for(i=0; i<count; i++) + { + pb_ad = results[i]; + WFIFOL(fd,i*size+5) = pb_ad->index; + memcpy(WFIFOP(fd,i*size+9),pb_ad->charname,NAME_LENGTH); + WFIFOL(fd,i*size+33) = pb_ad->starttime; // FIXME: This is expire time + WFIFOW(fd,i*size+37) = pb_ad->p_detail.level; + WFIFOW(fd,i*size+39) = pb_ad->p_detail.mapid; + for(j=0; j<PARTY_BOOKING_JOBS; j++) + WFIFOW(fd,i*size+41+j*2) = pb_ad->p_detail.job[j]; + } + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Request to delete own party booking advertisment (CZ_PARTY_BOOKING_REQ_DELETE). +/// 0806 +void clif_parse_PartyBookingDeleteReq(int fd, struct map_session_data* sd) +{ + if(party_booking_delete(sd)) + clif_PartyBookingDeleteAck(sd, 0); +} + + +/// Result of request to delete own party booking advertisment (ZC_PARTY_BOOKING_ACK_DELETE). +/// 0807 <result>.W +/// result: +/// 0 = success +/// 1 = success (auto-removed expired ad) +/// 2 = failure +/// 3 = nothing registered +void clif_PartyBookingDeleteAck(struct map_session_data* sd, int flag) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x807)); + WFIFOW(fd,0) = 0x807; + WFIFOW(fd,2) = flag; + WFIFOSET(fd,packet_len(0x807)); +} + + +/// Request to update party booking advertisment (CZ_PARTY_BOOKING_REQ_UPDATE). +/// 0808 { <job>.W }*6 +void clif_parse_PartyBookingUpdateReq(int fd, struct map_session_data* sd) +{ + short job[PARTY_BOOKING_JOBS]; + int i; + + for(i=0; i<PARTY_BOOKING_JOBS; i++) + job[i] = RFIFOW(fd,2+i*2); + + party_booking_update(sd, job); +} + + +/// Notification about new party booking advertisment (ZC_PARTY_BOOKING_NOTIFY_INSERT). +/// 0809 <index>.L <char name>.24B <expire time>.L <level>.W <map id>.W { <job>.W }*6 +void clif_PartyBookingInsertNotify(struct map_session_data* sd, struct party_booking_ad_info* pb_ad) +{ + int i; + uint8 buf[38+PARTY_BOOKING_JOBS*2]; + + if(pb_ad == NULL) return; + + WBUFW(buf,0) = 0x809; + WBUFL(buf,2) = pb_ad->index; + memcpy(WBUFP(buf,6),pb_ad->charname,NAME_LENGTH); + WBUFL(buf,30) = pb_ad->starttime; // FIXME: This is expire time + WBUFW(buf,34) = pb_ad->p_detail.level; + WBUFW(buf,36) = pb_ad->p_detail.mapid; + for(i=0; i<PARTY_BOOKING_JOBS; i++) + WBUFW(buf,38+i*2) = pb_ad->p_detail.job[i]; + + clif_send(buf, packet_len(0x809), &sd->bl, ALL_CLIENT); +} + + +/// Notification about updated party booking advertisment (ZC_PARTY_BOOKING_NOTIFY_UPDATE). +/// 080a <index>.L { <job>.W }*6 +void clif_PartyBookingUpdateNotify(struct map_session_data* sd, struct party_booking_ad_info* pb_ad) +{ + int i; + uint8 buf[6+PARTY_BOOKING_JOBS*2]; + + if(pb_ad == NULL) return; + + WBUFW(buf,0) = 0x80a; + WBUFL(buf,2) = pb_ad->index; + for(i=0; i<PARTY_BOOKING_JOBS; i++) + WBUFW(buf,6+i*2) = pb_ad->p_detail.job[i]; + clif_send(buf,packet_len(0x80a),&sd->bl,ALL_CLIENT); // Now UPDATE all client. +} + + +/// Notification about deleted party booking advertisment (ZC_PARTY_BOOKING_NOTIFY_DELETE). +/// 080b <index>.L +void clif_PartyBookingDeleteNotify(struct map_session_data* sd, int index) +{ + uint8 buf[6]; + + WBUFW(buf,0) = 0x80b; + WBUFL(buf,2) = index; + + clif_send(buf, packet_len(0x80b), &sd->bl, ALL_CLIENT); // Now UPDATE all client. +} + + +/// Request to close own vending (CZ_REQ_CLOSESTORE). +/// 012e +void clif_parse_CloseVending(int fd, struct map_session_data* sd) +{ + vending_closevending(sd); +} + + +/// Request to open a vending shop (CZ_REQ_BUY_FROMMC). +/// 0130 <account id>.L +void clif_parse_VendingListReq(int fd, struct map_session_data* sd) +{ + if( sd->npc_id ) + {// using an NPC + return; + } + vending_vendinglistreq(sd,RFIFOL(fd,2)); +} + + +/// Shop item(s) purchase request (CZ_PC_PURCHASE_ITEMLIST_FROMMC). +/// 0134 <packet len>.W <account id>.L { <amount>.W <index>.W }* +void clif_parse_PurchaseReq(int fd, struct map_session_data* sd) +{ + int len = (int)RFIFOW(fd,2) - 8; + int id = (int)RFIFOL(fd,4); + const uint8* data = (uint8*)RFIFOP(fd,8); + + vending_purchasereq(sd, id, sd->vended_id, data, len/4); + + // whether it fails or not, the buy window is closed + sd->vended_id = 0; +} + + +/// Shop item(s) purchase request (CZ_PC_PURCHASE_ITEMLIST_FROMMC2). +/// 0801 <packet len>.W <account id>.L <unique id>.L { <amount>.W <index>.W }* +void clif_parse_PurchaseReq2(int fd, struct map_session_data* sd) +{ + int len = (int)RFIFOW(fd,2) - 12; + int aid = (int)RFIFOL(fd,4); + int uid = (int)RFIFOL(fd,8); + const uint8* data = (uint8*)RFIFOP(fd,12); + + vending_purchasereq(sd, aid, uid, data, len/4); + + // whether it fails or not, the buy window is closed + sd->vended_id = 0; +} + + +/// Confirm or cancel the shop preparation window. +/// 012f <packet len>.W <shop name>.80B { <index>.W <amount>.W <price>.L }* (CZ_REQ_OPENSTORE) +/// 01b2 <packet len>.W <shop name>.80B <result>.B { <index>.W <amount>.W <price>.L }* (CZ_REQ_OPENSTORE2) +/// result: +/// 0 = canceled +/// 1 = open +void clif_parse_OpenVending(int fd, struct map_session_data* sd) +{ + short len = (short)RFIFOW(fd,2) - 85; + const char* message = (char*)RFIFOP(fd,4); + bool flag = (bool)RFIFOB(fd,84); + const uint8* data = (uint8*)RFIFOP(fd,85); + + if( sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOROOM ) + return; + if( map[sd->bl.m].flag.novending ) { + clif_displaymessage (sd->fd, msg_txt(276)); // "You can't open a shop on this map" + return; + } + if( map_getcell(sd->bl.m,sd->bl.x,sd->bl.y,CELL_CHKNOVENDING) ) { + clif_displaymessage (sd->fd, msg_txt(204)); // "You can't open a shop on this cell." + return; + } + + if( vending_checknearnpc(&sd->bl) ) { + char output[150]; + sprintf(output, msg_txt(662), battle_config.min_npc_vending_distance); + clif_displaymessage(sd->fd, output); + clif_skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0); + return; + } + + if( message[0] == '\0' ) // invalid input + return; + + vending_openvending(sd, message, flag, data, len/8); +} + + +/// Guild creation request (CZ_REQ_MAKE_GUILD). +/// 0165 <char id>.L <guild name>.24B +void clif_parse_CreateGuild(int fd,struct map_session_data *sd) +{ + char* name = (char*)RFIFOP(fd,6); + name[NAME_LENGTH-1] = '\0'; + + if(map[sd->bl.m].flag.guildlock) + { //Guild locked. + clif_displaymessage(fd, msg_txt(228)); + return; + } + + guild_create(sd, name); +} + + +/// Request for guild window interface permissions (CZ_REQ_GUILD_MENUINTERFACE). +/// 014d +void clif_parse_GuildCheckMaster(int fd, struct map_session_data *sd) +{ + clif_guild_masterormember(sd); +} + + +/// Request for guild window information (CZ_REQ_GUILD_MENU). +/// 014f <type>.L +/// type: +/// 0 = basic info +/// 1 = member manager +/// 2 = positions +/// 3 = skills +/// 4 = expulsion list +/// 5 = unknown (GM_ALLGUILDLIST) +/// 6 = notice +void clif_parse_GuildRequestInfo(int fd, struct map_session_data *sd) +{ + if( !sd->status.guild_id && !sd->bg_id ) + return; + + switch( RFIFOL(fd,2) ) + { + case 0: // Basic Information Guild, hostile alliance information + clif_guild_basicinfo(sd); + clif_guild_allianceinfo(sd); + break; + case 1: // Members list, list job title + clif_guild_positionnamelist(sd); + clif_guild_memberlist(sd); + break; + case 2: // List job title, title information list + clif_guild_positionnamelist(sd); + clif_guild_positioninfolist(sd); + break; + case 3: // Skill list + clif_guild_skillinfo(sd); + break; + case 4: // Expulsion list + clif_guild_expulsionlist(sd); + break; + default: + ShowError("clif: guild request info: unknown type %d\n", RFIFOL(fd,2)); + break; + } +} + + +/// Request to update guild positions (CZ_REG_CHANGE_GUILD_POSITIONINFO). +/// 0161 <packet len>.W { <position id>.L <mode>.L <ranking>.L <pay rate>.L <name>.24B }* +void clif_parse_GuildChangePositionInfo(int fd, struct map_session_data *sd) +{ + int i; + + if(!sd->state.gmaster_flag) + return; + + for(i = 4; i < RFIFOW(fd,2); i += 40 ){ + guild_change_position(sd->status.guild_id, RFIFOL(fd,i), RFIFOL(fd,i+4), RFIFOL(fd,i+12), (char*)RFIFOP(fd,i+16)); + } +} + + +/// Request to update the position of guild members (CZ_REQ_CHANGE_MEMBERPOS). +/// 0155 <packet len>.W { <account id>.L <char id>.L <position id>.L }* +void clif_parse_GuildChangeMemberPosition(int fd, struct map_session_data *sd) +{ + int i; + + if(!sd->state.gmaster_flag) + return; + + 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)); + } +} + + +/// Request for guild emblem data (CZ_REQ_GUILD_EMBLEM_IMG). +/// 0151 <guild id>.L +void clif_parse_GuildRequestEmblem(int fd,struct map_session_data *sd) +{ + struct guild* g; + int guild_id = RFIFOL(fd,2); + + if( (g = guild_search(guild_id)) != NULL ) + clif_guild_emblem(sd,g); +} + + +/// Validates data of a guild emblem (compressed bitmap) +static bool clif_validate_emblem(const uint8* emblem, unsigned long emblem_len) +{ + bool success; + uint8 buf[1800]; // no well-formed emblem bitmap is larger than 1782 (24 bit) / 1654 (8 bit) bytes + unsigned long buf_len = sizeof(buf); + + success = ( decode_zip(buf, &buf_len, emblem, emblem_len) == 0 && buf_len >= 18 ) // sizeof(BITMAPFILEHEADER) + sizeof(biSize) of the following info header struct + && RBUFW(buf,0) == 0x4d42 // BITMAPFILEHEADER.bfType (signature) + && RBUFL(buf,2) == buf_len // BITMAPFILEHEADER.bfSize (file size) + && RBUFL(buf,10) < buf_len // BITMAPFILEHEADER.bfOffBits (offset to bitmap bits) + ; + + return success; +} + + +/// Request to update the guild emblem (CZ_REGISTER_GUILD_EMBLEM_IMG). +/// 0153 <packet len>.W <emblem data>.?B +void clif_parse_GuildChangeEmblem(int fd,struct map_session_data *sd) +{ + unsigned long emblem_len = RFIFOW(fd,2)-4; + const uint8* emblem = RFIFOP(fd,4); + + if( !emblem_len || !sd->state.gmaster_flag ) + return; + + if( !clif_validate_emblem(emblem, emblem_len) ) + { + ShowWarning("clif_parse_GuildChangeEmblem: Rejected malformed guild emblem (size=%lu, accound_id=%d, char_id=%d, guild_id=%d).\n", emblem_len, sd->status.account_id, sd->status.char_id, sd->status.guild_id); + return; + } + + guild_change_emblem(sd, emblem_len, (const char*)emblem); +} + + +/// Guild notice update request (CZ_GUILD_NOTICE). +/// 016e <guild id>.L <msg1>.60B <msg2>.120B +void clif_parse_GuildChangeNotice(int fd, struct map_session_data* sd) +{ + int guild_id = RFIFOL(fd,2); + char* msg1 = (char*)RFIFOP(fd,6); + char* msg2 = (char*)RFIFOP(fd,66); + + if(!sd->state.gmaster_flag) + return; + + // compensate for some client defects when using multilanguage mode + if (msg1[0] == '|' && msg1[3] == '|') msg1+= 3; // skip duplicate marker + if (msg2[0] == '|' && msg2[3] == '|') msg2+= 3; // skip duplicate marker + if (msg2[0] == '|') msg2[strnlen(msg2, MAX_GUILDMES2)-1] = '\0'; // delete extra space at the end of string + + guild_change_notice(sd, guild_id, msg1, msg2); +} + + +/// Guild invite request (CZ_REQ_JOIN_GUILD). +/// 0168 <account id>.L <inviter account id>.L <inviter char id>.L +void clif_parse_GuildInvite(int fd,struct map_session_data *sd) +{ + struct map_session_data *t_sd; + + if(map[sd->bl.m].flag.guildlock) + { //Guild locked. + clif_displaymessage(fd, msg_txt(228)); + return; + } + + t_sd = map_id2sd(RFIFOL(fd,2)); + + // @noask [LuzZza] + if(t_sd && t_sd->state.noask) { + clif_noask_sub(sd, t_sd, 2); + return; + } + + guild_invite(sd,t_sd); +} + + +/// Answer to guild invitation (CZ_JOIN_GUILD). +/// 016b <guild id>.L <answer>.L +/// answer: +/// 0 = refuse +/// 1 = accept +void clif_parse_GuildReplyInvite(int fd,struct map_session_data *sd) +{ + guild_reply_invite(sd,RFIFOL(fd,2),RFIFOL(fd,6)); +} + + +/// Request to leave guild (CZ_REQ_LEAVE_GUILD). +/// 0159 <guild id>.L <account id>.L <char id>.L <reason>.40B +void clif_parse_GuildLeave(int fd,struct map_session_data *sd) +{ + if(map[sd->bl.m].flag.guildlock) + { //Guild locked. + clif_displaymessage(fd, msg_txt(228)); + return; + } + if( sd->bg_id ) + { + clif_displaymessage(fd, msg_txt(670)); //"You can't leave battleground guilds." + return; + } + + guild_leave(sd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),(char*)RFIFOP(fd,14)); +} + + +/// Request to expel a member of a guild (CZ_REQ_BAN_GUILD). +/// 015b <guild id>.L <account id>.L <char id>.L <reason>.40B +void clif_parse_GuildExpulsion(int fd,struct map_session_data *sd) +{ + if( map[sd->bl.m].flag.guildlock || sd->bg_id ) + { // Guild locked. + clif_displaymessage(fd, msg_txt(228)); + return; + } + guild_expulsion(sd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),(char*)RFIFOP(fd,14)); +} + + +/// Validates and processes guild messages (CZ_GUILD_CHAT). +/// 017e <packet len>.W <text>.?B (<name> : <message>) 00 +void clif_parse_GuildMessage(int fd, struct map_session_data* sd) +{ + const char* text = (char*)RFIFOP(fd,4); + int textlen = RFIFOW(fd,2) - 4; + + char *name, *message; + int namelen, messagelen; + + // validate packet and retrieve name and message + if( !clif_process_message(sd, 0, &name, &namelen, &message, &messagelen) ) + return; + + if( is_atcommand(fd, sd, message, 1) ) + return; + + if( sd->sc.data[SC_BERSERK] || sd->sc.data[SC__BLOODYLUST] || (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOCHAT) ) + return; + + if( battle_config.min_chat_delay ) + { //[Skotlex] + if (DIFF_TICK(sd->cantalk_tick, gettick()) > 0) + return; + sd->cantalk_tick = gettick() + battle_config.min_chat_delay; + } + + if( sd->bg_id ) + bg_send_message(sd, text, textlen); + else + guild_send_message(sd, text, textlen); +} + + +/// Guild alliance request (CZ_REQ_ALLY_GUILD). +/// 0170 <account id>.L <inviter account id>.L <inviter char id>.L +void clif_parse_GuildRequestAlliance(int fd, struct map_session_data *sd) +{ + struct map_session_data *t_sd; + + if(!sd->state.gmaster_flag) + return; + + if(map[sd->bl.m].flag.guildlock) + { //Guild locked. + clif_displaymessage(fd, msg_txt(228)); + return; + } + + t_sd = map_id2sd(RFIFOL(fd,2)); + + // @noask [LuzZza] + if(t_sd && t_sd->state.noask) { + clif_noask_sub(sd, t_sd, 3); + return; + } + + guild_reqalliance(sd,t_sd); +} + + +/// Answer to a guild alliance request (CZ_ALLY_GUILD). +/// 0172 <inviter account id>.L <answer>.L +/// answer: +/// 0 = refuse +/// 1 = accept +void clif_parse_GuildReplyAlliance(int fd, struct map_session_data *sd) +{ + guild_reply_reqalliance(sd,RFIFOL(fd,2),RFIFOL(fd,6)); +} + + +/// Request to delete a guild alliance or opposition (CZ_REQ_DELETE_RELATED_GUILD). +/// 0183 <opponent guild id>.L <relation>.L +/// relation: +/// 0 = Ally +/// 1 = Enemy +void clif_parse_GuildDelAlliance(int fd, struct map_session_data *sd) +{ + if(!sd->state.gmaster_flag) + return; + + if(map[sd->bl.m].flag.guildlock) + { //Guild locked. + clif_displaymessage(fd, msg_txt(228)); + return; + } + guild_delalliance(sd,RFIFOL(fd,2),RFIFOL(fd,6)); +} + + +/// Request to set a guild as opposition (CZ_REQ_HOSTILE_GUILD). +/// 0180 <account id>.L +void clif_parse_GuildOpposition(int fd, struct map_session_data *sd) +{ + struct map_session_data *t_sd; + + if(!sd->state.gmaster_flag) + return; + + if(map[sd->bl.m].flag.guildlock) + { //Guild locked. + clif_displaymessage(fd, msg_txt(228)); + return; + } + + t_sd = map_id2sd(RFIFOL(fd,2)); + + // @noask [LuzZza] + if(t_sd && t_sd->state.noask) { + clif_noask_sub(sd, t_sd, 4); + return; + } + + guild_opposition(sd,t_sd); +} + + +/// Request to delete own guild (CZ_REQ_DISORGANIZE_GUILD). +/// 015d <key>.40B +/// key: +/// now guild name; might have been (intended) email, since the +/// field name and size is same as the one in CH_DELETE_CHAR. +void clif_parse_GuildBreak(int fd, struct map_session_data *sd) +{ + if( map[sd->bl.m].flag.guildlock ) + { //Guild locked. + clif_displaymessage(fd, msg_txt(228)); + return; + } + guild_break(sd,(char*)RFIFOP(fd,2)); +} + + +/// Pet +/// + +/// Request to invoke a pet menu action (CZ_COMMAND_PET). +/// 01a1 <type>.B +/// type: +/// 0 = pet information +/// 1 = feed +/// 2 = performance +/// 3 = return to egg +/// 4 = unequip accessory +void clif_parse_PetMenu(int fd, struct map_session_data *sd) +{ + pet_menu(sd,RFIFOB(fd,2)); +} + + +/// Attempt to tame a monster (CZ_TRYCAPTURE_MONSTER). +/// 019f <id>.L +void clif_parse_CatchPet(int fd, struct map_session_data *sd) +{ + pet_catch_process2(sd,RFIFOL(fd,2)); +} + + +/// Answer to pet incubator egg selection dialog (CZ_SELECT_PETEGG). +/// 01a7 <index>.W +void clif_parse_SelectEgg(int fd, struct map_session_data *sd) +{ + if (sd->menuskill_id != SA_TAMINGMONSTER || sd->menuskill_val != -1) + { + //Forged packet, disconnect them [Kevin] + clif_authfail_fd(fd, 0); + return; + } + pet_select_egg(sd,RFIFOW(fd,2)-2); + clif_menuskill_clear(sd); +} + + +/// Request to display pet's emotion/talk (CZ_PET_ACT). +/// 01a9 <data>.L +/// data: +/// is either emotion (@see enum emotion_type) or a compound value +/// (((mob id)-100)*100+(act id)*10+(hunger)) that describes an +/// entry (given in parentheses) in data\pettalktable.xml +/// act id: +/// 0 = feeding +/// 1 = hunting +/// 2 = danger +/// 3 = dead +/// 4 = normal (stand) +/// 5 = special performance (perfor_s) +/// 6 = level up (levelup) +/// 7 = performance 1 (perfor_1) +/// 8 = performance 2 (perfor_2) +/// 9 = performance 3 (perfor_3) +/// 10 = log-in greeting (connect) +/// hungry value: +/// 0 = very hungry (hungry) +/// 1 = hungry (bit_hungry) +/// 2 = satisfied (noting) +/// 3 = stuffed (full) +/// 4 = full (so_full) +void clif_parse_SendEmotion(int fd, struct map_session_data *sd) +{ + if(sd->pd) + clif_pet_emotion(sd->pd,RFIFOL(fd,2)); +} + + +/// Request to change pet's name (CZ_RENAME_PET). +/// 01a5 <name>.24B +void clif_parse_ChangePetName(int fd, struct map_session_data *sd) +{ + pet_change_name(sd,(char*)RFIFOP(fd,2)); +} + + +/// /kill (CZ_DISCONNECT_CHARACTER). +/// Request to disconnect a character. +/// 00cc <account id>.L +/// NOTE: Also sent when using GM right click menu "(name) force to quit" +void clif_parse_GMKick(int fd, struct map_session_data *sd) +{ + struct block_list *target; + int tid; + + tid = RFIFOL(fd,2); + target = map_id2bl(tid); + if (!target) { + clif_GM_kickack(sd, 0); + return; + } + + switch (target->type) { + case BL_PC: + { + char command[NAME_LENGTH+6]; + sprintf(command, "%ckick %s", atcommand_symbol, status_get_name(target)); + is_atcommand(fd, sd, command, 1); + } + break; + + /** + * This one does not invoke any atcommand, so we need to check for permissions. + */ + case BL_MOB: + { + char command[100]; + if( !pc_can_use_command(sd, "killmonster", COMMAND_ATCOMMAND)) { + clif_GM_kickack(sd, 0); + return; + } + sprintf(command, "/kick %s (%d)", status_get_name(target), status_get_class(target)); + log_atcommand(sd, command); + status_percent_damage(&sd->bl, target, 100, 0, true); // can invalidate 'target' + } + break; + + case BL_NPC: + { + char command[NAME_LENGTH+11]; + sprintf(command, "%cunloadnpc %s", atcommand_symbol, status_get_name(target)); + is_atcommand(fd, sd, command, 1); + } + break; + + default: + clif_GM_kickack(sd, 0); + } +} + + +/// /killall (CZ_DISCONNECT_ALL_CHARACTER). +/// Request to disconnect all characters. +/// 00ce +void clif_parse_GMKickAll(int fd, struct map_session_data* sd) { + char cmd[15]; + sprintf(cmd,"%ckickall",atcommand_symbol); + is_atcommand(fd, sd, cmd, 1); +} + + +/// /remove (CZ_REMOVE_AID). +/// Request to warp to a character with given login ID. +/// 01ba <account name>.24B + +/// /shift (CZ_SHIFT). +/// Request to warp to a character with given name. +/// 01bb <char name>.24B +void clif_parse_GMShift(int fd, struct map_session_data *sd) +{// FIXME: remove is supposed to receive account name for clients prior 20100803RE + char *player_name; + char command[NAME_LENGTH+8]; + + player_name = (char*)RFIFOP(fd,2); + player_name[NAME_LENGTH-1] = '\0'; + + sprintf(command, "%cjumpto %s", atcommand_symbol, player_name); + is_atcommand(fd, sd, command, 1); +} + + +/// /remove (CZ_REMOVE_AID_SSO). +/// Request to warp to a character with given account ID. +/// 0843 <account id>.L +void clif_parse_GMRemove2(int fd, struct map_session_data* sd) +{ + int account_id; + struct map_session_data* pl_sd; + + account_id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + if( (pl_sd = map_id2sd(account_id)) != NULL ) + { + char command[NAME_LENGTH+8]; + sprintf(command, "%cjumpto %s", atcommand_symbol, pl_sd->status.name); + is_atcommand(fd, sd, command, 1); + } +} + + +/// /recall (CZ_RECALL). +/// Request to summon a player with given login ID to own position. +/// 01bc <account name>.24B + +/// /summon (CZ_RECALL_GID). +/// Request to summon a player with given name to own position. +/// 01bd <char name>.24B +void clif_parse_GMRecall(int fd, struct map_session_data *sd) +{// FIXME: recall is supposed to receive account name for clients prior 20100803RE + char *player_name; + char command [NAME_LENGTH+8]; + + player_name = (char*)RFIFOP(fd,2); + player_name[NAME_LENGTH-1] = '\0'; + + sprintf(command, "%crecall %s", atcommand_symbol, player_name); + is_atcommand(fd, sd, command, 1); +} + + +/// /recall (CZ_RECALL_SSO). +/// Request to summon a player with given account ID to own position. +/// 0842 <account id>.L +void clif_parse_GMRecall2(int fd, struct map_session_data* sd) +{ + int account_id; + struct map_session_data* pl_sd; + + account_id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + if( (pl_sd = map_id2sd(account_id)) != NULL ) + { + char command[NAME_LENGTH+8]; + sprintf(command, "%crecall %s", atcommand_symbol, pl_sd->status.name); + is_atcommand(fd, sd, command, 1); + } +} + + +/// /item /monster (CZ_ITEM_CREATE). +/// Request to make items or spawn monsters. +/// 013f <item/mob name>.24B +void clif_parse_GM_Monster_Item(int fd, struct map_session_data *sd) +{ + char *monster_item_name; + char command[NAME_LENGTH+10]; + + monster_item_name = (char*)RFIFOP(fd,2); + monster_item_name[NAME_LENGTH-1] = '\0'; + + // FIXME: Should look for item first, then for monster. + // FIXME: /monster takes mob_db Sprite_Name as argument + if( mobdb_searchname(monster_item_name) ) { + snprintf(command, sizeof(command)-1, "%cmonster %s", atcommand_symbol, monster_item_name); + is_atcommand(fd, sd, command, 1); + return; + } + // FIXME: Stackables have a quantity of 20. + // FIXME: Equips are supposed to be unidentified. + + if( itemdb_searchname(monster_item_name) ) { + snprintf(command, sizeof(command)-1, "%citem %s", atcommand_symbol, monster_item_name); + is_atcommand(fd, sd, command, 1); + return; + } +} + + +/// /hide (CZ_CHANGE_EFFECTSTATE). +/// 019d <effect state>.L +/// effect state: +/// TODO: Any OPTION_* ? +void clif_parse_GMHide(int fd, struct map_session_data *sd) { + char cmd[6]; + + sprintf(cmd,"%chide",atcommand_symbol); + + is_atcommand(fd, sd, cmd, 1); +} + + +/// Request to adjust player's manner points (CZ_REQ_GIVE_MANNER_POINT). +/// 0149 <account id>.L <type>.B <value>.W +/// type: +/// 0 = positive points +/// 1 = negative points +/// 2 = self mute (+10 minutes) +void clif_parse_GMReqNoChat(int fd,struct map_session_data *sd) +{ + int id, type, value; + struct map_session_data *dstsd; + char command[NAME_LENGTH+15]; + + id = RFIFOL(fd,2); + type = RFIFOB(fd,6); + value = RFIFOW(fd,7); + + if( type == 0 ) + value = -value; + + //If type is 2 and the ids don't match, this is a crafted hacked packet! + //Disabled because clients keep self-muting when you give players public @ commands... [Skotlex] + if (type == 2 /* && (pc_get_group_level(sd) > 0 || sd->bl.id != id)*/) + return; + + dstsd = map_id2sd(id); + if( dstsd == NULL ) + return; + + sprintf(command, "%cmute %d %s", atcommand_symbol, value, dstsd->status.name); + is_atcommand(fd, sd, command, 1); +} + + +/// /rc (CZ_REQ_GIVE_MANNER_BYNAME). +/// GM adjustment of a player's manner value by -60. +/// 0212 <char name>.24B +void clif_parse_GMRc(int fd, struct map_session_data* sd) +{ + char command[NAME_LENGTH+15]; + char *name = (char*)RFIFOP(fd,2); + + name[NAME_LENGTH-1] = '\0'; + sprintf(command, "%cmute %d %s", atcommand_symbol, 60, name); + is_atcommand(fd, sd, command, 1); +} + + +/// Result of request to resolve account name (ZC_ACK_ACCOUNTNAME). +/// 01e0 <account id>.L <account name>.24B +void clif_account_name(struct map_session_data* sd, int account_id, const char* accname) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x1e0)); + WFIFOW(fd,0) = 0x1e0; + WFIFOL(fd,2) = account_id; + safestrncpy((char*)WFIFOP(fd,6), accname, NAME_LENGTH); + WFIFOSET(fd,packet_len(0x1e0)); +} + + +/// GM requesting account name (for right-click gm menu) (CZ_REQ_ACCOUNTNAME). +/// 01df <account id>.L +void clif_parse_GMReqAccountName(int fd, struct map_session_data *sd) +{ + int account_id = RFIFOL(fd,2); + + //TODO: find out if this works for any player or only for authorized GMs + clif_account_name(sd, account_id, ""); // insert account name here >_< +} + + +/// /changemaptype <x> <y> <type> (CZ_CHANGE_MAPTYPE). +/// GM single cell type change request. +/// 0198 <x>.W <y>.W <type>.W +/// type: +/// 0 = not walkable +/// 1 = walkable +void clif_parse_GMChangeMapType(int fd, struct map_session_data *sd) +{ + int x,y,type; + + if( pc_has_permission(sd, PC_PERM_USE_CHANGEMAPTYPE) ) + return; + + x = RFIFOW(fd,2); + y = RFIFOW(fd,4); + type = RFIFOW(fd,6); + + map_setgatcell(sd->bl.m,x,y,type); + clif_changemapcell(0,sd->bl.m,x,y,type,ALL_SAMEMAP); + //FIXME: once players leave the map, the client 'forgets' this information. +} + + +/// /in /ex (CZ_SETTING_WHISPER_PC). +/// Request to allow/deny whispers from a nick. +/// 00cf <nick>.24B <type>.B +/// type: +/// 0 = (/ex nick) deny speech from nick +/// 1 = (/in nick) allow speech from nick +void clif_parse_PMIgnore(int fd, struct map_session_data* sd) +{ + char* nick; + uint8 type; + int i; + + nick = (char*)RFIFOP(fd,2); // speed up + nick[NAME_LENGTH-1] = '\0'; // to be sure that the player name has at most 23 characters + type = RFIFOB(fd,26); + + if( type == 0 ) + { // Add name to ignore list (block) + if (strcmp(wisp_server_name, nick) == 0) { + clif_wisexin(sd, type, 1); // fail + return; + } + + // try to find a free spot, while checking for duplicates at the same time + ARR_FIND( 0, MAX_IGNORE_LIST, i, sd->ignore[i].name[0] == '\0' || strcmp(sd->ignore[i].name, nick) == 0 ); + if( i == MAX_IGNORE_LIST ) + {// no space for new entry + clif_wisexin(sd, type, 2); // too many blocks + return; + } + if( sd->ignore[i].name[0] != '\0' ) + {// name already exists + clif_wisexin(sd, type, 0); // Aegis reports success. + return; + } + + //Insert in position i + safestrncpy(sd->ignore[i].name, nick, NAME_LENGTH); + } + else + { // Remove name from ignore list (unblock) + + // find entry + ARR_FIND( 0, MAX_IGNORE_LIST, i, sd->ignore[i].name[0] == '\0' || strcmp(sd->ignore[i].name, nick) == 0 ); + if( i == MAX_IGNORE_LIST || sd->ignore[i].name[i] == '\0' ) + { //Not found + clif_wisexin(sd, type, 1); // fail + return; + } + // move everything one place down to overwrite removed entry + memmove(sd->ignore[i].name, sd->ignore[i+1].name, (MAX_IGNORE_LIST-i-1)*sizeof(sd->ignore[0].name)); + // wipe last entry + memset(sd->ignore[MAX_IGNORE_LIST-1].name, 0, sizeof(sd->ignore[0].name)); + } + + clif_wisexin(sd, type, 0); // success +} + + +/// /inall /exall (CZ_SETTING_WHISPER_STATE). +/// Request to allow/deny all whispers. +/// 00d0 <type>.B +/// type: +/// 0 = (/exall) deny all speech +/// 1 = (/inall) allow all speech +void clif_parse_PMIgnoreAll(int fd, struct map_session_data *sd) +{ + int type = RFIFOB(fd,2), flag; + + if( type == 0 ) + {// Deny all + if( sd->state.ignoreAll ) { + flag = 1; // fail + } else { + sd->state.ignoreAll = 1; + flag = 0; // success + } + } + else + {//Unblock everyone + if( sd->state.ignoreAll ) { + sd->state.ignoreAll = 0; + flag = 0; // success + } else { + if (sd->ignore[0].name[0] != '\0') + { //Wipe the ignore list. + memset(sd->ignore, 0, sizeof(sd->ignore)); + flag = 0; // success + } else { + flag = 1; // fail + } + } + } + + clif_wisall(sd, type, flag); +} + + +/// Whisper ignore list (ZC_WHISPER_LIST). +/// 00d4 <packet len>.W { <char name>.24B }* +void clif_PMIgnoreList(struct map_session_data* sd) +{ + int i, fd = sd->fd; + + WFIFOHEAD(fd,4+ARRAYLENGTH(sd->ignore)*NAME_LENGTH); + WFIFOW(fd,0) = 0xd4; + + for( i = 0; i < ARRAYLENGTH(sd->ignore) && sd->ignore[i].name[0]; i++ ) + { + memcpy(WFIFOP(fd,4+i*NAME_LENGTH), sd->ignore[i].name, NAME_LENGTH); + } + + WFIFOW(fd,2) = 4+i*NAME_LENGTH; + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Whisper ignore list request (CZ_REQ_WHISPER_LIST). +/// 00d3 +void clif_parse_PMIgnoreList(int fd,struct map_session_data *sd) +{ + clif_PMIgnoreList(sd); +} + + +/// Request to invoke the /doridori recovery bonus (CZ_DORIDORI). +/// 01e7 +void clif_parse_NoviceDoriDori(int fd, struct map_session_data *sd) +{ + if (sd->state.doridori) return; + + switch (sd->class_&MAPID_UPPERMASK) + { + case MAPID_SOUL_LINKER: + case MAPID_STAR_GLADIATOR: + case MAPID_TAEKWON: + if (!sd->state.rest) + break; + case MAPID_SUPER_NOVICE: + sd->state.doridori=1; + break; + } +} + + +/// Request to invoke the effect of super novice's guardian angel prayer (CZ_CHOPOKGI). +/// 01ed +/// Note: This packet is caused by 7 lines of any text, followed by +/// the prayer and an another line of any text. The prayer is +/// defined by lines 790~793 in data\msgstringtable.txt +/// "Dear angel, can you hear my voice?" +/// "I am" (space separated player name) "Super Novice~" +/// "Help me out~ Please~ T_T" +void clif_parse_NoviceExplosionSpirits(int fd, struct map_session_data *sd) +{ + if( ( sd->class_&MAPID_UPPERMASK ) == MAPID_SUPER_NOVICE ) + { + unsigned int next = pc_nextbaseexp(sd); + if( next == 0 ) next = pc_thisbaseexp(sd); + if( next ) + { + int percent = (int)( ( (float)sd->status.base_exp/(float)next )*1000. ); + + if( percent && ( percent%100 ) == 0 ) + {// 10.0%, 20.0%, ..., 90.0% + sc_start(&sd->bl, status_skill2sc(MO_EXPLOSIONSPIRITS), 100, 17, skill_get_time(MO_EXPLOSIONSPIRITS, 5)); //Lv17-> +50 critical (noted by Poki) [Skotlex] + clif_skill_nodamage(&sd->bl, &sd->bl, MO_EXPLOSIONSPIRITS, 5, 1); // prayer always shows successful Lv5 cast and disregards noskill restrictions + } + } + } +} + + +/// Friends List +/// + +/// Toggles a single friend online/offline [Skotlex] (ZC_FRIENDS_STATE). +/// 0206 <account id>.L <char id>.L <state>.B +/// state: +/// 0 = online +/// 1 = offline +void clif_friendslist_toggle(struct map_session_data *sd,int account_id, int char_id, int online) +{ + int i, fd = sd->fd; + + //Seek friend. + for (i = 0; i < MAX_FRIENDS && sd->status.friends[i].char_id && + (sd->status.friends[i].char_id != char_id || sd->status.friends[i].account_id != account_id); i++); + + if(i == MAX_FRIENDS || sd->status.friends[i].char_id == 0) + return; //Not found + + WFIFOHEAD(fd,packet_len(0x206)); + WFIFOW(fd, 0) = 0x206; + WFIFOL(fd, 2) = sd->status.friends[i].account_id; + WFIFOL(fd, 6) = sd->status.friends[i].char_id; + WFIFOB(fd,10) = !online; //Yeah, a 1 here means "logged off", go figure... + WFIFOSET(fd, packet_len(0x206)); +} + + +//Subfunction called from clif_foreachclient to toggle friends on/off [Skotlex] +int clif_friendslist_toggle_sub(struct map_session_data *sd,va_list ap) +{ + int account_id, char_id, online; + account_id = va_arg(ap, int); + char_id = va_arg(ap, int); + online = va_arg(ap, int); + clif_friendslist_toggle(sd, account_id, char_id, online); + return 0; +} + + +/// Sends the whole friends list (ZC_FRIENDS_LIST). +/// 0201 <packet len>.W { <account id>.L <char id>.L <name>.24B }* +void clif_friendslist_send(struct map_session_data *sd) +{ + int i = 0, n, fd = sd->fd; + + // Send friends list + WFIFOHEAD(fd, MAX_FRIENDS * 32 + 4); + WFIFOW(fd, 0) = 0x201; + for(i = 0; i < MAX_FRIENDS && sd->status.friends[i].char_id; i++) + { + WFIFOL(fd, 4 + 32 * i + 0) = sd->status.friends[i].account_id; + WFIFOL(fd, 4 + 32 * i + 4) = sd->status.friends[i].char_id; + memcpy(WFIFOP(fd, 4 + 32 * i + 8), &sd->status.friends[i].name, NAME_LENGTH); + } + + if (i) { + WFIFOW(fd,2) = 4 + 32 * i; + WFIFOSET(fd, WFIFOW(fd,2)); + } + + for (n = 0; n < i; n++) + { //Sending the online players + if (map_charid2sd(sd->status.friends[n].char_id)) + clif_friendslist_toggle(sd, sd->status.friends[n].account_id, sd->status.friends[n].char_id, 1); + } +} + + +/// Notification about the result of a friend add request (ZC_ADD_FRIENDS_LIST). +/// 0209 <result>.W <account id>.L <char id>.L <name>.24B +/// result: +/// 0 = MsgStringTable[821]="You have become friends with (%s)." +/// 1 = MsgStringTable[822]="(%s) does not want to be friends with you." +/// 2 = MsgStringTable[819]="Your Friend List is full." +/// 3 = MsgStringTable[820]="(%s)'s Friend List is full." +void clif_friendslist_reqack(struct map_session_data *sd, struct map_session_data *f_sd, int type) +{ + int fd; + nullpo_retv(sd); + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0x209)); + WFIFOW(fd,0) = 0x209; + WFIFOW(fd,2) = type; + if (f_sd) + { + WFIFOL(fd,4) = f_sd->status.account_id; + WFIFOL(fd,8) = f_sd->status.char_id; + memcpy(WFIFOP(fd, 12), f_sd->status.name,NAME_LENGTH); + } + WFIFOSET(fd, packet_len(0x209)); +} + + +/// Asks a player for permission to be added as friend (ZC_REQ_ADD_FRIENDS). +/// 0207 <req account id>.L <req char id>.L <req char name>.24B +void clif_friendlist_req(struct map_session_data* sd, int account_id, int char_id, const char* name) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x207)); + WFIFOW(fd,0) = 0x207; + WFIFOL(fd,2) = account_id; + WFIFOL(fd,6) = char_id; + memcpy(WFIFOP(fd,10), name, NAME_LENGTH); + WFIFOSET(fd,packet_len(0x207)); +} + + +/// Request to add a player as friend (CZ_ADD_FRIENDS). +/// 0202 <name>.24B +void clif_parse_FriendsListAdd(int fd, struct map_session_data *sd) +{ + struct map_session_data *f_sd; + int i; + + f_sd = map_nick2sd((char*)RFIFOP(fd,2)); + + // ensure that the request player's friend list is not full + ARR_FIND(0, MAX_FRIENDS, i, sd->status.friends[i].char_id == 0); + + if( i == MAX_FRIENDS ) { + clif_friendslist_reqack(sd, f_sd, 2); + return; + } + + // Friend doesn't exist (no player with this name) + if (f_sd == NULL) { + clif_displaymessage(fd, msg_txt(3)); + return; + } + + if( sd->bl.id == f_sd->bl.id ) + {// adding oneself as friend + return; + } + + // @noask [LuzZza] + if(f_sd->state.noask) { + clif_noask_sub(sd, f_sd, 5); + return; + } + + // Friend already exists + for (i = 0; i < MAX_FRIENDS && sd->status.friends[i].char_id != 0; i++) { + if (sd->status.friends[i].char_id == f_sd->status.char_id) { + clif_displaymessage(fd, msg_txt(671)); //"Friend already exists." + return; + } + } + + f_sd->friend_req = sd->status.char_id; + sd->friend_req = f_sd->status.char_id; + + clif_friendlist_req(f_sd, sd->status.account_id, sd->status.char_id, sd->status.name); +} + + +/// Answer to a friend add request (CZ_ACK_REQ_ADD_FRIENDS). +/// 0208 <inviter account id>.L <inviter char id>.L <result>.B +/// 0208 <inviter account id>.L <inviter char id>.L <result>.L (PACKETVER >= 6) +/// result: +/// 0 = rejected +/// 1 = accepted +void clif_parse_FriendsListReply(int fd, struct map_session_data *sd) +{ + struct map_session_data *f_sd; + int account_id; + char reply; + + account_id = RFIFOL(fd,2); + //char_id = RFIFOL(fd,6); +#if PACKETVER < 6 + reply = RFIFOB(fd,10); +#else + reply = RFIFOL(fd,10); +#endif + + if( sd->bl.id == account_id ) + {// adding oneself as friend + return; + } + + f_sd = map_id2sd(account_id); //The account id is the same as the bl.id of players. + if (f_sd == NULL) + return; + + if (reply == 0 || !( sd->friend_req == f_sd->status.char_id && f_sd->friend_req == sd->status.char_id ) ) + clif_friendslist_reqack(f_sd, sd, 1); + else { + int i; + // Find an empty slot + for (i = 0; i < MAX_FRIENDS; i++) + if (f_sd->status.friends[i].char_id == 0) + break; + if (i == MAX_FRIENDS) { + clif_friendslist_reqack(f_sd, sd, 2); + return; + } + + f_sd->status.friends[i].account_id = sd->status.account_id; + f_sd->status.friends[i].char_id = sd->status.char_id; + memcpy(f_sd->status.friends[i].name, sd->status.name, NAME_LENGTH); + clif_friendslist_reqack(f_sd, sd, 0); + + if (battle_config.friend_auto_add) { + // Also add f_sd to sd's friendlist. + for (i = 0; i < MAX_FRIENDS; i++) { + if (sd->status.friends[i].char_id == f_sd->status.char_id) + return; //No need to add anything. + if (sd->status.friends[i].char_id == 0) + break; + } + if (i == MAX_FRIENDS) { + clif_friendslist_reqack(sd, f_sd, 2); + return; + } + + sd->status.friends[i].account_id = f_sd->status.account_id; + sd->status.friends[i].char_id = f_sd->status.char_id; + memcpy(sd->status.friends[i].name, f_sd->status.name, NAME_LENGTH); + clif_friendslist_reqack(sd, f_sd, 0); + } + } +} + + +/// Request to delete a friend (CZ_DELETE_FRIENDS). +/// 0203 <account id>.L <char id>.L +void clif_parse_FriendsListRemove(int fd, struct map_session_data *sd) +{ + struct map_session_data *f_sd = NULL; + int account_id, char_id; + int i, j; + + account_id = RFIFOL(fd,2); + char_id = RFIFOL(fd,6); + + // Search friend + for (i = 0; i < MAX_FRIENDS && + (sd->status.friends[i].char_id != char_id || sd->status.friends[i].account_id != account_id); i++); + + if (i == MAX_FRIENDS) { + clif_displaymessage(fd, msg_txt(672)); //"Name not found in list." + return; + } + + //remove from friend's list first + if( (f_sd = map_id2sd(account_id)) && f_sd->status.char_id == char_id) { + for (i = 0; i < MAX_FRIENDS && + (f_sd->status.friends[i].char_id != sd->status.char_id || f_sd->status.friends[i].account_id != sd->status.account_id); i++); + + if (i != MAX_FRIENDS) { + // move all chars up + for(j = i + 1; j < MAX_FRIENDS; j++) + memcpy(&f_sd->status.friends[j-1], &f_sd->status.friends[j], sizeof(f_sd->status.friends[0])); + + memset(&f_sd->status.friends[MAX_FRIENDS-1], 0, sizeof(f_sd->status.friends[MAX_FRIENDS-1])); + //should the guy be notified of some message? we should add it here if so + WFIFOHEAD(f_sd->fd,packet_len(0x20a)); + WFIFOW(f_sd->fd,0) = 0x20a; + WFIFOL(f_sd->fd,2) = sd->status.account_id; + WFIFOL(f_sd->fd,6) = sd->status.char_id; + WFIFOSET(f_sd->fd, packet_len(0x20a)); + } + + } else { //friend not online -- ask char server to delete from his friendlist + if(chrif_removefriend(char_id,sd->status.char_id)) { // char-server offline, abort + clif_displaymessage(fd, msg_txt(673)); //"This action can't be performed at the moment. Please try again later." + return; + } + } + + // We can now delete from original requester + for (i = 0; i < MAX_FRIENDS && + (sd->status.friends[i].char_id != char_id || sd->status.friends[i].account_id != account_id); i++); + // move all chars up + for(j = i + 1; j < MAX_FRIENDS; j++) + memcpy(&sd->status.friends[j-1], &sd->status.friends[j], sizeof(sd->status.friends[0])); + + memset(&sd->status.friends[MAX_FRIENDS-1], 0, sizeof(sd->status.friends[MAX_FRIENDS-1])); + clif_displaymessage(fd, msg_txt(674)); //"Friend removed" + + WFIFOHEAD(fd,packet_len(0x20a)); + WFIFOW(fd,0) = 0x20a; + WFIFOL(fd,2) = account_id; + WFIFOL(fd,6) = char_id; + WFIFOSET(fd, packet_len(0x20a)); +} + + +/// /pvpinfo list (ZC_ACK_PVPPOINT). +/// 0210 <char id>.L <account id>.L <win point>.L <lose point>.L <point>.L +void clif_PVPInfo(struct map_session_data* sd) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x210)); + WFIFOW(fd,0) = 0x210; + WFIFOL(fd,2) = sd->status.char_id; + WFIFOL(fd,6) = sd->status.account_id; + WFIFOL(fd,10) = sd->pvp_won; // times won + WFIFOL(fd,14) = sd->pvp_lost; // times lost + WFIFOL(fd,18) = sd->pvp_point; + WFIFOSET(fd, packet_len(0x210)); +} + + +/// /pvpinfo (CZ_REQ_PVPPOINT). +/// 020f <char id>.L <account id>.L +void clif_parse_PVPInfo(int fd,struct map_session_data *sd) +{ + // TODO: Is there a way to use this on an another player (char/acc id)? + clif_PVPInfo(sd); +} + + +/// /blacksmith list (ZC_BLACKSMITH_RANK). +/// 0219 { <name>.24B }*10 { <point>.L }*10 +void clif_blacksmith(struct map_session_data* sd) +{ + int i, fd = sd->fd; + const char* name; + + WFIFOHEAD(fd,packet_len(0x219)); + WFIFOW(fd,0) = 0x219; + //Packet size limits this list to 10 elements. [Skotlex] + for (i = 0; i < 10 && i < MAX_FAME_LIST; i++) { + if (smith_fame_list[i].id > 0) { + if (strcmp(smith_fame_list[i].name, "-") == 0 && + (name = map_charid2nick(smith_fame_list[i].id)) != NULL) + { + strncpy((char *)(WFIFOP(fd, 2 + 24 * i)), name, NAME_LENGTH); + } else + strncpy((char *)(WFIFOP(fd, 2 + 24 * i)), smith_fame_list[i].name, NAME_LENGTH); + } else + strncpy((char *)(WFIFOP(fd, 2 + 24 * i)), "None", 5); + WFIFOL(fd, 242 + i * 4) = smith_fame_list[i].fame; + } + for(;i < 10; i++) { //In case the MAX is less than 10. + strncpy((char *)(WFIFOP(fd, 2 + 24 * i)), "Unavailable", 12); + WFIFOL(fd, 242 + i * 4) = 0; + } + + WFIFOSET(fd, packet_len(0x219)); +} + + +/// /blacksmith (CZ_BLACKSMITH_RANK). +/// 0217 +void clif_parse_Blacksmith(int fd,struct map_session_data *sd) +{ + clif_blacksmith(sd); +} + + +/// Notification about backsmith points (ZC_BLACKSMITH_POINT). +/// 021b <points>.L <total points>.L +void clif_fame_blacksmith(struct map_session_data *sd, int points) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x21b)); + WFIFOW(fd,0) = 0x21b; + WFIFOL(fd,2) = points; + WFIFOL(fd,6) = sd->status.fame; + WFIFOSET(fd, packet_len(0x21b)); +} + + +/// /alchemist list (ZC_ALCHEMIST_RANK). +/// 021a { <name>.24B }*10 { <point>.L }*10 +void clif_alchemist(struct map_session_data* sd) +{ + int i, fd = sd->fd; + const char* name; + + WFIFOHEAD(fd,packet_len(0x21a)); + WFIFOW(fd,0) = 0x21a; + //Packet size limits this list to 10 elements. [Skotlex] + for (i = 0; i < 10 && i < MAX_FAME_LIST; i++) { + if (chemist_fame_list[i].id > 0) { + if (strcmp(chemist_fame_list[i].name, "-") == 0 && + (name = map_charid2nick(chemist_fame_list[i].id)) != NULL) + { + memcpy(WFIFOP(fd, 2 + 24 * i), name, NAME_LENGTH); + } else + memcpy(WFIFOP(fd, 2 + 24 * i), chemist_fame_list[i].name, NAME_LENGTH); + } else + memcpy(WFIFOP(fd, 2 + 24 * i), "None", NAME_LENGTH); + WFIFOL(fd, 242 + i * 4) = chemist_fame_list[i].fame; + } + for(;i < 10; i++) { //In case the MAX is less than 10. + memcpy(WFIFOP(fd, 2 + 24 * i), "Unavailable", NAME_LENGTH); + WFIFOL(fd, 242 + i * 4) = 0; + } + + WFIFOSET(fd, packet_len(0x21a)); +} + + +/// /alchemist (CZ_ALCHEMIST_RANK). +/// 0218 +void clif_parse_Alchemist(int fd,struct map_session_data *sd) +{ + clif_alchemist(sd); +} + + +/// Notification about alchemist points (ZC_ALCHEMIST_POINT). +/// 021c <points>.L <total points>.L +void clif_fame_alchemist(struct map_session_data *sd, int points) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x21c)); + WFIFOW(fd,0) = 0x21c; + WFIFOL(fd,2) = points; + WFIFOL(fd,6) = sd->status.fame; + WFIFOSET(fd, packet_len(0x21c)); +} + + +/// /taekwon list (ZC_TAEKWON_RANK). +/// 0226 { <name>.24B }*10 { <point>.L }*10 +void clif_taekwon(struct map_session_data* sd) +{ + int i, fd = sd->fd; + const char* name; + + WFIFOHEAD(fd,packet_len(0x226)); + WFIFOW(fd,0) = 0x226; + //Packet size limits this list to 10 elements. [Skotlex] + for (i = 0; i < 10 && i < MAX_FAME_LIST; i++) { + if (taekwon_fame_list[i].id > 0) { + if (strcmp(taekwon_fame_list[i].name, "-") == 0 && + (name = map_charid2nick(taekwon_fame_list[i].id)) != NULL) + { + memcpy(WFIFOP(fd, 2 + 24 * i), name, NAME_LENGTH); + } else + memcpy(WFIFOP(fd, 2 + 24 * i), taekwon_fame_list[i].name, NAME_LENGTH); + } else + memcpy(WFIFOP(fd, 2 + 24 * i), "None", NAME_LENGTH); + WFIFOL(fd, 242 + i * 4) = taekwon_fame_list[i].fame; + } + for(;i < 10; i++) { //In case the MAX is less than 10. + memcpy(WFIFOP(fd, 2 + 24 * i), "Unavailable", NAME_LENGTH); + WFIFOL(fd, 242 + i * 4) = 0; + } + WFIFOSET(fd, packet_len(0x226)); +} + + +/// /taekwon (CZ_TAEKWON_RANK). +/// 0225 +void clif_parse_Taekwon(int fd,struct map_session_data *sd) +{ + clif_taekwon(sd); +} + + +/// Notification about taekwon points (ZC_TAEKWON_POINT). +/// 0224 <points>.L <total points>.L +void clif_fame_taekwon(struct map_session_data *sd, int points) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x224)); + WFIFOW(fd,0) = 0x224; + WFIFOL(fd,2) = points; + WFIFOL(fd,6) = sd->status.fame; + WFIFOSET(fd, packet_len(0x224)); +} + + +/// /pk list (ZC_KILLER_RANK). +/// 0238 { <name>.24B }*10 { <point>.L }*10 +void clif_ranking_pk(struct map_session_data* sd) +{ + int i, fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x238)); + WFIFOW(fd,0) = 0x238; + for(i=0;i<10;i++){ + memcpy(WFIFOP(fd,i*24+2), "Unknown", NAME_LENGTH); + WFIFOL(fd,i*4+242) = 0; + } + WFIFOSET(fd, packet_len(0x238)); +} + + +/// /pk (CZ_KILLER_RANK). +/// 0237 +void clif_parse_RankingPk(int fd,struct map_session_data *sd) +{ + clif_ranking_pk(sd); +} + + +/// SG Feel save OK [Komurka] (CZ_AGREE_STARPLACE). +/// 0254 <which>.B +/// which: +/// 0 = sun +/// 1 = moon +/// 2 = star +void clif_parse_FeelSaveOk(int fd,struct map_session_data *sd) +{ + int i; + if (sd->menuskill_id != SG_FEEL) + return; + i = sd->menuskill_val-1; + if (i<0 || i >= MAX_PC_FEELHATE) return; //Bug? + + sd->feel_map[i].index = map_id2index(sd->bl.m); + sd->feel_map[i].m = sd->bl.m; + pc_setglobalreg(sd,sg_info[i].feel_var,sd->feel_map[i].index); + +//Are these really needed? Shouldn't they show up automatically from the feel save packet? +// clif_misceffect2(&sd->bl, 0x1b0); +// clif_misceffect2(&sd->bl, 0x21f); + clif_feel_info(sd, i, 0); + clif_menuskill_clear(sd); +} + + +/// Star Gladiator's Feeling map confirmation prompt (ZC_STARPLACE). +/// 0253 <which>.B +/// which: +/// 0 = sun +/// 1 = moon +/// 2 = star +void clif_feel_req(int fd, struct map_session_data *sd, uint16 skill_lv) +{ + WFIFOHEAD(fd,packet_len(0x253)); + WFIFOW(fd,0)=0x253; + WFIFOB(fd,2)=TOB(skill_lv-1); + WFIFOSET(fd, packet_len(0x253)); + sd->menuskill_id = SG_FEEL; + sd->menuskill_val = skill_lv; +} + + +/// Request to change homunculus' name (CZ_RENAME_MER). +/// 0231 <name>.24B +void clif_parse_ChangeHomunculusName(int fd, struct map_session_data *sd) +{ + merc_hom_change_name(sd,(char*)RFIFOP(fd,2)); +} + + +/// Request to warp/move homunculus/mercenary to it's owner (CZ_REQUEST_MOVETOOWNER). +/// 0234 <id>.L +void clif_parse_HomMoveToMaster(int fd, struct map_session_data *sd) +{ + int id = RFIFOL(fd,2); // Mercenary or Homunculus + struct block_list *bl = NULL; + struct unit_data *ud = NULL; + + if( sd->md && sd->md->bl.id == id ) + bl = &sd->md->bl; + else if( merc_is_hom_active(sd->hd) && sd->hd->bl.id == id ) + bl = &sd->hd->bl; // Moving Homunculus + else + return; + + unit_calc_pos(bl, sd->bl.x, sd->bl.y, sd->ud.dir); + ud = unit_bl2ud(bl); + unit_walktoxy(bl, ud->to_x, ud->to_y, 4); +} + + +/// Request to move homunculus/mercenary (CZ_REQUEST_MOVENPC). +/// 0232 <id>.L <position data>.3B +void clif_parse_HomMoveTo(int fd, struct map_session_data *sd) +{ + int id = RFIFOL(fd,2); // Mercenary or Homunculus + struct block_list *bl = NULL; + short x, y; + + RFIFOPOS(fd, packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[1], &x, &y, NULL); + + if( sd->md && sd->md->bl.id == id ) + bl = &sd->md->bl; // Moving Mercenary + else if( merc_is_hom_active(sd->hd) && sd->hd->bl.id == id ) + bl = &sd->hd->bl; // Moving Homunculus + else + return; + + unit_walktoxy(bl, x, y, 4); +} + + +/// Request to do an action with homunculus/mercenary (CZ_REQUEST_ACTNPC). +/// 0233 <id>.L <target id>.L <action>.B +/// action: +/// always 0 +void clif_parse_HomAttack(int fd,struct map_session_data *sd) +{ + struct block_list *bl = NULL; + int id = RFIFOL(fd,2), + target_id = RFIFOL(fd,6), + action_type = RFIFOB(fd,10); + + if( merc_is_hom_active(sd->hd) && sd->hd->bl.id == id ) + bl = &sd->hd->bl; + else if( sd->md && sd->md->bl.id == id ) + bl = &sd->md->bl; + else return; + + unit_stop_attack(bl); + unit_attack(bl, target_id, action_type != 0); +} + + +/// Request to invoke a homunculus menu action (CZ_COMMAND_MER). +/// 022d <type>.W <command>.B +/// type: +/// always 0 +/// command: +/// 0 = homunculus information +/// 1 = feed +/// 2 = delete +void clif_parse_HomMenu(int fd, struct map_session_data *sd) +{ //[orn] + int cmd; + + cmd = RFIFOW(fd,0); + + if(!merc_is_hom_active(sd->hd)) + return; + + merc_menu(sd,RFIFOB(fd,packet_db[sd->packet_ver][cmd].pos[1])); +} + + +/// Request to resurrect oneself using Token of Siegfried (CZ_STANDING_RESURRECTION). +/// 0292 +void clif_parse_AutoRevive(int fd, struct map_session_data *sd) +{ + int item_position = pc_search_inventory(sd, ITEMID_TOKEN_OF_SIEGFRIED); + + if (item_position < 0) + return; + + if (sd->sc.data[SC_HELLPOWER]) //Cannot res while under the effect of SC_HELLPOWER. + return; + + if (!status_revive(&sd->bl, 100, 100)) + return; + + clif_skill_nodamage(&sd->bl,&sd->bl,ALL_RESURRECTION,4,1); + pc_delitem(sd, item_position, 1, 0, 1, LOG_TYPE_CONSUME); +} + + +/// Information about character's status values (ZC_ACK_STATUS_GM). +/// 0214 <str>.B <standardStr>.B <agi>.B <standardAgi>.B <vit>.B <standardVit>.B +/// <int>.B <standardInt>.B <dex>.B <standardDex>.B <luk>.B <standardLuk>.B +/// <attPower>.W <refiningPower>.W <max_mattPower>.W <min_mattPower>.W +/// <itemdefPower>.W <plusdefPower>.W <mdefPower>.W <plusmdefPower>.W +/// <hitSuccessValue>.W <avoidSuccessValue>.W <plusAvoidSuccessValue>.W +/// <criticalSuccessValue>.W <ASPD>.W <plusASPD>.W +void clif_check(int fd, struct map_session_data* pl_sd) +{ + WFIFOHEAD(fd,packet_len(0x214)); + WFIFOW(fd, 0) = 0x214; + WFIFOB(fd, 2) = min(pl_sd->status.str, UINT8_MAX); + WFIFOB(fd, 3) = pc_need_status_point(pl_sd, SP_STR, 1); + WFIFOB(fd, 4) = min(pl_sd->status.agi, UINT8_MAX); + WFIFOB(fd, 5) = pc_need_status_point(pl_sd, SP_AGI, 1); + WFIFOB(fd, 6) = min(pl_sd->status.vit, UINT8_MAX); + WFIFOB(fd, 7) = pc_need_status_point(pl_sd, SP_VIT, 1); + WFIFOB(fd, 8) = min(pl_sd->status.int_, UINT8_MAX); + WFIFOB(fd, 9) = pc_need_status_point(pl_sd, SP_INT, 1); + WFIFOB(fd,10) = min(pl_sd->status.dex, UINT8_MAX); + WFIFOB(fd,11) = pc_need_status_point(pl_sd, SP_DEX, 1); + WFIFOB(fd,12) = min(pl_sd->status.luk, UINT8_MAX); + WFIFOB(fd,13) = pc_need_status_point(pl_sd, SP_LUK, 1); + WFIFOW(fd,14) = pl_sd->battle_status.batk+pl_sd->battle_status.rhw.atk+pl_sd->battle_status.lhw.atk; + WFIFOW(fd,16) = pl_sd->battle_status.rhw.atk2+pl_sd->battle_status.lhw.atk2; + WFIFOW(fd,18) = pl_sd->battle_status.matk_max; + WFIFOW(fd,20) = pl_sd->battle_status.matk_min; + WFIFOW(fd,22) = pl_sd->battle_status.def; + WFIFOW(fd,24) = pl_sd->battle_status.def2; + WFIFOW(fd,26) = pl_sd->battle_status.mdef; + WFIFOW(fd,28) = pl_sd->battle_status.mdef2; + WFIFOW(fd,30) = pl_sd->battle_status.hit; + WFIFOW(fd,32) = pl_sd->battle_status.flee; + WFIFOW(fd,34) = pl_sd->battle_status.flee2/10; + WFIFOW(fd,36) = pl_sd->battle_status.cri/10; + WFIFOW(fd,38) = (2000-pl_sd->battle_status.amotion)/10; // aspd + WFIFOW(fd,40) = 0; // FIXME: What is 'plusASPD' supposed to be? Maybe adelay? + WFIFOSET(fd,packet_len(0x214)); +} + + +/// /check (CZ_REQ_STATUS_GM). +/// Request character's status values. +/// 0213 <char name>.24B +void clif_parse_Check(int fd, struct map_session_data *sd) +{ + char charname[NAME_LENGTH]; + struct map_session_data* pl_sd; + + if(!pc_has_permission(sd, PC_PERM_USE_CHECK)) + return; + + safestrncpy(charname, (const char*)RFIFOP(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]), sizeof(charname)); + + if( ( pl_sd = map_nick2sd(charname) ) == NULL || pc_get_group_level(sd) < pc_get_group_level(pl_sd) ) + { + return; + } + + clif_check(fd, pl_sd); +} + + + +/// MAIL SYSTEM +/// By Zephyrus +/// + +/// Notification about the result of adding an item to mail (ZC_ACK_MAIL_ADD_ITEM). +/// 0255 <index>.W <result>.B +/// result: +/// 0 = success +/// 1 = failure +void clif_Mail_setattachment(int fd, int index, uint8 flag) +{ + WFIFOHEAD(fd,packet_len(0x255)); + WFIFOW(fd,0) = 0x255; + WFIFOW(fd,2) = index; + WFIFOB(fd,4) = flag; + WFIFOSET(fd,packet_len(0x255)); +} + + +/// Notification about the result of retrieving a mail attachment (ZC_MAIL_REQ_GET_ITEM). +/// 0245 <result>.B +/// result: +/// 0 = success +/// 1 = failure +/// 2 = too many items +void clif_Mail_getattachment(int fd, uint8 flag) +{ + WFIFOHEAD(fd,packet_len(0x245)); + WFIFOW(fd,0) = 0x245; + WFIFOB(fd,2) = flag; + WFIFOSET(fd,packet_len(0x245)); +} + + +/// Notification about the result of sending a mail (ZC_MAIL_REQ_SEND). +/// 0249 <result>.B +/// result: +/// 0 = success +/// 1 = recipinent does not exist +void clif_Mail_send(int fd, bool fail) +{ + WFIFOHEAD(fd,packet_len(0x249)); + WFIFOW(fd,0) = 0x249; + WFIFOB(fd,2) = fail; + WFIFOSET(fd,packet_len(0x249)); +} + + +/// Notification about the result of deleting a mail (ZC_ACK_MAIL_DELETE). +/// 0257 <mail id>.L <result>.W +/// result: +/// 0 = success +/// 1 = failure +void clif_Mail_delete(int fd, int mail_id, short fail) +{ + WFIFOHEAD(fd, packet_len(0x257)); + WFIFOW(fd,0) = 0x257; + WFIFOL(fd,2) = mail_id; + WFIFOW(fd,6) = fail; + WFIFOSET(fd, packet_len(0x257)); +} + + +/// Notification about the result of returning a mail (ZC_ACK_MAIL_RETURN). +/// 0274 <mail id>.L <result>.W +/// result: +/// 0 = success +/// 1 = failure +void clif_Mail_return(int fd, int mail_id, short fail) +{ + WFIFOHEAD(fd,packet_len(0x274)); + WFIFOW(fd,0) = 0x274; + WFIFOL(fd,2) = mail_id; + WFIFOW(fd,6) = fail; + WFIFOSET(fd,packet_len(0x274)); +} + + +/// Notification about new mail (ZC_MAIL_RECEIVE). +/// 024a <mail id>.L <title>.40B <sender>.24B +void clif_Mail_new(int fd, int mail_id, const char *sender, const char *title) +{ + WFIFOHEAD(fd,packet_len(0x24a)); + WFIFOW(fd,0) = 0x24a; + WFIFOL(fd,2) = mail_id; + safestrncpy((char*)WFIFOP(fd,6), title, MAIL_TITLE_LENGTH); + safestrncpy((char*)WFIFOP(fd,46), sender, NAME_LENGTH); + WFIFOSET(fd,packet_len(0x24a)); +} + + +/// Opens/closes the mail window (ZC_MAIL_WINDOWS). +/// 0260 <type>.L +/// type: +/// 0 = open +/// 1 = close +void clif_Mail_window(int fd, int flag) +{ + WFIFOHEAD(fd,packet_len(0x260)); + WFIFOW(fd,0) = 0x260; + WFIFOL(fd,2) = flag; + WFIFOSET(fd,packet_len(0x260)); +} + + +/// Lists mails stored in inbox (ZC_MAIL_REQ_GET_LIST). +/// 0240 <packet len>.W <amount>.L { <mail id>.L <title>.40B <read>.B <sender>.24B <time>.L }*amount +/// read: +/// 0 = unread +/// 1 = read +void clif_Mail_refreshinbox(struct map_session_data *sd) +{ + int fd = sd->fd; + struct mail_data *md = &sd->mail.inbox; + struct mail_message *msg; + int len, i, j; + + len = 8 + (73 * md->amount); + + WFIFOHEAD(fd,len); + WFIFOW(fd,0) = 0x240; + WFIFOW(fd,2) = len; + WFIFOL(fd,4) = md->amount; + for( i = j = 0; i < MAIL_MAX_INBOX && j < md->amount; i++ ) + { + msg = &md->msg[i]; + if (msg->id < 1) + continue; + + WFIFOL(fd,8+73*j) = msg->id; + memcpy(WFIFOP(fd,12+73*j), msg->title, MAIL_TITLE_LENGTH); + WFIFOB(fd,52+73*j) = (msg->status != MAIL_UNREAD); + memcpy(WFIFOP(fd,53+73*j), msg->send_name, NAME_LENGTH); + WFIFOL(fd,77+73*j) = (uint32)msg->timestamp; + j++; + } + WFIFOSET(fd,len); + + if( md->full ) + {// TODO: is this official? + char output[100]; + sprintf(output, "Inbox is full (Max %d). Delete some mails.", MAIL_MAX_INBOX); + clif_disp_onlyself(sd, output, strlen(output)); + } +} + + +/// Mail inbox list request (CZ_MAIL_GET_LIST). +/// 023f +void clif_parse_Mail_refreshinbox(int fd, struct map_session_data *sd) +{ + struct mail_data* md = &sd->mail.inbox; + + if( md->amount < MAIL_MAX_INBOX && (md->full || sd->mail.changed) ) + intif_Mail_requestinbox(sd->status.char_id, 1); + else + clif_Mail_refreshinbox(sd); + + mail_removeitem(sd, 0); + mail_removezeny(sd, 0); +} + + +/// Opens a mail (ZC_MAIL_REQ_OPEN). +/// 0242 <packet len>.W <mail id>.L <title>.40B <sender>.24B <time>.L <zeny>.L +/// <amount>.L <name id>.W <item type>.W <identified>.B <damaged>.B <refine>.B +/// <card1>.W <card2>.W <card3>.W <card4>.W <message>.?B +void clif_Mail_read(struct map_session_data *sd, int mail_id) +{ + int i, fd = sd->fd; + + ARR_FIND(0, MAIL_MAX_INBOX, i, sd->mail.inbox.msg[i].id == mail_id); + if( i == MAIL_MAX_INBOX ) + { + clif_Mail_return(sd->fd, mail_id, 1); // Mail doesn't exist + ShowWarning("clif_parse_Mail_read: char '%s' trying to read a message not the inbox.\n", sd->status.name); + return; + } + else + { + struct mail_message *msg = &sd->mail.inbox.msg[i]; + struct item *item = &msg->item; + struct item_data *data; + int msg_len = strlen(msg->body), len; + + if( msg_len == 0 ) { + strcpy(msg->body, "(no message)"); + msg_len = strlen(msg->body); + } + + len = 101 + msg_len; + + WFIFOHEAD(fd,len); + WFIFOW(fd,0) = 0x242; + WFIFOW(fd,2) = len; + WFIFOL(fd,4) = msg->id; + safestrncpy((char*)WFIFOP(fd,8), msg->title, MAIL_TITLE_LENGTH + 1); + safestrncpy((char*)WFIFOP(fd,48), msg->send_name, NAME_LENGTH + 1); + WFIFOL(fd,72) = 0; + WFIFOL(fd,76) = msg->zeny; + + if( item->nameid && (data = itemdb_exists(item->nameid)) != NULL ) + { + WFIFOL(fd,80) = item->amount; + WFIFOW(fd,84) = (data->view_id)?data->view_id:item->nameid; + WFIFOW(fd,86) = data->type; + WFIFOB(fd,88) = item->identify; + WFIFOB(fd,89) = item->attribute; + WFIFOB(fd,90) = item->refine; + WFIFOW(fd,91) = item->card[0]; + WFIFOW(fd,93) = item->card[1]; + WFIFOW(fd,95) = item->card[2]; + WFIFOW(fd,97) = item->card[3]; + } + else // no item, set all to zero + memset(WFIFOP(fd,80), 0x00, 19); + + WFIFOB(fd,99) = (unsigned char)msg_len; + safestrncpy((char*)WFIFOP(fd,100), msg->body, msg_len + 1); + WFIFOSET(fd,len); + + if (msg->status == MAIL_UNREAD) { + msg->status = MAIL_READ; + intif_Mail_read(mail_id); + clif_parse_Mail_refreshinbox(fd, sd); + } + } +} + + +/// Request to open a mail (CZ_MAIL_OPEN). +/// 0241 <mail id>.L +void clif_parse_Mail_read(int fd, struct map_session_data *sd) +{ + int mail_id = RFIFOL(fd,2); + + if( mail_id <= 0 ) + return; + if( mail_invalid_operation(sd) ) + return; + + clif_Mail_read(sd, RFIFOL(fd,2)); +} + + +/// Request to receive mail's attachment (CZ_MAIL_GET_ITEM). +/// 0244 <mail id>.L +void clif_parse_Mail_getattach(int fd, struct map_session_data *sd) +{ + int mail_id = RFIFOL(fd,2); + int i; + bool fail = false; + + if( !chrif_isconnected() ) + return; + if( mail_id <= 0 ) + return; + if( mail_invalid_operation(sd) ) + return; + + ARR_FIND(0, MAIL_MAX_INBOX, i, sd->mail.inbox.msg[i].id == mail_id); + if( i == MAIL_MAX_INBOX ) + return; + + if( sd->mail.inbox.msg[i].zeny < 1 && (sd->mail.inbox.msg[i].item.nameid < 1 || sd->mail.inbox.msg[i].item.amount < 1) ) + return; + + if( sd->mail.inbox.msg[i].zeny + sd->status.zeny > MAX_ZENY ) + { + clif_Mail_getattachment(fd, 1); + return; + } + + if( sd->mail.inbox.msg[i].item.nameid > 0 ) + { + struct item_data *data; + unsigned int weight; + + if ((data = itemdb_exists(sd->mail.inbox.msg[i].item.nameid)) == NULL) + return; + + switch( pc_checkadditem(sd, data->nameid, sd->mail.inbox.msg[i].item.amount) ) + { + case ADDITEM_NEW: + fail = ( pc_inventoryblank(sd) == 0 ); + break; + case ADDITEM_OVERAMOUNT: + fail = true; + } + + if( fail ) + { + clif_Mail_getattachment(fd, 1); + return; + } + + weight = data->weight * sd->mail.inbox.msg[i].item.amount; + if( sd->weight + weight > sd->max_weight ) + { + clif_Mail_getattachment(fd, 2); + return; + } + } + + sd->mail.inbox.msg[i].zeny = 0; + memset(&sd->mail.inbox.msg[i].item, 0, sizeof(struct item)); + clif_Mail_read(sd, mail_id); + + intif_Mail_getattach(sd->status.char_id, mail_id); +} + + +/// Request to delete a mail (CZ_MAIL_DELETE). +/// 0243 <mail id>.L +void clif_parse_Mail_delete(int fd, struct map_session_data *sd) +{ + int mail_id = RFIFOL(fd,2); + int i; + + if( !chrif_isconnected() ) + return; + if( mail_id <= 0 ) + return; + if( mail_invalid_operation(sd) ) + return; + + ARR_FIND(0, MAIL_MAX_INBOX, i, sd->mail.inbox.msg[i].id == mail_id); + if (i < MAIL_MAX_INBOX) + { + struct mail_message *msg = &sd->mail.inbox.msg[i]; + + if( (msg->item.nameid > 0 && msg->item.amount > 0) || msg->zeny > 0 ) + {// can't delete mail without removing attachment first + clif_Mail_delete(sd->fd, mail_id, 1); + return; + } + + intif_Mail_delete(sd->status.char_id, mail_id); + } +} + + +/// Request to return a mail (CZ_REQ_MAIL_RETURN). +/// 0273 <mail id>.L <receive name>.24B +void clif_parse_Mail_return(int fd, struct map_session_data *sd) +{ + int mail_id = RFIFOL(fd,2); + int i; + + if( mail_id <= 0 ) + return; + if( mail_invalid_operation(sd) ) + return; + + ARR_FIND(0, MAIL_MAX_INBOX, i, sd->mail.inbox.msg[i].id == mail_id); + if( i < MAIL_MAX_INBOX && sd->mail.inbox.msg[i].send_id != 0 ) + intif_Mail_return(sd->status.char_id, mail_id); + else + clif_Mail_return(sd->fd, mail_id, 1); +} + + +/// Request to add an item or Zeny to mail (CZ_MAIL_ADD_ITEM). +/// 0247 <index>.W <amount>.L +void clif_parse_Mail_setattach(int fd, struct map_session_data *sd) +{ + int idx = RFIFOW(fd,2); + int amount = RFIFOL(fd,4); + unsigned char flag; + + if( !chrif_isconnected() ) + return; + if (idx < 0 || amount < 0) + return; + + flag = mail_setitem(sd, idx, amount); + clif_Mail_setattachment(fd,idx,flag); +} + + +/// Request to reset mail item and/or Zeny (CZ_MAIL_RESET_ITEM). +/// 0246 <type>.W +/// type: +/// 0 = reset all +/// 1 = remove item +/// 2 = remove zeny +void clif_parse_Mail_winopen(int fd, struct map_session_data *sd) +{ + int flag = RFIFOW(fd,2); + + if (flag == 0 || flag == 1) + mail_removeitem(sd, 0); + if (flag == 0 || flag == 2) + mail_removezeny(sd, 0); +} + + +/// Request to send mail (CZ_MAIL_SEND). +/// 0248 <packet len>.W <recipient>.24B <title>.40B <body len>.B <body>.?B +void clif_parse_Mail_send(int fd, struct map_session_data *sd) +{ + struct mail_message msg; + int body_len; + + if( !chrif_isconnected() ) + return; + if( sd->state.trading ) + return; + + if( RFIFOW(fd,2) < 69 ) { + ShowWarning("Invalid Msg Len from account %d.\n", sd->status.account_id); + return; + } + + if( DIFF_TICK(sd->cansendmail_tick, gettick()) > 0 ) + { + clif_displaymessage(sd->fd,msg_txt(675)); //"Cannot send mails too fast!!." + clif_Mail_send(fd, true); // fail + return; + } + + body_len = RFIFOB(fd,68); + + if (body_len > MAIL_BODY_LENGTH) + body_len = MAIL_BODY_LENGTH; + + if( !mail_setattachment(sd, &msg) ) + { // Invalid Append condition + clif_Mail_send(sd->fd, true); // fail + mail_removeitem(sd,0); + mail_removezeny(sd,0); + return; + } + + msg.id = 0; // id will be assigned by charserver + msg.send_id = sd->status.char_id; + msg.dest_id = 0; // will attempt to resolve name + safestrncpy(msg.send_name, sd->status.name, NAME_LENGTH); + safestrncpy(msg.dest_name, (char*)RFIFOP(fd,4), NAME_LENGTH); + safestrncpy(msg.title, (char*)RFIFOP(fd,28), MAIL_TITLE_LENGTH); + + if (msg.title[0] == '\0') { + return; // Message has no length and somehow client verification was skipped. + } + + if (body_len) + safestrncpy(msg.body, (char*)RFIFOP(fd,69), body_len + 1); + else + memset(msg.body, 0x00, MAIL_BODY_LENGTH); + + msg.timestamp = time(NULL); + if( !intif_Mail_send(sd->status.account_id, &msg) ) + mail_deliveryfail(sd, &msg); + + sd->cansendmail_tick = gettick() + 1000; // 1 Second flood Protection +} + + +/// AUCTION SYSTEM +/// By Zephyrus +/// + +/// Opens/closes the auction window (ZC_AUCTION_WINDOWS). +/// 025f <type>.L +/// type: +/// 0 = open +/// 1 = close +void clif_Auction_openwindow(struct map_session_data *sd) +{ + int fd = sd->fd; + + if( sd->state.storage_flag || sd->state.vending || sd->state.buyingstore || sd->state.trading ) + return; + + WFIFOHEAD(fd,packet_len(0x25f)); + WFIFOW(fd,0) = 0x25f; + WFIFOL(fd,2) = 0; + WFIFOSET(fd,packet_len(0x25f)); +} + + +/// Returns auction item search results (ZC_AUCTION_ITEM_REQ_SEARCH). +/// 0252 <packet len>.W <pages>.L <count>.L { <auction id>.L <seller name>.24B <name id>.W <type>.L <amount>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W <now price>.L <max price>.L <buyer name>.24B <delete time>.L }* +void clif_Auction_results(struct map_session_data *sd, short count, short pages, uint8 *buf) +{ + int i, fd = sd->fd, len = sizeof(struct auction_data); + struct auction_data auction; + struct item_data *item; + int k; + + WFIFOHEAD(fd,12 + (count * 83)); + WFIFOW(fd,0) = 0x252; + WFIFOW(fd,2) = 12 + (count * 83); + WFIFOL(fd,4) = pages; + WFIFOL(fd,8) = count; + + for( i = 0; i < count; i++ ) + { + memcpy(&auction, RBUFP(buf,i * len), len); + k = 12 + (i * 83); + + WFIFOL(fd,k) = auction.auction_id; + safestrncpy((char*)WFIFOP(fd,4+k), auction.seller_name, NAME_LENGTH); + + if( (item = itemdb_exists(auction.item.nameid)) != NULL && item->view_id > 0 ) + WFIFOW(fd,28+k) = item->view_id; + else + WFIFOW(fd,28+k) = auction.item.nameid; + + WFIFOL(fd,30+k) = auction.type; + WFIFOW(fd,34+k) = auction.item.amount; // Always 1 + WFIFOB(fd,36+k) = auction.item.identify; + WFIFOB(fd,37+k) = auction.item.attribute; + WFIFOB(fd,38+k) = auction.item.refine; + WFIFOW(fd,39+k) = auction.item.card[0]; + WFIFOW(fd,41+k) = auction.item.card[1]; + WFIFOW(fd,43+k) = auction.item.card[2]; + WFIFOW(fd,45+k) = auction.item.card[3]; + WFIFOL(fd,47+k) = auction.price; + WFIFOL(fd,51+k) = auction.buynow; + safestrncpy((char*)WFIFOP(fd,55+k), auction.buyer_name, NAME_LENGTH); + WFIFOL(fd,79+k) = (uint32)auction.timestamp; + } + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Result from request to add an item (ZC_ACK_AUCTION_ADD_ITEM). +/// 0256 <index>.W <result>.B +/// result: +/// 0 = success +/// 1 = failure +static void clif_Auction_setitem(int fd, int index, bool fail) +{ + WFIFOHEAD(fd,packet_len(0x256)); + WFIFOW(fd,0) = 0x256; + WFIFOW(fd,2) = index; + WFIFOB(fd,4) = fail; + WFIFOSET(fd,packet_len(0x256)); +} + + +/// Request to initialize 'new auction' data (CZ_AUCTION_CREATE). +/// 024b <type>.W +/// type: +/// 0 = create (any other action in auction window) +/// 1 = cancel (cancel pressed on register tab) +/// ? = junk, uninitialized value (ex. when switching between list filters) +void clif_parse_Auction_cancelreg(int fd, struct map_session_data *sd) +{ + if( sd->auction.amount > 0 ) + clif_additem(sd, sd->auction.index, sd->auction.amount, 0); + + sd->auction.amount = 0; +} + + +/// Request to add an item to the action (CZ_AUCTION_ADD_ITEM). +/// 024c <index>.W <count>.L +void clif_parse_Auction_setitem(int fd, struct map_session_data *sd) +{ + int idx = RFIFOW(fd,2) - 2; + int amount = RFIFOL(fd,4); // Always 1 + struct item_data *item; + + if( sd->auction.amount > 0 ) + sd->auction.amount = 0; + + if( idx < 0 || idx >= MAX_INVENTORY ) + { + ShowWarning("Character %s trying to set invalid item index in auctions.\n", sd->status.name); + return; + } + + if( amount != 1 || amount > sd->status.inventory[idx].amount ) + { // By client, amount is always set to 1. Maybe this is a future implementation. + ShowWarning("Character %s trying to set invalid amount in auctions.\n", sd->status.name); + return; + } + + if( (item = itemdb_exists(sd->status.inventory[idx].nameid)) != NULL && !(item->type == IT_ARMOR || item->type == IT_PETARMOR || item->type == IT_WEAPON || item->type == IT_CARD || item->type == IT_ETC) ) + { // Consumable or pets are not allowed + clif_Auction_setitem(sd->fd, idx, true); + return; + } + + if( !pc_can_give_items(sd) || sd->status.inventory[idx].expire_time || + !sd->status.inventory[idx].identify || + !itemdb_canauction(&sd->status.inventory[idx],pc_get_group_level(sd)) ) { // Quest Item or something else + clif_Auction_setitem(sd->fd, idx, true); + return; + } + + sd->auction.index = idx; + sd->auction.amount = amount; + clif_Auction_setitem(fd, idx + 2, false); +} + +/// Result from an auction action (ZC_AUCTION_RESULT). +/// 0250 <result>.B +/// result: +/// 0 = You have failed to bid into the auction +/// 1 = You have successfully bid in the auction +/// 2 = The auction has been canceled +/// 3 = An auction with at least one bidder cannot be canceled +/// 4 = You cannot register more than 5 items in an auction at a time +/// 5 = You do not have enough Zeny to pay the Auction Fee +/// 6 = You have won the auction +/// 7 = You have failed to win the auction +/// 8 = You do not have enough Zeny +/// 9 = You cannot place more than 5 bids at a time +void clif_Auction_message(int fd, unsigned char flag) +{ + WFIFOHEAD(fd,packet_len(0x250)); + WFIFOW(fd,0) = 0x250; + WFIFOB(fd,2) = flag; + WFIFOSET(fd,packet_len(0x250)); +} + + +/// Result of the auction close request (ZC_AUCTION_ACK_MY_SELL_STOP). +/// 025e <result>.W +/// result: +/// 0 = You have ended the auction +/// 1 = You cannot end the auction +/// 2 = Auction ID is incorrect +void clif_Auction_close(int fd, unsigned char flag) +{ + WFIFOHEAD(fd,packet_len(0x25e)); + WFIFOW(fd,0) = 0x25d; // BUG: The client identifies this packet as 0x25d (CZ_AUCTION_REQ_MY_SELL_STOP) + WFIFOW(fd,2) = flag; + WFIFOSET(fd,packet_len(0x25e)); +} + + +/// Request to add an auction (CZ_AUCTION_ADD). +/// 024d <now money>.L <max money>.L <delete hour>.W +void clif_parse_Auction_register(int fd, struct map_session_data *sd) +{ + struct auction_data auction; + struct item_data *item; + + auction.price = RFIFOL(fd,2); + auction.buynow = RFIFOL(fd,6); + auction.hours = RFIFOW(fd,10); + + // Invalid Situations... + if( sd->auction.amount < 1 ) + { + ShowWarning("Character %s trying to register auction without item.\n", sd->status.name); + return; + } + + if( auction.price >= auction.buynow ) + { + ShowWarning("Character %s trying to alter auction prices.\n", sd->status.name); + return; + } + + if( auction.hours < 1 || auction.hours > 48 ) + { + ShowWarning("Character %s trying to enter an invalid time for auction.\n", sd->status.name); + return; + } + + // Auction checks... + if( sd->status.zeny < (auction.hours * battle_config.auction_feeperhour) ) + { + clif_Auction_message(fd, 5); // You do not have enough zeny to pay the Auction Fee. + return; + } + + if( auction.buynow > battle_config.auction_maximumprice ) + { // Zeny Limits + auction.buynow = battle_config.auction_maximumprice; + if( auction.price >= auction.buynow ) + auction.price = auction.buynow - 1; + } + + auction.auction_id = 0; + auction.seller_id = sd->status.char_id; + safestrncpy(auction.seller_name, sd->status.name, sizeof(auction.seller_name)); + auction.buyer_id = 0; + memset(auction.buyer_name, '\0', sizeof(auction.buyer_name)); + + if( sd->status.inventory[sd->auction.index].nameid == 0 || sd->status.inventory[sd->auction.index].amount < sd->auction.amount ) + { + clif_Auction_message(fd, 2); // The auction has been canceled + return; + } + + if( (item = itemdb_exists(sd->status.inventory[sd->auction.index].nameid)) == NULL ) + { // Just in case + clif_Auction_message(fd, 2); // The auction has been canceled + return; + } + + safestrncpy(auction.item_name, item->jname, sizeof(auction.item_name)); + auction.type = item->type; + memcpy(&auction.item, &sd->status.inventory[sd->auction.index], sizeof(struct item)); + auction.item.amount = 1; + auction.timestamp = 0; + + if( !intif_Auction_register(&auction) ) + clif_Auction_message(fd, 4); // No Char Server? lets say something to the client + else + { + int zeny = auction.hours*battle_config.auction_feeperhour; + + pc_delitem(sd, sd->auction.index, sd->auction.amount, 1, 6, LOG_TYPE_AUCTION); + sd->auction.amount = 0; + + pc_payzeny(sd, zeny, LOG_TYPE_AUCTION, NULL); + } +} + + +/// Cancels an auction (CZ_AUCTION_ADD_CANCEL). +/// 024e <auction id>.L +void clif_parse_Auction_cancel(int fd, struct map_session_data *sd) +{ + unsigned int auction_id = RFIFOL(fd,2); + + intif_Auction_cancel(sd->status.char_id, auction_id); +} + + +/// Closes an auction (CZ_AUCTION_REQ_MY_SELL_STOP). +/// 025d <auction id>.L +void clif_parse_Auction_close(int fd, struct map_session_data *sd) +{ + unsigned int auction_id = RFIFOL(fd,2); + + intif_Auction_close(sd->status.char_id, auction_id); +} + + +/// Places a bid on an auction (CZ_AUCTION_BUY). +/// 024f <auction id>.L <money>.L +void clif_parse_Auction_bid(int fd, struct map_session_data *sd) +{ + unsigned int auction_id = RFIFOL(fd,2); + int bid = RFIFOL(fd,6); + + if( !pc_can_give_items(sd) ) { //They aren't supposed to give zeny [Inkfish] + clif_displaymessage(sd->fd, msg_txt(246)); + return; + } + + if( bid <= 0 ) + clif_Auction_message(fd, 0); // You have failed to bid into the auction + else if( bid > sd->status.zeny ) + clif_Auction_message(fd, 8); // You do not have enough zeny + else if ( CheckForCharServer() ) // char server is down (bugreport:1138) + clif_Auction_message(fd, 0); // You have failed to bid into the auction + else { + pc_payzeny(sd, bid, LOG_TYPE_AUCTION, NULL); + intif_Auction_bid(sd->status.char_id, sd->status.name, auction_id, bid); + } +} + + +/// Auction Search (CZ_AUCTION_ITEM_SEARCH). +/// 0251 <search type>.W <auction id>.L <search text>.24B <page number>.W +/// search type: +/// 0 = armor +/// 1 = weapon +/// 2 = card +/// 3 = misc +/// 4 = name search +/// 5 = auction id search +void clif_parse_Auction_search(int fd, struct map_session_data* sd) +{ + char search_text[NAME_LENGTH]; + short type = RFIFOW(fd,2), page = RFIFOW(fd,32); + int price = RFIFOL(fd,4); // FIXME: bug #5071 + + clif_parse_Auction_cancelreg(fd, sd); + + safestrncpy(search_text, (char*)RFIFOP(fd,8), sizeof(search_text)); + intif_Auction_requestlist(sd->status.char_id, type, price, search_text, page); +} + + +/// Requests list of own currently active bids or auctions (CZ_AUCTION_REQ_MY_INFO). +/// 025c <type>.W +/// type: +/// 0 = sell (own auctions) +/// 1 = buy (own bids) +void clif_parse_Auction_buysell(int fd, struct map_session_data* sd) +{ + short type = RFIFOW(fd,2) + 6; + clif_parse_Auction_cancelreg(fd, sd); + + intif_Auction_requestlist(sd->status.char_id, type, 0, "", 1); +} + + +/// CASH/POINT SHOP +/// + +/// List of items offered in a cash shop (ZC_PC_CASH_POINT_ITEMLIST). +/// 0287 <packet len>.W <cash point>.L { <sell price>.L <discount price>.L <item type>.B <name id>.W }* +/// 0287 <packet len>.W <cash point>.L <kafra point>.L { <sell price>.L <discount price>.L <item type>.B <name id>.W }* (PACKETVER >= 20070711) +void clif_cashshop_show(struct map_session_data *sd, struct npc_data *nd) +{ + int fd,i; +#if PACKETVER < 20070711 + const int offset = 8; +#else + const int offset = 12; +#endif + + nullpo_retv(sd); + nullpo_retv(nd); + + fd = sd->fd; + sd->npc_shopid = nd->bl.id; + WFIFOHEAD(fd,offset+nd->u.shop.count*11); + WFIFOW(fd,0) = 0x287; + WFIFOW(fd,2) = offset+nd->u.shop.count*11; + WFIFOL(fd,4) = sd->cashPoints; // Cash Points +#if PACKETVER >= 20070711 + WFIFOL(fd,8) = sd->kafraPoints; // Kafra Points +#endif + + for( i = 0; i < nd->u.shop.count; i++ ) + { + struct item_data* id = itemdb_search(nd->u.shop.shop_item[i].nameid); + WFIFOL(fd,offset+0+i*11) = nd->u.shop.shop_item[i].value; + WFIFOL(fd,offset+4+i*11) = nd->u.shop.shop_item[i].value; // Discount Price + WFIFOB(fd,offset+8+i*11) = itemtype(id->type); + WFIFOW(fd,offset+9+i*11) = ( id->view_id > 0 ) ? id->view_id : id->nameid; + } + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Cashshop Buy Ack (ZC_PC_CASH_POINT_UPDATE). +/// 0289 <cash point>.L <error>.W +/// 0289 <cash point>.L <kafra point>.L <error>.W (PACKETVER >= 20070711) +/// error: +/// 0 = The deal has successfully completed. (ERROR_TYPE_NONE) +/// 1 = The Purchase has failed because the NPC does not exist. (ERROR_TYPE_NPC) +/// 2 = The Purchase has failed because the Kafra Shop System is not working correctly. (ERROR_TYPE_SYSTEM) +/// 3 = You are over your Weight Limit. (ERROR_TYPE_INVENTORY_WEIGHT) +/// 4 = You cannot purchase items while you are in a trade. (ERROR_TYPE_EXCHANGE) +/// 5 = The Purchase has failed because the Item Information was incorrect. (ERROR_TYPE_ITEM_ID) +/// 6 = You do not have enough Kafra Credit Points. (ERROR_TYPE_MONEY) +/// 7 = You can purchase up to 10 items. +/// 8 = Some items could not be purchased. +void clif_cashshop_ack(struct map_session_data* sd, int error) +{ + int fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0x289)); + WFIFOW(fd,0) = 0x289; + WFIFOL(fd,2) = sd->cashPoints; +#if PACKETVER < 20070711 + WFIFOW(fd,6) = TOW(error); +#else + WFIFOL(fd,6) = sd->kafraPoints; + WFIFOW(fd,10) = TOW(error); +#endif + WFIFOSET(fd, packet_len(0x289)); +} + + +/// Request to buy item(s) from cash shop (CZ_PC_BUY_CASH_POINT_ITEM). +/// 0288 <name id>.W <amount>.W +/// 0288 <name id>.W <amount>.W <kafra points>.L (PACKETVER >= 20070711) +/// 0288 <packet len>.W <kafra points>.L <count>.W { <amount>.W <name id>.W }.4B*count (PACKETVER >= 20100803) +void clif_parse_cashshop_buy(int fd, struct map_session_data *sd) +{ + int fail = 0; + nullpo_retv(sd); + + if( sd->state.trading || !sd->npc_shopid ) + fail = 1; + else + { +#if PACKETVER < 20101116 + short nameid = RFIFOW(fd,2); + short amount = RFIFOW(fd,4); + int points = RFIFOL(fd,6); + + fail = npc_cashshop_buy(sd, nameid, amount, points); +#else + int len = RFIFOW(fd,2); + int points = RFIFOL(fd,4); + int count = RFIFOW(fd,8); + unsigned short* item_list = (unsigned short*)RFIFOP(fd,10); + + if( len < 10 || len != 10 + count * 4) + { + ShowWarning("Player %u sent incorrect cash shop buy packet (len %u:%u)!\n", sd->status.char_id, len, 10 + count * 4); + return; + } + fail = npc_cashshop_buylist(sd,points,count,item_list); +#endif + } + + clif_cashshop_ack(sd,fail); +} + + +/// Adoption System +/// + +/// Adoption message (ZC_BABYMSG). +/// 0216 <msg>.L +/// msg: +/// 0 = "You cannot adopt more than 1 child." +/// 1 = "You must be at least character level 70 in order to adopt someone." +/// 2 = "You cannot adopt a married person." +void clif_Adopt_reply(struct map_session_data *sd, int type) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,6); + WFIFOW(fd,0) = 0x216; + WFIFOL(fd,2) = type; + WFIFOSET(fd,6); +} + + +/// Adoption confirmation (ZC_REQ_BABY). +/// 01f6 <account id>.L <char id>.L <name>.B +void clif_Adopt_request(struct map_session_data *sd, struct map_session_data *src, int p_id) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,34); + WFIFOW(fd,0) = 0x1f6; + WFIFOL(fd,2) = src->status.account_id; + WFIFOL(fd,6) = p_id; + memcpy(WFIFOP(fd,10), src->status.name, NAME_LENGTH); + WFIFOSET(fd,34); +} + + +/// Request to adopt a player (CZ_REQ_JOIN_BABY). +/// 01f9 <account id>.L +void clif_parse_Adopt_request(int fd, struct map_session_data *sd) +{ + struct map_session_data *tsd = map_id2sd(RFIFOL(fd,2)), *p_sd = map_charid2sd(sd->status.partner_id); + + if( pc_can_Adopt(sd, p_sd, tsd) ) + { + tsd->adopt_invite = sd->status.account_id; + clif_Adopt_request(tsd, sd, p_sd->status.account_id); + } +} + + +/// Answer to adopt confirmation (CZ_JOIN_BABY). +/// 01f7 <account id>.L <char id>.L <answer>.L +/// answer: +/// 0 = rejected +/// 1 = accepted +void clif_parse_Adopt_reply(int fd, struct map_session_data *sd) +{ + int p1_id = RFIFOL(fd,2); + int p2_id = RFIFOL(fd,6); + int result = RFIFOL(fd,10); + struct map_session_data* p1_sd = map_id2sd(p1_id); + struct map_session_data* p2_sd = map_id2sd(p2_id); + + int pid = sd->adopt_invite; + sd->adopt_invite = 0; + + if( p1_sd == NULL || p2_sd == NULL ) + return; // Both players need to be online + + if( pid != p1_sd->status.account_id ) + return; // Incorrect values + + if( result == 0 ) + return; // Rejected + + pc_adoption(p1_sd, p2_sd, sd); +} + + +/// Convex Mirror (ZC_BOSS_INFO). +/// 0293 <infoType>.B <x>.L <y>.L <minHours>.W <minMinutes>.W <maxHours>.W <maxMinutes>.W <monster name>.51B +/// infoType: +/// 0 = No boss on this map (BOSS_INFO_NOT). +/// 1 = Boss is alive (position update) (BOSS_INFO_ALIVE). +/// 2 = Boss is alive (initial announce) (BOSS_INFO_ALIVE_WITHMSG). +/// 3 = Boss is dead (BOSS_INFO_DEAD). +void clif_bossmapinfo(int fd, struct mob_data *md, short flag) +{ + WFIFOHEAD(fd,70); + memset(WFIFOP(fd,0),0,70); + WFIFOW(fd,0) = 0x293; + + if( md != NULL ) + { + if( md->bl.prev != NULL ) + { // Boss on This Map + if( flag ) + { + WFIFOB(fd,2) = 1; + WFIFOL(fd,3) = md->bl.x; + WFIFOL(fd,7) = md->bl.y; + } + else + WFIFOB(fd,2) = 2; // First Time + } + else if (md->spawn_timer != INVALID_TIMER) + { // Boss is Dead + const struct TimerData * timer_data = get_timer(md->spawn_timer); + unsigned int seconds; + int hours, minutes; + + seconds = DIFF_TICK(timer_data->tick, gettick()) / 1000 + 60; + hours = seconds / (60 * 60); + seconds = seconds - (60 * 60 * hours); + minutes = seconds / 60; + + WFIFOB(fd,2) = 3; + WFIFOW(fd,11) = hours; // Hours + WFIFOW(fd,13) = minutes; // Minutes + } + safestrncpy((char*)WFIFOP(fd,19), md->db->jname, NAME_LENGTH); + } + + WFIFOSET(fd,70); +} + + +/// Requesting equip of a player (CZ_EQUIPWIN_MICROSCOPE). +/// 02d6 <account id>.L +void clif_parse_ViewPlayerEquip(int fd, struct map_session_data* sd) +{ + int charid = RFIFOL(fd, 2); + struct map_session_data* tsd = map_id2sd(charid); + + if (!tsd) + return; + + if( tsd->status.show_equip || pc_has_permission(sd, PC_PERM_VIEW_EQUIPMENT) ) + clif_viewequip_ack(sd, tsd); + else + clif_viewequip_fail(sd); +} + + +/// Request to change equip window tick (CZ_CONFIG). +/// 02d8 <type>.L <value>.L +/// type: +/// 0 = open equip window +/// value: +/// 0 = disabled +/// 1 = enabled +void clif_parse_EquipTick(int fd, struct map_session_data* sd) +{ + bool flag = (bool)RFIFOL(fd,6); + sd->status.show_equip = flag; + clif_equiptickack(sd, flag); +} + + +/// Questlog System [Kevin] [Inkfish] +/// + +/// Sends list of all quest states (ZC_ALL_QUEST_LIST). +/// 02b1 <packet len>.W <num>.L { <quest id>.L <active>.B }*num +void clif_quest_send_list(struct map_session_data * sd) +{ + int fd = sd->fd; + int i; + int len = sd->avail_quests*5+8; + + WFIFOHEAD(fd,len); + WFIFOW(fd, 0) = 0x2b1; + WFIFOW(fd, 2) = len; + WFIFOL(fd, 4) = sd->avail_quests; + + for( i = 0; i < sd->avail_quests; i++ ) + { + WFIFOL(fd, i*5+8) = sd->quest_log[i].quest_id; + WFIFOB(fd, i*5+12) = sd->quest_log[i].state; + } + + WFIFOSET(fd, len); +} + + +/// Sends list of all quest missions (ZC_ALL_QUEST_MISSION). +/// 02b2 <packet len>.W <num>.L { <quest id>.L <start time>.L <expire time>.L <mobs>.W { <mob id>.L <mob count>.W <mob name>.24B }*3 }*num +void clif_quest_send_mission(struct map_session_data * sd) +{ + int fd = sd->fd; + int i, j; + int len = sd->avail_quests*104+8; + struct mob_db *mob; + + WFIFOHEAD(fd, len); + WFIFOW(fd, 0) = 0x2b2; + WFIFOW(fd, 2) = len; + WFIFOL(fd, 4) = sd->avail_quests; + + for( i = 0; i < sd->avail_quests; i++ ) + { + WFIFOL(fd, i*104+8) = sd->quest_log[i].quest_id; + WFIFOL(fd, i*104+12) = sd->quest_log[i].time - quest_db[sd->quest_index[i]].time; + WFIFOL(fd, i*104+16) = sd->quest_log[i].time; + WFIFOW(fd, i*104+20) = quest_db[sd->quest_index[i]].num_objectives; + + for( j = 0 ; j < quest_db[sd->quest_index[i]].num_objectives; j++ ) + { + WFIFOL(fd, i*104+22+j*30) = quest_db[sd->quest_index[i]].mob[j]; + WFIFOW(fd, i*104+26+j*30) = sd->quest_log[i].count[j]; + mob = mob_db(quest_db[sd->quest_index[i]].mob[j]); + memcpy(WFIFOP(fd, i*104+28+j*30), mob?mob->jname:"NULL", NAME_LENGTH); + } + } + + WFIFOSET(fd, len); +} + + +/// Notification about a new quest (ZC_ADD_QUEST). +/// 02b3 <quest id>.L <active>.B <start time>.L <expire time>.L <mobs>.W { <mob id>.L <mob count>.W <mob name>.24B }*3 +void clif_quest_add(struct map_session_data * sd, struct quest * qd, int index) +{ + int fd = sd->fd; + int i; + struct mob_db *mob; + + WFIFOHEAD(fd, packet_len(0x2b3)); + WFIFOW(fd, 0) = 0x2b3; + WFIFOL(fd, 2) = qd->quest_id; + WFIFOB(fd, 6) = qd->state; + WFIFOB(fd, 7) = qd->time - quest_db[index].time; + WFIFOL(fd, 11) = qd->time; + WFIFOW(fd, 15) = quest_db[index].num_objectives; + + for( i = 0; i < quest_db[index].num_objectives; i++ ) + { + WFIFOL(fd, i*30+17) = quest_db[index].mob[i]; + WFIFOW(fd, i*30+21) = qd->count[i]; + mob = mob_db(quest_db[index].mob[i]); + memcpy(WFIFOP(fd, i*30+23), mob?mob->jname:"NULL", NAME_LENGTH); + } + + WFIFOSET(fd, packet_len(0x2b3)); +} + + +/// Notification about a quest being removed (ZC_DEL_QUEST). +/// 02b4 <quest id>.L +void clif_quest_delete(struct map_session_data * sd, int quest_id) +{ + int fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0x2b4)); + WFIFOW(fd, 0) = 0x2b4; + WFIFOL(fd, 2) = quest_id; + WFIFOSET(fd, packet_len(0x2b4)); +} + + +/// Notification of an update to the hunting mission counter (ZC_UPDATE_MISSION_HUNT). +/// 02b5 <packet len>.W <mobs>.W { <quest id>.L <mob id>.L <total count>.W <current count>.W }*3 +void clif_quest_update_objective(struct map_session_data * sd, struct quest * qd, int index) +{ + int fd = sd->fd; + int i; + int len = quest_db[index].num_objectives*12+6; + + WFIFOHEAD(fd, len); + WFIFOW(fd, 0) = 0x2b5; + WFIFOW(fd, 2) = len; + WFIFOW(fd, 4) = quest_db[index].num_objectives; + + for( i = 0; i < quest_db[index].num_objectives; i++ ) + { + WFIFOL(fd, i*12+6) = qd->quest_id; + WFIFOL(fd, i*12+10) = quest_db[index].mob[i]; + WFIFOW(fd, i*12+14) = quest_db[index].count[i]; + WFIFOW(fd, i*12+16) = qd->count[i]; + } + + WFIFOSET(fd, len); +} + + +/// Request to change the state of a quest (CZ_ACTIVE_QUEST). +/// 02b6 <quest id>.L <active>.B +void clif_parse_questStateAck(int fd, struct map_session_data * sd) +{ + quest_update_status(sd, RFIFOL(fd,2), RFIFOB(fd,6)?Q_ACTIVE:Q_INACTIVE); +} + + +/// Notification about the change of a quest state (ZC_ACTIVE_QUEST). +/// 02b7 <quest id>.L <active>.B +void clif_quest_update_status(struct map_session_data * sd, int quest_id, bool active) +{ + int fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0x2b7)); + WFIFOW(fd, 0) = 0x2b7; + WFIFOL(fd, 2) = quest_id; + WFIFOB(fd, 6) = active; + WFIFOSET(fd, packet_len(0x2b7)); +} + + +/// Notification about an NPC's quest state (ZC_QUEST_NOTIFY_EFFECT). +/// 0446 <npc id>.L <x>.W <y>.W <effect>.W <type>.W +/// effect: +/// 0 = none +/// 1 = exclamation mark icon +/// 2 = question mark icon +/// type: +/// 0 = yellow +/// 1 = orange +/// 2 = green +/// 3 = purple +void clif_quest_show_event(struct map_session_data *sd, struct block_list *bl, short state, short color) +{ +#if PACKETVER >= 20090218 + int fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0x446)); + WFIFOW(fd, 0) = 0x446; + WFIFOL(fd, 2) = bl->id; + WFIFOW(fd, 6) = bl->x; + WFIFOW(fd, 8) = bl->y; + WFIFOW(fd, 10) = state; + WFIFOW(fd, 12) = color; + WFIFOSET(fd, packet_len(0x446)); +#endif +} + + +/// Mercenary System +/// + +/// Notification about a mercenary status parameter change (ZC_MER_PAR_CHANGE). +/// 02a2 <var id>.W <value>.L +void clif_mercenary_updatestatus(struct map_session_data *sd, int type) +{ + struct mercenary_data *md; + struct status_data *status; + int fd; + if( sd == NULL || (md = sd->md) == NULL ) + return; + + fd = sd->fd; + status = &md->battle_status; + WFIFOHEAD(fd,packet_len(0x2a2)); + WFIFOW(fd,0) = 0x2a2; + WFIFOW(fd,2) = type; + switch( type ) + { + case SP_ATK1: + { + int atk = rnd()%(status->rhw.atk2 - status->rhw.atk + 1) + status->rhw.atk; + WFIFOL(fd,4) = cap_value(atk, 0, INT16_MAX); + } + break; + case SP_MATK1: + WFIFOL(fd,4) = cap_value(status->matk_max, 0, INT16_MAX); + break; + case SP_HIT: + WFIFOL(fd,4) = status->hit; + break; + case SP_CRITICAL: + WFIFOL(fd,4) = status->cri/10; + break; + case SP_DEF1: + WFIFOL(fd,4) = status->def; + break; + case SP_MDEF1: + WFIFOL(fd,4) = status->mdef; + break; + case SP_MERCFLEE: + WFIFOL(fd,4) = status->flee; + break; + case SP_ASPD: + WFIFOL(fd,4) = status->amotion; + break; + case SP_HP: + WFIFOL(fd,4) = status->hp; + break; + case SP_MAXHP: + WFIFOL(fd,4) = status->max_hp; + break; + case SP_SP: + WFIFOL(fd,4) = status->sp; + break; + case SP_MAXSP: + WFIFOL(fd,4) = status->max_sp; + break; + case SP_MERCKILLS: + WFIFOL(fd,4) = md->mercenary.kill_count; + break; + case SP_MERCFAITH: + WFIFOL(fd,4) = mercenary_get_faith(md); + break; + } + WFIFOSET(fd,packet_len(0x2a2)); +} + + +/// Mercenary base status data (ZC_MER_INIT). +/// 029b <id>.L <atk>.W <matk>.W <hit>.W <crit>.W <def>.W <mdef>.W <flee>.W <aspd>.W +/// <name>.24B <level>.W <hp>.L <maxhp>.L <sp>.L <maxsp>.L <expire time>.L <faith>.W +/// <calls>.L <kills>.L <atk range>.W +void clif_mercenary_info(struct map_session_data *sd) +{ + int fd; + struct mercenary_data *md; + struct status_data *status; + int atk; + + if( sd == NULL || (md = sd->md) == NULL ) + return; + + fd = sd->fd; + status = &md->battle_status; + + WFIFOHEAD(fd,packet_len(0x29b)); + WFIFOW(fd,0) = 0x29b; + WFIFOL(fd,2) = md->bl.id; + + // Mercenary shows ATK as a random value between ATK ~ ATK2 + atk = rnd()%(status->rhw.atk2 - status->rhw.atk + 1) + status->rhw.atk; + WFIFOW(fd,6) = cap_value(atk, 0, INT16_MAX); + WFIFOW(fd,8) = cap_value(status->matk_max, 0, INT16_MAX); + WFIFOW(fd,10) = status->hit; + WFIFOW(fd,12) = status->cri/10; + WFIFOW(fd,14) = status->def; + WFIFOW(fd,16) = status->mdef; + WFIFOW(fd,18) = status->flee; + WFIFOW(fd,20) = status->amotion; + safestrncpy((char*)WFIFOP(fd,22), md->db->name, NAME_LENGTH); + WFIFOW(fd,46) = md->db->lv; + WFIFOL(fd,48) = status->hp; + WFIFOL(fd,52) = status->max_hp; + WFIFOL(fd,56) = status->sp; + WFIFOL(fd,60) = status->max_sp; + WFIFOL(fd,64) = (int)time(NULL) + (mercenary_get_lifetime(md) / 1000); + WFIFOW(fd,68) = mercenary_get_faith(md); + WFIFOL(fd,70) = mercenary_get_calls(md); + WFIFOL(fd,74) = md->mercenary.kill_count; + WFIFOW(fd,78) = md->battle_status.rhw.range; + WFIFOSET(fd,packet_len(0x29b)); +} + + +/// Mercenary skill tree (ZC_MER_SKILLINFO_LIST). +/// 029d <packet len>.W { <skill id>.W <type>.L <level>.W <sp cost>.W <attack range>.W <skill name>.24B <upgradable>.B }* +void clif_mercenary_skillblock(struct map_session_data *sd) +{ + struct mercenary_data *md; + int fd, i, len = 4, id, j; + + if( sd == NULL || (md = sd->md) == NULL ) + return; + + fd = sd->fd; + WFIFOHEAD(fd,4+37*MAX_MERCSKILL); + WFIFOW(fd,0) = 0x29d; + for( i = 0; i < MAX_MERCSKILL; i++ ) + { + if( (id = md->db->skill[i].id) == 0 ) + continue; + j = id - MC_SKILLBASE; + WFIFOW(fd,len) = id; + WFIFOL(fd,len+2) = skill_get_inf(id); + WFIFOW(fd,len+6) = md->db->skill[j].lv; + WFIFOW(fd,len+8) = skill_get_sp(id, md->db->skill[j].lv); + WFIFOW(fd,len+10) = skill_get_range2(&md->bl, id, md->db->skill[j].lv); + safestrncpy((char*)WFIFOP(fd,len+12), skill_get_name(id), NAME_LENGTH); + WFIFOB(fd,len+36) = 0; // Skillable for Mercenary? + len += 37; + } + + WFIFOW(fd,2) = len; + WFIFOSET(fd,len); +} + + +/// Request to invoke a mercenary menu action (CZ_MER_COMMAND). +/// 029f <command>.B +/// 1 = mercenary information +/// 2 = delete +void clif_parse_mercenary_action(int fd, struct map_session_data* sd) +{ + int option = RFIFOB(fd,2); + if( sd->md == NULL ) + return; + + if( option == 2 ) merc_delete(sd->md, 2); +} + + +/// Mercenary Message +/// message: +/// 0 = Mercenary soldier's duty hour is over. +/// 1 = Your mercenary soldier has been killed. +/// 2 = Your mercenary soldier has been fired. +/// 3 = Your mercenary soldier has ran away. +void clif_mercenary_message(struct map_session_data* sd, int message) +{ + clif_msg(sd, 1266 + message); +} + + +/// Notification about the remaining time of a rental item (ZC_CASH_TIME_COUNTER). +/// 0298 <name id>.W <seconds>.L +void clif_rental_time(int fd, int nameid, int seconds) +{ // '<ItemName>' item will disappear in <seconds/60> minutes. + WFIFOHEAD(fd,packet_len(0x298)); + WFIFOW(fd,0) = 0x298; + WFIFOW(fd,2) = nameid; + WFIFOL(fd,4) = seconds; + WFIFOSET(fd,packet_len(0x298)); +} + + +/// Deletes a rental item from client's inventory (ZC_CASH_ITEM_DELETE). +/// 0299 <index>.W <name id>.W +void clif_rental_expired(int fd, int index, int nameid) +{ // '<ItemName>' item has been deleted from the Inventory + WFIFOHEAD(fd,packet_len(0x299)); + WFIFOW(fd,0) = 0x299; + WFIFOW(fd,2) = index+2; + WFIFOW(fd,4) = nameid; + WFIFOSET(fd,packet_len(0x299)); +} + + +/// Book Reading (ZC_READ_BOOK). +/// 0294 <book id>.L <page>.L +void clif_readbook(int fd, int book_id, int page) +{ + WFIFOHEAD(fd,packet_len(0x294)); + WFIFOW(fd,0) = 0x294; + WFIFOL(fd,2) = book_id; + WFIFOL(fd,6) = page; + WFIFOSET(fd,packet_len(0x294)); +} + + +/// Battlegrounds +/// + +/// Updates HP bar of a camp member (ZC_BATTLEFIELD_NOTIFY_HP). +/// 02e0 <account id>.L <name>.24B <hp>.W <max hp>.W +void clif_bg_hp(struct map_session_data *sd) +{ + unsigned char buf[34]; + const int cmd = 0x2e0; + nullpo_retv(sd); + + WBUFW(buf,0) = cmd; + WBUFL(buf,2) = sd->status.account_id; + memcpy(WBUFP(buf,6), sd->status.name, NAME_LENGTH); + + if( sd->battle_status.max_hp > INT16_MAX ) + { // To correctly display the %hp bar. [Skotlex] + WBUFW(buf,30) = sd->battle_status.hp/(sd->battle_status.max_hp/100); + WBUFW(buf,32) = 100; + } + else + { + WBUFW(buf,30) = sd->battle_status.hp; + WBUFW(buf,32) = sd->battle_status.max_hp; + } + + clif_send(buf, packet_len(cmd), &sd->bl, BG_AREA_WOS); +} + + +/// Updates the position of a camp member on the minimap (ZC_BATTLEFIELD_NOTIFY_POSITION). +/// 02df <account id>.L <name>.24B <class>.W <x>.W <y>.W +void clif_bg_xy(struct map_session_data *sd) +{ + unsigned char buf[36]; + nullpo_retv(sd); + + WBUFW(buf,0)=0x2df; + WBUFL(buf,2)=sd->status.account_id; + memcpy(WBUFP(buf,6), sd->status.name, NAME_LENGTH); + WBUFW(buf,30)=sd->status.class_; + WBUFW(buf,32)=sd->bl.x; + WBUFW(buf,34)=sd->bl.y; + + clif_send(buf, packet_len(0x2df), &sd->bl, BG_SAMEMAP_WOS); +} + +void clif_bg_xy_remove(struct map_session_data *sd) +{ + unsigned char buf[36]; + nullpo_retv(sd); + + WBUFW(buf,0)=0x2df; + WBUFL(buf,2)=sd->status.account_id; + memset(WBUFP(buf,6), 0, NAME_LENGTH); + WBUFW(buf,30)=0; + WBUFW(buf,32)=-1; + WBUFW(buf,34)=-1; + + clif_send(buf, packet_len(0x2df), &sd->bl, BG_SAMEMAP_WOS); +} + + +/// Notifies clients of a battleground message (ZC_BATTLEFIELD_CHAT). +/// 02dc <packet len>.W <account id>.L <name>.24B <message>.?B +void clif_bg_message(struct battleground_data *bg, int src_id, const char *name, const char *mes, int len) +{ + struct map_session_data *sd; + unsigned char *buf; + if( (sd = bg_getavailablesd(bg)) == NULL ) + return; + + buf = (unsigned char*)aMalloc((len + NAME_LENGTH + 8)*sizeof(unsigned char)); + + WBUFW(buf,0) = 0x2dc; + WBUFW(buf,2) = len + NAME_LENGTH + 8; + WBUFL(buf,4) = src_id; + memcpy(WBUFP(buf,8), name, NAME_LENGTH); + memcpy(WBUFP(buf,32), mes, len); + clif_send(buf,WBUFW(buf,2), &sd->bl, BG); + + if( buf ) + aFree(buf); +} + + +/// Validates and processes battlechat messages [pakpil] (CZ_BATTLEFIELD_CHAT). +/// 0x2db <packet len>.W <text>.?B (<name> : <message>) 00 +void clif_parse_BattleChat(int fd, struct map_session_data* sd) +{ + const char* text = (char*)RFIFOP(fd,4); + int textlen = RFIFOW(fd,2) - 4; + + char *name, *message; + int namelen, messagelen; + + if( !clif_process_message(sd, 0, &name, &namelen, &message, &messagelen) ) + return; + + if( is_atcommand(fd, sd, message, 1) ) + return; + + if( sd->sc.data[SC_BERSERK] || sd->sc.data[SC__BLOODYLUST] || (sd->sc.data[SC_NOCHAT] && sd->sc.data[SC_NOCHAT]->val1&MANNER_NOCHAT) ) + return; + + if( battle_config.min_chat_delay ) + { + if( DIFF_TICK(sd->cantalk_tick, gettick()) > 0 ) + return; + sd->cantalk_tick = gettick() + battle_config.min_chat_delay; + } + + bg_send_message(sd, text, textlen); +} + + +/// Notifies client of a battleground score change (ZC_BATTLEFIELD_NOTIFY_POINT). +/// 02de <camp A points>.W <camp B points>.W +void clif_bg_updatescore(int16 m) +{ + struct block_list bl; + unsigned char buf[6]; + + bl.id = 0; + bl.type = BL_NUL; + bl.m = m; + + WBUFW(buf,0) = 0x2de; + WBUFW(buf,2) = map[m].bgscore_lion; + WBUFW(buf,4) = map[m].bgscore_eagle; + clif_send(buf,packet_len(0x2de),&bl,ALL_SAMEMAP); +} + +void clif_bg_updatescore_single(struct map_session_data *sd) +{ + int fd; + nullpo_retv(sd); + fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x2de)); + WFIFOW(fd,0) = 0x2de; + WFIFOW(fd,2) = map[sd->bl.m].bgscore_lion; + WFIFOW(fd,4) = map[sd->bl.m].bgscore_eagle; + WFIFOSET(fd,packet_len(0x2de)); +} + + +/// Battleground camp belong-information (ZC_BATTLEFIELD_NOTIFY_CAMPINFO). +/// 02dd <account id>.L <name>.24B <camp>.W +void clif_sendbgemblem_area(struct map_session_data *sd) +{ + unsigned char buf[33]; + nullpo_retv(sd); + + WBUFW(buf, 0) = 0x2dd; + WBUFL(buf,2) = sd->bl.id; + safestrncpy((char*)WBUFP(buf,6), sd->status.name, NAME_LENGTH); // name don't show in screen. + WBUFW(buf,30) = sd->bg_id; + clif_send(buf,packet_len(0x2dd), &sd->bl, AREA); +} + +void clif_sendbgemblem_single(int fd, struct map_session_data *sd) +{ + nullpo_retv(sd); + WFIFOHEAD(fd,32); + WFIFOW(fd,0) = 0x2dd; + WFIFOL(fd,2) = sd->bl.id; + safestrncpy((char*)WFIFOP(fd,6), sd->status.name, NAME_LENGTH); + WFIFOW(fd,30) = sd->bg_id; + WFIFOSET(fd,packet_len(0x2dd)); +} + + +/// Custom Fonts (ZC_NOTIFY_FONT). +/// 02ef <account_id>.L <font id>.W +void clif_font(struct map_session_data *sd) +{ +#if PACKETVER >= 20080102 + unsigned char buf[8]; + nullpo_retv(sd); + WBUFW(buf,0) = 0x2ef; + WBUFL(buf,2) = sd->bl.id; + WBUFW(buf,6) = sd->user_font; + clif_send(buf, packet_len(0x2ef), &sd->bl, AREA); +#endif +} + + +/*========================================== + * Instancing Window + *------------------------------------------*/ +int clif_instance(int instance_id, int type, int flag) +{ + struct map_session_data *sd; + struct party_data *p; + unsigned char buf[255]; + + if( (p = party_search(instance[instance_id].party_id)) == NULL || (sd = party_getavailablesd(p)) == NULL ) + return 0; + + switch( type ) + { + case 1: + // S 0x2cb <Instance name>.61B <Standby Position>.W + // Required to start the instancing information window on Client + // This window re-appear each "refresh" of client automatically until type 4 is send to client. + WBUFW(buf,0) = 0x02CB; + memcpy(WBUFP(buf,2),instance[instance_id].name,INSTANCE_NAME_LENGTH); + WBUFW(buf,63) = flag; + clif_send(buf,packet_len(0x02CB),&sd->bl,PARTY); + break; + case 2: + // S 0x2cc <Standby Position>.W + // To announce Instancing queue creation if no maps available + WBUFW(buf,0) = 0x02CC; + WBUFW(buf,2) = flag; + clif_send(buf,packet_len(0x02CC),&sd->bl,PARTY); + break; + case 3: + case 4: + // S 0x2cd <Instance Name>.61B <Instance Remaining Time>.L <Instance Noplayers close time>.L + WBUFW(buf,0) = 0x02CD; + memcpy(WBUFP(buf,2),instance[instance_id].name,61); + if( type == 3 ) + { + WBUFL(buf,63) = (uint32)instance[instance_id].progress_timeout; + WBUFL(buf,67) = 0; + } + else + { + WBUFL(buf,63) = 0; + WBUFL(buf,67) = (uint32)instance[instance_id].idle_timeout; + } + clif_send(buf,packet_len(0x02CD),&sd->bl,PARTY); + break; + case 5: + // S 0x2ce <Message ID>.L + // 0 = Notification (EnterLimitDate update?) + // 1 = The Memorial Dungeon expired; it has been destroyed + // 2 = The Memorial Dungeon's entry time limit expired; it has been destroyed + // 3 = The Memorial Dungeon has been removed. + // 4 = Create failure (removes the instance window) + WBUFW(buf,0) = 0x02CE; + WBUFL(buf,2) = flag; + //WBUFL(buf,6) = EnterLimitDate; + clif_send(buf,packet_len(0x02CE),&sd->bl,PARTY); + break; + } + return 0; +} + +void clif_instance_join(int fd, int instance_id) +{ + if( instance[instance_id].idle_timer != INVALID_TIMER ) + { + WFIFOHEAD(fd,packet_len(0x02CD)); + WFIFOW(fd,0) = 0x02CD; + memcpy(WFIFOP(fd,2),instance[instance_id].name,61); + WFIFOL(fd,63) = 0; + WFIFOL(fd,67) = (uint32)instance[instance_id].idle_timeout; + WFIFOSET(fd,packet_len(0x02CD)); + } + else if( instance[instance_id].progress_timer != INVALID_TIMER ) + { + WFIFOHEAD(fd,packet_len(0x02CD)); + WFIFOW(fd,0) = 0x02CD; + memcpy(WFIFOP(fd,2),instance[instance_id].name,61); + WFIFOL(fd,63) = (uint32)instance[instance_id].progress_timeout;; + WFIFOL(fd,67) = 0; + WFIFOSET(fd,packet_len(0x02CD)); + } + else + { + WFIFOHEAD(fd,packet_len(0x02CB)); + WFIFOW(fd,0) = 0x02CB; + memcpy(WFIFOP(fd,2),instance[instance_id].name,61); + WFIFOW(fd,63) = 0; + WFIFOSET(fd,packet_len(0x02CB)); + } +} + +void clif_instance_leave(int fd) +{ + WFIFOHEAD(fd,packet_len(0x02CE)); + WFIFOW(fd,0) = 0x02ce; + WFIFOL(fd,2) = 4; + WFIFOSET(fd,packet_len(0x02CE)); +} + + +/// Notifies clients about item picked up by a party member (ZC_ITEM_PICKUP_PARTY). +/// 02b8 <account id>.L <name id>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W <equip location>.W <item type>.B +void clif_party_show_picker(struct map_session_data * sd, struct item * item_data) +{ +#if PACKETVER >= 20071002 + unsigned char buf[22]; + struct item_data* id = itemdb_search(item_data->nameid); + + WBUFW(buf,0) = 0x2b8; + WBUFL(buf,2) = sd->status.account_id; + WBUFW(buf,6) = item_data->nameid; + WBUFB(buf,8) = item_data->identify; + WBUFB(buf,9) = item_data->attribute; + WBUFB(buf,10) = item_data->refine; + clif_addcards(WBUFP(buf,11), item_data); + WBUFW(buf,19) = id->equip; // equip location + WBUFB(buf,21) = itemtype(id->type); // item type + clif_send(buf, packet_len(0x2b8), &sd->bl, PARTY_SAMEMAP_WOS); +#endif +} + + +/// Display gained exp (ZC_NOTIFY_EXP). +/// 07f6 <account id>.L <amount>.L <var id>.W <exp type>.W +/// var id: +/// SP_BASEEXP, SP_JOBEXP +/// exp type: +/// 0 = normal exp gain/loss +/// 1 = quest exp gain/loss +void clif_displayexp(struct map_session_data *sd, unsigned int exp, char type, bool quest) +{ + int fd; + + nullpo_retv(sd); + + fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0x7f6)); + WFIFOW(fd,0) = 0x7f6; + WFIFOL(fd,2) = sd->bl.id; + WFIFOL(fd,6) = exp; + WFIFOW(fd,10) = type; + WFIFOW(fd,12) = quest?1:0;// Normal exp is shown in yellow, quest exp is shown in purple. + WFIFOSET(fd,packet_len(0x7f6)); +} + + +/// Displays digital clock digits on top of the screen (ZC_SHOWDIGIT). +/// type: +/// 0 = Displays 'value' for 5 seconds. +/// 1 = Incremental counter (1 tick/second), negated 'value' specifies start value (e.g. using -10 lets the counter start at 10). +/// 2 = Decremental counter (1 tick/second), negated 'value' specifies start value (does not stop when reaching 0, but overflows). +/// 3 = Decremental counter (1 tick/second), 'value' specifies start value (stops when reaching 0, displays at most 2 digits). +/// value: +/// Except for type 3 it is interpreted as seconds for displaying as DD:HH:MM:SS, HH:MM:SS, MM:SS or SS (leftmost '00' is not displayed). +void clif_showdigit(struct map_session_data* sd, unsigned char type, int value) +{ + WFIFOHEAD(sd->fd, packet_len(0x1b1)); + WFIFOW(sd->fd,0) = 0x1b1; + WFIFOB(sd->fd,2) = type; + WFIFOL(sd->fd,3) = value; + WFIFOSET(sd->fd, packet_len(0x1b1)); +} + + +/// Notification of the state of client command /effect (CZ_LESSEFFECT). +/// 021d <state>.L +/// state: +/// 0 = Full effects +/// 1 = Reduced effects +/// +/// NOTE: The state is used on Aegis for sending skill unit packet +/// 0x11f (ZC_SKILL_ENTRY) instead of 0x1c9 (ZC_SKILL_ENTRY2) +/// whenever possible. Due to the way the decision check is +/// constructed, this state tracking was rendered useless, +/// as the only skill unit, that is sent with 0x1c9 is +/// Graffiti. +void clif_parse_LessEffect(int fd, struct map_session_data* sd) +{ + int isLess = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + + sd->state.lesseffect = ( isLess != 0 ); +} + +/// S 07e4 <length>.w <option>.l <val>.l {<index>.w <amount>.w).4b* +void clif_parse_ItemListWindowSelected(int fd, struct map_session_data* sd) { + int n = (RFIFOW(fd,2)-12) / 4; + int type = RFIFOL(fd,4); + int flag = RFIFOL(fd,8); // Button clicked: 0 = Cancel, 1 = OK + unsigned short* item_list = (unsigned short*)RFIFOP(fd,12); + + if( sd->state.trading || sd->npc_shopid ) + return; + + if( flag == 0 || n == 0) { + clif_menuskill_clear(sd); + return; // Canceled by player. + } + + if( sd->menuskill_id != SO_EL_ANALYSIS && sd->menuskill_id != GN_CHANGEMATERIAL ) { + clif_menuskill_clear(sd); + return; // Prevent hacking. + } + + switch( type ) { + case 0: // Change Material + skill_changematerial(sd,n,item_list); + break; + case 1: // Level 1: Pure to Rough + case 2: // Level 2: Rough to Pure + skill_elementalanalysis(sd,n,type,item_list); + break; + } + clif_menuskill_clear(sd); + + return; +} + +/*========================================== + * Elemental System + *==========================================*/ +void clif_elemental_updatestatus(struct map_session_data *sd, int type) { + struct elemental_data *ed; + struct status_data *status; + int fd; + + if( sd == NULL || (ed = sd->ed) == NULL ) + return; + + fd = sd->fd; + status = &ed->battle_status; + WFIFOHEAD(fd,8); + WFIFOW(fd,0) = 0x81e; + WFIFOW(fd,2) = type; + switch( type ) { + case SP_HP: + WFIFOL(fd,4) = status->hp; + break; + case SP_MAXHP: + WFIFOL(fd,4) = status->max_hp; + break; + case SP_SP: + WFIFOL(fd,4) = status->sp; + break; + case SP_MAXSP: + WFIFOL(fd,4) = status->max_sp; + break; + } + WFIFOSET(fd,8); +} + +void clif_elemental_info(struct map_session_data *sd) { + int fd; + struct elemental_data *ed; + struct status_data *status; + + if( sd == NULL || (ed = sd->ed) == NULL ) + return; + + fd = sd->fd; + status = &ed->battle_status; + + WFIFOHEAD(fd,22); + WFIFOW(fd, 0) = 0x81d; + WFIFOL(fd, 2) = ed->bl.id; + WFIFOL(fd, 6) = status->hp; + WFIFOL(fd,10) = status->max_hp; + WFIFOL(fd,14) = status->sp; + WFIFOL(fd,18) = status->max_sp; + WFIFOSET(fd,22); +} + + +/// Buying Store System +/// + +/// Opens preparation window for buying store (ZC_OPEN_BUYING_STORE). +/// 0810 <slots>.B +void clif_buyingstore_open(struct map_session_data* sd) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x810)); + WFIFOW(fd,0) = 0x810; + WFIFOB(fd,2) = sd->buyingstore.slots; + WFIFOSET(fd,packet_len(0x810)); +} + + +/// Request to create a buying store (CZ_REQ_OPEN_BUYING_STORE). +/// 0811 <packet len>.W <limit zeny>.L <result>.B <store name>.80B { <name id>.W <amount>.W <price>.L }* +/// result: +/// 0 = cancel +/// 1 = open +static void clif_parse_ReqOpenBuyingStore(int fd, struct map_session_data* sd) +{ + const unsigned int blocksize = 8; + uint8* itemlist; + char storename[MESSAGE_SIZE]; + unsigned char result; + int zenylimit; + unsigned int count, packet_len; + struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)]; + + packet_len = RFIFOW(fd,info->pos[0]); + + // TODO: Make this check global for all variable length packets. + if( packet_len < 89 ) + {// minimum packet length + ShowError("clif_parse_ReqOpenBuyingStore: Malformed packet (expected length=%u, length=%u, account_id=%d).\n", 89, packet_len, sd->bl.id); + return; + } + + zenylimit = RFIFOL(fd,info->pos[1]); + result = RFIFOL(fd,info->pos[2]); + safestrncpy(storename, (const char*)RFIFOP(fd,info->pos[3]), sizeof(storename)); + itemlist = RFIFOP(fd,info->pos[4]); + + // so that buyingstore_create knows, how many elements it has access to + packet_len-= info->pos[4]; + + if( packet_len%blocksize ) + { + ShowError("clif_parse_ReqOpenBuyingStore: Unexpected item list size %u (account_id=%d, block size=%u)\n", packet_len, sd->bl.id, blocksize); + return; + } + count = packet_len/blocksize; + + buyingstore_create(sd, zenylimit, result, storename, itemlist, count); +} + + +/// Notification, that the requested buying store could not be created (ZC_FAILED_OPEN_BUYING_STORE_TO_BUYER). +/// 0812 <result>.W <total weight>.L +/// result: +/// 1 = "Failed to open buying store." (0x6cd, MSI_BUYINGSTORE_OPEN_FAILED) +/// 2 = "Total amount of then possessed items exceeds the weight limit by <weight/10-maxweight*90%>. Please re-enter." (0x6ce, MSI_BUYINGSTORE_OVERWEIGHT) +/// 8 = "No sale (purchase) information available." (0x705) +/// ? = nothing +void clif_buyingstore_open_failed(struct map_session_data* sd, unsigned short result, unsigned int weight) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x812)); + WFIFOW(fd,0) = 0x812; + WFIFOW(fd,2) = result; + WFIFOL(fd,4) = weight; + WFIFOSET(fd,packet_len(0x812)); +} + + +/// Notification, that the requested buying store was created (ZC_MYITEMLIST_BUYING_STORE). +/// 0813 <packet len>.W <account id>.L <limit zeny>.L { <price>.L <count>.W <type>.B <name id>.W }* +void clif_buyingstore_myitemlist(struct map_session_data* sd) +{ + int fd = sd->fd; + unsigned int i; + + WFIFOHEAD(fd,12+sd->buyingstore.slots*9); + WFIFOW(fd,0) = 0x813; + WFIFOW(fd,2) = 12+sd->buyingstore.slots*9; + WFIFOL(fd,4) = sd->bl.id; + WFIFOL(fd,8) = sd->buyingstore.zenylimit; + + for( i = 0; i < sd->buyingstore.slots; i++ ) + { + WFIFOL(fd,12+i*9) = sd->buyingstore.items[i].price; + WFIFOW(fd,16+i*9) = sd->buyingstore.items[i].amount; + WFIFOB(fd,18+i*9) = itemtype(itemdb_type(sd->buyingstore.items[i].nameid)); + WFIFOW(fd,19+i*9) = sd->buyingstore.items[i].nameid; + } + + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Notifies clients in area of a buying store (ZC_BUYING_STORE_ENTRY). +/// 0814 <account id>.L <store name>.80B +void clif_buyingstore_entry(struct map_session_data* sd) +{ + uint8 buf[86]; + + WBUFW(buf,0) = 0x814; + WBUFL(buf,2) = sd->bl.id; + memcpy(WBUFP(buf,6), sd->message, MESSAGE_SIZE); + + clif_send(buf, packet_len(0x814), &sd->bl, AREA_WOS); +} +void clif_buyingstore_entry_single(struct map_session_data* sd, struct map_session_data* pl_sd) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x814)); + WFIFOW(fd,0) = 0x814; + WFIFOL(fd,2) = pl_sd->bl.id; + memcpy(WFIFOP(fd,6), pl_sd->message, MESSAGE_SIZE); + WFIFOSET(fd,packet_len(0x814)); +} + + +/// Request to close own buying store (CZ_REQ_CLOSE_BUYING_STORE). +/// 0815 +static void clif_parse_ReqCloseBuyingStore(int fd, struct map_session_data* sd) +{ + buyingstore_close(sd); +} + + +/// Notifies clients in area that a buying store was closed (ZC_DISAPPEAR_BUYING_STORE_ENTRY). +/// 0816 <account id>.L +void clif_buyingstore_disappear_entry(struct map_session_data* sd) +{ + uint8 buf[6]; + + WBUFW(buf,0) = 0x816; + WBUFL(buf,2) = sd->bl.id; + + clif_send(buf, packet_len(0x816), &sd->bl, AREA_WOS); +} +void clif_buyingstore_disappear_entry_single(struct map_session_data* sd, struct map_session_data* pl_sd) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x816)); + WFIFOW(fd,0) = 0x816; + WFIFOL(fd,2) = pl_sd->bl.id; + WFIFOSET(fd,packet_len(0x816)); +} + + +/// Request to open someone else's buying store (CZ_REQ_CLICK_TO_BUYING_STORE). +/// 0817 <account id>.L +static void clif_parse_ReqClickBuyingStore(int fd, struct map_session_data* sd) +{ + int account_id; + + account_id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + + buyingstore_open(sd, account_id); +} + + +/// Sends buying store item list (ZC_ACK_ITEMLIST_BUYING_STORE). +/// 0818 <packet len>.W <account id>.L <store id>.L <limit zeny>.L { <price>.L <amount>.W <type>.B <name id>.W }* +void clif_buyingstore_itemlist(struct map_session_data* sd, struct map_session_data* pl_sd) +{ + int fd = sd->fd; + unsigned int i; + + WFIFOHEAD(fd,16+pl_sd->buyingstore.slots*9); + WFIFOW(fd,0) = 0x818; + WFIFOW(fd,2) = 16+pl_sd->buyingstore.slots*9; + WFIFOL(fd,4) = pl_sd->bl.id; + WFIFOL(fd,8) = pl_sd->buyer_id; + WFIFOL(fd,12) = pl_sd->buyingstore.zenylimit; + + for( i = 0; i < pl_sd->buyingstore.slots; i++ ) + { + WFIFOL(fd,16+i*9) = pl_sd->buyingstore.items[i].price; + WFIFOW(fd,20+i*9) = pl_sd->buyingstore.items[i].amount; // TODO: Figure out, if no longer needed items (amount == 0) are listed on official. + WFIFOB(fd,22+i*9) = itemtype(itemdb_type(pl_sd->buyingstore.items[i].nameid)); + WFIFOW(fd,23+i*9) = pl_sd->buyingstore.items[i].nameid; + } + + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Request to sell items to a buying store (CZ_REQ_TRADE_BUYING_STORE). +/// 0819 <packet len>.W <account id>.L <store id>.L { <index>.W <name id>.W <amount>.W }* +static void clif_parse_ReqTradeBuyingStore(int fd, struct map_session_data* sd) +{ + const unsigned int blocksize = 6; + uint8* itemlist; + int account_id; + unsigned int count, packet_len, buyer_id; + struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)]; + + packet_len = RFIFOW(fd,info->pos[0]); + + if( packet_len < 12 ) + {// minimum packet length + ShowError("clif_parse_ReqTradeBuyingStore: Malformed packet (expected length=%u, length=%u, account_id=%d).\n", 12, packet_len, sd->bl.id); + return; + } + + account_id = RFIFOL(fd,info->pos[1]); + buyer_id = RFIFOL(fd,info->pos[2]); + itemlist = RFIFOP(fd,info->pos[3]); + + // so that buyingstore_trade knows, how many elements it has access to + packet_len-= info->pos[3]; + + if( packet_len%blocksize ) + { + ShowError("clif_parse_ReqTradeBuyingStore: Unexpected item list size %u (account_id=%d, buyer_id=%d, block size=%u)\n", packet_len, sd->bl.id, account_id, blocksize); + return; + } + count = packet_len/blocksize; + + buyingstore_trade(sd, account_id, buyer_id, itemlist, count); +} + + +/// Notifies the buyer, that the buying store has been closed due to a post-trade condition (ZC_FAILED_TRADE_BUYING_STORE_TO_BUYER). +/// 081a <result>.W +/// result: +/// 3 = "All items within the buy limit were purchased." (0x6cf, MSI_BUYINGSTORE_TRADE_OVERLIMITZENY) +/// 4 = "All items were purchased." (0x6d0, MSI_BUYINGSTORE_TRADE_BUYCOMPLETE) +/// ? = nothing +void clif_buyingstore_trade_failed_buyer(struct map_session_data* sd, short result) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x81a)); + WFIFOW(fd,0) = 0x81a; + WFIFOW(fd,2) = result; + WFIFOSET(fd,packet_len(0x81a)); +} + + +/// Updates the zeny limit and an item in the buying store item list (ZC_UPDATE_ITEM_FROM_BUYING_STORE). +/// 081b <name id>.W <amount>.W <limit zeny>.L +void clif_buyingstore_update_item(struct map_session_data* sd, unsigned short nameid, unsigned short amount) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x81b)); + WFIFOW(fd,0) = 0x81b; + WFIFOW(fd,2) = nameid; + WFIFOW(fd,4) = amount; // amount of nameid received + WFIFOL(fd,6) = sd->buyingstore.zenylimit; + WFIFOSET(fd,packet_len(0x81b)); +} + + +/// Deletes item from inventory, that was sold to a buying store (ZC_ITEM_DELETE_BUYING_STORE). +/// 081c <index>.W <amount>.W <price>.L +/// message: +/// "%s (%d) were sold at %dz." (0x6d2, MSI_BUYINGSTORE_TRADE_SELLCOMPLETE) +/// +/// NOTE: This function has to be called _instead_ of clif_delitem/clif_dropitem. +void clif_buyingstore_delete_item(struct map_session_data* sd, short index, unsigned short amount, int price) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x81c)); + WFIFOW(fd,0) = 0x81c; + WFIFOW(fd,2) = index+2; + WFIFOW(fd,4) = amount; + WFIFOL(fd,6) = price; // price per item, client calculates total Zeny by itself + WFIFOSET(fd,packet_len(0x81c)); +} + + +/// Notifies the seller, that a buying store trade failed (ZC_FAILED_TRADE_BUYING_STORE_TO_SELLER). +/// 0824 <result>.W <name id>.W +/// result: +/// 5 = "The deal has failed." (0x39, MSI_DEAL_FAIL) +/// 6 = "The trade failed, because the entered amount of item %s is higher, than the buyer is willing to buy." (0x6d3, MSI_BUYINGSTORE_TRADE_OVERCOUNT) +/// 7 = "The trade failed, because the buyer is lacking required balance." (0x6d1, MSI_BUYINGSTORE_TRADE_LACKBUYERZENY) +/// ? = nothing +void clif_buyingstore_trade_failed_seller(struct map_session_data* sd, short result, unsigned short nameid) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x824)); + WFIFOW(fd,0) = 0x824; + WFIFOW(fd,2) = result; + WFIFOW(fd,4) = nameid; + WFIFOSET(fd,packet_len(0x824)); +} + + +/// Search Store Info System +/// + +/// Request to search for stores (CZ_SEARCH_STORE_INFO). +/// 0835 <packet len>.W <type>.B <max price>.L <min price>.L <name id count>.B <card count>.B { <name id>.W }* { <card>.W }* +/// type: +/// 0 = Vending +/// 1 = Buying Store +/// +/// NOTE: The client determines the item ids by specifying a name and optionally, +/// amount of card slots. If the client does not know about the item it +/// cannot be searched. +static void clif_parse_SearchStoreInfo(int fd, struct map_session_data* sd) +{ + const unsigned int blocksize = 2; + const uint8* itemlist; + const uint8* cardlist; + unsigned char type; + unsigned int min_price, max_price, packet_len, count, item_count, card_count; + struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)]; + + packet_len = RFIFOW(fd,info->pos[0]); + + if( packet_len < 15 ) + {// minimum packet length + ShowError("clif_parse_SearchStoreInfo: Malformed packet (expected length=%u, length=%u, account_id=%d).\n", 15, packet_len, sd->bl.id); + return; + } + + type = RFIFOB(fd,info->pos[1]); + max_price = RFIFOL(fd,info->pos[2]); + min_price = RFIFOL(fd,info->pos[3]); + item_count = RFIFOB(fd,info->pos[4]); + card_count = RFIFOB(fd,info->pos[5]); + itemlist = RFIFOP(fd,info->pos[6]); + cardlist = RFIFOP(fd,info->pos[6]+blocksize*item_count); + + // check, if there is enough data for the claimed count of items + packet_len-= info->pos[6]; + + if( packet_len%blocksize ) + { + ShowError("clif_parse_SearchStoreInfo: Unexpected item list size %u (account_id=%d, block size=%u)\n", packet_len, sd->bl.id, blocksize); + return; + } + count = packet_len/blocksize; + + if( count < item_count+card_count ) + { + ShowError("clif_parse_SearchStoreInfo: Malformed packet (expected count=%u, count=%u, account_id=%d).\n", item_count+card_count, count, sd->bl.id); + return; + } + + searchstore_query(sd, type, min_price, max_price, (const unsigned short*)itemlist, item_count, (const unsigned short*)cardlist, card_count); +} + + +/// Results for a store search request (ZC_SEARCH_STORE_INFO_ACK). +/// 0836 <packet len>.W <is first page>.B <is next page>.B <remaining uses>.B { <store id>.L <account id>.L <shop name>.80B <nameid>.W <item type>.B <price>.L <amount>.W <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W }* +/// is first page: +/// 0 = appends to existing results +/// 1 = clears previous results before displaying this result set +/// is next page: +/// 0 = no "next" label +/// 1 = "next" label to retrieve more results +void clif_search_store_info_ack(struct map_session_data* sd) +{ + const unsigned int blocksize = MESSAGE_SIZE+26; + int fd = sd->fd; + unsigned int i, start, end; + + start = sd->searchstore.pages*SEARCHSTORE_RESULTS_PER_PAGE; + end = min(sd->searchstore.count, start+SEARCHSTORE_RESULTS_PER_PAGE); + + WFIFOHEAD(fd,7+(end-start)*blocksize); + WFIFOW(fd,0) = 0x836; + WFIFOW(fd,2) = 7+(end-start)*blocksize; + WFIFOB(fd,4) = !sd->searchstore.pages; + WFIFOB(fd,5) = searchstore_querynext(sd); + WFIFOB(fd,6) = (unsigned char)min(sd->searchstore.uses, UINT8_MAX); + + for( i = start; i < end; i++ ) + { + struct s_search_store_info_item* ssitem = &sd->searchstore.items[i]; + struct item it; + + WFIFOL(fd,i*blocksize+ 7) = ssitem->store_id; + WFIFOL(fd,i*blocksize+11) = ssitem->account_id; + memcpy(WFIFOP(fd,i*blocksize+15), ssitem->store_name, MESSAGE_SIZE); + WFIFOW(fd,i*blocksize+15+MESSAGE_SIZE) = ssitem->nameid; + WFIFOB(fd,i*blocksize+17+MESSAGE_SIZE) = itemtype(itemdb_type(ssitem->nameid)); + WFIFOL(fd,i*blocksize+18+MESSAGE_SIZE) = ssitem->price; + WFIFOW(fd,i*blocksize+22+MESSAGE_SIZE) = ssitem->amount; + WFIFOB(fd,i*blocksize+24+MESSAGE_SIZE) = ssitem->refine; + + // make-up an item for clif_addcards + memset(&it, 0, sizeof(it)); + memcpy(&it.card, &ssitem->card, sizeof(it.card)); + it.nameid = ssitem->nameid; + it.amount = ssitem->amount; + + clif_addcards(WFIFOP(fd,i*blocksize+25+MESSAGE_SIZE), &it); + } + + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Notification of failure when searching for stores (ZC_SEARCH_STORE_INFO_FAILED). +/// 0837 <reason>.B +/// reason: +/// 0 = "No matching stores were found." (0x70b) +/// 1 = "There are too many results. Please enter more detailed search term." (0x6f8) +/// 2 = "You cannot search anymore." (0x706) +/// 3 = "You cannot search yet." (0x708) +/// 4 = "No sale (purchase) information available." (0x705) +void clif_search_store_info_failed(struct map_session_data* sd, unsigned char reason) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x837)); + WFIFOW(fd,0) = 0x837; + WFIFOB(fd,2) = reason; + WFIFOSET(fd,packet_len(0x837)); +} + + +/// Request to display next page of results (CZ_SEARCH_STORE_INFO_NEXT_PAGE). +/// 0838 +static void clif_parse_SearchStoreInfoNextPage(int fd, struct map_session_data* sd) +{ + searchstore_next(sd); +} + + +/// Opens the search store window (ZC_OPEN_SEARCH_STORE_INFO). +/// 083a <type>.W <remaining uses>.B +/// type: +/// 0 = Search Stores +/// 1 = Search Stores (Cash), asks for confirmation, when clicking a store +void clif_open_search_store_info(struct map_session_data* sd) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x83a)); + WFIFOW(fd,0) = 0x83a; + WFIFOW(fd,2) = sd->searchstore.effect; +#if PACKETVER > 20100701 + WFIFOB(fd,4) = (unsigned char)min(sd->searchstore.uses, UINT8_MAX); +#endif + WFIFOSET(fd,packet_len(0x83a)); +} + + +/// Request to close the store search window (CZ_CLOSE_SEARCH_STORE_INFO). +/// 083b +static void clif_parse_CloseSearchStoreInfo(int fd, struct map_session_data* sd) +{ + searchstore_close(sd); +} + + +/// Request to invoke catalog effect on a store from search results (CZ_SSILIST_ITEM_CLICK). +/// 083c <account id>.L <store id>.L <nameid>.W +static void clif_parse_SearchStoreInfoListItemClick(int fd, struct map_session_data* sd) +{ + unsigned short nameid; + int account_id, store_id; + struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)]; + + account_id = RFIFOL(fd,info->pos[0]); + store_id = RFIFOL(fd,info->pos[1]); + nameid = RFIFOW(fd,info->pos[2]); + + searchstore_click(sd, account_id, store_id, nameid); +} + + +/// Notification of the store position on current map (ZC_SSILIST_ITEM_CLICK_ACK). +/// 083d <xPos>.W <yPos>.W +void clif_search_store_info_click_ack(struct map_session_data* sd, short x, short y) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x83d)); + WFIFOW(fd,0) = 0x83d; + WFIFOW(fd,2) = x; + WFIFOW(fd,4) = y; + WFIFOSET(fd,packet_len(0x83d)); +} + + +/// Parse function for packet debugging. +void clif_parse_debug(int fd,struct map_session_data *sd) +{ + int cmd, packet_len; + + // clif_parse ensures, that there is at least 2 bytes of data + cmd = RFIFOW(fd,0); + + if( sd ) + { + packet_len = packet_db[sd->packet_ver][cmd].len; + + if( packet_len == 0 ) + {// unknown + packet_len = RFIFOREST(fd); + } + else if( packet_len == -1 ) + {// variable length + packet_len = RFIFOW(fd,2); // clif_parse ensures, that this amount of data is already received + } + ShowDebug("Packet debug of 0x%04X (length %d), %s session #%d, %d/%d (AID/CID)\n", cmd, packet_len, sd->state.active ? "authed" : "unauthed", fd, sd->status.account_id, sd->status.char_id); + } + else + { + packet_len = RFIFOREST(fd); + ShowDebug("Packet debug of 0x%04X (length %d), session #%d\n", cmd, packet_len, fd); + } + + ShowDump(RFIFOP(fd,0), packet_len); +} +/*========================================== + * Server tells client to display a window similar to Magnifier (item) one + * Server populates the window with avilable elemental converter options according to player's inventory + *------------------------------------------*/ +int clif_elementalconverter_list(struct map_session_data *sd) { + int i,c,view,fd; + + nullpo_ret(sd); + + +/// Main client packet processing function + fd=sd->fd; + WFIFOHEAD(fd, MAX_SKILL_PRODUCE_DB *2+4); + WFIFOW(fd, 0)=0x1ad; + + for(i=0,c=0;i<MAX_SKILL_PRODUCE_DB;i++){ + if( skill_can_produce_mix(sd,skill_produce_db[i].nameid,23, 1) ){ + if((view = itemdb_viewid(skill_produce_db[i].nameid)) > 0) + WFIFOW(fd,c*2+ 4)= view; + else + WFIFOW(fd,c*2+ 4)= skill_produce_db[i].nameid; + c++; + } + } + WFIFOW(fd,2) = c*2+4; + WFIFOSET(fd, WFIFOW(fd,2)); + if (c > 0) { + sd->menuskill_id = SA_CREATECON; + sd->menuskill_val = c; + } + + return 0; +} +/** + * Rune Knight + **/ +void clif_millenniumshield(struct map_session_data *sd, short shields ) { +#if PACKETVER >= 20081217 + unsigned char buf[10]; + + WBUFW(buf,0) = 0x440; + WBUFL(buf,2) = sd->bl.id; + WBUFW(buf,6) = shields; + WBUFW(buf,8) = 0; + clif_send(buf,packet_len(0x440),&sd->bl,AREA); +#endif +} +/** + * Warlock + **/ +/*========================================== + * Spellbook list [LimitLine/3CeAM] + *------------------------------------------*/ +int clif_spellbook_list(struct map_session_data *sd) +{ + int i, c; + int fd; + + nullpo_ret(sd); + + fd = sd->fd; + WFIFOHEAD(fd, 8 * 8 + 8); + WFIFOW(fd,0) = 0x1ad; + + for( i = 0, c = 0; i < MAX_INVENTORY; i ++ ) + { + if( itemdb_is_spellbook(sd->status.inventory[i].nameid) ) + { + WFIFOW(fd, c * 2 + 4) = sd->status.inventory[i].nameid; + c ++; + } + } + + if( c > 0 ) + { + WFIFOW(fd,2) = c * 2 + 4; + WFIFOSET(fd, WFIFOW(fd, 2)); + sd->menuskill_id = WL_READING_SB; + sd->menuskill_val = c; + } + else{ + status_change_end(&sd->bl,SC_STOP,INVALID_TIMER); + clif_skill_fail(sd, WL_READING_SB, USESKILL_FAIL_SPELLBOOK, 0); + } + + return 1; +} +/** + * Mechanic + **/ +/*========================================== + * Magic Decoy Material List + *------------------------------------------*/ +int clif_magicdecoy_list(struct map_session_data *sd, uint16 skill_lv, short x, short y) { + int i, c; + int fd; + + nullpo_ret(sd); + + fd = sd->fd; + WFIFOHEAD(fd, 8 * 8 + 8); + WFIFOW(fd,0) = 0x1ad; // This is the official packet. [pakpil] + + for( i = 0, c = 0; i < MAX_INVENTORY; i ++ ) { + if( itemdb_is_element(sd->status.inventory[i].nameid) ) { + WFIFOW(fd, c * 2 + 4) = sd->status.inventory[i].nameid; + c ++; + } + } + if( c > 0 ) { + sd->menuskill_id = NC_MAGICDECOY; + sd->menuskill_val = skill_lv; + sd->sc.comet_x = x; + sd->sc.comet_y = y; + WFIFOW(fd,2) = c * 2 + 4; + WFIFOSET(fd, WFIFOW(fd, 2)); + } else { + clif_skill_fail(sd,NC_MAGICDECOY,USESKILL_FAIL_LEVEL,0); + return 0; + } + + return 1; +} +/** + * Guilotine Cross + **/ +/*========================================== + * Guillotine Cross Poisons List + *------------------------------------------*/ +int clif_poison_list(struct map_session_data *sd, uint16 skill_lv) { + int i, c; + int fd; + + nullpo_ret(sd); + + fd = sd->fd; + WFIFOHEAD(fd, 8 * 8 + 8); + WFIFOW(fd,0) = 0x1ad; // This is the official packet. [pakpil] + + for( i = 0, c = 0; i < MAX_INVENTORY; i ++ ) { + if( itemdb_is_poison(sd->status.inventory[i].nameid) ) { + WFIFOW(fd, c * 2 + 4) = sd->status.inventory[i].nameid; + c ++; + } + } + if( c > 0 ) { + sd->menuskill_id = GC_POISONINGWEAPON; + sd->menuskill_val = skill_lv; + WFIFOW(fd,2) = c * 2 + 4; + WFIFOSET(fd, WFIFOW(fd, 2)); + } else { + clif_skill_fail(sd,GC_POISONINGWEAPON,USESKILL_FAIL_GUILLONTINE_POISON,0); + return 0; + } + + return 1; +} +int clif_autoshadowspell_list(struct map_session_data *sd) { + int fd, i, c; + nullpo_ret(sd); + fd = sd->fd; + if( !fd ) return 0; + + if( sd->menuskill_id == SC_AUTOSHADOWSPELL ) + return 0; + + WFIFOHEAD(fd, 2 * 6 + 4); + WFIFOW(fd,0) = 0x442; + for( i = 0, c = 0; i < MAX_SKILL; i++ ) + if( sd->status.skill[i].flag == SKILL_FLAG_PLAGIARIZED && sd->status.skill[i].id > 0 && + sd->status.skill[i].id < GS_GLITTERING && skill_get_type(sd->status.skill[i].id) == BF_MAGIC ) + { // Can't auto cast both Extended class and 3rd class skills. + WFIFOW(fd,8+c*2) = sd->status.skill[i].id; + c++; + } + + if( c > 0 ) { + WFIFOW(fd,2) = 8 + c * 2; + WFIFOL(fd,4) = c; + WFIFOSET(fd,WFIFOW(fd,2)); + sd->menuskill_id = SC_AUTOSHADOWSPELL; + sd->menuskill_val = c; + } else { + status_change_end(&sd->bl,SC_STOP,INVALID_TIMER); + clif_skill_fail(sd,SC_AUTOSHADOWSPELL,USESKILL_FAIL_IMITATION_SKILL_NONE,0); + } + + return 1; +} +/*=========================================== + * Skill list for Four Elemental Analysis + * and Change Material skills. + *------------------------------------------*/ +int clif_skill_itemlistwindow( struct map_session_data *sd, uint16 skill_id, uint16 skill_lv ) +{ +#if PACKETVER >= 20090922 + int fd; + + nullpo_ret(sd); + + sd->menuskill_id = skill_id; // To prevent hacking. + sd->menuskill_val = skill_lv; + + if( skill_id == GN_CHANGEMATERIAL ) + skill_lv = 0; // Changematerial + + fd = sd->fd; + WFIFOHEAD(fd,packet_len(0x7e3)); + WFIFOW(fd,0) = 0x7e3; + WFIFOL(fd,2) = skill_lv; + WFIFOL(fd,4) = 0; + WFIFOSET(fd,packet_len(0x7e3)); + +#endif + + return 1; + +} +/** + * Sends a new status without a tick (currently used by the new mounts) + **/ +int clif_status_load_notick(struct block_list *bl,int type,int flag,int val1, int val2, int val3) { + unsigned char buf[32]; + + nullpo_ret(bl); + + WBUFW(buf,0)=0x043f; + WBUFW(buf,2)=type; + WBUFL(buf,4)=bl->id; + WBUFB(buf,8)=flag; + WBUFL(buf,9) = 0; + WBUFL(buf,13) = val1; + WBUFL(buf,17) = val2; + WBUFL(buf,21) = val3; + + clif_send(buf,packet_len(0x043f),bl,AREA); + return 0; +} +//Notifies FD of ID's type +int clif_status_load_single(int fd, int id,int type,int flag,int val1, int val2, int val3) { + WFIFOHEAD(fd, packet_len(0x043f)); + WFIFOW(fd,0)=0x043f; + WFIFOW(fd,2)=type; + WFIFOL(fd,4)=id; + WFIFOB(fd,8)=flag; + WFIFOL(fd,9) = 0; + WFIFOL(fd,13) = val1; + WFIFOL(fd,17) = val2; + WFIFOL(fd,21) = val3; + WFIFOSET(fd, packet_len(0x043f)); + return 0; +} +// msgstringtable.txt +// 0x291 <line>.W +void clif_msgtable(int fd, int line) { + WFIFOHEAD(fd, packet_len(0x291)); + WFIFOW(fd, 0) = 0x291; + WFIFOW(fd, 2) = line; + WFIFOSET(fd, packet_len(0x291)); +} + +// msgstringtable.txt +// 0x7e2 <line>.W <value>.L +void clif_msgtable_num(int fd, int line, int num) { +#if PACKETVER >= 20090805 + WFIFOHEAD(fd, packet_len(0x7e2)); + WFIFOW(fd, 0) = 0x7e2; + WFIFOW(fd, 2) = line; + WFIFOL(fd, 4) = num; + WFIFOSET(fd, packet_len(0x7e2)); +#endif +} +/*========================================== + * used by SC_AUTOSHADOWSPELL + * RFIFOL(fd,2) - flag (currently not used) + *------------------------------------------*/ +void clif_parse_SkillSelectMenu(int fd, struct map_session_data *sd) { + + if( sd->menuskill_id != SC_AUTOSHADOWSPELL ) + return; + + if( pc_istrading(sd) ) { + clif_skill_fail(sd,sd->ud.skill_id,0,0); + clif_menuskill_clear(sd); + return; + } + + skill_select_menu(sd,RFIFOW(fd,6)); + + clif_menuskill_clear(sd); +} +/*========================================== + * Kagerou/Oboro amulet spirit + *------------------------------------------*/ +void clif_talisman(struct map_session_data *sd,short type) +{ + unsigned char buf[10]; + + nullpo_retv(sd); + + WBUFW(buf,0)=0x08cf; + WBUFL(buf,2)=sd->bl.id; + WBUFW(buf,6)=type; + WBUFW(buf,8)=sd->talisman[type]; + clif_send(buf,packet_len(0x08cf),&sd->bl,AREA); +} +/// Move Item from or to Personal Tab (CZ_WHATSOEVER) [FE] +/// 0907 <index>.W +/// +/// R 0908 <index>.w <type>.b +/// type: +/// 0 = move item to personal tab +/// 1 = move item to normal tab +void clif_parse_MoveItem(int fd, struct map_session_data *sd) { +#if PACKETVER >= 20111122 + int index; + + /* can't move while dead. */ + if(pc_isdead(sd)) { + return; + } + + index = RFIFOW(fd,2)-2; + + if (index < 0 || index >= MAX_INVENTORY) + return; + + if ( sd->status.inventory[index].favorite && RFIFOB(fd, 4) == 1 ) + sd->status.inventory[index].favorite = 0; + else if( RFIFOB(fd, 4) == 0 ) + sd->status.inventory[index].favorite = 1; + else + return;/* nothing to do. */ + + clif_favorite_item(sd, index); +#endif +} + + +/// Items that are in favorite tab of inventory (ZC_ITEM_FAVORITE). +/// 0900 <index>.W <favorite>.B +void clif_favorite_item(struct map_session_data* sd, unsigned short index) { + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x908)); + WFIFOW(fd,0) = 0x908; + WFIFOW(fd,2) = index+2; + WFIFOL(fd,4) = (sd->status.inventory[index].favorite == 1) ? 0 : 1; + WFIFOSET(fd,packet_len(0x908)); +} + +void clif_snap( struct block_list *bl, short x, short y ) { + unsigned char buf[10]; + + WBUFW(buf,0) = 0x8d2; + WBUFL(buf,2) = bl->id; + WBUFW(buf,6) = x; + WBUFW(buf,8) = y; + + clif_send(buf,packet_len(0x8d2),bl,AREA); +} + +void clif_monster_hp_bar( struct mob_data* md, int fd ) { +#if PACKETVER >= 20120404 + WFIFOHEAD(fd,packet_len(0x977)); + + WFIFOW(fd,0) = 0x977; + WFIFOL(fd,2) = md->bl.id; + WFIFOL(fd,6) = md->status.hp; + WFIFOL(fd,10) = md->status.max_hp; + + WFIFOSET(fd,packet_len(0x977)); +#endif +} + +/*========================================== + * Main client packet processing function + *------------------------------------------*/ +static int clif_parse(int fd) +{ + int cmd, packet_ver, packet_len, err; + TBL_PC* sd; + int pnum; + + //TODO apply delays or disconnect based on packet throughput [FlavioJS] + // Note: "click masters" can do 80+ clicks in 10 seconds + + for( pnum = 0; pnum < 3; ++pnum )// Limit max packets per cycle to 3 (delay packet spammers) [FlavioJS] -- This actually aids packet spammers, but stuff like /str+ gets slow without it [Ai4rei] + { // begin main client packet processing loop + + sd = (TBL_PC *)session[fd]->session_data; + if (session[fd]->flag.eof) { + if (sd) { + if (sd->state.autotrade) { + //Disassociate character from the socket connection. + session[fd]->session_data = NULL; + sd->fd = 0; + ShowInfo("Character '"CL_WHITE"%s"CL_RESET"' logged off (using @autotrade).\n", sd->status.name); + } else + if (sd->state.active) { + // Player logout display [Valaris] + ShowInfo("Character '"CL_WHITE"%s"CL_RESET"' logged off.\n", sd->status.name); + clif_quitsave(fd, sd); + } else { + //Unusual logout (during log on/off/map-changer procedure) + ShowInfo("Player AID:%d/CID:%d logged off.\n", sd->status.account_id, sd->status.char_id); + map_quit(sd); + } + } else { + ShowInfo("Closed connection from '"CL_WHITE"%s"CL_RESET"'.\n", ip2str(session[fd]->client_addr, NULL)); + } + do_close(fd); + return 0; + } + + if (RFIFOREST(fd) < 2) + return 0; + + cmd = RFIFOW(fd,0); + + // identify client's packet version + if (sd) { + packet_ver = sd->packet_ver; + } else { + // check authentification packet to know packet version + packet_ver = clif_guess_PacketVer(fd, 0, &err); + if( err ) {// failed to identify packet version + ShowInfo("clif_parse: Disconnecting session #%d with unknown packet version%s (p:0x%04x,l:%d).\n", fd, ( + err == 1 ? "" : + err == 2 ? ", possibly for having an invalid account_id" : + err == 3 ? ", possibly for having an invalid char_id." : + /* Uncomment when checks are added in clif_guess_PacketVer. [FlavioJS] + err == 4 ? ", possibly for having an invalid login_id1." : + err == 5 ? ", possibly for having an invalid client_tick." : + */ + err == 6 ? ", possibly for having an invalid sex." : + ". ERROR invalid error code"), cmd, RFIFOREST(fd)); + WFIFOHEAD(fd,packet_len(0x6a)); + WFIFOW(fd,0) = 0x6a; + WFIFOB(fd,2) = 3; // Rejected from Server + WFIFOSET(fd,packet_len(0x6a)); + +#ifdef DUMP_INVALID_PACKET + ShowDump(RFIFOP(fd,0), RFIFOREST(fd)); +#endif + + RFIFOSKIP(fd, RFIFOREST(fd)); + set_eof(fd); + return 0; + } + } + + // filter out invalid / unsupported packets + if (cmd > MAX_PACKET_DB || packet_db[packet_ver][cmd].len == 0) { + ShowWarning("clif_parse: Received unsupported packet (packet 0x%04x, %d bytes received), disconnecting session #%d.\n", cmd, RFIFOREST(fd), fd); +#ifdef DUMP_INVALID_PACKET + ShowDump(RFIFOP(fd,0), RFIFOREST(fd)); +#endif + set_eof(fd); + return 0; + } + + // determine real packet length + packet_len = packet_db[packet_ver][cmd].len; + if (packet_len == -1) { // variable-length packet + if (RFIFOREST(fd) < 4) + return 0; + + packet_len = RFIFOW(fd,2); + if (packet_len < 4 || packet_len > 32768) { + ShowWarning("clif_parse: Received packet 0x%04x specifies invalid packet_len (%d), disconnecting session #%d.\n", cmd, packet_len, fd); +#ifdef DUMP_INVALID_PACKET + ShowDump(RFIFOP(fd,0), RFIFOREST(fd)); +#endif + set_eof(fd); + return 0; + } + } + if ((int)RFIFOREST(fd) < packet_len) + return 0; // not enough data received to form the packet + + if( packet_db[packet_ver][cmd].func == clif_parse_debug ) + packet_db[packet_ver][cmd].func(fd, sd); + else if( packet_db[packet_ver][cmd].func != NULL ) { + if( !sd && packet_db[packet_ver][cmd].func != clif_parse_WantToConnection ) + ; //Only valid packet when there is no session + else + if( sd && sd->bl.prev == NULL && packet_db[packet_ver][cmd].func != clif_parse_LoadEndAck ) + ; //Only valid packet when player is not on a map + else + if( sd && session[sd->fd]->flag.eof ) + ; //No more packets accepted + else + packet_db[packet_ver][cmd].func(fd, sd); + } +#ifdef DUMP_UNKNOWN_PACKET + else { + const char* packet_txt = "save/packet.txt"; + FILE* fp; + + if( ( fp = fopen( packet_txt , "a" ) ) != NULL ) { + if( sd ) { + fprintf(fp, "Unknown packet 0x%04X (length %d), %s session #%d, %d/%d (AID/CID)\n", cmd, packet_len, sd->state.active ? "authed" : "unauthed", fd, sd->status.account_id, sd->status.char_id); + } else { + fprintf(fp, "Unknown packet 0x%04X (length %d), session #%d\n", cmd, packet_len, fd); + } + + WriteDump(fp, RFIFOP(fd,0), packet_len); + fprintf(fp, "\n"); + fclose(fp); + } else { + ShowError("Failed to write '%s'.\n", packet_txt); + + // Dump on console instead + if( sd ) { + ShowDebug("Unknown packet 0x%04X (length %d), %s session #%d, %d/%d (AID/CID)\n", cmd, packet_len, sd->state.active ? "authed" : "unauthed", fd, sd->status.account_id, sd->status.char_id); + } else { + ShowDebug("Unknown packet 0x%04X (length %d), session #%d\n", cmd, packet_len, fd); + } + + ShowDump(RFIFOP(fd,0), packet_len); + } + } +#endif + + RFIFOSKIP(fd, packet_len); + + }; // main loop end + + return 0; +} + +/*========================================== + * Reads packet_db.txt and setups its array reference + *------------------------------------------*/ +static int packetdb_readdb(void) +{ + FILE *fp; + char line[1024]; + int ln=0; + int cmd,i,j,packet_ver; + int max_cmd=-1; + int skip_ver = 0; + int warned = 0; + char *str[64],*p,*str2[64],*p2,w1[64],w2[64]; + int packet_len_table[MAX_PACKET_DB] = { + 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, +#if PACKETVER <= 20081217 + 0, 0, 0, 0, 55, 17, 3, 37, 46, -1, 23, -1, 3,110, 3, 2, +#else + 0, 0, 0, 0, 55, 17, 3, 37, 46, -1, 23, -1, 3,114, 3, 2, +#endif +#if PACKETVER < 2 + 3, 28, 19, 11, 3, -1, 9, 5, 52, 51, 56, 58, 41, 2, 6, 6, +#elif PACKETVER < 20071106 // 78-7b Lv99 effect for later Kameshima + 3, 28, 19, 11, 3, -1, 9, 5, 54, 53, 58, 60, 41, 2, 6, 6, +#elif PACKETVER <= 20081217 // change in 0x78 and 0x7c + 3, 28, 19, 11, 3, -1, 9, 5, 55, 53, 58, 60, 42, 2, 6, 6, +#else + 3, 28, 19, 11, 3, -1, 9, 5, 55, 53, 58, 60, 44, 2, 6, 6, +#endif + //#0x0080 + 7, 3, 2, 2, 2, 5, 16, 12, 10, 7, 29, 2, -1, -1, -1, 0, // 0x8b changed to 2 (was 23) + 7, 22, 28, 2, 6, 30, -1, -1, 3, -1, -1, 5, 9, 17, 17, 6, +#if PACKETVER <= 20100622 + 23, 6, 6, -1, -1, -1, -1, 8, 7, 6, 7, 4, 7, 0, -1, 6, +#else + 23, 6, 6, -1, -1, -1, -1, 8, 7, 6, 9, 4, 7, 0, -1, 6, // 0xaa changed to 9 (was 7) +#endif + 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, 3, 2, 27, // 0xcd change to 3 (was 6) + 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 icon status display for later + 90, 86, 24, 6, 30,102, 9, 4, 8, 4, 14, 10, -1, 6, 2, 6, +#endif +#if PACKETVER < 20081126 + 3, 3, 35, 5, 11, 26, -1, 4, 4, 6, 10, 12, 6, -1, 4, 4, +#else // 0x1a2 changed (35->37) + 3, 3, 37, 5, 11, 26, -1, 4, 4, 6, 10, 12, 6, -1, 4, 4, +#endif + 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, 3, 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, + //#0x0200 + 26, -1, 26, 10, 18, 26, 11, 34, 14, 36, 10, 0, 0, -1, 32, 10, // 0x20c change to 0 (was 19) + 22, 0, 26, 26, 42, 6, 6, 2, 2,282,282, 10, 10, -1, -1, 66, +#if PACKETVER < 20071106 + 10, -1, -1, 8, 10, 2,282, 18, 18, 15, 58, 57, 64, 5, 71, 5, +#else // 0x22c changed + 10, -1, -1, 8, 10, 2,282, 18, 18, 15, 58, 57, 65, 5, 71, 5, +#endif + 12, 26, 9, 11, -1, -1, 10, 2,282, 11, 4, 36, 6, -1, 4, 2, + //#0x0240 + -1, -1, -1, -1, -1, 3, 4, 8, -1, 3, 70, 4, 8, 12, 4, 10, + 3, 32, -1, 3, 3, 5, 5, 8, 2, 3, -1, 6, 4, 6, 4, 6, + 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x0280 +#if PACKETVER < 20070711 + 0, 0, 0, 6, 14, 0, 0, -1, 6, 8, 18, 0, 0, 0, 0, 0, +#else + 0, 0, 0, 6, 14, 0, 0, -1, 10, 12, 18, 0, 0, 0, 0, 0, // 0x288, 0x289 increase by 4 (kafra points) +#endif + 0, 4, 0, 70, 10, 0, 0, 0, 8, 6, 27, 80, 0, -1, 0, 0, + 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 85, -1, -1,107, 6, -1, 7, 7, 22,191, 0, 8, 0, 0, 0, 0, + //#0x02C0 + 0, -1, 0, 0, 0, 30, 30, 0, 0, 3, 0, 65, 4, 71, 10, 0, + -1, -1, -1, 0, 29, 0, 6, -1, 10, 10, 3, 0, -1, 32, 6, 36, + 34, 33, 0, 0, 0, 0, 0, 0, -1, -1, -1, 13, 67, 59, 60, 8, + 10, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x0300 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x0340 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x0380 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x03C0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x0400 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 25, + //#0x0440 + 10, 4, -1, 0, 0, 0, 14, 0, 0, 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, + //#0x0480 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x04C0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x0500 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, + //#0x0540 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x0580 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x05C0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x0600 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, + //#0x0640 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x0680 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x06C0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x0700 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, + //#0x0740 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x0780 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x07C0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +#if PACKETVER < 20090617 + 6, 2, -1, 4, 4, 4, 4, 8, 8,254, 6, 8, 6, 54, 30, 54, +#else // 0x7d9 changed + 6, 2, -1, 4, 4, 4, 4, 8, 8,268, 6, 8, 6, 54, 30, 54, +#endif + 0, 15, 8, 6, -1, 8, 8, 32, -1, 5, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 14, -1, -1, -1, 8, 25, 0, 0, 26, 0, + //#0x0800 +#if PACKETVER < 20091229 + -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 20, +#else // for Party booking ( PACKETVER >= 20091229 ) + -1, -1, 18, 4, 8, 6, 2, 4, 14, 50, 18, 6, 2, 3, 14, 20, +#endif + 3, -1, 8, -1, 86, 2, 6, 6, -1, -1, 4, 10, 10, 0, 0, 0, + 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, -1, -1, 3, 2, 66, 5, 2, 12, 6, 0, 0, + //#0x0840 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, -1, -1, -1, -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, + //#0x0880 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + //#0x08C0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, + 0, 0, 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, + //#0x0900 + 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, + //#0x0940 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, + + }; + struct { + void (*func)(int, struct map_session_data *); + char *name; + } clif_parse_func[]={ + {clif_parse_WantToConnection,"wanttoconnection"}, + {clif_parse_LoadEndAck,"loadendack"}, + {clif_parse_TickSend,"ticksend"}, + {clif_parse_WalkToXY,"walktoxy"}, + {clif_parse_QuitGame,"quitgame"}, + {clif_parse_GetCharNameRequest,"getcharnamerequest"}, + {clif_parse_GlobalMessage,"globalmessage"}, + {clif_parse_MapMove,"mapmove"}, + {clif_parse_ChangeDir,"changedir"}, + {clif_parse_Emotion,"emotion"}, + {clif_parse_HowManyConnections,"howmanyconnections"}, + {clif_parse_ActionRequest,"actionrequest"}, + {clif_parse_Restart,"restart"}, + {clif_parse_WisMessage,"wis"}, + {clif_parse_Broadcast,"broadcast"}, + {clif_parse_TakeItem,"takeitem"}, + {clif_parse_DropItem,"dropitem"}, + {clif_parse_UseItem,"useitem"}, + {clif_parse_EquipItem,"equipitem"}, + {clif_parse_UnequipItem,"unequipitem"}, + {clif_parse_NpcClicked,"npcclicked"}, + {clif_parse_NpcBuySellSelected,"npcbuysellselected"}, + {clif_parse_NpcBuyListSend,"npcbuylistsend"}, + {clif_parse_NpcSellListSend,"npcselllistsend"}, + {clif_parse_CreateChatRoom,"createchatroom"}, + {clif_parse_ChatAddMember,"chataddmember"}, + {clif_parse_ChatRoomStatusChange,"chatroomstatuschange"}, + {clif_parse_ChangeChatOwner,"changechatowner"}, + {clif_parse_KickFromChat,"kickfromchat"}, + {clif_parse_ChatLeave,"chatleave"}, + {clif_parse_TradeRequest,"traderequest"}, + {clif_parse_TradeAck,"tradeack"}, + {clif_parse_TradeAddItem,"tradeadditem"}, + {clif_parse_TradeOk,"tradeok"}, + {clif_parse_TradeCancel,"tradecancel"}, + {clif_parse_TradeCommit,"tradecommit"}, + {clif_parse_StopAttack,"stopattack"}, + {clif_parse_PutItemToCart,"putitemtocart"}, + {clif_parse_GetItemFromCart,"getitemfromcart"}, + {clif_parse_RemoveOption,"removeoption"}, + {clif_parse_ChangeCart,"changecart"}, + {clif_parse_StatusUp,"statusup"}, + {clif_parse_SkillUp,"skillup"}, + {clif_parse_UseSkillToId,"useskilltoid"}, + {clif_parse_UseSkillToPos,"useskilltopos"}, + {clif_parse_UseSkillToPosMoreInfo,"useskilltoposinfo"}, + {clif_parse_UseSkillMap,"useskillmap"}, + {clif_parse_RequestMemo,"requestmemo"}, + {clif_parse_ProduceMix,"producemix"}, + {clif_parse_Cooking,"cooking"}, + {clif_parse_NpcSelectMenu,"npcselectmenu"}, + {clif_parse_NpcNextClicked,"npcnextclicked"}, + {clif_parse_NpcAmountInput,"npcamountinput"}, + {clif_parse_NpcStringInput,"npcstringinput"}, + {clif_parse_NpcCloseClicked,"npccloseclicked"}, + {clif_parse_ItemIdentify,"itemidentify"}, + {clif_parse_SelectArrow,"selectarrow"}, + {clif_parse_AutoSpell,"autospell"}, + {clif_parse_UseCard,"usecard"}, + {clif_parse_InsertCard,"insertcard"}, + {clif_parse_RepairItem,"repairitem"}, + {clif_parse_WeaponRefine,"weaponrefine"}, + {clif_parse_SolveCharName,"solvecharname"}, + {clif_parse_ResetChar,"resetchar"}, + {clif_parse_LocalBroadcast,"localbroadcast"}, + {clif_parse_MoveToKafra,"movetokafra"}, + {clif_parse_MoveFromKafra,"movefromkafra"}, + {clif_parse_MoveToKafraFromCart,"movetokafrafromcart"}, + {clif_parse_MoveFromKafraToCart,"movefromkafratocart"}, + {clif_parse_CloseKafra,"closekafra"}, + {clif_parse_CreateParty,"createparty"}, + {clif_parse_CreateParty2,"createparty2"}, + {clif_parse_PartyInvite,"partyinvite"}, + {clif_parse_PartyInvite2,"partyinvite2"}, + {clif_parse_ReplyPartyInvite,"replypartyinvite"}, + {clif_parse_ReplyPartyInvite2,"replypartyinvite2"}, + {clif_parse_LeaveParty,"leaveparty"}, + {clif_parse_RemovePartyMember,"removepartymember"}, + {clif_parse_PartyChangeOption,"partychangeoption"}, + {clif_parse_PartyMessage,"partymessage"}, + {clif_parse_PartyChangeLeader,"partychangeleader"}, + {clif_parse_CloseVending,"closevending"}, + {clif_parse_VendingListReq,"vendinglistreq"}, + {clif_parse_PurchaseReq,"purchasereq"}, + {clif_parse_PurchaseReq2,"purchasereq2"}, + {clif_parse_OpenVending,"openvending"}, + {clif_parse_CreateGuild,"createguild"}, + {clif_parse_GuildCheckMaster,"guildcheckmaster"}, + {clif_parse_GuildRequestInfo,"guildrequestinfo"}, + {clif_parse_GuildChangePositionInfo,"guildchangepositioninfo"}, + {clif_parse_GuildChangeMemberPosition,"guildchangememberposition"}, + {clif_parse_GuildRequestEmblem,"guildrequestemblem"}, + {clif_parse_GuildChangeEmblem,"guildchangeemblem"}, + {clif_parse_GuildChangeNotice,"guildchangenotice"}, + {clif_parse_GuildInvite,"guildinvite"}, + {clif_parse_GuildReplyInvite,"guildreplyinvite"}, + {clif_parse_GuildLeave,"guildleave"}, + {clif_parse_GuildExpulsion,"guildexpulsion"}, + {clif_parse_GuildMessage,"guildmessage"}, + {clif_parse_GuildRequestAlliance,"guildrequestalliance"}, + {clif_parse_GuildReplyAlliance,"guildreplyalliance"}, + {clif_parse_GuildDelAlliance,"guilddelalliance"}, + {clif_parse_GuildOpposition,"guildopposition"}, + {clif_parse_GuildBreak,"guildbreak"}, + {clif_parse_PetMenu,"petmenu"}, + {clif_parse_CatchPet,"catchpet"}, + {clif_parse_SelectEgg,"selectegg"}, + {clif_parse_SendEmotion,"sendemotion"}, + {clif_parse_ChangePetName,"changepetname"}, + + {clif_parse_GMKick,"gmkick"}, + {clif_parse_GMHide,"gmhide"}, + {clif_parse_GMReqNoChat,"gmreqnochat"}, + {clif_parse_GMReqAccountName,"gmreqaccname"}, + {clif_parse_GMKickAll,"killall"}, + {clif_parse_GMRecall,"recall"}, + {clif_parse_GMRecall,"summon"}, + {clif_parse_GM_Monster_Item,"itemmonster"}, + {clif_parse_GMShift,"remove"}, + {clif_parse_GMShift,"shift"}, + {clif_parse_GMChangeMapType,"changemaptype"}, + {clif_parse_GMRc,"rc"}, + {clif_parse_GMRecall2,"recall2"}, + {clif_parse_GMRemove2,"remove2"}, + + {clif_parse_NoviceDoriDori,"sndoridori"}, + {clif_parse_NoviceExplosionSpirits,"snexplosionspirits"}, + {clif_parse_PMIgnore,"wisexin"}, + {clif_parse_PMIgnoreList,"wisexlist"}, + {clif_parse_PMIgnoreAll,"wisall"}, + {clif_parse_FriendsListAdd,"friendslistadd"}, + {clif_parse_FriendsListRemove,"friendslistremove"}, + {clif_parse_FriendsListReply,"friendslistreply"}, + {clif_parse_Blacksmith,"blacksmith"}, + {clif_parse_Alchemist,"alchemist"}, + {clif_parse_Taekwon,"taekwon"}, + {clif_parse_RankingPk,"rankingpk"}, + {clif_parse_FeelSaveOk,"feelsaveok"}, + {clif_parse_debug,"debug"}, + {clif_parse_ChangeHomunculusName,"changehomunculusname"}, + {clif_parse_HomMoveToMaster,"hommovetomaster"}, + {clif_parse_HomMoveTo,"hommoveto"}, + {clif_parse_HomAttack,"homattack"}, + {clif_parse_HomMenu,"hommenu"}, + {clif_parse_StoragePassword,"storagepassword"}, + {clif_parse_Hotkey,"hotkey"}, + {clif_parse_AutoRevive,"autorevive"}, + {clif_parse_Check,"check"}, + {clif_parse_Adopt_request,"adoptrequest"}, + {clif_parse_Adopt_reply,"adoptreply"}, + // MAIL SYSTEM + {clif_parse_Mail_refreshinbox,"mailrefresh"}, + {clif_parse_Mail_read,"mailread"}, + {clif_parse_Mail_getattach,"mailgetattach"}, + {clif_parse_Mail_delete,"maildelete"}, + {clif_parse_Mail_return,"mailreturn"}, + {clif_parse_Mail_setattach,"mailsetattach"}, + {clif_parse_Mail_winopen,"mailwinopen"}, + {clif_parse_Mail_send,"mailsend"}, + // AUCTION SYSTEM + {clif_parse_Auction_search,"auctionsearch"}, + {clif_parse_Auction_buysell,"auctionbuysell"}, + {clif_parse_Auction_setitem,"auctionsetitem"}, + {clif_parse_Auction_cancelreg,"auctioncancelreg"}, + {clif_parse_Auction_register,"auctionregister"}, + {clif_parse_Auction_cancel,"auctioncancel"}, + {clif_parse_Auction_close,"auctionclose"}, + {clif_parse_Auction_bid,"auctionbid"}, + // Quest Log System + {clif_parse_questStateAck,"queststate"}, + {clif_parse_cashshop_buy,"cashshopbuy"}, + {clif_parse_ViewPlayerEquip,"viewplayerequip"}, + {clif_parse_EquipTick,"equiptickbox"}, + {clif_parse_BattleChat,"battlechat"}, + {clif_parse_mercenary_action,"mermenu"}, + {clif_parse_progressbar,"progressbar"}, + {clif_parse_SkillSelectMenu,"skillselectmenu"}, + {clif_parse_ItemListWindowSelected,"itemlistwindowselected"}, +#if PACKETVER >= 20091229 + {clif_parse_PartyBookingRegisterReq,"bookingregreq"}, + {clif_parse_PartyBookingSearchReq,"bookingsearchreq"}, + {clif_parse_PartyBookingUpdateReq,"bookingupdatereq"}, + {clif_parse_PartyBookingDeleteReq,"bookingdelreq"}, +#endif + {clif_parse_PVPInfo,"pvpinfo"}, + {clif_parse_LessEffect,"lesseffect"}, + // Buying Store + {clif_parse_ReqOpenBuyingStore,"reqopenbuyingstore"}, + {clif_parse_ReqCloseBuyingStore,"reqclosebuyingstore"}, + {clif_parse_ReqClickBuyingStore,"reqclickbuyingstore"}, + {clif_parse_ReqTradeBuyingStore,"reqtradebuyingstore"}, + // Store Search + {clif_parse_SearchStoreInfo,"searchstoreinfo"}, + {clif_parse_SearchStoreInfoNextPage,"searchstoreinfonextpage"}, + {clif_parse_CloseSearchStoreInfo,"closesearchstoreinfo"}, + {clif_parse_SearchStoreInfoListItemClick,"searchstoreinfolistitemclick"}, + /* */ + { clif_parse_MoveItem , "moveitem" }, + {NULL,NULL} + }; + + // initialize packet_db[SERVER] from hardcoded packet_len_table[] values + memset(packet_db,0,sizeof(packet_db)); + for( i = 0; i < ARRAYLENGTH(packet_len_table); ++i ) + packet_len(i) = packet_len_table[i]; + + sprintf(line, "%s/packet_db.txt", db_path); + if( (fp=fopen(line,"r"))==NULL ){ + ShowFatalError("can't read %s\n", line); + exit(EXIT_FAILURE); + } + + clif_config.packet_db_ver = MAX_PACKET_VER; + packet_ver = MAX_PACKET_VER; // read into packet_db's version by default + while( fgets(line, sizeof(line), fp) ) + { + ln++; + if(line[0]=='/' && line[1]=='/') + continue; + if (sscanf(line,"%256[^:]: %256[^\r\n]",w1,w2) == 2) + { + if(strcmpi(w1,"packet_ver")==0) { + int prev_ver = packet_ver; + skip_ver = 0; + packet_ver = atoi(w2); + if ( packet_ver > MAX_PACKET_VER ) + { //Check to avoid overflowing. [Skotlex] + if( (warned&1) == 0 ) + ShowWarning("The packet_db table only has support up to version %d.\n", MAX_PACKET_VER); + warned &= 1; + skip_ver = 1; + } + else if( packet_ver < 0 ) + { + if( (warned&2) == 0 ) + ShowWarning("Negative packet versions are not supported.\n"); + warned &= 2; + skip_ver = 1; + } + else if( packet_ver == SERVER ) + { + if( (warned&4) == 0 ) + ShowWarning("Packet version %d is reserved for server use only.\n", SERVER); + warned &= 4; + skip_ver = 1; + } + + if( skip_ver ) + { + ShowWarning("Skipping packet version %d.\n", packet_ver); + packet_ver = prev_ver; + continue; + } + // copy from previous version into new version and continue + // - indicating all following packets should be read into the newer version + memcpy(&packet_db[packet_ver], &packet_db[prev_ver], sizeof(packet_db[0])); + continue; + } else if(strcmpi(w1,"packet_db_ver")==0) { + if(strcmpi(w2,"default")==0) //This is the preferred version. + clif_config.packet_db_ver = MAX_PACKET_VER; + else // to manually set the packet DB version + clif_config.packet_db_ver = cap_value(atoi(w2), 0, MAX_PACKET_VER); + + continue; + } + } + + if( skip_ver != 0 ) + continue; // Skipping current packet version + + memset(str,0,sizeof(str)); + for(j=0,p=line;j<4 && p; ++j) + { + str[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + if(str[0]==NULL) + continue; + cmd=strtol(str[0],(char **)NULL,0); + if(max_cmd < cmd) + max_cmd = cmd; + if(cmd <= 0 || cmd > MAX_PACKET_DB) + continue; + if(str[1]==NULL){ + ShowError("packet_db: packet len error\n"); + continue; + } + + packet_db[packet_ver][cmd].len = (short)atoi(str[1]); + + if(str[2]==NULL){ + packet_db[packet_ver][cmd].func = NULL; + ln++; + continue; + } + + // look up processing function by name + ARR_FIND( 0, ARRAYLENGTH(clif_parse_func), j, clif_parse_func[j].name != NULL && strcmp(str[2],clif_parse_func[j].name)==0 ); + if( j < ARRAYLENGTH(clif_parse_func) ) + packet_db[packet_ver][cmd].func = clif_parse_func[j].func; + + // set the identifying cmd for the packet_db version + if (strcmp(str[2],"wanttoconnection")==0) + clif_config.connect_cmd[packet_ver] = cmd; + + if(str[3]==NULL){ + ShowError("packet_db: packet error\n"); + exit(EXIT_FAILURE); + } + for(j=0,p2=str[3];p2;j++){ + short k; + str2[j]=p2; + p2=strchr(p2,':'); + if(p2) *p2++=0; + k = atoi(str2[j]); + // if (packet_db[packet_ver][cmd].pos[j] != k && clif_config.prefer_packet_db) // not used for now + + if( j >= MAX_PACKET_POS ) + { + ShowError("Too many positions found for packet 0x%04x (max=%d).\n", cmd, MAX_PACKET_POS); + break; + } + + packet_db[packet_ver][cmd].pos[j] = k; + } + } + fclose(fp); + if(max_cmd > MAX_PACKET_DB) + { + ShowWarning("Found packets up to 0x%X, ignored 0x%X and above.\n", max_cmd, MAX_PACKET_DB); + ShowWarning("Please increase MAX_PACKET_DB and recompile.\n"); + } + if (!clif_config.connect_cmd[clif_config.packet_db_ver]) + { //Locate the nearest version that we still support. [Skotlex] + for(j = clif_config.packet_db_ver; j >= 0 && !clif_config.connect_cmd[j]; j--); + + clif_config.packet_db_ver = j?j:MAX_PACKET_VER; + } + ShowStatus("Done reading packet database from '"CL_WHITE"%s"CL_RESET"'. Using default packet version: "CL_WHITE"%d"CL_RESET".\n", "packet_db.txt", clif_config.packet_db_ver); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int do_init_clif(void) { + const char* colors[COLOR_MAX] = { "0xFF0000" }; + int i; + /** + * Setup Color Table (saves unnecessary load of strtoul on every call) + **/ + for(i = 0; i < COLOR_MAX; i++) { + color_table[i] = strtoul(colors[i],NULL,0); + color_table[i] = (color_table[i] & 0x0000FF) << 16 | (color_table[i] & 0x00FF00) | (color_table[i] & 0xFF0000) >> 16;//RGB to BGR + } + + clif_config.packet_db_ver = -1; // the main packet version of the DB + memset(clif_config.connect_cmd, 0, sizeof(clif_config.connect_cmd)); //The default connect command will be determined after reading the packet_db [Skotlex] + + memset(packet_db,0,sizeof(packet_db)); + //Using the packet_db file is the only way to set up packets now [Skotlex] + packetdb_readdb(); + + set_defaultparse(clif_parse); + if( make_listen_bind(bind_ip,map_port) == -1 ) { + ShowFatalError("can't bind game port\n"); + exit(EXIT_FAILURE); + } + + add_timer_func_list(clif_clearunit_delayed_sub, "clif_clearunit_delayed_sub"); + add_timer_func_list(clif_delayquit, "clif_delayquit"); + + delay_clearunit_ers = ers_new(sizeof(struct block_list),"clif.c::delay_clearunit_ers",ERS_OPT_CLEAR); + + return 0; +} + +void do_final_clif(void) { + ers_destroy(delay_clearunit_ers); +} diff --git a/src/map/script.c b/src/map/script.c index a918bc853..27e0bb549 100644 --- a/src/map/script.c +++ b/src/map/script.c @@ -1,17779 +1,17784 @@ -// Copyright (c) Athena Dev Teams - Licensed under GNU GPL -// For more information, see LICENCE in the main folder - -//#define DEBUG_DISP -//#define DEBUG_DISASM -//#define DEBUG_RUN -//#define DEBUG_HASH -//#define DEBUG_DUMP_STACK - -#include "../common/cbasetypes.h" -#include "../common/malloc.h" -#include "../common/md5calc.h" -#include "../common/nullpo.h" -#include "../common/random.h" -#include "../common/showmsg.h" -#include "../common/socket.h" // usage: getcharip -#include "../common/strlib.h" -#include "../common/timer.h" -#include "../common/utils.h" - -#include "map.h" -#include "path.h" -#include "clif.h" -#include "chrif.h" -#include "itemdb.h" -#include "pc.h" -#include "status.h" -#include "storage.h" -#include "mob.h" -#include "npc.h" -#include "pet.h" -#include "mapreg.h" -#include "homunculus.h" -#include "instance.h" -#include "mercenary.h" -#include "intif.h" -#include "skill.h" -#include "status.h" -#include "chat.h" -#include "battle.h" -#include "battleground.h" -#include "party.h" -#include "guild.h" -#include "atcommand.h" -#include "log.h" -#include "unit.h" -#include "pet.h" -#include "mail.h" -#include "script.h" -#include "quest.h" -#include "elemental.h" - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <math.h> -#ifndef WIN32 - #include <sys/time.h> -#endif -#include <time.h> -#include <setjmp.h> -#include <errno.h> - -#ifdef BETA_THREAD_TEST - #include "../common/atomic.h" - #include "../common/spinlock.h" - #include "../common/thread.h" - #include "../common/mutex.h" -#endif - - -/////////////////////////////////////////////////////////////////////////////// -//## TODO possible enhancements: [FlavioJS] -// - 'callfunc' supporting labels in the current npc "::LabelName" -// - 'callfunc' supporting labels in other npcs "NpcName::LabelName" -// - 'function FuncName;' function declarations reverting to global functions -// if local label isn't found -// - join callfunc and callsub's functionality -// - remove dynamic allocation in add_word() -// - remove GETVALUE / SETVALUE -// - clean up the set_reg / set_val / setd_sub mess -// - detect invalid label references at parse-time - -// -// struct script_state* st; -// - -/// Returns the script_data at the target index -#define script_getdata(st,i) ( &((st)->stack->stack_data[(st)->start + (i)]) ) -/// Returns if the stack contains data at the target index -#define script_hasdata(st,i) ( (st)->end > (st)->start + (i) ) -/// Returns the index of the last data in the stack -#define script_lastdata(st) ( (st)->end - (st)->start - 1 ) -/// Pushes an int into the stack -#define script_pushint(st,val) push_val((st)->stack, C_INT, (val)) -/// Pushes a string into the stack (script engine frees it automatically) -#define script_pushstr(st,val) push_str((st)->stack, C_STR, (val)) -/// Pushes a copy of a string into the stack -#define script_pushstrcopy(st,val) push_str((st)->stack, C_STR, aStrdup(val)) -/// Pushes a constant string into the stack (must never change or be freed) -#define script_pushconststr(st,val) push_str((st)->stack, C_CONSTSTR, (val)) -/// Pushes a nil into the stack -#define script_pushnil(st) push_val((st)->stack, C_NOP, 0) -/// Pushes a copy of the data in the target index -#define script_pushcopy(st,i) push_copy((st)->stack, (st)->start + (i)) - -#define script_isstring(st,i) data_isstring(script_getdata(st,i)) -#define script_isint(st,i) data_isint(script_getdata(st,i)) - -#define script_getnum(st,val) conv_num(st, script_getdata(st,val)) -#define script_getstr(st,val) conv_str(st, script_getdata(st,val)) -#define script_getref(st,val) ( script_getdata(st,val)->ref ) - -// Note: "top" functions/defines use indexes relative to the top of the stack -// -1 is the index of the data at the top - -/// Returns the script_data at the target index relative to the top of the stack -#define script_getdatatop(st,i) ( &((st)->stack->stack_data[(st)->stack->sp + (i)]) ) -/// Pushes a copy of the data in the target index relative to the top of the stack -#define script_pushcopytop(st,i) push_copy((st)->stack, (st)->stack->sp + (i)) -/// Removes the range of values [start,end[ relative to the top of the stack -#define script_removetop(st,start,end) ( pop_stack((st), ((st)->stack->sp + (start)), (st)->stack->sp + (end)) ) - -// -// struct script_data* data; -// - -/// Returns if the script data is a string -#define data_isstring(data) ( (data)->type == C_STR || (data)->type == C_CONSTSTR ) -/// Returns if the script data is an int -#define data_isint(data) ( (data)->type == C_INT ) -/// Returns if the script data is a reference -#define data_isreference(data) ( (data)->type == C_NAME ) -/// Returns if the script data is a label -#define data_islabel(data) ( (data)->type == C_POS ) -/// Returns if the script data is an internal script function label -#define data_isfunclabel(data) ( (data)->type == C_USERFUNC_POS ) - -/// Returns if this is a reference to a constant -#define reference_toconstant(data) ( str_data[reference_getid(data)].type == C_INT ) -/// Returns if this a reference to a param -#define reference_toparam(data) ( str_data[reference_getid(data)].type == C_PARAM ) -/// Returns if this a reference to a variable -//##TODO confirm it's C_NAME [FlavioJS] -#define reference_tovariable(data) ( str_data[reference_getid(data)].type == C_NAME ) -/// Returns the unique id of the reference (id and index) -#define reference_getuid(data) ( (data)->u.num ) -/// Returns the id of the reference -#define reference_getid(data) ( (int32)(reference_getuid(data) & 0x00ffffff) ) -/// Returns the array index of the reference -#define reference_getindex(data) ( (int32)(((uint32)(reference_getuid(data) & 0xff000000)) >> 24) ) -/// Returns the name of the reference -#define reference_getname(data) ( str_buf + str_data[reference_getid(data)].str ) -/// Returns the linked list of uid-value pairs of the reference (can be NULL) -#define reference_getref(data) ( (data)->ref ) -/// Returns the value of the constant -#define reference_getconstant(data) ( str_data[reference_getid(data)].val ) -/// Returns the type of param -#define reference_getparamtype(data) ( str_data[reference_getid(data)].val ) - -/// Composes the uid of a reference from the id and the index -#define reference_uid(id,idx) ( (int32)((((uint32)(id)) & 0x00ffffff) | (((uint32)(idx)) << 24)) ) - -#define not_server_variable(prefix) ( (prefix) != '$' && (prefix) != '.' && (prefix) != '\'') -#define not_array_variable(prefix) ( (prefix) != '$' && (prefix) != '@' && (prefix) != '.' && (prefix) != '\'' ) -#define is_string_variable(name) ( (name)[strlen(name) - 1] == '$' ) - -#define FETCH(n, t) \ - if( script_hasdata(st,n) ) \ - (t)=script_getnum(st,n); - -/// Maximum amount of elements in script arrays -#define SCRIPT_MAX_ARRAYSIZE 128 - -#define SCRIPT_BLOCK_SIZE 512 -enum { LABEL_NEXTLINE=1,LABEL_START }; - -/// temporary buffer for passing around compiled bytecode -/// @see add_scriptb, set_label, parse_script -static unsigned char* script_buf = NULL; -static int script_pos = 0, script_size = 0; - -static inline int GETVALUE(const unsigned char* buf, int i) -{ - return (int)MakeDWord(MakeWord(buf[i], buf[i+1]), MakeWord(buf[i+2], 0)); -} -static inline void SETVALUE(unsigned char* buf, int i, int n) -{ - buf[i] = GetByte(n, 0); - buf[i+1] = GetByte(n, 1); - buf[i+2] = GetByte(n, 2); -} - -// String buffer structures. -// str_data stores string information -static struct str_data_struct { - enum c_op type; - int str; - int backpatch; - int label; - int (*func)(struct script_state *st); - int val; - int next; -} *str_data = NULL; -static int str_data_size = 0; // size of the data -static int str_num = LABEL_START; // next id to be assigned - -// str_buf holds the strings themselves -static char *str_buf; -static int str_size = 0; // size of the buffer -static int str_pos = 0; // next position to be assigned - - -// Using a prime number for SCRIPT_HASH_SIZE should give better distributions -#define SCRIPT_HASH_SIZE 1021 -int str_hash[SCRIPT_HASH_SIZE]; -// Specifies which string hashing method to use -//#define SCRIPT_HASH_DJB2 -//#define SCRIPT_HASH_SDBM -#define SCRIPT_HASH_ELF - -static DBMap* scriptlabel_db=NULL; // const char* label_name -> int script_pos -static DBMap* userfunc_db=NULL; // const char* func_name -> struct script_code* -static int parse_options=0; -DBMap* script_get_label_db(void){ return scriptlabel_db; } -DBMap* script_get_userfunc_db(void){ return userfunc_db; } - -// important buildin function references for usage in scripts -static int buildin_set_ref = 0; -static int buildin_callsub_ref = 0; -static int buildin_callfunc_ref = 0; -static int buildin_getelementofarray_ref = 0; - -// Caches compiled autoscript item code. -// Note: This is not cleared when reloading itemdb. -static DBMap* autobonus_db=NULL; // char* script -> char* bytecode - -struct Script_Config script_config = { - 1, // warn_func_mismatch_argtypes - 1, 65535, 2048, //warn_func_mismatch_paramnum/check_cmdcount/check_gotocount - 0, INT_MAX, // input_min_value/input_max_value - "OnPCDieEvent", //die_event_name - "OnPCKillEvent", //kill_pc_event_name - "OnNPCKillEvent", //kill_mob_event_name - "OnPCLoginEvent", //login_event_name - "OnPCLogoutEvent", //logout_event_name - "OnPCLoadMapEvent", //loadmap_event_name - "OnPCBaseLvUpEvent", //baselvup_event_name - "OnPCJobLvUpEvent", //joblvup_event_name - "OnTouch_", //ontouch_name (runs on first visible char to enter area, picks another char if the first char leaves) - "OnTouch", //ontouch2_name (run whenever a char walks into the OnTouch area) -}; - -static jmp_buf error_jump; -static char* error_msg; -static const char* error_pos; -static int error_report; // if the error should produce output - -// for advanced scripting support ( nested if, switch, while, for, do-while, function, etc ) -// [Eoe / jA 1080, 1081, 1094, 1164] -enum curly_type { - TYPE_NULL = 0, - TYPE_IF, - TYPE_SWITCH, - TYPE_WHILE, - TYPE_FOR, - TYPE_DO, - TYPE_USERFUNC, - TYPE_ARGLIST // function argument list -}; - -enum e_arglist -{ - ARGLIST_UNDEFINED = 0, - ARGLIST_NO_PAREN = 1, - ARGLIST_PAREN = 2, -}; - -static struct { - struct { - enum curly_type type; - int index; - int count; - int flag; - struct linkdb_node *case_label; - } curly[256]; // Information right parenthesis - int curly_count; // The number of right brackets - int index; // Number of the syntax used in the script -} syntax; - -const char* parse_curly_close(const char* p); -const char* parse_syntax_close(const char* p); -const char* parse_syntax_close_sub(const char* p,int* flag); -const char* parse_syntax(const char* p); -static int parse_syntax_for_flag = 0; - -extern int current_equip_item_index; //for New CARDS Scripts. It contains Inventory Index of the EQUIP_SCRIPT caller item. [Lupus] -int potion_flag=0; //For use on Alchemist improved potions/Potion Pitcher. [Skotlex] -int potion_hp=0, potion_per_hp=0, potion_sp=0, potion_per_sp=0; -int potion_target=0; - - -c_op get_com(unsigned char *script,int *pos); -int get_num(unsigned char *script,int *pos); - -typedef struct script_function { - int (*func)(struct script_state *st); - const char *name; - const char *arg; -} script_function; - -extern script_function buildin_func[]; - -static struct linkdb_node* sleep_db;// int oid -> struct script_state* - -#ifdef BETA_THREAD_TEST -/** - * MySQL Query Slave - **/ -static SPIN_LOCK queryThreadLock; -static rAthread queryThread = NULL; -static ramutex queryThreadMutex = NULL; -static racond queryThreadCond = NULL; -static volatile int32 queryThreadTerminate = 0; - -struct queryThreadEntry { - bool ok; - bool type; /* main db or log db? */ - struct script_state *st; -}; - -/* Ladies and Gentleman the Manager! */ -struct { - struct queryThreadEntry **entry;/* array of structs */ - int count; - int timer;/* used to receive processed entries */ -} queryThreadData; -#endif - -/*========================================== - * (Only those needed) local declaration prototype - *------------------------------------------*/ -const char* parse_subexpr(const char* p,int limit); -int run_func(struct script_state *st); - -enum { - MF_NOMEMO, //0 - MF_NOTELEPORT, - MF_NOSAVE, - MF_NOBRANCH, - MF_NOPENALTY, - MF_NOZENYPENALTY, - MF_PVP, - MF_PVP_NOPARTY, - MF_PVP_NOGUILD, - MF_GVG, - MF_GVG_NOPARTY, //10 - MF_NOTRADE, - MF_NOSKILL, - MF_NOWARP, - MF_PARTYLOCK, - MF_NOICEWALL, - MF_SNOW, - MF_FOG, - MF_SAKURA, - MF_LEAVES, - /** - * No longer available, keeping here just in case it's back someday. [Ind] - **/ - //MF_RAIN, //20 - // 21 free - MF_NOGO = 22, - MF_CLOUDS, - MF_CLOUDS2, - MF_FIREWORKS, - MF_GVG_CASTLE, - MF_GVG_DUNGEON, - MF_NIGHTENABLED, - MF_NOBASEEXP, - MF_NOJOBEXP, //30 - MF_NOMOBLOOT, - MF_NOMVPLOOT, - MF_NORETURN, - MF_NOWARPTO, - MF_NIGHTMAREDROP, - MF_RESTRICTED, - MF_NOCOMMAND, - MF_NODROP, - MF_JEXP, - MF_BEXP, //40 - MF_NOVENDING, - MF_LOADEVENT, - MF_NOCHAT, - MF_NOEXPPENALTY, - MF_GUILDLOCK, - MF_TOWN, - MF_AUTOTRADE, - MF_ALLOWKS, - MF_MONSTER_NOTELEPORT, - MF_PVP_NOCALCRANK, //50 - MF_BATTLEGROUND, - MF_RESET -}; - -const char* script_op2name(int op) -{ -#define RETURN_OP_NAME(type) case type: return #type - switch( op ) - { - RETURN_OP_NAME(C_NOP); - RETURN_OP_NAME(C_POS); - RETURN_OP_NAME(C_INT); - RETURN_OP_NAME(C_PARAM); - RETURN_OP_NAME(C_FUNC); - RETURN_OP_NAME(C_STR); - RETURN_OP_NAME(C_CONSTSTR); - RETURN_OP_NAME(C_ARG); - RETURN_OP_NAME(C_NAME); - RETURN_OP_NAME(C_EOL); - RETURN_OP_NAME(C_RETINFO); - RETURN_OP_NAME(C_USERFUNC); - RETURN_OP_NAME(C_USERFUNC_POS); - - // operators - RETURN_OP_NAME(C_OP3); - RETURN_OP_NAME(C_LOR); - RETURN_OP_NAME(C_LAND); - RETURN_OP_NAME(C_LE); - RETURN_OP_NAME(C_LT); - RETURN_OP_NAME(C_GE); - RETURN_OP_NAME(C_GT); - RETURN_OP_NAME(C_EQ); - RETURN_OP_NAME(C_NE); - RETURN_OP_NAME(C_XOR); - RETURN_OP_NAME(C_OR); - RETURN_OP_NAME(C_AND); - RETURN_OP_NAME(C_ADD); - RETURN_OP_NAME(C_SUB); - RETURN_OP_NAME(C_MUL); - RETURN_OP_NAME(C_DIV); - RETURN_OP_NAME(C_MOD); - RETURN_OP_NAME(C_NEG); - RETURN_OP_NAME(C_LNOT); - RETURN_OP_NAME(C_NOT); - RETURN_OP_NAME(C_R_SHIFT); - RETURN_OP_NAME(C_L_SHIFT); - - default: - ShowDebug("script_op2name: unexpected op=%d\n", op); - return "???"; - } -#undef RETURN_OP_NAME -} - -#ifdef DEBUG_DUMP_STACK -static void script_dump_stack(struct script_state* st) -{ - int i; - ShowMessage("\tstart = %d\n", st->start); - ShowMessage("\tend = %d\n", st->end); - ShowMessage("\tdefsp = %d\n", st->stack->defsp); - ShowMessage("\tsp = %d\n", st->stack->sp); - for( i = 0; i < st->stack->sp; ++i ) - { - struct script_data* data = &st->stack->stack_data[i]; - ShowMessage("\t[%d] %s", i, script_op2name(data->type)); - switch( data->type ) - { - case C_INT: - case C_POS: - ShowMessage(" %d\n", data->u.num); - break; - - case C_STR: - case C_CONSTSTR: - ShowMessage(" \"%s\"\n", data->u.str); - break; - - case C_NAME: - ShowMessage(" \"%s\" (id=%d ref=%p subtype=%s)\n", reference_getname(data), data->u.num, data->ref, script_op2name(str_data[data->u.num].type)); - break; - - case C_RETINFO: - { - struct script_retinfo* ri = data->u.ri; - ShowMessage(" %p {var_function=%p, script=%p, pos=%d, nargs=%d, defsp=%d}\n", ri, ri->var_function, ri->script, ri->pos, ri->nargs, ri->defsp); - } - break; - default: - ShowMessage("\n"); - break; - } - } -} -#endif - -/// Reports on the console the src of a script error. -static void script_reportsrc(struct script_state *st) -{ - struct block_list* bl; - - if( st->oid == 0 ) - return; //Can't report source. - - bl = map_id2bl(st->oid); - if( bl == NULL ) - return; - - switch( bl->type ) - { - case BL_NPC: - if( bl->m >= 0 ) - ShowDebug("Source (NPC): %s at %s (%d,%d)\n", ((struct npc_data *)bl)->name, map[bl->m].name, bl->x, bl->y); - else - ShowDebug("Source (NPC): %s (invisible/not on a map)\n", ((struct npc_data *)bl)->name); - break; - default: - if( bl->m >= 0 ) - ShowDebug("Source (Non-NPC type %d): name %s at %s (%d,%d)\n", bl->type, status_get_name(bl), map[bl->m].name, bl->x, bl->y); - else - ShowDebug("Source (Non-NPC type %d): name %s (invisible/not on a map)\n", bl->type, status_get_name(bl)); - break; - } -} - -/// Reports on the console information about the script data. -static void script_reportdata(struct script_data* data) -{ - if( data == NULL ) - return; - switch( data->type ) - { - case C_NOP:// no value - ShowDebug("Data: nothing (nil)\n"); - break; - case C_INT:// number - ShowDebug("Data: number value=%d\n", data->u.num); - break; - case C_STR: - case C_CONSTSTR:// string - if( data->u.str ) - { - ShowDebug("Data: string value=\"%s\"\n", data->u.str); - } - else - { - ShowDebug("Data: string value=NULL\n"); - } - break; - case C_NAME:// reference - if( reference_tovariable(data) ) - {// variable - const char* name = reference_getname(data); - if( not_array_variable(*name) ) - ShowDebug("Data: variable name='%s'\n", name); - else - ShowDebug("Data: variable name='%s' index=%d\n", name, reference_getindex(data)); - } - else if( reference_toconstant(data) ) - {// constant - ShowDebug("Data: constant name='%s' value=%d\n", reference_getname(data), reference_getconstant(data)); - } - else if( reference_toparam(data) ) - {// param - ShowDebug("Data: param name='%s' type=%d\n", reference_getname(data), reference_getparamtype(data)); - } - else - {// ??? - ShowDebug("Data: reference name='%s' type=%s\n", reference_getname(data), script_op2name(data->type)); - ShowDebug("Please report this!!! - str_data.type=%s\n", script_op2name(str_data[reference_getid(data)].type)); - } - break; - case C_POS:// label - ShowDebug("Data: label pos=%d\n", data->u.num); - break; - default: - ShowDebug("Data: %s\n", script_op2name(data->type)); - break; - } -} - - -/// Reports on the console information about the current built-in function. -static void script_reportfunc(struct script_state* st) -{ - int i, params, id; - struct script_data* data; - - if( !script_hasdata(st,0) ) - {// no stack - return; - } - - data = script_getdata(st,0); - - if( !data_isreference(data) || str_data[reference_getid(data)].type != C_FUNC ) - {// script currently not executing a built-in function or corrupt stack - return; - } - - id = reference_getid(data); - params = script_lastdata(st)-1; - - if( params > 0 ) - { - ShowDebug("Function: %s (%d parameter%s):\n", get_str(id), params, ( params == 1 ) ? "" : "s"); - - for( i = 2; i <= script_lastdata(st); i++ ) - { - script_reportdata(script_getdata(st,i)); - } - } - else - { - ShowDebug("Function: %s (no parameters)\n", get_str(id)); - } -} - - -/*========================================== - * Output error message - *------------------------------------------*/ -static void disp_error_message2(const char *mes,const char *pos,int report) -{ - error_msg = aStrdup(mes); - error_pos = pos; - error_report = report; - longjmp( error_jump, 1 ); -} -#define disp_error_message(mes,pos) disp_error_message2(mes,pos,1) - -/// Checks event parameter validity -static void check_event(struct script_state *st, const char *evt) -{ - if( evt && evt[0] && !stristr(evt, "::On") ) - { - ShowWarning("NPC event parameter deprecated! Please use 'NPCNAME::OnEVENT' instead of '%s'.\n", evt); - script_reportsrc(st); - } -} - -/*========================================== - * Hashes the input string - *------------------------------------------*/ -static unsigned int calc_hash(const char* p) -{ - unsigned int h; - -#if defined(SCRIPT_HASH_DJB2) - h = 5381; - while( *p ) // hash*33 + c - h = ( h << 5 ) + h + ((unsigned char)TOLOWER(*p++)); -#elif defined(SCRIPT_HASH_SDBM) - h = 0; - while( *p ) // hash*65599 + c - h = ( h << 6 ) + ( h << 16 ) - h + ((unsigned char)TOLOWER(*p++)); -#elif defined(SCRIPT_HASH_ELF) // UNIX ELF hash - h = 0; - while( *p ){ - unsigned int g; - h = ( h << 4 ) + ((unsigned char)TOLOWER(*p++)); - g = h & 0xF0000000; - if( g ) - { - h ^= g >> 24; - h &= ~g; - } - } -#else // athena hash - h = 0; - while( *p ) - h = ( h << 1 ) + ( h >> 3 ) + ( h >> 5 ) + ( h >> 8 ) + (unsigned char)TOLOWER(*p++); -#endif - - return h % SCRIPT_HASH_SIZE; -} - - -/*========================================== - * str_data manipulation functions - *------------------------------------------*/ - -/// Looks up string using the provided id. -const char* get_str(int id) -{ - Assert( id >= LABEL_START && id < str_size ); - return str_buf+str_data[id].str; -} - -/// Returns the uid of the string, or -1. -static int search_str(const char* p) -{ - int i; - - for( i = str_hash[calc_hash(p)]; i != 0; i = str_data[i].next ) - if( strcasecmp(get_str(i),p) == 0 ) - return i; - - return -1; -} - -/// Stores a copy of the string and returns its id. -/// If an identical string is already present, returns its id instead. -int add_str(const char* p) -{ - int i, h; - int len; - - h = calc_hash(p); - - if( str_hash[h] == 0 ) - {// empty bucket, add new node here - str_hash[h] = str_num; - } - else - {// scan for end of list, or occurence of identical string - for( i = str_hash[h]; ; i = str_data[i].next ) - { - if( strcasecmp(get_str(i),p) == 0 ) - return i; // string already in list - if( str_data[i].next == 0 ) - break; // reached the end - } - - // append node to end of list - str_data[i].next = str_num; - } - - // grow list if neccessary - if( str_num >= str_data_size ) - { - str_data_size += 128; - RECREATE(str_data,struct str_data_struct,str_data_size); - memset(str_data + (str_data_size - 128), '\0', 128); - } - - len=(int)strlen(p); - - // grow string buffer if neccessary - while( str_pos+len+1 >= str_size ) - { - str_size += 256; - RECREATE(str_buf,char,str_size); - memset(str_buf + (str_size - 256), '\0', 256); - } - - safestrncpy(str_buf+str_pos, p, len+1); - 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 += len+1; - - return str_num++; -} - - -/// Appends 1 byte to the script buffer. -static void add_scriptb(int a) -{ - if( script_pos+1 >= script_size ) - { - script_size += SCRIPT_BLOCK_SIZE; - RECREATE(script_buf,unsigned char,script_size); - } - script_buf[script_pos++] = (uint8)(a); -} - -/// Appends a c_op value to the script buffer. -/// The value is variable-length encoded into 8-bit blocks. -/// The encoding scheme is ( 01?????? )* 00??????, LSB first. -/// All blocks but the last hold 7 bits of data, topmost bit is always 1 (carries). -static void add_scriptc(int a) -{ - while( a >= 0x40 ) - { - add_scriptb((a&0x3f)|0x40); - a = (a - 0x40) >> 6; - } - - add_scriptb(a); -} - -/// Appends an integer value to the script buffer. -/// The value is variable-length encoded into 8-bit blocks. -/// The encoding scheme is ( 11?????? )* 10??????, LSB first. -/// All blocks but the last hold 7 bits of data, topmost bit is always 1 (carries). -static void add_scripti(int a) -{ - while( a >= 0x40 ) - { - add_scriptb((a&0x3f)|0xc0); - a = (a - 0x40) >> 6; - } - add_scriptb(a|0x80); -} - -/// Appends a str_data object (label/function/variable/integer) to the script buffer. - -/// -/// @param l The id of the str_data entry -// Maximum up to 16M -static void add_scriptl(int l) -{ - int backpatch = str_data[l].backpatch; - - switch(str_data[l].type){ - case C_POS: - case C_USERFUNC_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: - case C_USERFUNC: - // Embedded data backpatch there is a possibility of label - 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(abs(str_data[l].val)); - if( str_data[l].val < 0 ) //Notice that this is negative, from jA (Rayce) - add_scriptc(C_NEG); - break; - default: // assume C_NAME - add_scriptc(C_NAME); - add_scriptb(l); - add_scriptb(l>>8); - add_scriptb(l>>16); - break; - } -} - -/*========================================== - * Resolve the label - *------------------------------------------*/ -void set_label(int l,int pos, const char* script_pos) -{ - int i,next; - - if(str_data[l].type==C_INT || str_data[l].type==C_PARAM || str_data[l].type==C_FUNC) - { //Prevent overwriting constants values, parameters and built-in functions [Skotlex] - disp_error_message("set_label: invalid label name",script_pos); - return; - } - if(str_data[l].label!=-1){ - disp_error_message("set_label: dup label ",script_pos); - return; - } - str_data[l].type=(str_data[l].type == C_USERFUNC ? C_USERFUNC_POS : C_POS); - str_data[l].label=pos; - for(i=str_data[l].backpatch;i>=0 && i!=0x00ffffff;){ - next=GETVALUE(script_buf,i); - script_buf[i-1]=(str_data[l].type == C_USERFUNC ? C_USERFUNC_POS : C_POS); - SETVALUE(script_buf,i,pos); - i=next; - } -} - -/// Skips spaces and/or comments. -const char* skip_space(const char* p) -{ - if( p == NULL ) - return NULL; - for(;;) - { - while( ISSPACE(*p) ) - ++p; - if( *p == '/' && p[1] == '/' ) - {// line comment - while(*p && *p!='\n') - ++p; - } - else if( *p == '/' && p[1] == '*' ) - {// block comment - p += 2; - for(;;) - { - if( *p == '\0' ) - return p;//disp_error_message("script:skip_space: end of file while parsing block comment. expected "CL_BOLD"*/"CL_NORM, p); - if( *p == '*' && p[1] == '/' ) - {// end of block comment - p += 2; - break; - } - ++p; - } - } - else - break; - } - return p; -} - -/// Skips a word. -/// A word consists of undercores and/or alfanumeric characters, -/// and valid variable prefixes/postfixes. -static -const char* skip_word(const char* p) -{ - // prefix - switch( *p ) - { - case '@':// temporary char variable - ++p; break; - case '#':// account variable - p += ( p[1] == '#' ? 2 : 1 ); break; - case '\'':// instance variable - ++p; break; - case '.':// npc variable - p += ( p[1] == '@' ? 2 : 1 ); break; - case '$':// global variable - p += ( p[1] == '@' ? 2 : 1 ); break; - } - - while( ISALNUM(*p) || *p == '_' ) - ++p; - - // postfix - if( *p == '$' )// string - p++; - - return p; -} - -/// Adds a word to str_data. -/// @see skip_word -/// @see add_str -static -int add_word(const char* p) -{ - char* word; - int len; - int i; - - // Check for a word - len = skip_word(p) - p; - if( len == 0 ) - disp_error_message("script:add_word: invalid word. A word consists of undercores and/or alfanumeric characters, and valid variable prefixes/postfixes.", p); - - // Duplicate the word - word = (char*)aMalloc(len+1); - memcpy(word, p, len); - word[len] = 0; - - // add the word - i = add_str(word); - aFree(word); - return i; -} - -/// Parses a function call. -/// The argument list can have parenthesis or not. -/// The number of arguments is checked. -static -const char* parse_callfunc(const char* p, int require_paren, int is_custom) -{ - const char* p2; - const char* arg=NULL; - int func; - - func = add_word(p); - if( str_data[func].type == C_FUNC ){ - // buildin function - add_scriptl(func); - add_scriptc(C_ARG); - arg = buildin_func[str_data[func].val].arg; - } else if( str_data[func].type == C_USERFUNC || str_data[func].type == C_USERFUNC_POS ){ - // script defined function - add_scriptl(buildin_callsub_ref); - add_scriptc(C_ARG); - add_scriptl(func); - arg = buildin_func[str_data[buildin_callsub_ref].val].arg; - if( *arg == 0 ) - disp_error_message("parse_callfunc: callsub has no arguments, please review it's definition",p); - if( *arg != '*' ) - ++arg; // count func as argument - } else { -#ifdef SCRIPT_CALLFUNC_CHECK - const char* name = get_str(func); - if( !is_custom && strdb_get(userfunc_db, name) == NULL ) { -#endif - disp_error_message("parse_line: expect command, missing function name or calling undeclared function",p); -#ifdef SCRIPT_CALLFUNC_CHECK - } else {; - add_scriptl(buildin_callfunc_ref); - add_scriptc(C_ARG); - add_scriptc(C_STR); - while( *name ) add_scriptb(*name ++); - add_scriptb(0); - arg = buildin_func[str_data[buildin_callfunc_ref].val].arg; - if( *arg != '*' ) ++ arg; - } -#endif - } - - p = skip_word(p); - p = skip_space(p); - syntax.curly[syntax.curly_count].type = TYPE_ARGLIST; - syntax.curly[syntax.curly_count].count = 0; - if( *p == ';' ) - {// <func name> ';' - syntax.curly[syntax.curly_count].flag = ARGLIST_NO_PAREN; - } else if( *p == '(' && *(p2=skip_space(p+1)) == ')' ) - {// <func name> '(' ')' - syntax.curly[syntax.curly_count].flag = ARGLIST_PAREN; - p = p2; - /* - } else if( 0 && require_paren && *p != '(' ) - {// <func name> - syntax.curly[syntax.curly_count].flag = ARGLIST_NO_PAREN; - */ - } else - {// <func name> <arg list> - if( require_paren ){ - if( *p != '(' ) - disp_error_message("need '('",p); - ++p; // skip '(' - syntax.curly[syntax.curly_count].flag = ARGLIST_PAREN; - } else if( *p == '(' ){ - syntax.curly[syntax.curly_count].flag = ARGLIST_UNDEFINED; - } else { - syntax.curly[syntax.curly_count].flag = ARGLIST_NO_PAREN; - } - ++syntax.curly_count; - while( *arg ) { - p2=parse_subexpr(p,-1); - if( p == p2 ) - break; // not an argument - if( *arg != '*' ) - ++arg; // next argument - - p=skip_space(p2); - if( *arg == 0 || *p != ',' ) - break; // no more arguments - ++p; // skip comma - } - --syntax.curly_count; - } - if( *arg && *arg != '?' && *arg != '*' ) - disp_error_message2("parse_callfunc: not enough arguments, expected ','", p, script_config.warn_func_mismatch_paramnum); - if( syntax.curly[syntax.curly_count].type != TYPE_ARGLIST ) - disp_error_message("parse_callfunc: DEBUG last curly is not an argument list",p); - if( syntax.curly[syntax.curly_count].flag == ARGLIST_PAREN ){ - if( *p != ')' ) - disp_error_message("parse_callfunc: expected ')' to close argument list",p); - ++p; - } - add_scriptc(C_FUNC); - return p; -} - -/// Processes end of logical script line. -/// @param first When true, only fix up scheduling data is initialized -/// @param p Script position for error reporting in set_label -static void parse_nextline(bool first, const char* p) -{ - if( !first ) - { - add_scriptc(C_EOL); // mark end of line for stack cleanup - set_label(LABEL_NEXTLINE, script_pos, p); // fix up '-' labels - } - - // initialize data for new '-' label fix up scheduling - str_data[LABEL_NEXTLINE].type = C_NOP; - str_data[LABEL_NEXTLINE].backpatch = -1; - str_data[LABEL_NEXTLINE].label = -1; -} - -/// Parse a variable assignment using the direct equals operator -/// @param p script position where the function should run from -/// @return NULL if not a variable assignment, the new position otherwise -const char* parse_variable(const char* p) { - int i, j, word; - c_op type = C_NOP; - const char *p2 = NULL; - const char *var = p; - - // skip the variable where applicable - p = skip_word(p); - p = skip_space(p); - - if( p == NULL ) {// end of the line or invalid buffer - return NULL; - } - - if( *p == '[' ) {// array variable so process the array as appropriate - for( p2 = p, i = 0, j = 1; p; ++ i ) { - if( *p ++ == ']' && --(j) == 0 ) break; - if( *p == '[' ) ++ j; - } - - if( !(p = skip_space(p)) ) {// end of line or invalid characters remaining - disp_error_message("Missing right expression or closing bracket for variable.", p); - } - } - - if( type == C_NOP && - !( ( p[0] == '=' && p[1] != '=' && (type = C_EQ) ) // = - || ( p[0] == '+' && p[1] == '=' && (type = C_ADD) ) // += - || ( p[0] == '-' && p[1] == '=' && (type = C_SUB) ) // -= - || ( p[0] == '^' && p[1] == '=' && (type = C_XOR) ) // ^= - || ( p[0] == '|' && p[1] == '=' && (type = C_OR ) ) // |= - || ( p[0] == '&' && p[1] == '=' && (type = C_AND) ) // &= - || ( p[0] == '*' && p[1] == '=' && (type = C_MUL) ) // *= - || ( p[0] == '/' && p[1] == '=' && (type = C_DIV) ) // /= - || ( p[0] == '%' && p[1] == '=' && (type = C_MOD) ) // %= - || ( p[0] == '~' && p[1] == '=' && (type = C_NOT) ) // ~= - || ( p[0] == '+' && p[1] == '+' && (type = C_ADD_PP) ) // ++ - || ( p[0] == '-' && p[1] == '-' && (type = C_SUB_PP) ) // -- - || ( p[0] == '<' && p[1] == '<' && p[2] == '=' && (type = C_L_SHIFT) ) // <<= - || ( p[0] == '>' && p[1] == '>' && p[2] == '=' && (type = C_R_SHIFT) ) // >>= - ) ) - {// failed to find a matching operator combination so invalid - return NULL; - } - - switch( type ) { - case C_EQ: {// incremental modifier - p = skip_space( &p[1] ); - } - break; - - case C_L_SHIFT: - case C_R_SHIFT: {// left or right shift modifier - p = skip_space( &p[3] ); - } - break; - - default: {// normal incremental command - p = skip_space( &p[2] ); - } - } - - if( p == NULL ) {// end of line or invalid buffer - return NULL; - } - - // push the set function onto the stack - add_scriptl(buildin_set_ref); - add_scriptc(C_ARG); - - // always append parenthesis to avoid errors - syntax.curly[syntax.curly_count].type = TYPE_ARGLIST; - syntax.curly[syntax.curly_count].count = 0; - syntax.curly[syntax.curly_count].flag = ARGLIST_PAREN; - - // increment the total curly count for the position in the script - ++ syntax.curly_count; - - // parse the variable currently being modified - word = add_word(var); - - if( str_data[word].type == C_FUNC || str_data[word].type == C_USERFUNC || str_data[word].type == C_USERFUNC_POS ) - {// cannot assign a variable which exists as a function or label - disp_error_message("Cannot modify a variable which has the same name as a function or label.", p); - } - - if( p2 ) {// process the variable index - const char* p3 = NULL; - - // push the getelementofarray method into the stack - add_scriptl(buildin_getelementofarray_ref); - add_scriptc(C_ARG); - add_scriptl(word); - - // process the sub-expression for this assignment - p3 = parse_subexpr(p2 + 1, 1); - p3 = skip_space(p3); - - if( *p3 != ']' ) {// closing parenthesis is required for this script - disp_error_message("Missing closing ']' parenthesis for the variable assignment.", p3); - } - - // push the closing function stack operator onto the stack - add_scriptc(C_FUNC); - p3 ++; - } else {// simply push the variable or value onto the stack - add_scriptl(word); - } - - if( type != C_EQ ) - add_scriptc(C_REF); - - if( type == C_ADD_PP || type == C_SUB_PP ) {// incremental operator for the method - add_scripti(1); - add_scriptc(type == C_ADD_PP ? C_ADD : C_SUB); - } else {// process the value as an expression - p = parse_subexpr(p, -1); - - if( type != C_EQ ) - {// push the type of modifier onto the stack - add_scriptc(type); - } - } - - // decrement the curly count for the position within the script - -- syntax.curly_count; - - // close the script by appending the function operator - add_scriptc(C_FUNC); - - // push the buffer from the method - return p; -} - -/*========================================== - * Analysis section - *------------------------------------------*/ -const char* parse_simpleexpr(const char *p) -{ - int i; - p=skip_space(p); - - if(*p==';' || *p==',') - disp_error_message("parse_simpleexpr: unexpected expr end",p); - if(*p=='('){ - if( (i=syntax.curly_count-1) >= 0 && syntax.curly[i].type == TYPE_ARGLIST ) - ++syntax.curly[i].count; - p=parse_subexpr(p+1,-1); - p=skip_space(p); - if( (i=syntax.curly_count-1) >= 0 && syntax.curly[i].type == TYPE_ARGLIST && - syntax.curly[i].flag == ARGLIST_UNDEFINED && --syntax.curly[i].count == 0 - ){ - if( *p == ',' ){ - syntax.curly[i].flag = ARGLIST_PAREN; - return p; - } else - syntax.curly[i].flag = ARGLIST_NO_PAREN; - } - if( *p != ')' ) - disp_error_message("parse_simpleexpr: unmatch ')'",p); - ++p; - } else if(ISDIGIT(*p) || ((*p=='-' || *p=='+') && ISDIGIT(p[1]))){ - char *np; - while(*p == '0' && ISDIGIT(p[1])) p++; - i=strtoul(p,&np,0); - add_scripti(i); - p=np; - } else if(*p=='"'){ - add_scriptc(C_STR); - p++; - while( *p && *p != '"' ){ - if( (unsigned char)p[-1] <= 0x7e && *p == '\\' ) - { - char buf[8]; - size_t len = skip_escaped_c(p) - p; - size_t n = sv_unescape_c(buf, p, len); - if( n != 1 ) - ShowDebug("parse_simpleexpr: unexpected length %d after unescape (\"%.*s\" -> %.*s)\n", (int)n, (int)len, p, (int)n, buf); - p += len; - add_scriptb(*buf); - continue; - } - else if( *p == '\n' ) - disp_error_message("parse_simpleexpr: unexpected newline @ string",p); - add_scriptb(*p++); - } - if(!*p) - disp_error_message("parse_simpleexpr: unexpected eof @ string",p); - add_scriptb(0); - p++; //'"' - } else { - int l; - const char* pv; - - // label , register , function etc - if(skip_word(p)==p) - disp_error_message("parse_simpleexpr: unexpected character",p); - - l=add_word(p); - if( str_data[l].type == C_FUNC || str_data[l].type == C_USERFUNC || str_data[l].type == C_USERFUNC_POS) - return parse_callfunc(p,1,0); -#ifdef SCRIPT_CALLFUNC_CHECK - else { - const char* name = get_str(l); - if( strdb_get(userfunc_db,name) != NULL ) { - return parse_callfunc(p,1,1); - } - } -#endif - - if( (pv = parse_variable(p)) ) - {// successfully processed a variable assignment - return pv; - } - - p=skip_word(p); - if( *p == '[' ){ - // array(name[i] => getelementofarray(name,i) ) - add_scriptl(buildin_getelementofarray_ref); - add_scriptc(C_ARG); - add_scriptl(l); - - p=parse_subexpr(p+1,-1); - p=skip_space(p); - if( *p != ']' ) - disp_error_message("parse_simpleexpr: unmatch ']'",p); - ++p; - add_scriptc(C_FUNC); - }else - add_scriptl(l); - - } - - return p; -} - -/*========================================== - * Analysis of the expression - *------------------------------------------*/ -const char* parse_subexpr(const char* p,int limit) -{ - int op,opl,len; - const char* tmpp; - - p=skip_space(p); - - if( *p == '-' ){ - tmpp = skip_space(p+1); - if( *tmpp == ';' || *tmpp == ',' ){ - add_scriptl(LABEL_NEXTLINE); - p++; - return p; - } - } - - if((op=C_NEG,*p=='-') || (op=C_LNOT,*p=='!') || (op=C_NOT,*p=='~')){ - p=parse_subexpr(p+1,10); - add_scriptc(op); - } else - p=parse_simpleexpr(p); - p=skip_space(p); - while(( - (op=C_OP3,opl=0,len=1,*p=='?') || - (op=C_ADD,opl=8,len=1,*p=='+') || - (op=C_SUB,opl=8,len=1,*p=='-') || - (op=C_MUL,opl=9,len=1,*p=='*') || - (op=C_DIV,opl=9,len=1,*p=='/') || - (op=C_MOD,opl=9,len=1,*p=='%') || - (op=C_LAND,opl=2,len=2,*p=='&' && p[1]=='&') || - (op=C_AND,opl=6,len=1,*p=='&') || - (op=C_LOR,opl=1,len=2,*p=='|' && p[1]=='|') || - (op=C_OR,opl=5,len=1,*p=='|') || - (op=C_XOR,opl=4,len=1,*p=='^') || - (op=C_EQ,opl=3,len=2,*p=='=' && p[1]=='=') || - (op=C_NE,opl=3,len=2,*p=='!' && p[1]=='=') || - (op=C_R_SHIFT,opl=7,len=2,*p=='>' && p[1]=='>') || - (op=C_GE,opl=3,len=2,*p=='>' && p[1]=='=') || - (op=C_GT,opl=3,len=1,*p=='>') || - (op=C_L_SHIFT,opl=7,len=2,*p=='<' && p[1]=='<') || - (op=C_LE,opl=3,len=2,*p=='<' && p[1]=='=') || - (op=C_LT,opl=3,len=1,*p=='<')) && opl>limit){ - p+=len; - if(op == C_OP3) { - p=parse_subexpr(p,-1); - p=skip_space(p); - if( *(p++) != ':') - disp_error_message("parse_subexpr: need ':'", p-1); - p=parse_subexpr(p,-1); - } else { - p=parse_subexpr(p,opl); - } - add_scriptc(op); - p=skip_space(p); - } - - return p; /* return first untreated operator */ -} - -/*========================================== - * Evaluation of the expression - *------------------------------------------*/ -const char* parse_expr(const char *p) -{ - switch(*p){ - case ')': case ';': case ':': case '[': case ']': - case '}': - disp_error_message("parse_expr: unexpected char",p); - } - p=parse_subexpr(p,-1); - return p; -} - -/*========================================== - * Analysis of the line - *------------------------------------------*/ -const char* parse_line(const char* p) -{ - const char* p2; - - p=skip_space(p); - if(*p==';') { - //Close decision for if(); for(); while(); - p = parse_syntax_close(p + 1); - return p; - } - if(*p==')' && parse_syntax_for_flag) - return p+1; - - p = skip_space(p); - if(p[0] == '{') { - syntax.curly[syntax.curly_count].type = TYPE_NULL; - syntax.curly[syntax.curly_count].count = -1; - syntax.curly[syntax.curly_count].index = -1; - syntax.curly_count++; - return p + 1; - } else if(p[0] == '}') { - return parse_curly_close(p); - } - - // Syntax-related processing - p2 = parse_syntax(p); - if(p2 != NULL) - return p2; - - // attempt to process a variable assignment - p2 = parse_variable(p); - - if( p2 != NULL ) - {// variable assignment processed so leave the method - return parse_syntax_close(p2 + 1); - } - - p = parse_callfunc(p,0,0); - p = skip_space(p); - - if(parse_syntax_for_flag) { - if( *p != ')' ) - disp_error_message("parse_line: need ')'",p); - } else { - if( *p != ';' ) - disp_error_message("parse_line: need ';'",p); - } - - //Binding decision for if(), for(), while() - p = parse_syntax_close(p+1); - - return p; -} - -// { ... } Closing process -const char* parse_curly_close(const char* p) -{ - if(syntax.curly_count <= 0) { - disp_error_message("parse_curly_close: unexpected string",p); - return p + 1; - } else if(syntax.curly[syntax.curly_count-1].type == TYPE_NULL) { - syntax.curly_count--; - //Close decision if, for , while - p = parse_syntax_close(p + 1); - return p; - } else if(syntax.curly[syntax.curly_count-1].type == TYPE_SWITCH) { - //Closing switch() - int pos = syntax.curly_count-1; - char label[256]; - int l; - // Remove temporary variables - sprintf(label,"set $@__SW%x_VAL,0;",syntax.curly[pos].index); - syntax.curly[syntax.curly_count++].type = TYPE_NULL; - parse_line(label); - syntax.curly_count--; - - // Go to the end pointer unconditionally - sprintf(label,"goto __SW%x_FIN;",syntax.curly[pos].index); - syntax.curly[syntax.curly_count++].type = TYPE_NULL; - parse_line(label); - syntax.curly_count--; - - // You are here labeled - sprintf(label,"__SW%x_%x",syntax.curly[pos].index,syntax.curly[pos].count); - l=add_str(label); - set_label(l,script_pos, p); - - if(syntax.curly[pos].flag) { - //Exists default - sprintf(label,"goto __SW%x_DEF;",syntax.curly[pos].index); - syntax.curly[syntax.curly_count++].type = TYPE_NULL; - parse_line(label); - syntax.curly_count--; - } - - // Label end - sprintf(label,"__SW%x_FIN",syntax.curly[pos].index); - l=add_str(label); - set_label(l,script_pos, p); - linkdb_final(&syntax.curly[pos].case_label); // free the list of case label - syntax.curly_count--; - //Closing decision if, for , while - p = parse_syntax_close(p + 1); - return p; - } else { - disp_error_message("parse_curly_close: unexpected string",p); - return p + 1; - } -} - -// Syntax-related processing -// break, case, continue, default, do, for, function, -// if, switch, while ? will handle this internally. -const char* parse_syntax(const char* p) -{ - const char *p2 = skip_word(p); - - switch(*p) { - case 'B': - case 'b': - if(p2 - p == 5 && !strncasecmp(p,"break",5)) { - // break Processing - char label[256]; - int pos = syntax.curly_count - 1; - while(pos >= 0) { - if(syntax.curly[pos].type == TYPE_DO) { - sprintf(label,"goto __DO%x_FIN;",syntax.curly[pos].index); - break; - } else if(syntax.curly[pos].type == TYPE_FOR) { - sprintf(label,"goto __FR%x_FIN;",syntax.curly[pos].index); - break; - } else if(syntax.curly[pos].type == TYPE_WHILE) { - sprintf(label,"goto __WL%x_FIN;",syntax.curly[pos].index); - break; - } else if(syntax.curly[pos].type == TYPE_SWITCH) { - sprintf(label,"goto __SW%x_FIN;",syntax.curly[pos].index); - break; - } - pos--; - } - if(pos < 0) { - disp_error_message("parse_syntax: unexpected 'break'",p); - } else { - syntax.curly[syntax.curly_count++].type = TYPE_NULL; - parse_line(label); - syntax.curly_count--; - } - p = skip_space(p2); - if(*p != ';') - disp_error_message("parse_syntax: need ';'",p); - // Closing decision if, for , while - p = parse_syntax_close(p + 1); - return p; - } - break; - case 'c': - case 'C': - if(p2 - p == 4 && !strncasecmp(p,"case",4)) { - //Processing case - int pos = syntax.curly_count-1; - if(pos < 0 || syntax.curly[pos].type != TYPE_SWITCH) { - disp_error_message("parse_syntax: unexpected 'case' ",p); - return p+1; - } else { - char label[256]; - int l,v; - char *np; - if(syntax.curly[pos].count != 1) { - //Jump for FALLTHRU - sprintf(label,"goto __SW%x_%xJ;",syntax.curly[pos].index,syntax.curly[pos].count); - syntax.curly[syntax.curly_count++].type = TYPE_NULL; - parse_line(label); - syntax.curly_count--; - - // You are here labeled - sprintf(label,"__SW%x_%x",syntax.curly[pos].index,syntax.curly[pos].count); - l=add_str(label); - set_label(l,script_pos, p); - } - //Decision statement switch - p = skip_space(p2); - if(p == p2) { - disp_error_message("parse_syntax: expect space ' '",p); - } - // check whether case label is integer or not - v = strtol(p,&np,0); - if(np == p) { //Check for constants - p2 = skip_word(p); - v = p2-p; // length of word at p2 - memcpy(label,p,v); - label[v]='\0'; - if( !script_get_constant(label, &v) ) - disp_error_message("parse_syntax: 'case' label not integer",p); - p = skip_word(p); - } else { //Numeric value - if((*p == '-' || *p == '+') && ISDIGIT(p[1])) // pre-skip because '-' can not skip_word - p++; - p = skip_word(p); - if(np != p) - disp_error_message("parse_syntax: 'case' label not integer",np); - } - p = skip_space(p); - if(*p != ':') - disp_error_message("parse_syntax: expect ':'",p); - sprintf(label,"if(%d != $@__SW%x_VAL) goto __SW%x_%x;", - v,syntax.curly[pos].index,syntax.curly[pos].index,syntax.curly[pos].count+1); - syntax.curly[syntax.curly_count++].type = TYPE_NULL; - // Bad I do not parse twice - p2 = parse_line(label); - parse_line(p2); - syntax.curly_count--; - if(syntax.curly[pos].count != 1) { - // Label after the completion of FALLTHRU - sprintf(label,"__SW%x_%xJ",syntax.curly[pos].index,syntax.curly[pos].count); - l=add_str(label); - set_label(l,script_pos,p); - } - // check duplication of case label [Rayce] - if(linkdb_search(&syntax.curly[pos].case_label, (void*)__64BPRTSIZE(v)) != NULL) - disp_error_message("parse_syntax: dup 'case'",p); - linkdb_insert(&syntax.curly[pos].case_label, (void*)__64BPRTSIZE(v), (void*)1); - - sprintf(label,"set $@__SW%x_VAL,0;",syntax.curly[pos].index); - syntax.curly[syntax.curly_count++].type = TYPE_NULL; - - parse_line(label); - syntax.curly_count--; - syntax.curly[pos].count++; - } - return p + 1; - } else if(p2 - p == 8 && !strncasecmp(p,"continue",8)) { - // Processing continue - char label[256]; - int pos = syntax.curly_count - 1; - while(pos >= 0) { - if(syntax.curly[pos].type == TYPE_DO) { - sprintf(label,"goto __DO%x_NXT;",syntax.curly[pos].index); - syntax.curly[pos].flag = 1; //Flag put the link for continue - break; - } else if(syntax.curly[pos].type == TYPE_FOR) { - sprintf(label,"goto __FR%x_NXT;",syntax.curly[pos].index); - break; - } else if(syntax.curly[pos].type == TYPE_WHILE) { - sprintf(label,"goto __WL%x_NXT;",syntax.curly[pos].index); - break; - } - pos--; - } - if(pos < 0) { - disp_error_message("parse_syntax: unexpected 'continue'",p); - } else { - syntax.curly[syntax.curly_count++].type = TYPE_NULL; - parse_line(label); - syntax.curly_count--; - } - p = skip_space(p2); - if(*p != ';') - disp_error_message("parse_syntax: need ';'",p); - //Closing decision if, for , while - p = parse_syntax_close(p + 1); - return p; - } - break; - case 'd': - case 'D': - if(p2 - p == 7 && !strncasecmp(p,"default",7)) { - // Switch - default processing - int pos = syntax.curly_count-1; - if(pos < 0 || syntax.curly[pos].type != TYPE_SWITCH) { - disp_error_message("parse_syntax: unexpected 'default'",p); - } else if(syntax.curly[pos].flag) { - disp_error_message("parse_syntax: dup 'default'",p); - } else { - char label[256]; - int l; - // Put the label location - p = skip_space(p2); - if(*p != ':') { - disp_error_message("parse_syntax: need ':'",p); - } - sprintf(label,"__SW%x_%x",syntax.curly[pos].index,syntax.curly[pos].count); - l=add_str(label); - set_label(l,script_pos,p); - - // Skip to the next link w/o condition - sprintf(label,"goto __SW%x_%x;",syntax.curly[pos].index,syntax.curly[pos].count+1); - syntax.curly[syntax.curly_count++].type = TYPE_NULL; - parse_line(label); - syntax.curly_count--; - - // The default label - sprintf(label,"__SW%x_DEF",syntax.curly[pos].index); - l=add_str(label); - set_label(l,script_pos,p); - - syntax.curly[syntax.curly_count - 1].flag = 1; - syntax.curly[pos].count++; - } - return p + 1; - } else if(p2 - p == 2 && !strncasecmp(p,"do",2)) { - int l; - char label[256]; - p=skip_space(p2); - - syntax.curly[syntax.curly_count].type = TYPE_DO; - syntax.curly[syntax.curly_count].count = 1; - syntax.curly[syntax.curly_count].index = syntax.index++; - syntax.curly[syntax.curly_count].flag = 0; - // Label of the (do) form here - sprintf(label,"__DO%x_BGN",syntax.curly[syntax.curly_count].index); - l=add_str(label); - set_label(l,script_pos,p); - syntax.curly_count++; - return p; - } - break; - case 'f': - case 'F': - if(p2 - p == 3 && !strncasecmp(p,"for",3)) { - int l; - char label[256]; - int pos = syntax.curly_count; - syntax.curly[syntax.curly_count].type = TYPE_FOR; - syntax.curly[syntax.curly_count].count = 1; - syntax.curly[syntax.curly_count].index = syntax.index++; - syntax.curly[syntax.curly_count].flag = 0; - syntax.curly_count++; - - p=skip_space(p2); - - if(*p != '(') - disp_error_message("parse_syntax: need '('",p); - p++; - - // Execute the initialization statement - syntax.curly[syntax.curly_count++].type = TYPE_NULL; - p=parse_line(p); - syntax.curly_count--; - - // Form the start of label decision - sprintf(label,"__FR%x_J",syntax.curly[pos].index); - l=add_str(label); - set_label(l,script_pos,p); - - p=skip_space(p); - if(*p == ';') { - // For (; Because the pattern of always true ;) - ; - } else { - // Skip to the end point if the condition is false - sprintf(label,"__FR%x_FIN",syntax.curly[pos].index); - add_scriptl(add_str("jump_zero")); - add_scriptc(C_ARG); - p=parse_expr(p); - p=skip_space(p); - add_scriptl(add_str(label)); - add_scriptc(C_FUNC); - } - if(*p != ';') - disp_error_message("parse_syntax: need ';'",p); - p++; - - // Skip to the beginning of the loop - sprintf(label,"goto __FR%x_BGN;",syntax.curly[pos].index); - syntax.curly[syntax.curly_count++].type = TYPE_NULL; - parse_line(label); - syntax.curly_count--; - - // Labels to form the next loop - sprintf(label,"__FR%x_NXT",syntax.curly[pos].index); - l=add_str(label); - set_label(l,script_pos,p); - - // Process the next time you enter the loop - // A ')' last for; flag to be treated as' - parse_syntax_for_flag = 1; - syntax.curly[syntax.curly_count++].type = TYPE_NULL; - p=parse_line(p); - syntax.curly_count--; - parse_syntax_for_flag = 0; - - // Skip to the determination process conditions - sprintf(label,"goto __FR%x_J;",syntax.curly[pos].index); - syntax.curly[syntax.curly_count++].type = TYPE_NULL; - parse_line(label); - syntax.curly_count--; - - // Loop start labeling - sprintf(label,"__FR%x_BGN",syntax.curly[pos].index); - l=add_str(label); - set_label(l,script_pos,p); - return p; - } - else if( p2 - p == 8 && strncasecmp(p,"function",8) == 0 ) - {// internal script function - const char *func_name; - - func_name = skip_space(p2); - p = skip_word(func_name); - if( p == func_name ) - disp_error_message("parse_syntax:function: function name is missing or invalid", p); - p2 = skip_space(p); - if( *p2 == ';' ) - {// function <name> ; - // function declaration - just register the name - int l; - l = add_word(func_name); - if( str_data[l].type == C_NOP )// register only, if the name was not used by something else - str_data[l].type = C_USERFUNC; - else if( str_data[l].type == C_USERFUNC ) - ; // already registered - else - disp_error_message("parse_syntax:function: function name is invalid", func_name); - - // Close condition of if, for, while - p = parse_syntax_close(p2 + 1); - return p; - } - else if(*p2 == '{') - {// function <name> <line/block of code> - char label[256]; - int l; - - syntax.curly[syntax.curly_count].type = TYPE_USERFUNC; - syntax.curly[syntax.curly_count].count = 1; - syntax.curly[syntax.curly_count].index = syntax.index++; - syntax.curly[syntax.curly_count].flag = 0; - ++syntax.curly_count; - - // Jump over the function code - sprintf(label, "goto __FN%x_FIN;", syntax.curly[syntax.curly_count-1].index); - syntax.curly[syntax.curly_count].type = TYPE_NULL; - ++syntax.curly_count; - parse_line(label); - --syntax.curly_count; - - // Set the position of the function (label) - l=add_word(func_name); - if( str_data[l].type == C_NOP || str_data[l].type == C_USERFUNC )// register only, if the name was not used by something else - { - str_data[l].type = C_USERFUNC; - set_label(l, script_pos, p); - if( parse_options&SCRIPT_USE_LABEL_DB ) - strdb_iput(scriptlabel_db, get_str(l), script_pos); - } - else - disp_error_message("parse_syntax:function: function name is invalid", func_name); - - return skip_space(p); - } - else - { - disp_error_message("expect ';' or '{' at function syntax",p); - } - } - break; - case 'i': - case 'I': - if(p2 - p == 2 && !strncasecmp(p,"if",2)) { - // If process - char label[256]; - p=skip_space(p2); - if(*p != '(') { //Prevent if this {} non-c syntax. from Rayce (jA) - disp_error_message("need '('",p); - } - syntax.curly[syntax.curly_count].type = TYPE_IF; - syntax.curly[syntax.curly_count].count = 1; - syntax.curly[syntax.curly_count].index = syntax.index++; - syntax.curly[syntax.curly_count].flag = 0; - sprintf(label,"__IF%x_%x",syntax.curly[syntax.curly_count].index,syntax.curly[syntax.curly_count].count); - syntax.curly_count++; - add_scriptl(add_str("jump_zero")); - add_scriptc(C_ARG); - p=parse_expr(p); - p=skip_space(p); - add_scriptl(add_str(label)); - add_scriptc(C_FUNC); - return p; - } - break; - case 's': - case 'S': - if(p2 - p == 6 && !strncasecmp(p,"switch",6)) { - // Processing of switch () - char label[256]; - p=skip_space(p2); - if(*p != '(') { - disp_error_message("need '('",p); - } - syntax.curly[syntax.curly_count].type = TYPE_SWITCH; - syntax.curly[syntax.curly_count].count = 1; - syntax.curly[syntax.curly_count].index = syntax.index++; - syntax.curly[syntax.curly_count].flag = 0; - sprintf(label,"$@__SW%x_VAL",syntax.curly[syntax.curly_count].index); - syntax.curly_count++; - add_scriptl(add_str("set")); - add_scriptc(C_ARG); - add_scriptl(add_str(label)); - p=parse_expr(p); - p=skip_space(p); - if(*p != '{') { - disp_error_message("parse_syntax: need '{'",p); - } - add_scriptc(C_FUNC); - return p + 1; - } - break; - case 'w': - case 'W': - if(p2 - p == 5 && !strncasecmp(p,"while",5)) { - int l; - char label[256]; - p=skip_space(p2); - if(*p != '(') { - disp_error_message("need '('",p); - } - syntax.curly[syntax.curly_count].type = TYPE_WHILE; - syntax.curly[syntax.curly_count].count = 1; - syntax.curly[syntax.curly_count].index = syntax.index++; - syntax.curly[syntax.curly_count].flag = 0; - // Form the start of label decision - sprintf(label,"__WL%x_NXT",syntax.curly[syntax.curly_count].index); - l=add_str(label); - set_label(l,script_pos,p); - - // Skip to the end point if the condition is false - sprintf(label,"__WL%x_FIN",syntax.curly[syntax.curly_count].index); - syntax.curly_count++; - add_scriptl(add_str("jump_zero")); - add_scriptc(C_ARG); - p=parse_expr(p); - p=skip_space(p); - add_scriptl(add_str(label)); - add_scriptc(C_FUNC); - return p; - } - break; - } - return NULL; -} - -const char* parse_syntax_close(const char *p) { - // If (...) for (...) hoge (); as to make sure closed closed once again - int flag; - - do { - p = parse_syntax_close_sub(p,&flag); - } while(flag); - return p; -} - -// Close judgment if, for, while, of do -// flag == 1 : closed -// flag == 0 : not closed -const char* parse_syntax_close_sub(const char* p,int* flag) -{ - char label[256]; - int pos = syntax.curly_count - 1; - int l; - *flag = 1; - - if(syntax.curly_count <= 0) { - *flag = 0; - return p; - } else if(syntax.curly[pos].type == TYPE_IF) { - const char *bp = p; - const char *p2; - - // if-block and else-block end is a new line - parse_nextline(false, p); - - // Skip to the last location if - sprintf(label,"goto __IF%x_FIN;",syntax.curly[pos].index); - syntax.curly[syntax.curly_count++].type = TYPE_NULL; - parse_line(label); - syntax.curly_count--; - - // Put the label of the location - sprintf(label,"__IF%x_%x",syntax.curly[pos].index,syntax.curly[pos].count); - l=add_str(label); - set_label(l,script_pos,p); - - syntax.curly[pos].count++; - p = skip_space(p); - p2 = skip_word(p); - if(!syntax.curly[pos].flag && p2 - p == 4 && !strncasecmp(p,"else",4)) { - // else or else - if - p = skip_space(p2); - p2 = skip_word(p); - if(p2 - p == 2 && !strncasecmp(p,"if",2)) { - // else - if - p=skip_space(p2); - if(*p != '(') { - disp_error_message("need '('",p); - } - sprintf(label,"__IF%x_%x",syntax.curly[pos].index,syntax.curly[pos].count); - add_scriptl(add_str("jump_zero")); - add_scriptc(C_ARG); - p=parse_expr(p); - p=skip_space(p); - add_scriptl(add_str(label)); - add_scriptc(C_FUNC); - *flag = 0; - return p; - } else { - // else - if(!syntax.curly[pos].flag) { - syntax.curly[pos].flag = 1; - *flag = 0; - return p; - } - } - } - // Close if - syntax.curly_count--; - // Put the label of the final location - sprintf(label,"__IF%x_FIN",syntax.curly[pos].index); - l=add_str(label); - set_label(l,script_pos,p); - if(syntax.curly[pos].flag == 1) { - // Because the position of the pointer is the same if not else for this - return bp; - } - return p; - } else if(syntax.curly[pos].type == TYPE_DO) { - int l; - char label[256]; - const char *p2; - - if(syntax.curly[pos].flag) { - // (Come here continue) to form the label here - sprintf(label,"__DO%x_NXT",syntax.curly[pos].index); - l=add_str(label); - set_label(l,script_pos,p); - } - - // Skip to the end point if the condition is false - p = skip_space(p); - p2 = skip_word(p); - if(p2 - p != 5 || strncasecmp(p,"while",5)) - disp_error_message("parse_syntax: need 'while'",p); - - p = skip_space(p2); - if(*p != '(') { - disp_error_message("need '('",p); - } - - // do-block end is a new line - parse_nextline(false, p); - - sprintf(label,"__DO%x_FIN",syntax.curly[pos].index); - add_scriptl(add_str("jump_zero")); - add_scriptc(C_ARG); - p=parse_expr(p); - p=skip_space(p); - add_scriptl(add_str(label)); - add_scriptc(C_FUNC); - - // Skip to the starting point - sprintf(label,"goto __DO%x_BGN;",syntax.curly[pos].index); - syntax.curly[syntax.curly_count++].type = TYPE_NULL; - parse_line(label); - syntax.curly_count--; - - // Form label of the end point conditions - sprintf(label,"__DO%x_FIN",syntax.curly[pos].index); - l=add_str(label); - set_label(l,script_pos,p); - p = skip_space(p); - if(*p != ';') { - disp_error_message("parse_syntax: need ';'",p); - return p+1; - } - p++; - syntax.curly_count--; - return p; - } else if(syntax.curly[pos].type == TYPE_FOR) { - // for-block end is a new line - parse_nextline(false, p); - - // Skip to the next loop - sprintf(label,"goto __FR%x_NXT;",syntax.curly[pos].index); - syntax.curly[syntax.curly_count++].type = TYPE_NULL; - parse_line(label); - syntax.curly_count--; - - // End for labeling - sprintf(label,"__FR%x_FIN",syntax.curly[pos].index); - l=add_str(label); - set_label(l,script_pos,p); - syntax.curly_count--; - return p; - } else if(syntax.curly[pos].type == TYPE_WHILE) { - // while-block end is a new line - parse_nextline(false, p); - - // Skip to the decision while - sprintf(label,"goto __WL%x_NXT;",syntax.curly[pos].index); - syntax.curly[syntax.curly_count++].type = TYPE_NULL; - parse_line(label); - syntax.curly_count--; - - // End while labeling - sprintf(label,"__WL%x_FIN",syntax.curly[pos].index); - l=add_str(label); - set_label(l,script_pos,p); - syntax.curly_count--; - return p; - } else if(syntax.curly[syntax.curly_count-1].type == TYPE_USERFUNC) { - int pos = syntax.curly_count-1; - char label[256]; - int l; - // Back - sprintf(label,"return;"); - syntax.curly[syntax.curly_count++].type = TYPE_NULL; - parse_line(label); - syntax.curly_count--; - - // Put the label of the location - sprintf(label,"__FN%x_FIN",syntax.curly[pos].index); - l=add_str(label); - set_label(l,script_pos,p); - syntax.curly_count--; - return p; - } else { - *flag = 0; - return p; - } -} - -/*========================================== - * Added built-in functions - *------------------------------------------*/ -static void add_buildin_func(void) -{ - int i,n; - const char* p; - for( i = 0; buildin_func[i].func; i++ ) - { - // arg must follow the pattern: (v|s|i|r|l)*\?*\*? - // 'v' - value (either string or int or reference) - // 's' - string - // 'i' - int - // 'r' - reference (of a variable) - // 'l' - label - // '?' - one optional parameter - // '*' - unknown number of optional parameters - p = buildin_func[i].arg; - while( *p == 'v' || *p == 's' || *p == 'i' || *p == 'r' || *p == 'l' ) ++p; - while( *p == '?' ) ++p; - if( *p == '*' ) ++p; - if( *p != 0){ - ShowWarning("add_buildin_func: ignoring function \"%s\" with invalid arg \"%s\".\n", buildin_func[i].name, buildin_func[i].arg); - } else if( *skip_word(buildin_func[i].name) != 0 ){ - ShowWarning("add_buildin_func: ignoring function with invalid name \"%s\" (must be a word).\n", buildin_func[i].name); - } else { - 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; - - if (!strcmp(buildin_func[i].name, "set")) buildin_set_ref = n; - else if (!strcmp(buildin_func[i].name, "callsub")) buildin_callsub_ref = n; - else if (!strcmp(buildin_func[i].name, "callfunc")) buildin_callfunc_ref = n; - else if( !strcmp(buildin_func[i].name, "getelementofarray") ) buildin_getelementofarray_ref = n; - } - } -} - -/// Retrieves the value of a constant. -bool script_get_constant(const char* name, int* value) -{ - int n = search_str(name); - - if( n == -1 || str_data[n].type != C_INT ) - {// not found or not a constant - return false; - } - value[0] = str_data[n].val; - - return true; -} - -/// Creates new constant or parameter with given value. -void script_set_constant(const char* name, int value, bool isparameter) -{ - int n = add_str(name); - - if( str_data[n].type == C_NOP ) - {// new - str_data[n].type = isparameter ? C_PARAM : C_INT; - str_data[n].val = value; - } - else if( str_data[n].type == C_PARAM || str_data[n].type == C_INT ) - {// existing parameter or constant - ShowError("script_set_constant: Attempted to overwrite existing %s '%s' (old value=%d, new value=%d).\n", ( str_data[n].type == C_PARAM ) ? "parameter" : "constant", name, str_data[n].val, value); - } - else - {// existing name - ShowError("script_set_constant: Invalid name for %s '%s' (already defined as %s).\n", isparameter ? "parameter" : "constant", name, script_op2name(str_data[n].type)); - } -} - -/*========================================== - * Reading constant databases - * const.txt - *------------------------------------------*/ -static void read_constdb(void) -{ - FILE *fp; - char line[1024],name[1024],val[1024]; - int type; - - sprintf(line, "%s/const.txt", db_path); - fp=fopen(line, "r"); - if(fp==NULL){ - ShowError("can't read %s\n", line); - return ; - } - while(fgets(line, sizeof(line), fp)) - { - if(line[0]=='/' && line[1]=='/') - continue; - type=0; - if(sscanf(line,"%[A-Za-z0-9_],%[-0-9xXA-Fa-f],%d",name,val,&type)>=2 || - sscanf(line,"%[A-Za-z0-9_] %[-0-9xXA-Fa-f] %d",name,val,&type)>=2){ - script_set_constant(name, (int)strtol(val, NULL, 0), (bool)type); - } - } - fclose(fp); -} - -/*========================================== - * Display emplacement line of script - *------------------------------------------*/ -static const char* script_print_line(StringBuf* buf, const char* p, const char* mark, int line) -{ - int i; - if( p == NULL || !p[0] ) return NULL; - if( line < 0 ) - StringBuf_Printf(buf, "*% 5d : ", -line); - else - StringBuf_Printf(buf, " % 5d : ", line); - for(i=0;p[i] && p[i] != '\n';i++){ - if(p + i != mark) - StringBuf_Printf(buf, "%c", p[i]); - else - StringBuf_Printf(buf, "\'%c\'", p[i]); - } - StringBuf_AppendStr(buf, "\n"); - return p+i+(p[i] == '\n' ? 1 : 0); -} - -void script_error(const char* src, const char* file, int start_line, const char* error_msg, const char* error_pos) -{ - // Find the line where the error occurred - int j; - int line = start_line; - const char *p; - const char *linestart[5] = { NULL, NULL, NULL, NULL, NULL }; - StringBuf buf; - - for(p=src;p && *p;line++){ - const char *lineend=strchr(p,'\n'); - if(lineend==NULL || error_pos<lineend){ - break; - } - for( j = 0; j < 4; j++ ) { - linestart[j] = linestart[j+1]; - } - linestart[4] = p; - p=lineend+1; - } - - StringBuf_Init(&buf); - StringBuf_AppendStr(&buf, "\a\n"); - StringBuf_Printf(&buf, "script error on %s line %d\n", file, line); - StringBuf_Printf(&buf, " %s\n", error_msg); - for(j = 0; j < 5; j++ ) { - script_print_line(&buf, linestart[j], NULL, line + j - 5); - } - p = script_print_line(&buf, p, error_pos, -line); - for(j = 0; j < 5; j++) { - p = script_print_line(&buf, p, NULL, line + j + 1 ); - } - ShowError("%s", StringBuf_Value(&buf)); - StringBuf_Destroy(&buf); -} - -/*========================================== - * Analysis of the script - *------------------------------------------*/ -struct script_code* parse_script(const char *src,const char *file,int line,int options) -{ - const char *p,*tmpp; - int i; - struct script_code* code = NULL; - static int first=1; - char end; - bool unresolved_names = false; - - if( src == NULL ) - return NULL;// empty script - - memset(&syntax,0,sizeof(syntax)); - if(first){ - add_buildin_func(); - read_constdb(); - first=0; - } - - script_buf=(unsigned char *)aMalloc(SCRIPT_BLOCK_SIZE*sizeof(unsigned char)); - script_pos=0; - script_size=SCRIPT_BLOCK_SIZE; - parse_nextline(true, NULL); - - // who called parse_script is responsible for clearing the database after using it, but just in case... lets clear it here - if( options&SCRIPT_USE_LABEL_DB ) - db_clear(scriptlabel_db); - parse_options = options; - - if( setjmp( error_jump ) != 0 ) { - //Restore program state when script has problems. [from jA] - int i; - const int size = ARRAYLENGTH(syntax.curly); - if( error_report ) - script_error(src,file,line,error_msg,error_pos); - aFree( error_msg ); - aFree( script_buf ); - script_pos = 0; - script_size = 0; - script_buf = NULL; - for(i=LABEL_START;i<str_num;i++) - if(str_data[i].type == C_NOP) str_data[i].type = C_NAME; - for(i=0; i<size; i++) - linkdb_final(&syntax.curly[i].case_label); - return NULL; - } - - parse_syntax_for_flag=0; - p=src; - p=skip_space(p); - if( options&SCRIPT_IGNORE_EXTERNAL_BRACKETS ) - {// does not require brackets around the script - if( *p == '\0' && !(options&SCRIPT_RETURN_EMPTY_SCRIPT) ) - {// empty script and can return NULL - aFree( script_buf ); - script_pos = 0; - script_size = 0; - script_buf = NULL; - return NULL; - } - end = '\0'; - } - else - {// requires brackets around the script - if( *p != '{' ) - disp_error_message("not found '{'",p); - p = skip_space(p+1); - if( *p == '}' && !(options&SCRIPT_RETURN_EMPTY_SCRIPT) ) - {// empty script and can return NULL - aFree( script_buf ); - script_pos = 0; - script_size = 0; - script_buf = NULL; - return NULL; - } - end = '}'; - } - - // clear references of labels, variables and internal functions - 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_USERFUNC || str_data[i].type == C_USERFUNC_POS - ){ - str_data[i].type=C_NOP; - str_data[i].backpatch=-1; - str_data[i].label=-1; - } - } - - while( syntax.curly_count != 0 || *p != end ) - { - if( *p == '\0' ) - disp_error_message("unexpected end of script",p); - // Special handling only label - tmpp=skip_space(skip_word(p)); - if(*tmpp==':' && !(!strncasecmp(p,"default:",8) && p + 7 == tmpp)){ - i=add_word(p); - set_label(i,script_pos,p); - if( parse_options&SCRIPT_USE_LABEL_DB ) - strdb_iput(scriptlabel_db, get_str(i), script_pos); - p=tmpp+1; - p=skip_space(p); - continue; - } - - // All other lumped - p=parse_line(p); - p=skip_space(p); - - parse_nextline(false, p); - } - - add_scriptc(C_NOP); - - // trim code to size - script_size = script_pos; - RECREATE(script_buf,unsigned char,script_pos); - - // default unknown references to variables - 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=GETVALUE(script_buf,j); - SETVALUE(script_buf,j,i); - j=next; - } - } - else if( str_data[i].type == C_USERFUNC ) - {// 'function name;' without follow-up code - ShowError("parse_script: function '%s' declared but not defined.\n", str_buf+str_data[i].str); - unresolved_names = true; - } - } - - if( unresolved_names ) - { - disp_error_message("parse_script: unresolved function references", p); - } - -#ifdef DEBUG_DISP - for(i=0;i<script_pos;i++){ - if((i&15)==0) ShowMessage("%04x : ",i); - ShowMessage("%02x ",script_buf[i]); - if((i&15)==15) ShowMessage("\n"); - } - ShowMessage("\n"); -#endif -#ifdef DEBUG_DISASM - { - int i = 0,j; - while(i < script_pos) { - c_op op = get_com(script_buf,&i); - - ShowMessage("%06x %s", i, script_op2name(op)); - j = i; - switch(op) { - case C_INT: - ShowMessage(" %d", get_num(script_buf,&i)); - break; - case C_POS: - ShowMessage(" 0x%06x", *(int*)(script_buf+i)&0xffffff); - i += 3; - break; - case C_NAME: - j = (*(int*)(script_buf+i)&0xffffff); - ShowMessage(" %s", ( j == 0xffffff ) ? "?? unknown ??" : get_str(j)); - i += 3; - break; - case C_STR: - j = strlen(script_buf + i); - ShowMessage(" %s", script_buf + i); - i += j+1; - break; - } - ShowMessage(CL_CLL"\n"); - } - } -#endif - - CREATE(code,struct script_code,1); - code->script_buf = script_buf; - code->script_size = script_size; - code->script_vars = idb_alloc(DB_OPT_RELEASE_DATA); - return code; -} - -/// Returns the player attached to this script, identified by the rid. -/// If there is no player attached, the script is terminated. -TBL_PC *script_rid2sd(struct script_state *st) -{ - TBL_PC *sd=map_id2sd(st->rid); - if(!sd){ - ShowError("script_rid2sd: fatal error ! player not attached!\n"); - script_reportfunc(st); - script_reportsrc(st); - st->state = END; - } - return sd; -} - -/// Dereferences a variable/constant, replacing it with a copy of the value. -/// -/// @param st Script state -/// @param data Variable/constant -void get_val(struct script_state* st, struct script_data* data) -{ - const char* name; - char prefix; - char postfix; - TBL_PC* sd = NULL; - - if( !data_isreference(data) ) - return;// not a variable/constant - - name = reference_getname(data); - prefix = name[0]; - postfix = name[strlen(name) - 1]; - - //##TODO use reference_tovariable(data) when it's confirmed that it works [FlavioJS] - if( !reference_toconstant(data) && not_server_variable(prefix) ) - { - sd = script_rid2sd(st); - if( sd == NULL ) - {// needs player attached - if( postfix == '$' ) - {// string variable - ShowWarning("script:get_val: cannot access player variable '%s', defaulting to \"\"\n", name); - data->type = C_CONSTSTR; - data->u.str = ""; - } - else - {// integer variable - ShowWarning("script:get_val: cannot access player variable '%s', defaulting to 0\n", name); - data->type = C_INT; - data->u.num = 0; - } - return; - } - } - - if( postfix == '$' ) - {// string variable - - switch( prefix ) - { - case '@': - data->u.str = pc_readregstr(sd, data->u.num); - break; - case '$': - data->u.str = mapreg_readregstr(data->u.num); - break; - case '#': - if( name[1] == '#' ) - data->u.str = pc_readaccountreg2str(sd, name);// global - else - data->u.str = pc_readaccountregstr(sd, name);// local - break; - case '.': - { - struct DBMap* n = - data->ref ? *data->ref: - name[1] == '@' ? st->stack->var_function:// instance/scope variable - st->script->script_vars;// npc variable - if( n ) - data->u.str = (char*)idb_get(n,reference_getuid(data)); - else - data->u.str = NULL; - } - break; - case '\'': - if (st->instance_id) { - data->u.str = (char*)idb_get(instance[st->instance_id].vars,reference_getuid(data)); - } else { - ShowWarning("script:get_val: cannot access instance variable '%s', defaulting to \"\"\n", name); - data->u.str = NULL; - } - break; - default: - data->u.str = pc_readglobalreg_str(sd, name); - break; - } - - if( data->u.str == NULL || data->u.str[0] == '\0' ) - {// empty string - data->type = C_CONSTSTR; - data->u.str = ""; - } - else - {// duplicate string - data->type = C_STR; - data->u.str = aStrdup(data->u.str); - } - - } - else - {// integer variable - - data->type = C_INT; - - if( reference_toconstant(data) ) - { - data->u.num = reference_getconstant(data); - } - else if( reference_toparam(data) ) - { - data->u.num = pc_readparam(sd, reference_getparamtype(data)); - } - else - switch( prefix ) - { - case '@': - data->u.num = pc_readreg(sd, data->u.num); - break; - case '$': - data->u.num = mapreg_readreg(data->u.num); - break; - case '#': - if( name[1] == '#' ) - data->u.num = pc_readaccountreg2(sd, name);// global - else - data->u.num = pc_readaccountreg(sd, name);// local - break; - case '.': - { - struct DBMap* n = - data->ref ? *data->ref: - name[1] == '@' ? st->stack->var_function:// instance/scope variable - st->script->script_vars;// npc variable - if( n ) - data->u.num = (int)idb_iget(n,reference_getuid(data)); - else - data->u.num = 0; - } - break; - case '\'': - if( st->instance_id ) - data->u.num = (int)idb_iget(instance[st->instance_id].vars,reference_getuid(data)); - else { - ShowWarning("script:get_val: cannot access instance variable '%s', defaulting to 0\n", name); - data->u.num = 0; - } - break; - default: - data->u.num = pc_readglobalreg(sd, name); - break; - } - - } - - return; -} - -struct script_data* push_val2(struct script_stack* stack, enum c_op type, int val, struct DBMap** ref); - -/// Retrieves the value of a reference identified by uid (variable, constant, param) -/// The value is left in the top of the stack and needs to be removed manually. -void* get_val2(struct script_state* st, int uid, struct DBMap** ref) -{ - struct script_data* data; - push_val2(st->stack, C_NAME, uid, ref); - data = script_getdatatop(st, -1); - get_val(st, data); - return (data->type == C_INT ? (void*)__64BPRTSIZE(data->u.num) : (void*)__64BPRTSIZE(data->u.str)); -} - -/*========================================== - * Stores the value of a script variable - * Return value is 0 on fail, 1 on success. - *------------------------------------------*/ -static int set_reg(struct script_state* st, TBL_PC* sd, int num, const char* name, const void* value, struct DBMap** ref) -{ - char prefix = name[0]; - - if( is_string_variable(name) ) - {// string variable - const char* str = (const char*)value; - switch (prefix) { - case '@': - return pc_setregstr(sd, num, str); - case '$': - return mapreg_setregstr(num, str); - case '#': - return (name[1] == '#') ? - pc_setaccountreg2str(sd, name, str) : - pc_setaccountregstr(sd, name, str); - case '.': - { - struct DBMap* n; - n = (ref) ? *ref : (name[1] == '@') ? st->stack->var_function : st->script->script_vars; - if( n ) { - idb_remove(n, num); - if (str[0]) idb_put(n, num, aStrdup(str)); - } - } - return 1; - case '\'': - if( st->instance_id ) { - idb_remove(instance[st->instance_id].vars, num); - if( str[0] ) idb_put(instance[st->instance_id].vars, num, aStrdup(str)); - } - return 1; - default: - return pc_setglobalreg_str(sd, name, str); - } - } - else - {// integer variable - int val = (int)__64BPRTSIZE(value); - if(str_data[num&0x00ffffff].type == C_PARAM) - { - if( pc_setparam(sd, str_data[num&0x00ffffff].val, val) == 0 ) - { - if( st != NULL ) - { - ShowError("script:set_reg: failed to set param '%s' to %d.\n", name, val); - script_reportsrc(st); - st->state = END; - } - return 0; - } - return 1; - } - - switch (prefix) { - case '@': - return pc_setreg(sd, num, val); - case '$': - return mapreg_setreg(num, val); - case '#': - return (name[1] == '#') ? - pc_setaccountreg2(sd, name, val) : - pc_setaccountreg(sd, name, val); - case '.': - { - struct DBMap* n; - n = (ref) ? *ref : (name[1] == '@') ? st->stack->var_function : st->script->script_vars; - if( n ) { - idb_remove(n, num); - if( val != 0 ) - idb_iput(n, num, val); - } - } - return 1; - case '\'': - if( st->instance_id ) { - idb_remove(instance[st->instance_id].vars, num); - if( val != 0 ) - idb_iput(instance[st->instance_id].vars, num, val); - } - return 1; - default: - return pc_setglobalreg(sd, name, val); - } - } -} - -int set_var(TBL_PC* sd, char* name, void* val) -{ - return set_reg(NULL, sd, reference_uid(add_str(name),0), name, val, NULL); -} - -void setd_sub(struct script_state *st, TBL_PC *sd, const char *varname, int elem, void *value, struct DBMap **ref) -{ - set_reg(st, sd, reference_uid(add_str(varname),elem), varname, value, ref); -} - -/// Converts the data to a string -const char* conv_str(struct script_state* st, struct script_data* data) -{ - char* p; - - get_val(st, data); - if( data_isstring(data) ) - {// nothing to convert - } - else if( data_isint(data) ) - {// int -> string - CREATE(p, char, ITEM_NAME_LENGTH); - snprintf(p, ITEM_NAME_LENGTH, "%d", data->u.num); - p[ITEM_NAME_LENGTH-1] = '\0'; - data->type = C_STR; - data->u.str = p; - } - else if( data_isreference(data) ) - {// reference -> string - //##TODO when does this happen (check get_val) [FlavioJS] - data->type = C_CONSTSTR; - data->u.str = reference_getname(data); - } - else - {// unsupported data type - ShowError("script:conv_str: cannot convert to string, defaulting to \"\"\n"); - script_reportdata(data); - script_reportsrc(st); - data->type = C_CONSTSTR; - data->u.str = ""; - } - return data->u.str; -} - -/// Converts the data to an int -int conv_num(struct script_state* st, struct script_data* data) -{ - char* p; - long num; - - get_val(st, data); - if( data_isint(data) ) - {// nothing to convert - } - else if( data_isstring(data) ) - {// string -> int - // the result does not overflow or underflow, it is capped instead - // ex: 999999999999 is capped to INT_MAX (2147483647) - p = data->u.str; - errno = 0; - num = strtol(data->u.str, NULL, 10);// change radix to 0 to support octal numbers "o377" and hex numbers "0xFF" - if( errno == ERANGE -#if LONG_MAX > INT_MAX - || num < INT_MIN || num > INT_MAX -#endif - ) - { - if( num <= INT_MIN ) - { - num = INT_MIN; - ShowError("script:conv_num: underflow detected, capping to %ld\n", num); - } - else//if( num >= INT_MAX ) - { - num = INT_MAX; - ShowError("script:conv_num: overflow detected, capping to %ld\n", num); - } - script_reportdata(data); - script_reportsrc(st); - } - if( data->type == C_STR ) - aFree(p); - data->type = C_INT; - data->u.num = (int)num; - } -#if 0 - // FIXME this function is being used to retrieve the position of labels and - // probably other stuff [FlavioJS] - else - {// unsupported data type - ShowError("script:conv_num: cannot convert to number, defaulting to 0\n"); - script_reportdata(data); - script_reportsrc(st); - data->type = C_INT; - data->u.num = 0; - } -#endif - return data->u.num; -} - -// -// Stack operations -// - -/// Increases the size of the stack -void stack_expand(struct script_stack* stack) -{ - stack->sp_max += 64; - stack->stack_data = (struct script_data*)aRealloc(stack->stack_data, - stack->sp_max * sizeof(stack->stack_data[0]) ); - memset(stack->stack_data + (stack->sp_max - 64), 0, - 64 * sizeof(stack->stack_data[0]) ); -} - -/// Pushes a value into the stack -#define push_val(stack,type,val) push_val2(stack, type, val, NULL) - -/// Pushes a value into the stack (with reference) -struct script_data* push_val2(struct script_stack* stack, enum c_op type, int val, struct DBMap** ref) -{ - if( stack->sp >= stack->sp_max ) - stack_expand(stack); - stack->stack_data[stack->sp].type = type; - stack->stack_data[stack->sp].u.num = val; - stack->stack_data[stack->sp].ref = ref; - stack->sp++; - return &stack->stack_data[stack->sp-1]; -} - -/// Pushes a string into the stack -struct script_data* push_str(struct script_stack* stack, enum c_op type, char* str) -{ - if( stack->sp >= stack->sp_max ) - stack_expand(stack); - stack->stack_data[stack->sp].type = type; - stack->stack_data[stack->sp].u.str = str; - stack->stack_data[stack->sp].ref = NULL; - stack->sp++; - return &stack->stack_data[stack->sp-1]; -} - -/// Pushes a retinfo into the stack -struct script_data* push_retinfo(struct script_stack* stack, struct script_retinfo* ri, DBMap **ref) -{ - if( stack->sp >= stack->sp_max ) - stack_expand(stack); - stack->stack_data[stack->sp].type = C_RETINFO; - stack->stack_data[stack->sp].u.ri = ri; - stack->stack_data[stack->sp].ref = ref; - stack->sp++; - return &stack->stack_data[stack->sp-1]; -} - -/// Pushes a copy of the target position into the stack -struct script_data* push_copy(struct script_stack* stack, int pos) -{ - switch( stack->stack_data[pos].type ) - { - case C_CONSTSTR: - return push_str(stack, C_CONSTSTR, stack->stack_data[pos].u.str); - break; - case C_STR: - return push_str(stack, C_STR, aStrdup(stack->stack_data[pos].u.str)); - break; - case C_RETINFO: - ShowFatalError("script:push_copy: can't create copies of C_RETINFO. Exiting...\n"); - exit(1); - break; - default: - return push_val2( - stack,stack->stack_data[pos].type, - stack->stack_data[pos].u.num, - stack->stack_data[pos].ref - ); - break; - } -} - -/// Removes the values in indexes [start,end[ from the stack. -/// Adjusts all stack pointers. -void pop_stack(struct script_state* st, int start, int end) -{ - struct script_stack* stack = st->stack; - struct script_data* data; - int i; - - if( start < 0 ) - start = 0; - if( end > stack->sp ) - end = stack->sp; - if( start >= end ) - return;// nothing to pop - - // free stack elements - for( i = start; i < end; i++ ) - { - data = &stack->stack_data[i]; - if( data->type == C_STR ) - aFree(data->u.str); - if( data->type == C_RETINFO ) - { - struct script_retinfo* ri = data->u.ri; - if( ri->var_function ) - script_free_vars(ri->var_function); - if( data->ref ) - aFree(data->ref); - aFree(ri); - } - data->type = C_NOP; - } - // move the rest of the elements - if( stack->sp > end ) - { - memmove(&stack->stack_data[start], &stack->stack_data[end], sizeof(stack->stack_data[0])*(stack->sp - end)); - for( i = start + stack->sp - end; i < stack->sp; ++i ) - stack->stack_data[i].type = C_NOP; - } - // adjust stack pointers - if( st->start > end ) st->start -= end - start; - else if( st->start > start ) st->start = start; - if( st->end > end ) st->end -= end - start; - else if( st->end > start ) st->end = start; - if( stack->defsp > end ) stack->defsp -= end - start; - else if( stack->defsp > start ) stack->defsp = start; - stack->sp -= end - start; -} - -/// -/// -/// - -/*========================================== - * Release script dependent variable, dependent variable of function - *------------------------------------------*/ -void script_free_vars(struct DBMap* storage) -{ - if( storage ) - {// destroy the storage construct containing the variables - db_destroy(storage); - } -} - -void script_free_code(struct script_code* code) -{ - script_free_vars( code->script_vars ); - aFree( code->script_buf ); - aFree( code ); -} - -/// Creates a new script state. -/// -/// @param script Script code -/// @param pos Position in the code -/// @param rid Who is running the script (attached player) -/// @param oid Where the code is being run (npc 'object') -/// @return Script state -struct script_state* script_alloc_state(struct script_code* script, int pos, int rid, int oid) -{ - struct script_state* st; - CREATE(st, struct script_state, 1); - st->stack = (struct script_stack*)aMalloc(sizeof(struct script_stack)); - st->stack->sp = 0; - st->stack->sp_max = 64; - CREATE(st->stack->stack_data, struct script_data, st->stack->sp_max); - st->stack->defsp = st->stack->sp; - st->stack->var_function = idb_alloc(DB_OPT_RELEASE_DATA); - st->state = RUN; - st->script = script; - //st->scriptroot = script; - st->pos = pos; - st->rid = rid; - st->oid = oid; - st->sleep.timer = INVALID_TIMER; - return st; -} - -/// Frees a script state. -/// -/// @param st Script state -void script_free_state(struct script_state* st) -{ - if(st->bk_st) - {// backup was not restored - ShowDebug("script_free_state: Previous script state lost (rid=%d, oid=%d, state=%d, bk_npcid=%d).\n", st->bk_st->rid, st->bk_st->oid, st->bk_st->state, st->bk_npcid); - } - if( st->sleep.timer != INVALID_TIMER ) - delete_timer(st->sleep.timer, run_script_timer); - script_free_vars(st->stack->var_function); - pop_stack(st, 0, st->stack->sp); - aFree(st->stack->stack_data); - aFree(st->stack); - st->stack = NULL; - st->pos = -1; - aFree(st); -} - -// -// Main execution unit -// -/*========================================== - * Read command - *------------------------------------------*/ -c_op get_com(unsigned char *script,int *pos) -{ - int i = 0, j = 0; - - if(script[*pos]>=0x80){ - return C_INT; - } - while(script[*pos]>=0x40){ - i=script[(*pos)++]<<j; - j+=6; - } - return (c_op)(i+(script[(*pos)++]<<j)); -} - -/*========================================== - * Income figures - *------------------------------------------*/ -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); -} - -/*========================================== - * Remove the value from the stack - *------------------------------------------*/ -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; -} - -/// Ternary operators -/// test ? if_true : if_false -void op_3(struct script_state* st, int op) -{ - struct script_data* data; - int flag = 0; - - data = script_getdatatop(st, -3); - get_val(st, data); - - if( data_isstring(data) ) - flag = data->u.str[0];// "" -> false - else if( data_isint(data) ) - flag = data->u.num;// 0 -> false - else - { - ShowError("script:op_3: invalid data for the ternary operator test\n"); - script_reportdata(data); - script_reportsrc(st); - script_removetop(st, -3, 0); - script_pushnil(st); - return; - } - if( flag ) - script_pushcopytop(st, -2); - else - script_pushcopytop(st, -1); - script_removetop(st, -4, -1); -} - -/// Binary string operators -/// s1 EQ s2 -> i -/// s1 NE s2 -> i -/// s1 GT s2 -> i -/// s1 GE s2 -> i -/// s1 LT s2 -> i -/// s1 LE s2 -> i -/// s1 ADD s2 -> s -void op_2str(struct script_state* st, int op, const char* s1, const char* s2) -{ - 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; - case C_ADD: - { - char* buf = (char *)aMalloc((strlen(s1)+strlen(s2)+1)*sizeof(char)); - strcpy(buf, s1); - strcat(buf, s2); - script_pushstr(st, buf); - return; - } - default: - ShowError("script:op2_str: unexpected string operator %s\n", script_op2name(op)); - script_reportsrc(st); - script_pushnil(st); - st->state = END; - return; - } - - script_pushint(st,a); -} - -/// Binary number operators -/// i OP i -> i -void op_2num(struct script_state* st, int op, int i1, int i2) -{ - int ret; - double ret_double; - - switch( op ) - { - case C_AND: ret = i1 & i2; break; - case C_OR: ret = i1 | i2; break; - case C_XOR: ret = i1 ^ i2; break; - case C_LAND: ret = (i1 && i2); break; - case C_LOR: ret = (i1 || i2); break; - case C_EQ: ret = (i1 == i2); break; - case C_NE: ret = (i1 != i2); break; - case C_GT: ret = (i1 > i2); break; - case C_GE: ret = (i1 >= i2); break; - case C_LT: ret = (i1 < i2); break; - case C_LE: ret = (i1 <= i2); break; - case C_R_SHIFT: ret = i1>>i2; break; - case C_L_SHIFT: ret = i1<<i2; break; - case C_DIV: - case C_MOD: - if( i2 == 0 ) - { - ShowError("script:op_2num: division by zero detected op=%s i1=%d i2=%d\n", script_op2name(op), i1, i2); - script_reportsrc(st); - script_pushnil(st); - st->state = END; - return; - } - else if( op == C_DIV ) - ret = i1 / i2; - else//if( op == C_MOD ) - ret = i1 % i2; - break; - default: - switch( op ) - {// operators that can overflow/underflow - case C_ADD: ret = i1 + i2; ret_double = (double)i1 + (double)i2; break; - case C_SUB: ret = i1 - i2; ret_double = (double)i1 - (double)i2; break; - case C_MUL: ret = i1 * i2; ret_double = (double)i1 * (double)i2; break; - default: - ShowError("script:op_2num: unexpected number operator %s i1=%d i2=%d\n", script_op2name(op), i1, i2); - script_reportsrc(st); - script_pushnil(st); - return; - } - if( ret_double < (double)INT_MIN ) - { - ShowWarning("script:op_2num: underflow detected op=%s i1=%d i2=%d\n", script_op2name(op), i1, i2); - script_reportsrc(st); - ret = INT_MIN; - } - else if( ret_double > (double)INT_MAX ) - { - ShowWarning("script:op_2num: overflow detected op=%s i1=%d i2=%d\n", script_op2name(op), i1, i2); - script_reportsrc(st); - ret = INT_MAX; - } - } - script_pushint(st, ret); -} - -/// Binary operators -void op_2(struct script_state *st, int op) -{ - struct script_data* left, leftref; - struct script_data* right; - - leftref.type = C_NOP; - - left = script_getdatatop(st, -2); - right = script_getdatatop(st, -1); - - if (st->op2ref) { - if (data_isreference(left)) { - leftref = *left; - } - - st->op2ref = 0; - } - - get_val(st, left); - get_val(st, right); - - // automatic conversions - switch( op ) - { - case C_ADD: - if( data_isint(left) && data_isstring(right) ) - {// convert int-string to string-string - conv_str(st, left); - } - else if( data_isstring(left) && data_isint(right) ) - {// convert string-int to string-string - conv_str(st, right); - } - break; - } - - if( data_isstring(left) && data_isstring(right) ) - {// ss => op_2str - op_2str(st, op, left->u.str, right->u.str); - script_removetop(st, leftref.type == C_NOP ? -3 : -2, -1);// pop the two values before the top one - - if (leftref.type != C_NOP) - { - aFree(left->u.str); - *left = leftref; - } - } - else if( data_isint(left) && data_isint(right) ) - {// ii => op_2num - int i1 = left->u.num; - int i2 = right->u.num; - - script_removetop(st, leftref.type == C_NOP ? -2 : -1, 0); - op_2num(st, op, i1, i2); - - if (leftref.type != C_NOP) - *left = leftref; - } - else - {// invalid argument - ShowError("script:op_2: invalid data for operator %s\n", script_op2name(op)); - script_reportdata(left); - script_reportdata(right); - script_reportsrc(st); - script_removetop(st, -2, 0); - script_pushnil(st); - st->state = END; - } -} - -/// Unary operators -/// NEG i -> i -/// NOT i -> i -/// LNOT i -> i -void op_1(struct script_state* st, int op) -{ - struct script_data* data; - int i1; - - data = script_getdatatop(st, -1); - get_val(st, data); - - if( !data_isint(data) ) - {// not a number - ShowError("script:op_1: argument is not a number (op=%s)\n", script_op2name(op)); - script_reportdata(data); - script_reportsrc(st); - script_pushnil(st); - st->state = END; - return; - } - - i1 = data->u.num; - script_removetop(st, -1, 0); - switch( op ) - { - case C_NEG: i1 = -i1; break; - case C_NOT: i1 = ~i1; break; - case C_LNOT: i1 = !i1; break; - default: - ShowError("script:op_1: unexpected operator %s i1=%d\n", script_op2name(op), i1); - script_reportsrc(st); - script_pushnil(st); - st->state = END; - return; - } - script_pushint(st, i1); -} - - -/// Checks the type of all arguments passed to a built-in function. -/// -/// @param st Script state whose stack arguments should be inspected. -/// @param func Built-in function for which the arguments are intended. -static void script_check_buildin_argtype(struct script_state* st, int func) -{ - char type; - int idx, invalid = 0; - script_function* sf = &buildin_func[str_data[func].val]; - - for( idx = 2; script_hasdata(st, idx); idx++ ) - { - struct script_data* data = script_getdata(st, idx); - - type = sf->arg[idx-2]; - - if( type == '?' || type == '*' ) - {// optional argument or unknown number of optional parameters ( no types are after this ) - break; - } - else if( type == 0 ) - {// more arguments than necessary ( should not happen, as it is checked before ) - ShowWarning("Found more arguments than necessary. unexpected arg type %s\n",script_op2name(data->type)); - invalid++; - break; - } - else - { - const char* name = NULL; - - if( data_isreference(data) ) - {// get name for variables to determine the type they refer to - name = reference_getname(data); - } - - switch( type ) - { - case 'v': - if( !data_isstring(data) && !data_isint(data) && !data_isreference(data) ) - {// variant - ShowWarning("Unexpected type for argument %d. Expected string, number or variable.\n", idx-1); - script_reportdata(data); - invalid++; - } - break; - case 's': - if( !data_isstring(data) && !( data_isreference(data) && is_string_variable(name) ) ) - {// string - ShowWarning("Unexpected type for argument %d. Expected string.\n", idx-1); - script_reportdata(data); - invalid++; - } - break; - case 'i': - if( !data_isint(data) && !( data_isreference(data) && ( reference_toparam(data) || reference_toconstant(data) || !is_string_variable(name) ) ) ) - {// int ( params and constants are always int ) - ShowWarning("Unexpected type for argument %d. Expected number.\n", idx-1); - script_reportdata(data); - invalid++; - } - break; - case 'r': - if( !data_isreference(data) ) - {// variables - ShowWarning("Unexpected type for argument %d. Expected variable, got %s.\n", idx-1,script_op2name(data->type)); - script_reportdata(data); - invalid++; - } - break; - case 'l': - if( !data_islabel(data) && !data_isfunclabel(data) ) - {// label - ShowWarning("Unexpected type for argument %d. Expected label, got %s\n", idx-1,script_op2name(data->type)); - script_reportdata(data); - invalid++; - } - break; - } - } - } - - if(invalid) - { - ShowDebug("Function: %s\n", get_str(func)); - script_reportsrc(st); - } -} - - -/// Executes a buildin command. -/// Stack: C_NAME(<command>) C_ARG <arg0> <arg1> ... <argN> -int run_func(struct script_state *st) -{ - struct script_data* data; - int i,start_sp,end_sp,func; - - end_sp = st->stack->sp;// position after the last argument - for( i = end_sp-1; i > 0 ; --i ) - if( st->stack->stack_data[i].type == C_ARG ) - break; - if( i == 0 ) - { - ShowError("script:run_func: C_ARG not found. please report this!!!\n"); - st->state = END; - script_reportsrc(st); - return 1; - } - start_sp = i-1;// C_NAME of the command - st->start = start_sp; - st->end = end_sp; - - data = &st->stack->stack_data[st->start]; - if( data->type == C_NAME && str_data[data->u.num].type == C_FUNC ) - func = data->u.num; - else - { - ShowError("script:run_func: not a buildin command.\n"); - script_reportdata(data); - script_reportsrc(st); - st->state = END; - return 1; - } - - if( script_config.warn_func_mismatch_argtypes ) - { - script_check_buildin_argtype(st, func); - } - - if(str_data[func].func){ - if (str_data[func].func(st)) //Report error - script_reportsrc(st); - } else { - ShowError("script:run_func: '%s' (id=%d type=%s) has no C function. please report this!!!\n", get_str(func), func, script_op2name(str_data[func].type)); - script_reportsrc(st); - st->state = END; - } - - // Stack's datum are used when re-running functions [Eoe] - if( st->state == RERUNLINE ) - return 0; - - pop_stack(st, st->start, st->end); - if( st->state == RETFUNC ) - {// return from a user-defined function - struct script_retinfo* ri; - int olddefsp = st->stack->defsp; - int nargs; - - pop_stack(st, st->stack->defsp, st->start);// pop distractions from the stack - if( st->stack->defsp < 1 || st->stack->stack_data[st->stack->defsp-1].type != C_RETINFO ) - { - ShowWarning("script:run_func: return without callfunc or callsub!\n"); - script_reportsrc(st); - st->state = END; - return 1; - } - script_free_vars( st->stack->var_function ); - - ri = st->stack->stack_data[st->stack->defsp-1].u.ri; - nargs = ri->nargs; - st->pos = ri->pos; - st->script = ri->script; - st->stack->var_function = ri->var_function; - st->stack->defsp = ri->defsp; - memset(ri, 0, sizeof(struct script_retinfo)); - - pop_stack(st, olddefsp-nargs-1, olddefsp);// pop arguments and retinfo - - st->state = GOTO; - } - - return 0; -} - -/*========================================== - * script execution - *------------------------------------------*/ -void run_script(struct script_code *rootscript,int pos,int rid,int oid) -{ - struct script_state *st; - - if( rootscript == NULL || pos < 0 ) - return; - - // TODO In jAthena, this function can take over the pending script in the player. [FlavioJS] - // It is unclear how that can be triggered, so it needs the be traced/checked in more detail. - // NOTE At the time of this change, this function wasn't capable of taking over the script state because st->scriptroot was never set. - st = script_alloc_state(rootscript, pos, rid, oid); - run_script_main(st); -} - -void script_stop_sleeptimers(int id) -{ - struct script_state* st; - for(;;) - { - st = (struct script_state*)linkdb_erase(&sleep_db,(void*)__64BPRTSIZE(id)); - if( st == NULL ) - break; // no more sleep timers - script_free_state(st); - } -} - -/*========================================== - * Delete the specified node from sleep_db - *------------------------------------------*/ -struct linkdb_node* script_erase_sleepdb(struct linkdb_node *n) -{ - struct linkdb_node *retnode; - - if( n == NULL) - return NULL; - if( n->prev == NULL ) - sleep_db = n->next; - else - n->prev->next = n->next; - if( n->next ) - n->next->prev = n->prev; - retnode = n->next; - aFree( n ); - return retnode; // The following; return retnode -} - -/*========================================== - * Timer function for sleep - *------------------------------------------*/ -int run_script_timer(int tid, unsigned int tick, int id, intptr_t data) -{ - struct script_state *st = (struct script_state *)data; - struct linkdb_node *node = (struct linkdb_node *)sleep_db; - TBL_PC *sd = map_id2sd(st->rid); - - if((sd && sd->status.char_id != id) || (st->rid && !sd)) - { //Character mismatch. Cancel execution. - st->rid = 0; - st->state = END; - } - while( node && st->sleep.timer != INVALID_TIMER ) { - if( (int)__64BPRTSIZE(node->key) == st->oid && ((struct script_state *)node->data)->sleep.timer == st->sleep.timer ) { - script_erase_sleepdb(node); - st->sleep.timer = INVALID_TIMER; - break; - } - node = node->next; - } - if(st->state != RERUNLINE) - st->sleep.tick = 0; - run_script_main(st); - return 0; -} - -/// Detaches script state from possibly attached character and restores it's previous script if any. -/// -/// @param st Script state to detach. -/// @param dequeue_event Whether to schedule any queued events, when there was no previous script. -static void script_detach_state(struct script_state* st, bool dequeue_event) -{ - struct map_session_data* sd; - - if(st->rid && (sd = map_id2sd(st->rid))!=NULL) - { - sd->st = st->bk_st; - sd->npc_id = st->bk_npcid; - /** - * For the Secure NPC Timeout option (check config/Secure.h) [RR] - **/ - #if SECURE_NPCTIMEOUT - /** - * We're done with this NPC session, so we cancel the timer (if existent) and move on - **/ - if( sd->npc_idle_timer != INVALID_TIMER ) { - delete_timer(sd->npc_idle_timer,npc_rr_secure_timeout_timer); - sd->npc_idle_timer = INVALID_TIMER; - } - #endif - if(st->bk_st) - { - //Remove tag for removal. - st->bk_st = NULL; - st->bk_npcid = 0; - } - else if(dequeue_event) - { - npc_event_dequeue(sd); - } - } - else if(st->bk_st) - {// rid was set to 0, before detaching the script state - ShowError("script_detach_state: Found previous script state without attached player (rid=%d, oid=%d, state=%d, bk_npcid=%d)\n", st->bk_st->rid, st->bk_st->oid, st->bk_st->state, st->bk_npcid); - script_reportsrc(st->bk_st); - - script_free_state(st->bk_st); - st->bk_st = NULL; - } -} - -/// Attaches script state to possibly attached character and backups it's previous script, if any. -/// -/// @param st Script state to attach. -static void script_attach_state(struct script_state* st) -{ - struct map_session_data* sd; - - if(st->rid && (sd = map_id2sd(st->rid))!=NULL) - { - if(st!=sd->st) - { - if(st->bk_st) - {// there is already a backup - ShowDebug("script_free_state: Previous script state lost (rid=%d, oid=%d, state=%d, bk_npcid=%d).\n", st->bk_st->rid, st->bk_st->oid, st->bk_st->state, st->bk_npcid); - } - st->bk_st = sd->st; - st->bk_npcid = sd->npc_id; - } - sd->st = st; - sd->npc_id = st->oid; -/** - * For the Secure NPC Timeout option (check config/Secure.h) [RR] - **/ -#if SECURE_NPCTIMEOUT - if( sd->npc_idle_timer == INVALID_TIMER ) - sd->npc_idle_timer = add_timer(gettick() + (SECURE_NPCTIMEOUT_INTERVAL*1000),npc_rr_secure_timeout_timer,sd->bl.id,0); - sd->npc_idle_tick = gettick(); -#endif - } -} - -/*========================================== - * The main part of the script execution - *------------------------------------------*/ -void run_script_main(struct script_state *st) -{ - int cmdcount = script_config.check_cmdcount; - int gotocount = script_config.check_gotocount; - TBL_PC *sd; - struct script_stack *stack=st->stack; - struct npc_data *nd; - - script_attach_state(st); - - nd = map_id2nd(st->oid); - if( nd && map[nd->bl.m].instance_id > 0 ) - st->instance_id = map[nd->bl.m].instance_id; - - if(st->state == RERUNLINE) { - run_func(st); - if(st->state == GOTO) - st->state = RUN; - } else if(st->state != END) - st->state = RUN; - - while(st->state == RUN) - { - enum c_op c = get_com(st->script->script_buf,&st->pos); - switch(c){ - case C_EOL: - if( stack->defsp > stack->sp ) - ShowError("script:run_script_main: unexpected stack position (defsp=%d sp=%d). please report this!!!\n", stack->defsp, stack->sp); - else - pop_stack(st, stack->defsp, stack->sp);// pop unused stack data. (unused return value) - break; - case C_INT: - push_val(stack,C_INT,get_num(st->script->script_buf,&st->pos)); - break; - case C_POS: - case C_NAME: - push_val(stack,c,GETVALUE(st->script->script_buf,st->pos)); - st->pos+=3; - break; - case C_ARG: - push_val(stack,c,0); - break; - case C_STR: - push_str(stack,C_CONSTSTR,(char*)(st->script->script_buf+st->pos)); - while(st->script->script_buf[st->pos++]); - break; - case C_FUNC: - run_func(st); - if(st->state==GOTO){ - st->state = RUN; - if( !st->freeloop && gotocount>0 && (--gotocount)<=0 ){ - ShowError("run_script: infinity loop !\n"); - script_reportsrc(st); - st->state=END; - } - } - break; - - case C_REF: - st->op2ref = 1; - break; - - case C_NEG: - case C_NOT: - case C_LNOT: - op_1(st ,c); - break; - - case C_ADD: - 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_OP3: - op_3(st, c); - break; - - case C_NOP: - st->state=END; - break; - - default: - ShowError("unknown command : %d @ %d\n",c,st->pos); - st->state=END; - break; - } - if( !st->freeloop && cmdcount>0 && (--cmdcount)<=0 ){ - ShowError("run_script: infinity loop !\n"); - script_reportsrc(st); - st->state=END; - } - } - - if(st->sleep.tick > 0) { - //Restore previous script - script_detach_state(st, false); - //Delay execution - sd = map_id2sd(st->rid); // Get sd since script might have attached someone while running. [Inkfish] - st->sleep.charid = sd?sd->status.char_id:0; - st->sleep.timer = add_timer(gettick()+st->sleep.tick, - run_script_timer, st->sleep.charid, (intptr_t)st); - linkdb_insert(&sleep_db, (void*)__64BPRTSIZE(st->oid), st); - } - else if(st->state != END && st->rid){ - //Resume later (st is already attached to player). - if(st->bk_st) { - ShowWarning("Unable to restore stack! Double continuation!\n"); - //Report BOTH scripts to see if that can help somehow. - ShowDebug("Previous script (lost):\n"); - script_reportsrc(st->bk_st); - ShowDebug("Current script:\n"); - script_reportsrc(st); - - script_free_state(st->bk_st); - st->bk_st = NULL; - } - } else { - //Dispose of script. - if ((sd = map_id2sd(st->rid))!=NULL) - { //Restore previous stack and save char. - if(sd->state.using_fake_npc){ - clif_clearunit_single(sd->npc_id, CLR_OUTSIGHT, sd->fd); - sd->state.using_fake_npc = 0; - } - //Restore previous script if any. - script_detach_state(st, true); - if (sd->state.reg_dirty&2) - intif_saveregistry(sd,2); - if (sd->state.reg_dirty&1) - intif_saveregistry(sd,1); - } - script_free_state(st); - st = NULL; - } -} - -int script_config_read(char *cfgName) -{ - int i; - char line[1024],w1[1024],w2[1024]; - FILE *fp; - - - fp=fopen(cfgName,"r"); - if(fp==NULL){ - ShowError("File not found: %s\n", cfgName); - return 1; - } - while(fgets(line, sizeof(line), fp)) - { - if(line[0] == '/' && line[1] == '/') - continue; - i=sscanf(line,"%[^:]: %[^\r\n]",w1,w2); - if(i!=2) - continue; - - if(strcmpi(w1,"warn_func_mismatch_paramnum")==0) { - script_config.warn_func_mismatch_paramnum = config_switch(w2); - } - else if(strcmpi(w1,"check_cmdcount")==0) { - script_config.check_cmdcount = config_switch(w2); - } - else if(strcmpi(w1,"check_gotocount")==0) { - script_config.check_gotocount = config_switch(w2); - } - else if(strcmpi(w1,"input_min_value")==0) { - script_config.input_min_value = config_switch(w2); - } - else if(strcmpi(w1,"input_max_value")==0) { - script_config.input_max_value = config_switch(w2); - } - else if(strcmpi(w1,"warn_func_mismatch_argtypes")==0) { - script_config.warn_func_mismatch_argtypes = config_switch(w2); - } - else if(strcmpi(w1,"import")==0){ - script_config_read(w2); - } - else { - ShowWarning("Unknown setting '%s' in file %s\n", w1, cfgName); - } - } - fclose(fp); - - return 0; -} - -/** - * @see DBApply - */ -static int db_script_free_code_sub(DBKey key, DBData *data, va_list ap) -{ - struct script_code *code = db_data2ptr(data); - if (code) - script_free_code(code); - return 0; -} - -void script_run_autobonus(const char *autobonus, int id, int pos) -{ - struct script_code *script = (struct script_code *)strdb_get(autobonus_db, autobonus); - - if( script ) - { - current_equip_item_index = pos; - run_script(script,0,id,0); - } -} - -void script_add_autobonus(const char *autobonus) -{ - if( strdb_get(autobonus_db, autobonus) == NULL ) - { - struct script_code *script = parse_script(autobonus, "autobonus", 0, 0); - - if( script ) - strdb_put(autobonus_db, autobonus, script); - } -} - - -/// resets a temporary character array variable to given value -void script_cleararray_pc(struct map_session_data* sd, const char* varname, void* value) -{ - int key; - uint8 idx; - - if( not_array_variable(varname[0]) || !not_server_variable(varname[0]) ) - { - ShowError("script_cleararray_pc: Variable '%s' has invalid scope (char_id=%d).\n", varname, sd->status.char_id); - return; - } - - key = add_str(varname); - - if( is_string_variable(varname) ) - { - for( idx = 0; idx < SCRIPT_MAX_ARRAYSIZE; idx++ ) - { - pc_setregstr(sd, reference_uid(key, idx), (const char*)value); - } - } - else - { - for( idx = 0; idx < SCRIPT_MAX_ARRAYSIZE; idx++ ) - { - pc_setreg(sd, reference_uid(key, idx), (int)__64BPRTSIZE(value)); - } - } -} - - -/// sets a temporary character array variable element idx to given value -/// @param refcache Pointer to an int variable, which keeps a copy of the reference to varname and must be initialized to 0. Can be NULL if only one element is set. -void script_setarray_pc(struct map_session_data* sd, const char* varname, uint8 idx, void* value, int* refcache) -{ - int key; - - if( not_array_variable(varname[0]) || !not_server_variable(varname[0]) ) - { - ShowError("script_setarray_pc: Variable '%s' has invalid scope (char_id=%d).\n", varname, sd->status.char_id); - return; - } - - if( idx >= SCRIPT_MAX_ARRAYSIZE ) - { - ShowError("script_setarray_pc: Variable '%s' has invalid index '%d' (char_id=%d).\n", varname, (int)idx, sd->status.char_id); - return; - } - - key = ( refcache && refcache[0] ) ? refcache[0] : add_str(varname); - - if( is_string_variable(varname) ) - { - pc_setregstr(sd, reference_uid(key, idx), (const char*)value); - } - else - { - pc_setreg(sd, reference_uid(key, idx), (int)__64BPRTSIZE(value)); - } - - if( refcache ) - {// save to avoid repeated add_str calls - refcache[0] = key; - } -} -#ifdef BETA_THREAD_TEST -int buildin_query_sql_sub(struct script_state* st, Sql* handle); - -/* used to receive items the queryThread has already processed */ -int queryThread_timer(int tid, unsigned int tick, int id, intptr_t data) { - int i, cursor = 0; - bool allOk = true; - - EnterSpinLock(&queryThreadLock); - - for( i = 0; i < queryThreadData.count; i++ ) { - struct queryThreadEntry *entry = queryThreadData.entry[i]; - - if( !entry->ok ) { - allOk = false; - continue; - } - - run_script_main(entry->st); - - entry->st = NULL;/* empty entries */ - aFree(entry); - queryThreadData.entry[i] = NULL; - } - - - if( allOk ) { - /* cancel the repeating timer -- it'll re-create itself when necessary, dont need to remain looping */ - delete_timer(queryThreadData.timer, queryThread_timer); - queryThreadData.timer = INVALID_TIMER; - } - - /* now lets clear the mess. */ - for( i = 0; i < queryThreadData.count; i++ ) { - struct queryThreadEntry *entry = queryThreadData.entry[i]; - if( entry == NULL ) - continue;/* entry on hold */ - - /* move */ - memmove(&queryThreadData.entry[cursor], &queryThreadData.entry[i], sizeof(struct queryThreadEntry*)); - - cursor++; - } - - queryThreadData.count = cursor; - - LeaveSpinLock(&queryThreadLock); - - return 0; -} - -void queryThread_add(struct script_state *st, bool type) { - int idx = 0; - struct queryThreadEntry* entry = NULL; - - EnterSpinLock(&queryThreadLock); - - if( queryThreadData.count++ != 0 ) - RECREATE(queryThreadData.entry, struct queryThreadEntry* , queryThreadData.count); - - idx = queryThreadData.count-1; - - CREATE(queryThreadData.entry[idx],struct queryThreadEntry,1); - - entry = queryThreadData.entry[idx]; - - entry->st = st; - entry->ok = false; - entry->type = type; - if( queryThreadData.timer == INVALID_TIMER ) { /* start the receiver timer */ - queryThreadData.timer = add_timer_interval(gettick() + 100, queryThread_timer, 0, 0, 100); - } - - LeaveSpinLock(&queryThreadLock); - - /* unlock the queryThread */ - racond_signal(queryThreadCond); -} -/* adds a new log to the queue */ -void queryThread_log(char * entry, int length) { - int idx = logThreadData.count; - - EnterSpinLock(&queryThreadLock); - - if( logThreadData.count++ != 0 ) - RECREATE(logThreadData.entry, char* , logThreadData.count); - - CREATE(logThreadData.entry[idx], char, length + 1 ); - safestrncpy(logThreadData.entry[idx], entry, length + 1 ); - - LeaveSpinLock(&queryThreadLock); - - /* unlock the queryThread */ - racond_signal(queryThreadCond); -} - -/* queryThread_main */ -static void *queryThread_main(void *x) { - Sql *queryThread_handle = Sql_Malloc(); - int i; - - if ( SQL_ERROR == Sql_Connect(queryThread_handle, map_server_id, map_server_pw, map_server_ip, map_server_port, map_server_db) ) - exit(EXIT_FAILURE); - - if( strlen(default_codepage) > 0 ) - if ( SQL_ERROR == Sql_SetEncoding(queryThread_handle, default_codepage) ) - Sql_ShowDebug(queryThread_handle); - - if( log_config.sql_logs ) { - logmysql_handle = Sql_Malloc(); - - if ( SQL_ERROR == Sql_Connect(logmysql_handle, log_db_id, log_db_pw, log_db_ip, log_db_port, log_db_db) ) - exit(EXIT_FAILURE); - - if( strlen(default_codepage) > 0 ) - if ( SQL_ERROR == Sql_SetEncoding(logmysql_handle, default_codepage) ) - Sql_ShowDebug(logmysql_handle); - } - - while( 1 ) { - - if(queryThreadTerminate > 0) - break; - - EnterSpinLock(&queryThreadLock); - - /* mess with queryThreadData within the lock */ - for( i = 0; i < queryThreadData.count; i++ ) { - struct queryThreadEntry *entry = queryThreadData.entry[i]; - - if( entry->ok ) - continue; - else if ( !entry->st || !entry->st->stack ) { - entry->ok = true;/* dispose */ - continue; - } - - buildin_query_sql_sub(entry->st, entry->type ? logmysql_handle : queryThread_handle); - - entry->ok = true;/* we're done with this */ - } - - /* also check for any logs in need to be sent */ - if( log_config.sql_logs ) { - for( i = 0; i < logThreadData.count; i++ ) { - if( SQL_ERROR == Sql_Query(logmysql_handle, logThreadData.entry[i]) ) - Sql_ShowDebug(logmysql_handle); - aFree(logThreadData.entry[i]); - } - logThreadData.count = 0; - } - - LeaveSpinLock(&queryThreadLock); - - ramutex_lock( queryThreadMutex ); - racond_wait( queryThreadCond, queryThreadMutex, -1 ); - ramutex_unlock( queryThreadMutex ); - - } - - Sql_Free(queryThread_handle); - - if( log_config.sql_logs ) { - Sql_Free(logmysql_handle); - } - - return NULL; -} -#endif -/*========================================== - * Destructor - *------------------------------------------*/ -int do_final_script() { - int i; -#ifdef DEBUG_HASH - if (battle_config.etc_log) - { - FILE *fp = fopen("hash_dump.txt","wt"); - if(fp) { - int count[SCRIPT_HASH_SIZE]; - int count2[SCRIPT_HASH_SIZE]; // number of buckets with a certain number of items - int n=0; - int min=INT_MAX,max=0,zero=0; - double mean=0.0f; - double median=0.0f; - - ShowNotice("Dumping script str hash information to hash_dump.txt\n"); - memset(count, 0, sizeof(count)); - fprintf(fp,"num : hash : data_name\n"); - fprintf(fp,"---------------------------------------------------------------\n"); - for(i=LABEL_START; i<str_num; i++) { - unsigned int h = calc_hash(get_str(i)); - fprintf(fp,"%04d : %4u : %s\n",i,h, get_str(i)); - ++count[h]; - } - fprintf(fp,"--------------------\n\n"); - memset(count2, 0, sizeof(count2)); - for(i=0; i<SCRIPT_HASH_SIZE; i++) { - fprintf(fp," hash %3d = %d\n",i,count[i]); - if(min > count[i]) - min = count[i]; // minimun count of collision - if(max < count[i]) - max = count[i]; // maximun count of collision - if(count[i] == 0) - zero++; - ++count2[count[i]]; - } - fprintf(fp,"\n--------------------\n items : buckets\n--------------------\n"); - for( i=min; i <= max; ++i ){ - fprintf(fp," %5d : %7d\n",i,count2[i]); - mean += 1.0f*i*count2[i]/SCRIPT_HASH_SIZE; // Note: this will always result in <nr labels>/<nr buckets> - } - for( i=min; i <= max; ++i ){ - n += count2[i]; - if( n*2 >= SCRIPT_HASH_SIZE ) - { - if( SCRIPT_HASH_SIZE%2 == 0 && SCRIPT_HASH_SIZE/2 == n ) - median = (i+i+1)/2.0f; - else - median = i; - break; - } - } - fprintf(fp,"--------------------\n min = %d, max = %d, zero = %d\n mean = %lf, median = %lf\n",min,max,zero,mean,median); - fclose(fp); - } - } -#endif - - mapreg_final(); - - db_destroy(scriptlabel_db); - userfunc_db->destroy(userfunc_db, db_script_free_code_sub); - autobonus_db->destroy(autobonus_db, db_script_free_code_sub); - if(sleep_db) { - struct linkdb_node *n = (struct linkdb_node *)sleep_db; - while(n) { - struct script_state *st = (struct script_state *)n->data; - script_free_state(st); - n = n->next; - } - linkdb_final(&sleep_db); - } - - if (str_data) - aFree(str_data); - if (str_buf) - aFree(str_buf); - - for( i = 0; i < atcmd_binding_count; i++ ) { - aFree(atcmd_binding[i]); - } - - if( atcmd_binding_count != 0 ) - aFree(atcmd_binding); -#ifdef BETA_THREAD_TEST - /* QueryThread */ - InterlockedIncrement(&queryThreadTerminate); - racond_signal(queryThreadCond); - rathread_wait(queryThread, NULL); - - // Destroy cond var and mutex. - racond_destroy( queryThreadCond ); - ramutex_destroy( queryThreadMutex ); - - /* Clear missing vars */ - for( i = 0; i < queryThreadData.count; i++ ) { - aFree(queryThreadData.entry[i]); - } - - aFree(queryThreadData.entry); - - for( i = 0; i < logThreadData.count; i++ ) { - aFree(logThreadData.entry[i]); - } - - aFree(logThreadData.entry); -#endif - - return 0; -} -/*========================================== - * Initialization - *------------------------------------------*/ -int do_init_script() { - userfunc_db=strdb_alloc(DB_OPT_DUP_KEY,0); - scriptlabel_db=strdb_alloc(DB_OPT_DUP_KEY,50); - autobonus_db = strdb_alloc(DB_OPT_DUP_KEY,0); - - mapreg_init(); -#ifdef BETA_THREAD_TEST - CREATE(queryThreadData.entry, struct queryThreadEntry*, 1); - queryThreadData.count = 0; - CREATE(logThreadData.entry, char *, 1); - logThreadData.count = 0; - /* QueryThread Start */ - - InitializeSpinLock(&queryThreadLock); - - queryThreadData.timer = INVALID_TIMER; - queryThreadTerminate = 0; - queryThreadMutex = ramutex_create(); - queryThreadCond = racond_create(); - - queryThread = rathread_create(queryThread_main, NULL); - - if(queryThread == NULL){ - ShowFatalError("do_init_script: cannot spawn Query Thread.\n"); - exit(EXIT_FAILURE); - } - - add_timer_func_list(queryThread_timer, "queryThread_timer"); -#endif - return 0; -} - -int script_reload() { - int i; - -#ifdef BETA_THREAD_TEST - /* we're reloading so any queries undergoing should be...exterminated. */ - EnterSpinLock(&queryThreadLock); - - for( i = 0; i < queryThreadData.count; i++ ) { - aFree(queryThreadData.entry[i]); - } - queryThreadData.count = 0; - - if( queryThreadData.timer != INVALID_TIMER ) { - delete_timer(queryThreadData.timer, queryThread_timer); - queryThreadData.timer = INVALID_TIMER; - } - - LeaveSpinLock(&queryThreadLock); -#endif - - - userfunc_db->clear(userfunc_db, db_script_free_code_sub); - db_clear(scriptlabel_db); - - // @commands (script based) - // Clear bindings - for( i = 0; i < atcmd_binding_count; i++ ) { - aFree(atcmd_binding[i]); - } - - if( atcmd_binding_count != 0 ) - aFree(atcmd_binding); - - atcmd_binding_count = 0; - - if(sleep_db) { - struct linkdb_node *n = (struct linkdb_node *)sleep_db; - while(n) { - struct script_state *st = (struct script_state *)n->data; - script_free_state(st); - n = n->next; - } - linkdb_final(&sleep_db); - } - mapreg_reload(); - return 0; -} - -//----------------------------------------------------------------------------- -// buildin functions -// - -#define BUILDIN_DEF(x,args) { buildin_ ## x , #x , args } -#define BUILDIN_DEF2(x,x2,args) { buildin_ ## x , x2 , args } -#define BUILDIN_FUNC(x) int buildin_ ## x (struct script_state* st) - -///////////////////////////////////////////////////////////////////// -// NPC interaction -// - -/// Appends a message to the npc dialog. -/// If a dialog doesn't exist yet, one is created. -/// -/// mes "<message>"; -BUILDIN_FUNC(mes) -{ - TBL_PC* sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - if( !script_hasdata(st, 3) ) - {// only a single line detected in the script - clif_scriptmes(sd, st->oid, script_getstr(st, 2)); - } - else - {// parse multiple lines as they exist - int i; - - for( i = 2; script_hasdata(st, i); i++ ) - { - // send the message to the client - clif_scriptmes(sd, st->oid, script_getstr(st, i)); - } - } - - return 0; -} - -/// Displays the button 'next' in the npc dialog. -/// The dialog text is cleared and the script continues when the button is pressed. -/// -/// next; -BUILDIN_FUNC(next) -{ - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - st->state = STOP; - clif_scriptnext(sd, st->oid); - return 0; -} - -/// Ends the script and displays the button 'close' on the npc dialog. -/// The dialog is closed when the button is pressed. -/// -/// close; -BUILDIN_FUNC(close) -{ - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - st->state = END; - clif_scriptclose(sd, st->oid); - return 0; -} - -/// Displays the button 'close' on the npc dialog. -/// The dialog is closed and the script continues when the button is pressed. -/// -/// close2; -BUILDIN_FUNC(close2) -{ - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - st->state = STOP; - clif_scriptclose(sd, st->oid); - return 0; -} - -/// Counts the number of valid and total number of options in 'str' -/// If max_count > 0 the counting stops when that valid option is reached -/// total is incremented for each option (NULL is supported) -static int menu_countoptions(const char* str, int max_count, int* total) -{ - int count = 0; - int bogus_total; - - if( total == NULL ) - total = &bogus_total; - ++(*total); - - // initial empty options - while( *str == ':' ) - { - ++str; - ++(*total); - } - // count menu options - while( *str != '\0' ) - { - ++count; - --max_count; - if( max_count == 0 ) - break; - while( *str != ':' && *str != '\0' ) - ++str; - while( *str == ':' ) - { - ++str; - ++(*total); - } - } - return count; -} - -/// Displays a menu with options and goes to the target label. -/// The script is stopped if cancel is pressed. -/// Options with no text are not displayed in the client. -/// -/// Options can be grouped together, separated by the character ':' in the text: -/// ex: menu "A:B:C",L_target; -/// All these options go to the specified target label. -/// -/// The index of the selected option is put in the variable @menu. -/// Indexes start with 1 and are consistent with grouped and empty options. -/// ex: menu "A::B",-,"",L_Impossible,"C",-; -/// // displays "A", "B" and "C", corresponding to indexes 1, 3 and 5 -/// -/// NOTE: the client closes the npc dialog when cancel is pressed -/// -/// menu "<option_text>",<target_label>{,"<option_text>",<target_label>,...}; -BUILDIN_FUNC(menu) -{ - int i; - const char* text; - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - // TODO detect multiple scripts waiting for input at the same time, and what to do when that happens - if( sd->state.menu_or_input == 0 ) - { - struct StringBuf buf; - struct script_data* data; - - if( script_lastdata(st) % 2 == 0 ) - {// argument count is not even (1st argument is at index 2) - ShowError("script:menu: illegal number of arguments (%d).\n", (script_lastdata(st) - 1)); - st->state = END; - return 1; - } - - StringBuf_Init(&buf); - sd->npc_menu = 0; - for( i = 2; i < script_lastdata(st); i += 2 ) - { - // menu options - text = script_getstr(st, i); - - // target label - data = script_getdata(st, i+1); - if( !data_islabel(data) ) - {// not a label - StringBuf_Destroy(&buf); - ShowError("script:menu: argument #%d (from 1) is not a label or label not found.\n", i); - script_reportdata(data); - st->state = END; - return 1; - } - - // append option(s) - if( text[0] == '\0' ) - continue;// empty string, ignore - if( sd->npc_menu > 0 ) - StringBuf_AppendStr(&buf, ":"); - StringBuf_AppendStr(&buf, text); - sd->npc_menu += menu_countoptions(text, 0, NULL); - } - st->state = RERUNLINE; - sd->state.menu_or_input = 1; - - /** - * menus beyond this length crash the client (see bugreport:6402) - **/ - if( StringBuf_Length(&buf) >= 2047 ) { - struct npc_data * nd = map_id2nd(st->oid); - char* menu; - CREATE(menu, char, 2048); - safestrncpy(menu, StringBuf_Value(&buf), 2047); - ShowWarning("NPC Menu too long! (source:%s / length:%d)\n",nd?nd->name:"Unknown",StringBuf_Length(&buf)); - clif_scriptmenu(sd, st->oid, menu); - aFree(menu); - } else - clif_scriptmenu(sd, st->oid, StringBuf_Value(&buf)); - - StringBuf_Destroy(&buf); - - if( sd->npc_menu >= 0xff ) - {// client supports only up to 254 entries; 0 is not used and 255 is reserved for cancel; excess entries are displayed but cause 'uint8' overflow - ShowWarning("buildin_menu: Too many options specified (current=%d, max=254).\n", sd->npc_menu); - script_reportsrc(st); - } - } - else if( sd->npc_menu == 0xff ) - {// Cancel was pressed - sd->state.menu_or_input = 0; - st->state = END; - } - else - {// goto target label - int menu = 0; - - sd->state.menu_or_input = 0; - if( sd->npc_menu <= 0 ) - { - ShowDebug("script:menu: unexpected selection (%d)\n", sd->npc_menu); - st->state = END; - return 1; - } - - // get target label - for( i = 2; i < script_lastdata(st); i += 2 ) - { - text = script_getstr(st, i); - sd->npc_menu -= menu_countoptions(text, sd->npc_menu, &menu); - if( sd->npc_menu <= 0 ) - break;// entry found - } - if( sd->npc_menu > 0 ) - {// Invalid selection - ShowDebug("script:menu: selection is out of range (%d pairs are missing?) - please report this\n", sd->npc_menu); - st->state = END; - return 1; - } - if( !data_islabel(script_getdata(st, i + 1)) ) - {// TODO remove this temporary crash-prevention code (fallback for multiple scripts requesting user input) - ShowError("script:menu: unexpected data in label argument\n"); - script_reportdata(script_getdata(st, i + 1)); - st->state = END; - return 1; - } - pc_setreg(sd, add_str("@menu"), menu); - st->pos = script_getnum(st, i + 1); - st->state = GOTO; - } - return 0; -} - -/// Displays a menu with options and returns the selected option. -/// Behaves like 'menu' without the target labels. -/// -/// select(<option_text>{,<option_text>,...}) -> <selected_option> -/// -/// @see menu -BUILDIN_FUNC(select) -{ - int i; - const char* text; - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - if( sd->state.menu_or_input == 0 ) { - struct StringBuf buf; - - StringBuf_Init(&buf); - sd->npc_menu = 0; - for( i = 2; i <= script_lastdata(st); ++i ) { - text = script_getstr(st, i); - - if( sd->npc_menu > 0 ) - StringBuf_AppendStr(&buf, ":"); - - StringBuf_AppendStr(&buf, text); - sd->npc_menu += menu_countoptions(text, 0, NULL); - } - - st->state = RERUNLINE; - sd->state.menu_or_input = 1; - - /** - * menus beyond this length crash the client (see bugreport:6402) - **/ - if( StringBuf_Length(&buf) >= 2047 ) { - struct npc_data * nd = map_id2nd(st->oid); - char* menu; - CREATE(menu, char, 2048); - safestrncpy(menu, StringBuf_Value(&buf), 2047); - ShowWarning("NPC Menu too long! (source:%s / length:%d)\n",nd?nd->name:"Unknown",StringBuf_Length(&buf)); - clif_scriptmenu(sd, st->oid, menu); - aFree(menu); - } else - clif_scriptmenu(sd, st->oid, StringBuf_Value(&buf)); - StringBuf_Destroy(&buf); - - if( sd->npc_menu >= 0xff ) { - ShowWarning("buildin_select: Too many options specified (current=%d, max=254).\n", sd->npc_menu); - script_reportsrc(st); - } - } else if( sd->npc_menu == 0xff ) {// Cancel was pressed - sd->state.menu_or_input = 0; - st->state = END; - } else {// return selected option - int menu = 0; - - sd->state.menu_or_input = 0; - for( i = 2; i <= script_lastdata(st); ++i ) { - text = script_getstr(st, i); - sd->npc_menu -= menu_countoptions(text, sd->npc_menu, &menu); - if( sd->npc_menu <= 0 ) - break;// entry found - } - pc_setreg(sd, add_str("@menu"), menu); - script_pushint(st, menu); - st->state = RUN; - } - return 0; -} - -/// Displays a menu with options and returns the selected option. -/// Behaves like 'menu' without the target labels, except when cancel is -/// pressed. -/// When cancel is pressed, the script continues and 255 is returned. -/// -/// prompt(<option_text>{,<option_text>,...}) -> <selected_option> -/// -/// @see menu -BUILDIN_FUNC(prompt) -{ - int i; - const char *text; - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - if( sd->state.menu_or_input == 0 ) - { - struct StringBuf buf; - - StringBuf_Init(&buf); - sd->npc_menu = 0; - for( i = 2; i <= script_lastdata(st); ++i ) - { - text = script_getstr(st, i); - if( sd->npc_menu > 0 ) - StringBuf_AppendStr(&buf, ":"); - StringBuf_AppendStr(&buf, text); - sd->npc_menu += menu_countoptions(text, 0, NULL); - } - - st->state = RERUNLINE; - sd->state.menu_or_input = 1; - - /** - * menus beyond this length crash the client (see bugreport:6402) - **/ - if( StringBuf_Length(&buf) >= 2047 ) { - struct npc_data * nd = map_id2nd(st->oid); - char* menu; - CREATE(menu, char, 2048); - safestrncpy(menu, StringBuf_Value(&buf), 2047); - ShowWarning("NPC Menu too long! (source:%s / length:%d)\n",nd?nd->name:"Unknown",StringBuf_Length(&buf)); - clif_scriptmenu(sd, st->oid, menu); - aFree(menu); - } else - clif_scriptmenu(sd, st->oid, StringBuf_Value(&buf)); - StringBuf_Destroy(&buf); - - if( sd->npc_menu >= 0xff ) - { - ShowWarning("buildin_prompt: Too many options specified (current=%d, max=254).\n", sd->npc_menu); - script_reportsrc(st); - } - } - else if( sd->npc_menu == 0xff ) - {// Cancel was pressed - sd->state.menu_or_input = 0; - pc_setreg(sd, add_str("@menu"), 0xff); - script_pushint(st, 0xff); - st->state = RUN; - } - else - {// return selected option - int menu = 0; - - sd->state.menu_or_input = 0; - for( i = 2; i <= script_lastdata(st); ++i ) - { - text = script_getstr(st, i); - sd->npc_menu -= menu_countoptions(text, sd->npc_menu, &menu); - if( sd->npc_menu <= 0 ) - break;// entry found - } - pc_setreg(sd, add_str("@menu"), menu); - script_pushint(st, menu); - st->state = RUN; - } - return 0; -} - -///////////////////////////////////////////////////////////////////// -// ... -// - -/// Jumps to the target script label. -/// -/// goto <label>; -BUILDIN_FUNC(goto) -{ - if( !data_islabel(script_getdata(st,2)) ) - { - ShowError("script:goto: not a label\n"); - script_reportdata(script_getdata(st,2)); - st->state = END; - return 1; - } - - st->pos = script_getnum(st,2); - st->state = GOTO; - return 0; -} - -/*========================================== - * user-defined function call - *------------------------------------------*/ -BUILDIN_FUNC(callfunc) -{ - int i, j; - struct script_retinfo* ri; - struct script_code* scr; - const char* str = script_getstr(st,2); - DBMap **ref = NULL; - - scr = (struct script_code*)strdb_get(userfunc_db, str); - if( !scr ) - { - ShowError("script:callfunc: function not found! [%s]\n", str); - st->state = END; - return 1; - } - - for( i = st->start+3, j = 0; i < st->end; i++, j++ ) - { - struct script_data* data = push_copy(st->stack,i); - if( data_isreference(data) && !data->ref ) - { - const char* name = reference_getname(data); - if( name[0] == '.' ) { - if ( !ref ) { - ref = (struct DBMap**)aCalloc(sizeof(struct DBMap*), 1); - ref[0] = (name[1] == '@' ? st->stack->var_function : st->script->script_vars); - } - data->ref = ref; - } - } - } - - CREATE(ri, struct script_retinfo, 1); - ri->script = st->script;// script code - ri->var_function = st->stack->var_function;// scope variables - ri->pos = st->pos;// script location - ri->nargs = j;// argument count - ri->defsp = st->stack->defsp;// default stack pointer - push_retinfo(st->stack, ri, ref); - - st->pos = 0; - st->script = scr; - st->stack->defsp = st->stack->sp; - st->state = GOTO; - st->stack->var_function = idb_alloc(DB_OPT_RELEASE_DATA); - - return 0; -} -/*========================================== - * subroutine call - *------------------------------------------*/ -BUILDIN_FUNC(callsub) -{ - int i,j; - struct script_retinfo* ri; - int pos = script_getnum(st,2); - DBMap **ref = NULL; - - if( !data_islabel(script_getdata(st,2)) && !data_isfunclabel(script_getdata(st,2)) ) - { - ShowError("script:callsub: argument is not a label\n"); - script_reportdata(script_getdata(st,2)); - st->state = END; - return 1; - } - - for( i = st->start+3, j = 0; i < st->end; i++, j++ ) - { - struct script_data* data = push_copy(st->stack,i); - if( data_isreference(data) && !data->ref ) - { - const char* name = reference_getname(data); - if( name[0] == '.' && name[1] == '@' ) { - if ( !ref ) { - ref = (struct DBMap**)aCalloc(sizeof(struct DBMap*), 1); - ref[0] = st->stack->var_function; - } - data->ref = ref; - } - } - } - - CREATE(ri, struct script_retinfo, 1); - ri->script = st->script;// script code - ri->var_function = st->stack->var_function;// scope variables - ri->pos = st->pos;// script location - ri->nargs = j;// argument count - ri->defsp = st->stack->defsp;// default stack pointer - push_retinfo(st->stack, ri, ref); - - st->pos = pos; - st->stack->defsp = st->stack->sp; - st->state = GOTO; - st->stack->var_function = idb_alloc(DB_OPT_RELEASE_DATA); - - return 0; -} - -/// Retrieves an argument provided to callfunc/callsub. -/// If the argument doesn't exist -/// -/// getarg(<index>{,<default_value>}) -> <value> -BUILDIN_FUNC(getarg) -{ - struct script_retinfo* ri; - int idx; - - if( st->stack->defsp < 1 || st->stack->stack_data[st->stack->defsp - 1].type != C_RETINFO ) - { - ShowError("script:getarg: no callfunc or callsub!\n"); - st->state = END; - return 1; - } - ri = st->stack->stack_data[st->stack->defsp - 1].u.ri; - - idx = script_getnum(st,2); - - if( idx >= 0 && idx < ri->nargs ) - push_copy(st->stack, st->stack->defsp - 1 - ri->nargs + idx); - else if( script_hasdata(st,3) ) - script_pushcopy(st, 3); - else - { - ShowError("script:getarg: index (idx=%d) out of range (nargs=%d) and no default value found\n", idx, ri->nargs); - st->state = END; - return 1; - } - - return 0; -} - -/// Returns from the current function, optionaly returning a value from the functions. -/// Don't use outside script functions. -/// -/// return; -/// return <value>; -BUILDIN_FUNC(return) -{ - if( script_hasdata(st,2) ) - {// return value - struct script_data* data; - script_pushcopy(st, 2); - data = script_getdatatop(st, -1); - if( data_isreference(data) ) - { - const char* name = reference_getname(data); - if( name[0] == '.' && name[1] == '@' ) - {// scope variable - if( !data->ref || data->ref == (DBMap**)&st->stack->var_function ) - get_val(st, data);// current scope, convert to value - } - else if( name[0] == '.' && !data->ref ) - {// script variable, link to current script - data->ref = &st->script->script_vars; - } - } - } - else - {// no return value - script_pushnil(st); - } - st->state = RETFUNC; - return 0; -} - -/// Returns a random number from 0 to <range>-1. -/// Or returns a random number from <min> to <max>. -/// If <min> is greater than <max>, their numbers are switched. -/// rand(<range>) -> <int> -/// rand(<min>,<max>) -> <int> -BUILDIN_FUNC(rand) -{ - int range; - int min; - int max; - - if( script_hasdata(st,3) ) - {// min,max - min = script_getnum(st,2); - max = script_getnum(st,3); - if( max < min ) - swap(min, max); - range = max - min + 1; - } - else - {// range - min = 0; - range = script_getnum(st,2); - } - if( range <= 1 ) - script_pushint(st, min); - else - script_pushint(st, rnd()%range + min); - - return 0; -} - -/*========================================== - * Warp sd to str,x,y or Random or SavePoint/Save - *------------------------------------------*/ -BUILDIN_FUNC(warp) -{ - int ret; - int x,y; - const char* str; - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - str = script_getstr(st,2); - x = script_getnum(st,3); - y = script_getnum(st,4); - - if(strcmp(str,"Random")==0) - ret = pc_randomwarp(sd,CLR_TELEPORT); - else if(strcmp(str,"SavePoint")==0 || strcmp(str,"Save")==0) - ret = pc_setpos(sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,CLR_TELEPORT); - else - ret = pc_setpos(sd,mapindex_name2id(str),x,y,CLR_OUTSIGHT); - - if( ret ) { - ShowError("buildin_warp: moving player '%s' to \"%s\",%d,%d failed.\n", sd->status.name, str, x, y); - script_reportsrc(st); - } - - return 0; -} -/*========================================== - * Warp a specified area - *------------------------------------------*/ -static int buildin_areawarp_sub(struct block_list *bl,va_list ap) -{ - int x2,y2,x3,y3; - unsigned int index; - - index = va_arg(ap,unsigned int); - x2 = va_arg(ap,int); - y2 = va_arg(ap,int); - x3 = va_arg(ap,int); - y3 = va_arg(ap,int); - - if(index == 0) - pc_randomwarp((TBL_PC *)bl,CLR_TELEPORT); - else if(x3 && y3) { - int max, tx, ty, j = 0; - - // choose a suitable max number of attempts - if( (max = (y3-y2+1)*(x3-x2+1)*3) > 1000 ) - max = 1000; - - // find a suitable map cell - do { - tx = rnd()%(x3-x2+1)+x2; - ty = rnd()%(y3-y2+1)+y2; - j++; - } while( map_getcell(index,tx,ty,CELL_CHKNOPASS) && j < max ); - - pc_setpos((TBL_PC *)bl,index,tx,ty,CLR_OUTSIGHT); - } - else - pc_setpos((TBL_PC *)bl,index,x2,y2,CLR_OUTSIGHT); - return 0; -} -BUILDIN_FUNC(areawarp) -{ - int16 m, x0,y0,x1,y1, x2,y2,x3=0,y3=0; - unsigned int index; - const char *str; - const char *mapname; - - mapname = script_getstr(st,2); - x0 = script_getnum(st,3); - y0 = script_getnum(st,4); - x1 = script_getnum(st,5); - y1 = script_getnum(st,6); - str = script_getstr(st,7); - x2 = script_getnum(st,8); - y2 = script_getnum(st,9); - - if( script_hasdata(st,10) && script_hasdata(st,11) ) { // Warp area to area - if( (x3 = script_getnum(st,10)) < 0 || (y3 = script_getnum(st,11)) < 0 ){ - x3 = 0; - y3 = 0; - } else if( x3 && y3 ) { - // normalize x3/y3 coordinates - if( x3 < x2 ) swap(x3,x2); - if( y3 < y2 ) swap(y3,y2); - } - } - - if( (m = map_mapname2mapid(mapname)) < 0 ) - return 0; - - if( strcmp(str,"Random") == 0 ) - index = 0; - else if( !(index=mapindex_name2id(str)) ) - return 0; - - map_foreachinarea(buildin_areawarp_sub, m,x0,y0,x1,y1, BL_PC, index,x2,y2,x3,y3); - return 0; -} - -/*========================================== - * areapercentheal <map>,<x1>,<y1>,<x2>,<y2>,<hp>,<sp> - *------------------------------------------*/ -static int buildin_areapercentheal_sub(struct block_list *bl,va_list ap) -{ - int hp, sp; - hp = va_arg(ap, int); - sp = va_arg(ap, int); - pc_percentheal((TBL_PC *)bl,hp,sp); - return 0; -} -BUILDIN_FUNC(areapercentheal) -{ - int hp,sp,m; - const char *mapname; - int x0,y0,x1,y1; - - mapname=script_getstr(st,2); - x0=script_getnum(st,3); - y0=script_getnum(st,4); - x1=script_getnum(st,5); - y1=script_getnum(st,6); - hp=script_getnum(st,7); - sp=script_getnum(st,8); - - if( (m=map_mapname2mapid(mapname))< 0) - return 0; - - map_foreachinarea(buildin_areapercentheal_sub,m,x0,y0,x1,y1,BL_PC,hp,sp); - return 0; -} - -/*========================================== - * warpchar [LuzZza] - * Useful for warp one player from - * another player npc-session. - * Using: warpchar "mapname",x,y,Char_ID; - *------------------------------------------*/ -BUILDIN_FUNC(warpchar) -{ - int x,y,a; - const char *str; - TBL_PC *sd; - - str=script_getstr(st,2); - x=script_getnum(st,3); - y=script_getnum(st,4); - a=script_getnum(st,5); - - sd = map_charid2sd(a); - if( sd == NULL ) - return 0; - - if(strcmp(str, "Random") == 0) - pc_randomwarp(sd, CLR_TELEPORT); - else - if(strcmp(str, "SavePoint") == 0) - pc_setpos(sd, sd->status.save_point.map,sd->status.save_point.x, sd->status.save_point.y, CLR_TELEPORT); - else - pc_setpos(sd, mapindex_name2id(str), x, y, CLR_TELEPORT); - - return 0; -} -/*========================================== - * Warpparty - [Fredzilla] [Paradox924X] - * Syntax: warpparty "to_mapname",x,y,Party_ID,{"from_mapname"}; - * If 'from_mapname' is specified, only the party members on that map will be warped - *------------------------------------------*/ -BUILDIN_FUNC(warpparty) -{ - TBL_PC *sd = NULL; - TBL_PC *pl_sd; - struct party_data* p; - int type; - int mapindex; - int i; - - const char* str = script_getstr(st,2); - int x = script_getnum(st,3); - int y = script_getnum(st,4); - int p_id = script_getnum(st,5); - const char* str2 = NULL; - if ( script_hasdata(st,6) ) - str2 = script_getstr(st,6); - - p = party_search(p_id); - if(!p) - return 0; - - type = ( strcmp(str,"Random")==0 ) ? 0 - : ( strcmp(str,"SavePointAll")==0 ) ? 1 - : ( strcmp(str,"SavePoint")==0 ) ? 2 - : ( strcmp(str,"Leader")==0 ) ? 3 - : 4; - - switch (type) - { - case 3: - for(i = 0; i < MAX_PARTY && !p->party.member[i].leader; i++); - if (i == MAX_PARTY || !p->data[i].sd) //Leader not found / not online - return 0; - pl_sd = p->data[i].sd; - mapindex = pl_sd->mapindex; - x = pl_sd->bl.x; - y = pl_sd->bl.y; - break; - case 4: - mapindex = mapindex_name2id(str); - break; - case 2: - //"SavePoint" uses save point of the currently attached player - if (( sd = script_rid2sd(st) ) == NULL ) - return 0; - default: - mapindex = 0; - break; - } - - for (i = 0; i < MAX_PARTY; i++) - { - if( !(pl_sd = p->data[i].sd) || pl_sd->status.party_id != p_id ) - continue; - - if( str2 && strcmp(str2, map[pl_sd->bl.m].name) != 0 ) - continue; - - if( pc_isdead(pl_sd) ) - continue; - - switch( type ) - { - case 0: // Random - if(!map[pl_sd->bl.m].flag.nowarp) - pc_randomwarp(pl_sd,CLR_TELEPORT); - break; - case 1: // SavePointAll - if(!map[pl_sd->bl.m].flag.noreturn) - pc_setpos(pl_sd,pl_sd->status.save_point.map,pl_sd->status.save_point.x,pl_sd->status.save_point.y,CLR_TELEPORT); - break; - case 2: // SavePoint - if(!map[pl_sd->bl.m].flag.noreturn) - pc_setpos(pl_sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,CLR_TELEPORT); - break; - case 3: // Leader - case 4: // m,x,y - if(!map[pl_sd->bl.m].flag.noreturn && !map[pl_sd->bl.m].flag.nowarp) - pc_setpos(pl_sd,mapindex,x,y,CLR_TELEPORT); - break; - } - } - - return 0; -} -/*========================================== - * Warpguild - [Fredzilla] - * Syntax: warpguild "mapname",x,y,Guild_ID; - *------------------------------------------*/ -BUILDIN_FUNC(warpguild) -{ - TBL_PC *sd = NULL; - TBL_PC *pl_sd; - struct guild* g; - struct s_mapiterator* iter; - int type; - - const char* str = script_getstr(st,2); - int x = script_getnum(st,3); - int y = script_getnum(st,4); - int gid = script_getnum(st,5); - - g = guild_search(gid); - if( g == NULL ) - return 0; - - type = ( strcmp(str,"Random")==0 ) ? 0 - : ( strcmp(str,"SavePointAll")==0 ) ? 1 - : ( strcmp(str,"SavePoint")==0 ) ? 2 - : 3; - - if( type == 2 && ( sd = script_rid2sd(st) ) == NULL ) - {// "SavePoint" uses save point of the currently attached player - return 0; - } - - iter = mapit_getallusers(); - for( pl_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); pl_sd = (TBL_PC*)mapit_next(iter) ) - { - if( pl_sd->status.guild_id != gid ) - continue; - - switch( type ) - { - case 0: // Random - if(!map[pl_sd->bl.m].flag.nowarp) - pc_randomwarp(pl_sd,CLR_TELEPORT); - break; - case 1: // SavePointAll - if(!map[pl_sd->bl.m].flag.noreturn) - pc_setpos(pl_sd,pl_sd->status.save_point.map,pl_sd->status.save_point.x,pl_sd->status.save_point.y,CLR_TELEPORT); - break; - case 2: // SavePoint - if(!map[pl_sd->bl.m].flag.noreturn) - pc_setpos(pl_sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,CLR_TELEPORT); - break; - case 3: // m,x,y - if(!map[pl_sd->bl.m].flag.noreturn && !map[pl_sd->bl.m].flag.nowarp) - pc_setpos(pl_sd,mapindex_name2id(str),x,y,CLR_TELEPORT); - break; - } - } - mapit_free(iter); - - return 0; -} -/*========================================== - * Force Heal a player (hp and sp) - *------------------------------------------*/ -BUILDIN_FUNC(heal) -{ - TBL_PC *sd; - int hp,sp; - - sd = script_rid2sd(st); - if (!sd) return 0; - - hp=script_getnum(st,2); - sp=script_getnum(st,3); - status_heal(&sd->bl, hp, sp, 1); - return 0; -} -/*========================================== - * Heal a player by item (get vit bonus etc) - *------------------------------------------*/ -BUILDIN_FUNC(itemheal) -{ - TBL_PC *sd; - int hp,sp; - - hp=script_getnum(st,2); - sp=script_getnum(st,3); - - if(potion_flag==1) { - potion_hp = hp; - potion_sp = sp; - return 0; - } - - sd = script_rid2sd(st); - if (!sd) return 0; - pc_itemheal(sd,sd->itemid,hp,sp); - return 0; -} -/*========================================== - * - *------------------------------------------*/ -BUILDIN_FUNC(percentheal) -{ - int hp,sp; - TBL_PC* sd; - - hp=script_getnum(st,2); - sp=script_getnum(st,3); - - if(potion_flag==1) { - potion_per_hp = hp; - potion_per_sp = sp; - return 0; - } - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; -#ifdef RENEWAL - if( sd->sc.data[SC_EXTREMITYFIST2] ) - sp = 0; -#endif - pc_percentheal(sd,hp,sp); - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -BUILDIN_FUNC(jobchange) -{ - int job, upper=-1; - - job=script_getnum(st,2); - if( script_hasdata(st,3) ) - upper=script_getnum(st,3); - - if (pcdb_checkid(job)) - { - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - pc_jobchange(sd, job, upper); - } - - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -BUILDIN_FUNC(jobname) -{ - int class_=script_getnum(st,2); - script_pushconststr(st, (char*)job_name(class_)); - return 0; -} - -/// Get input from the player. -/// For numeric inputs the value is capped to the range [min,max]. Returns 1 if -/// the value was higher than 'max', -1 if lower than 'min' and 0 otherwise. -/// For string inputs it returns 1 if the string was longer than 'max', -1 is -/// shorter than 'min' and 0 otherwise. -/// -/// input(<var>{,<min>{,<max>}}) -> <int> -BUILDIN_FUNC(input) -{ - TBL_PC* sd; - struct script_data* data; - int uid; - const char* name; - int min; - int max; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - data = script_getdata(st,2); - if( !data_isreference(data) ){ - ShowError("script:input: not a variable\n"); - script_reportdata(data); - st->state = END; - return 1; - } - uid = reference_getuid(data); - name = reference_getname(data); - min = (script_hasdata(st,3) ? script_getnum(st,3) : script_config.input_min_value); - max = (script_hasdata(st,4) ? script_getnum(st,4) : script_config.input_max_value); - - if( !sd->state.menu_or_input ) - { // first invocation, display npc input box - sd->state.menu_or_input = 1; - st->state = RERUNLINE; - if( is_string_variable(name) ) - clif_scriptinputstr(sd,st->oid); - else - clif_scriptinput(sd,st->oid); - } - else - { // take received text/value and store it in the designated variable - sd->state.menu_or_input = 0; - if( is_string_variable(name) ) - { - int len = (int)strlen(sd->npc_str); - set_reg(st, sd, uid, name, (void*)sd->npc_str, script_getref(st,2)); - script_pushint(st, (len > max ? 1 : len < min ? -1 : 0)); - } - else - { - int amount = sd->npc_amount; - set_reg(st, sd, uid, name, (void*)__64BPRTSIZE(cap_value(amount,min,max)), script_getref(st,2)); - script_pushint(st, (amount > max ? 1 : amount < min ? -1 : 0)); - } - st->state = RUN; - } - return 0; -} - -// declare the copyarray method here for future reference -BUILDIN_FUNC(copyarray); - -/// Sets the value of a variable. -/// The value is converted to the type of the variable. -/// -/// set(<variable>,<value>) -> <variable> -BUILDIN_FUNC(set) -{ - TBL_PC* sd = NULL; - struct script_data* data; - //struct script_data* datavalue; - int num; - const char* name; - char prefix; - - data = script_getdata(st,2); - //datavalue = script_getdata(st,3); - if( !data_isreference(data) ) - { - ShowError("script:set: not a variable\n"); - script_reportdata(script_getdata(st,2)); - st->state = END; - return 1; - } - - num = reference_getuid(data); - name = reference_getname(data); - prefix = *name; - - if( not_server_variable(prefix) ) - { - sd = script_rid2sd(st); - if( sd == NULL ) - { - ShowError("script:set: no player attached for player variable '%s'\n", name); - return 0; - } - } - -#if 0 - if( data_isreference(datavalue) ) - {// the value being referenced is a variable - const char* namevalue = reference_getname(datavalue); - - if( !not_array_variable(*namevalue) ) - {// array variable being copied into another array variable - if( sd == NULL && not_server_variable(*namevalue) && !(sd = script_rid2sd(st)) ) - {// player must be attached in order to copy a player variable - ShowError("script:set: no player attached for player variable '%s'\n", namevalue); - return 0; - } - - if( is_string_variable(namevalue) != is_string_variable(name) ) - {// non-matching array value types - ShowWarning("script:set: two array variables do not match in type.\n"); - return 0; - } - - // push the maximum number of array values to the stack - push_val(st->stack, C_INT, SCRIPT_MAX_ARRAYSIZE); - - // call the copy array method directly - return buildin_copyarray(st); - } - } -#endif - - if( is_string_variable(name) ) - set_reg(st,sd,num,name,(void*)script_getstr(st,3),script_getref(st,2)); - else - set_reg(st,sd,num,name,(void*)__64BPRTSIZE(script_getnum(st,3)),script_getref(st,2)); - - // return a copy of the variable reference - script_pushcopy(st,2); - - return 0; -} - -///////////////////////////////////////////////////////////////////// -/// Array variables -/// - -/// Returns the size of the specified array -static int32 getarraysize(struct script_state* st, int32 id, int32 idx, int isstring, struct DBMap** ref) -{ - int32 ret = idx; - - if( isstring ) - { - for( ; idx < SCRIPT_MAX_ARRAYSIZE; ++idx ) - { - char* str = (char*)get_val2(st, reference_uid(id, idx), ref); - if( str && *str ) - ret = idx + 1; - script_removetop(st, -1, 0); - } - } - else - { - for( ; idx < SCRIPT_MAX_ARRAYSIZE; ++idx ) - { - int32 num = (int32)__64BPRTSIZE(get_val2(st, reference_uid(id, idx), ref)); - if( num ) - ret = idx + 1; - script_removetop(st, -1, 0); - } - } - return ret; -} - -/// Sets values of an array, from the starting index. -/// ex: setarray arr[1],1,2,3; -/// -/// setarray <array variable>,<value1>{,<value2>...}; -BUILDIN_FUNC(setarray) -{ - struct script_data* data; - const char* name; - int32 start; - int32 end; - int32 id; - int32 i; - TBL_PC* sd = NULL; - - data = script_getdata(st, 2); - if( !data_isreference(data) ) - { - ShowError("script:setarray: not a variable\n"); - script_reportdata(data); - st->state = END; - return 1;// not a variable - } - - id = reference_getid(data); - start = reference_getindex(data); - name = reference_getname(data); - if( not_array_variable(*name) ) - { - ShowError("script:setarray: illegal scope\n"); - script_reportdata(data); - st->state = END; - return 1;// not supported - } - - if( not_server_variable(*name) ) - { - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached - } - - end = start + script_lastdata(st) - 2; - if( end > SCRIPT_MAX_ARRAYSIZE ) - end = SCRIPT_MAX_ARRAYSIZE; - - if( is_string_variable(name) ) - {// string array - for( i = 3; start < end; ++start, ++i ) - set_reg(st, sd, reference_uid(id, start), name, (void*)script_getstr(st,i), reference_getref(data)); - } - else - {// int array - for( i = 3; start < end; ++start, ++i ) - set_reg(st, sd, reference_uid(id, start), name, (void*)__64BPRTSIZE(script_getnum(st,i)), reference_getref(data)); - } - return 0; -} - -/// Sets count values of an array, from the starting index. -/// ex: cleararray arr[0],0,1; -/// -/// cleararray <array variable>,<value>,<count>; -BUILDIN_FUNC(cleararray) -{ - struct script_data* data; - const char* name; - int32 start; - int32 end; - int32 id; - void* v; - TBL_PC* sd = NULL; - - data = script_getdata(st, 2); - if( !data_isreference(data) ) - { - ShowError("script:cleararray: not a variable\n"); - script_reportdata(data); - st->state = END; - return 1;// not a variable - } - - id = reference_getid(data); - start = reference_getindex(data); - name = reference_getname(data); - if( not_array_variable(*name) ) - { - ShowError("script:cleararray: illegal scope\n"); - script_reportdata(data); - st->state = END; - return 1;// not supported - } - - if( not_server_variable(*name) ) - { - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached - } - - if( is_string_variable(name) ) - v = (void*)script_getstr(st, 3); - else - v = (void*)__64BPRTSIZE(script_getnum(st, 3)); - - end = start + script_getnum(st, 4); - if( end > SCRIPT_MAX_ARRAYSIZE ) - end = SCRIPT_MAX_ARRAYSIZE; - - for( ; start < end; ++start ) - set_reg(st, sd, reference_uid(id, start), name, v, script_getref(st,2)); - return 0; -} - -/// Copies data from one array to another. -/// ex: copyarray arr[0],arr[2],2; -/// -/// copyarray <destination array variable>,<source array variable>,<count>; -BUILDIN_FUNC(copyarray) -{ - struct script_data* data1; - struct script_data* data2; - const char* name1; - const char* name2; - int32 idx1; - int32 idx2; - int32 id1; - int32 id2; - void* v; - int32 i; - int32 count; - TBL_PC* sd = NULL; - - data1 = script_getdata(st, 2); - data2 = script_getdata(st, 3); - if( !data_isreference(data1) || !data_isreference(data2) ) - { - ShowError("script:copyarray: not a variable\n"); - script_reportdata(data1); - script_reportdata(data2); - st->state = END; - return 1;// not a variable - } - - id1 = reference_getid(data1); - id2 = reference_getid(data2); - idx1 = reference_getindex(data1); - idx2 = reference_getindex(data2); - name1 = reference_getname(data1); - name2 = reference_getname(data2); - if( not_array_variable(*name1) || not_array_variable(*name2) ) - { - ShowError("script:copyarray: illegal scope\n"); - script_reportdata(data1); - script_reportdata(data2); - st->state = END; - return 1;// not supported - } - - if( is_string_variable(name1) != is_string_variable(name2) ) - { - ShowError("script:copyarray: type mismatch\n"); - script_reportdata(data1); - script_reportdata(data2); - st->state = END; - return 1;// data type mismatch - } - - if( not_server_variable(*name1) || not_server_variable(*name2) ) - { - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached - } - - count = script_getnum(st, 4); - if( count > SCRIPT_MAX_ARRAYSIZE - idx1 ) - count = SCRIPT_MAX_ARRAYSIZE - idx1; - if( count <= 0 || (id1 == id2 && idx1 == idx2) ) - return 0;// nothing to copy - - if( id1 == id2 && idx1 > idx2 ) - {// destination might be overlapping the source - copy in reverse order - for( i = count - 1; i >= 0; --i ) - { - v = get_val2(st, reference_uid(id2, idx2 + i), reference_getref(data2)); - set_reg(st, sd, reference_uid(id1, idx1 + i), name1, v, reference_getref(data1)); - script_removetop(st, -1, 0); - } - } - else - {// normal copy - for( i = 0; i < count; ++i ) - { - if( idx2 + i < SCRIPT_MAX_ARRAYSIZE ) - { - v = get_val2(st, reference_uid(id2, idx2 + i), reference_getref(data2)); - set_reg(st, sd, reference_uid(id1, idx1 + i), name1, v, reference_getref(data1)); - script_removetop(st, -1, 0); - } - else// out of range - assume ""/0 - set_reg(st, sd, reference_uid(id1, idx1 + i), name1, (is_string_variable(name1)?(void*)"":(void*)0), reference_getref(data1)); - } - } - return 0; -} - -/// Returns the size of the array. -/// Assumes that everything before the starting index exists. -/// ex: getarraysize(arr[3]) -/// -/// getarraysize(<array variable>) -> <int> -BUILDIN_FUNC(getarraysize) -{ - struct script_data* data; - const char* name; - - data = script_getdata(st, 2); - if( !data_isreference(data) ) - { - ShowError("script:getarraysize: not a variable\n"); - script_reportdata(data); - script_pushnil(st); - st->state = END; - return 1;// not a variable - } - - name = reference_getname(data); - if( not_array_variable(*name) ) - { - ShowError("script:getarraysize: illegal scope\n"); - script_reportdata(data); - script_pushnil(st); - st->state = END; - return 1;// not supported - } - - script_pushint(st, getarraysize(st, reference_getid(data), reference_getindex(data), is_string_variable(name), reference_getref(data))); - return 0; -} - -/// Deletes count or all the elements in an array, from the starting index. -/// ex: deletearray arr[4],2; -/// -/// deletearray <array variable>; -/// deletearray <array variable>,<count>; -BUILDIN_FUNC(deletearray) -{ - struct script_data* data; - const char* name; - int start; - int end; - int id; - TBL_PC *sd = NULL; - - data = script_getdata(st, 2); - if( !data_isreference(data) ) - { - ShowError("script:deletearray: not a variable\n"); - script_reportdata(data); - st->state = END; - return 1;// not a variable - } - - id = reference_getid(data); - start = reference_getindex(data); - name = reference_getname(data); - if( not_array_variable(*name) ) - { - ShowError("script:deletearray: illegal scope\n"); - script_reportdata(data); - st->state = END; - return 1;// not supported - } - - if( not_server_variable(*name) ) - { - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached - } - - end = SCRIPT_MAX_ARRAYSIZE; - - if( start >= end ) - return 0;// nothing to free - - if( script_hasdata(st,3) ) - { - int count = script_getnum(st, 3); - if( count > end - start ) - count = end - start; - if( count <= 0 ) - return 0;// nothing to free - - // move rest of the elements backward - for( ; start + count < end; ++start ) - { - void* v = get_val2(st, reference_uid(id, start + count), reference_getref(data)); - set_reg(st, sd, reference_uid(id, start), name, v, reference_getref(data)); - script_removetop(st, -1, 0); - } - } - - // clear the rest of the array - if( is_string_variable(name) ) - { - for( ; start < end; ++start ) - set_reg(st, sd, reference_uid(id, start), name, (void *)"", reference_getref(data)); - } - else - { - for( ; start < end; ++start ) - set_reg(st, sd, reference_uid(id, start), name, (void*)0, reference_getref(data)); - } - return 0; -} - -/// Returns a reference to the target index of the array variable. -/// Equivalent to var[index]. -/// -/// getelementofarray(<array variable>,<index>) -> <variable reference> -BUILDIN_FUNC(getelementofarray) -{ - struct script_data* data; - const char* name; - int32 id; - int i; - - data = script_getdata(st, 2); - if( !data_isreference(data) ) - { - ShowError("script:getelementofarray: not a variable\n"); - script_reportdata(data); - script_pushnil(st); - st->state = END; - return 1;// not a variable - } - - id = reference_getid(data); - name = reference_getname(data); - if( not_array_variable(*name) ) - { - ShowError("script:getelementofarray: illegal scope\n"); - script_reportdata(data); - script_pushnil(st); - st->state = END; - return 1;// not supported - } - - i = script_getnum(st, 3); - if( i < 0 || i >= SCRIPT_MAX_ARRAYSIZE ) - { - ShowWarning("script:getelementofarray: index out of range (%d)\n", i); - script_reportdata(data); - script_pushnil(st); - st->state = END; - return 1;// out of range - } - - push_val2(st->stack, C_NAME, reference_uid(id, i), reference_getref(data)); - return 0; -} - -///////////////////////////////////////////////////////////////////// -/// ... -/// - -/*========================================== - * - *------------------------------------------*/ -BUILDIN_FUNC(setlook) -{ - int type,val; - TBL_PC* sd; - - type=script_getnum(st,2); - val=script_getnum(st,3); - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - pc_changelook(sd,type,val); - - return 0; -} - -BUILDIN_FUNC(changelook) -{ // As setlook but only client side - int type,val; - TBL_PC* sd; - - type=script_getnum(st,2); - val=script_getnum(st,3); - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - clif_changelook(&sd->bl,type,val); - - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -BUILDIN_FUNC(cutin) -{ - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - clif_cutin(sd,script_getstr(st,2),script_getnum(st,3)); - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -BUILDIN_FUNC(viewpoint) -{ - int type,x,y,id,color; - TBL_PC* sd; - - type=script_getnum(st,2); - x=script_getnum(st,3); - y=script_getnum(st,4); - id=script_getnum(st,5); - color=script_getnum(st,6); - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - clif_viewpoint(sd,st->oid,type,x,y,id,color); - - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -BUILDIN_FUNC(countitem) -{ - int nameid, i; - int count = 0; - struct item_data* id = NULL; - struct script_data* data; - - TBL_PC* sd = script_rid2sd(st); - if (!sd) { - script_pushint(st,0); - return 0; - } - - data = script_getdata(st,2); - get_val(st, data); // convert into value in case of a variable - - if( data_isstring(data) ) - {// item name - id = itemdb_searchname(conv_str(st, data)); - } - else - {// item id - id = itemdb_exists(conv_num(st, data)); - } - - if( id == NULL ) - { - ShowError("buildin_countitem: Invalid item '%s'.\n", script_getstr(st,2)); // returns string, regardless of what it was - script_pushint(st,0); - return 1; - } - - nameid = id->nameid; - - for(i = 0; i < MAX_INVENTORY; i++) - if(sd->status.inventory[i].nameid == nameid) - count += sd->status.inventory[i].amount; - - script_pushint(st,count); - return 0; -} - -/*========================================== - * countitem2(nameID,Identified,Refine,Attribute,Card0,Card1,Card2,Card3) [Lupus] - * returns number of items that meet the conditions - *------------------------------------------*/ -BUILDIN_FUNC(countitem2) -{ - int nameid, iden, ref, attr, c1, c2, c3, c4; - int count = 0; - int i; - struct item_data* id = NULL; - struct script_data* data; - - TBL_PC* sd = script_rid2sd(st); - if (!sd) { - script_pushint(st,0); - return 0; - } - - data = script_getdata(st,2); - get_val(st, data); // convert into value in case of a variable - - if( data_isstring(data) ) - {// item name - id = itemdb_searchname(conv_str(st, data)); - } - else - {// item id - id = itemdb_exists(conv_num(st, data)); - } - - if( id == NULL ) - { - ShowError("buildin_countitem2: Invalid item '%s'.\n", script_getstr(st,2)); // returns string, regardless of what it was - script_pushint(st,0); - return 1; - } - - nameid = id->nameid; - iden = script_getnum(st,3); - ref = script_getnum(st,4); - attr = script_getnum(st,5); - c1 = (short)script_getnum(st,6); - c2 = (short)script_getnum(st,7); - c3 = (short)script_getnum(st,8); - c4 = (short)script_getnum(st,9); - - for(i = 0; i < MAX_INVENTORY; i++) - if (sd->status.inventory[i].nameid > 0 && sd->inventory_data[i] != NULL && - sd->status.inventory[i].amount > 0 && sd->status.inventory[i].nameid == nameid && - sd->status.inventory[i].identify == iden && sd->status.inventory[i].refine == ref && - sd->status.inventory[i].attribute == attr && sd->status.inventory[i].card[0] == c1 && - sd->status.inventory[i].card[1] == c2 && sd->status.inventory[i].card[2] == c3 && - sd->status.inventory[i].card[3] == c4 - ) - count += sd->status.inventory[i].amount; - - script_pushint(st,count); - return 0; -} - -/*========================================== - * Check if item with this amount can fit in inventory - * Checking : weight, stack amount >32k, slots amount >(MAX_INVENTORY) - * Return - * 0 : fail - * 1 : success (npc side only) - *------------------------------------------*/ -BUILDIN_FUNC(checkweight) -{ - int nameid, amount, slots, amount2=0; - unsigned int weight=0, i, nbargs; - struct item_data* id = NULL; - struct map_session_data* sd; - struct script_data* data; - - if( ( sd = script_rid2sd(st) ) == NULL ){ - return 0; - } - nbargs = script_lastdata(st)+1; - if(nbargs%2){ - ShowError("buildin_checkweight: Invalid nb of args should be a multiple of 2.\n"); - script_pushint(st,0); - return 1; - } - slots = pc_inventoryblank(sd); //nb of empty slot - - for(i=2; i<nbargs; i=i+2){ - data = script_getdata(st,i); - get_val(st, data); // convert into value in case of a variable - if( data_isstring(data) ){// item name - id = itemdb_searchname(conv_str(st, data)); - } else {// item id - id = itemdb_exists(conv_num(st, data)); - } - if( id == NULL ) { - ShowError("buildin_checkweight: Invalid item '%s'.\n", script_getstr(st,i)); // returns string, regardless of what it was - script_pushint(st,0); - return 1; - } - nameid = id->nameid; - - amount = script_getnum(st,i+1); - if( amount < 1 ) { - ShowError("buildin_checkweight: Invalid amount '%d'.\n", amount); - script_pushint(st,0); - return 1; - } - - weight += itemdb_weight(nameid)*amount; //total weight for all chk - if( weight + sd->weight > sd->max_weight ) - {// too heavy - script_pushint(st,0); - return 0; - } - - switch( pc_checkadditem(sd, nameid, amount) ) - { - case ADDITEM_EXIST: - // item is already in inventory, but there is still space for the requested amount - break; - case ADDITEM_NEW: - if( itemdb_isstackable(nameid) ) {// stackable - amount2++; - if( slots < amount2 ) { - script_pushint(st,0); - return 0; - } - } - else {// non-stackable - amount2 += amount; - if( slots < amount2){ - script_pushint(st,0); - return 0; - } - } - break; - case ADDITEM_OVERAMOUNT: - script_pushint(st,0); - return 0; - } - } - script_pushint(st,1); - return 0; -} - -BUILDIN_FUNC(checkweight2) -{ - //variable sub checkweight - int32 nameid=-1, amount=-1; - int i=0, amount2=0, slots=0, weight=0; - short fail=0; - - //variable for array parsing - struct script_data* data_it; - struct script_data* data_nb; - const char* name_it; - const char* name_nb; - int32 id_it, id_nb; - int32 idx_it, idx_nb; - int nb_it, nb_nb; //array size - - TBL_PC *sd = script_rid2sd(st); - nullpo_retr(1,sd); - - data_it = script_getdata(st, 2); - data_nb = script_getdata(st, 3); - - if( !data_isreference(data_it) || !data_isreference(data_nb)) - { - ShowError("script:checkweight2: parameter not a variable\n"); - script_pushint(st,0); - return 1;// not a variable - } - id_it = reference_getid(data_it); - id_nb = reference_getid(data_nb); - idx_it = reference_getindex(data_it); - idx_nb = reference_getindex(data_nb); - name_it = reference_getname(data_it); - name_nb = reference_getname(data_nb); - - if( not_array_variable(*name_it) || not_array_variable(*name_nb)) - { - ShowError("script:checkweight2: illegal scope\n"); - script_pushint(st,0); - return 1;// not supported - } - if(is_string_variable(name_it) || is_string_variable(name_nb)){ - ShowError("script:checkweight2: illegal type, need int\n"); - script_pushint(st,0); - return 1;// not supported - } - nb_it = getarraysize(st, id_it, idx_it, 0, reference_getref(data_it)); - nb_nb = getarraysize(st, id_nb, idx_nb, 0, reference_getref(data_nb)); - if(nb_it != nb_nb){ - ShowError("Size mistmatch: nb_it=%d, nb_nb=%d\n",nb_it,nb_nb); - fail = 1; - } - - slots = pc_inventoryblank(sd); - for(i=0; i<nb_it; i++){ - nameid = (int32)__64BPRTSIZE(get_val2(st,reference_uid(id_it,idx_it+i),reference_getref(data_it))); - script_removetop(st, -1, 0); - amount = (int32)__64BPRTSIZE(get_val2(st,reference_uid(id_nb,idx_nb+i),reference_getref(data_nb))); - script_removetop(st, -1, 0); - if(fail) continue; //cpntonie to depop rest - - if(itemdb_exists(nameid) == NULL ){ - ShowError("buildin_checkweight2: Invalid item '%d'.\n", nameid); - fail=1; - continue; - } - if(amount < 0 ){ - ShowError("buildin_checkweight2: Invalid amount '%d'.\n", amount); - fail = 1; - continue; - } - weight += itemdb_weight(nameid)*amount; - if( weight + sd->weight > sd->max_weight ){ - fail = 1; - continue; - } - switch( pc_checkadditem(sd, nameid, amount) ) { - case ADDITEM_EXIST: - // item is already in inventory, but there is still space for the requested amount - break; - case ADDITEM_NEW: - if( itemdb_isstackable(nameid) ){// stackable - amount2++; - if( slots < amount2 ) - fail = 1; - } - else {// non-stackable - amount2 += amount; - if( slots < amount2 ){ - fail = 1; - } - } - break; - case ADDITEM_OVERAMOUNT: - fail = 1; - } //end switch - } //end loop DO NOT break it prematurly we need to depop all stack - - fail?script_pushint(st,0):script_pushint(st,1); - return 0; -} - -/*========================================== - * getitem <item id>,<amount>{,<account ID>}; - * getitem "<item name>",<amount>{,<account ID>}; - *------------------------------------------*/ -BUILDIN_FUNC(getitem) -{ - int nameid,amount,get_count,i,flag = 0; - struct item it; - TBL_PC *sd; - struct script_data *data; - - data=script_getdata(st,2); - get_val(st,data); - if( data_isstring(data) ) - {// "<item name>" - const char *name=conv_str(st,data); - struct item_data *item_data = itemdb_searchname(name); - if( item_data == NULL ){ - ShowError("buildin_getitem: Nonexistant item %s requested.\n", name); - return 1; //No item created. - } - nameid=item_data->nameid; - } else if( data_isint(data) ) - {// <item id> - nameid=conv_num(st,data); - //Violet Box, Blue Box, etc - random item pick - if( nameid < 0 ) { - nameid = -nameid; - flag = 1; - } - if( nameid <= 0 || !itemdb_exists(nameid) ){ - ShowError("buildin_getitem: Nonexistant item %d requested.\n", nameid); - return 1; //No item created. - } - } else { - ShowError("buildin_getitem: invalid data type for argument #1 (%d).", data->type); - return 1; - } - - // <amount> - if( (amount=script_getnum(st,3)) <= 0) - return 0; //return if amount <=0, skip the useles iteration - - memset(&it,0,sizeof(it)); - it.nameid=nameid; - if(!flag) - it.identify=1; - else - it.identify=itemdb_isidentified(nameid); - - if( script_hasdata(st,4) ) - sd=map_id2sd(script_getnum(st,4)); // <Account ID> - else - sd=script_rid2sd(st); // Attached player - - if( sd == NULL ) // no target - return 0; - - //Check if it's stackable. - if (!itemdb_isstackable(nameid)) - get_count = 1; - else - get_count = amount; - - for (i = 0; i < amount; i += get_count) - { - // if not pet egg - if (!pet_create_egg(sd, nameid)) - { - if ((flag = pc_additem(sd, &it, get_count, LOG_TYPE_SCRIPT))) - { - clif_additem(sd, 0, 0, flag); - if( pc_candrop(sd,&it) ) - map_addflooritem(&it,get_count,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); - } - } - } - - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -BUILDIN_FUNC(getitem2) -{ - int nameid,amount,get_count,i,flag = 0; - int iden,ref,attr,c1,c2,c3,c4; - struct item_data *item_data; - struct item item_tmp; - TBL_PC *sd; - struct script_data *data; - - if( script_hasdata(st,11) ) - sd=map_id2sd(script_getnum(st,11)); // <Account ID> - else - sd=script_rid2sd(st); // Attached player - - if( sd == NULL ) // no target - return 0; - - data=script_getdata(st,2); - get_val(st,data); - if( data_isstring(data) ){ - const char *name=conv_str(st,data); - struct item_data *item_data = itemdb_searchname(name); - if( item_data ) - nameid=item_data->nameid; - else - nameid=UNKNOWN_ITEM_ID; - }else - nameid=conv_num(st,data); - - amount=script_getnum(st,3); - iden=script_getnum(st,4); - ref=script_getnum(st,5); - attr=script_getnum(st,6); - c1=(short)script_getnum(st,7); - c2=(short)script_getnum(st,8); - c3=(short)script_getnum(st,9); - c4=(short)script_getnum(st,10); - - if(nameid<0) { // Invalide nameid - nameid = -nameid; - flag = 1; - } - - if(nameid > 0) { - memset(&item_tmp,0,sizeof(item_tmp)); - item_data=itemdb_exists(nameid); - if (item_data == NULL) - return -1; - if(item_data->type==IT_WEAPON || item_data->type==IT_ARMOR){ - if(ref > MAX_REFINE) ref = MAX_REFINE; - } - else if(item_data->type==IT_PETEGG) { - 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==IT_WEAPON || item_data->type==IT_ARMOR) - item_tmp.identify=0; - item_tmp.refine=ref; - item_tmp.attribute=attr; - item_tmp.card[0]=(short)c1; - item_tmp.card[1]=(short)c2; - item_tmp.card[2]=(short)c3; - item_tmp.card[3]=(short)c4; - - //Check if it's stackable. - if (!itemdb_isstackable(nameid)) - get_count = 1; - else - get_count = amount; - - for (i = 0; i < amount; i += get_count) - { - // if not pet egg - if (!pet_create_egg(sd, nameid)) - { - if ((flag = pc_additem(sd, &item_tmp, get_count, LOG_TYPE_SCRIPT))) - { - clif_additem(sd, 0, 0, flag); - if( pc_candrop(sd,&item_tmp) ) - map_addflooritem(&item_tmp,get_count,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); - } - } - } - } - - return 0; -} - -/*========================================== - * rentitem <item id>,<seconds> - * rentitem "<item name>",<seconds> - *------------------------------------------*/ -BUILDIN_FUNC(rentitem) -{ - struct map_session_data *sd; - struct script_data *data; - struct item it; - int seconds; - int nameid = 0, flag; - - data = script_getdata(st,2); - get_val(st,data); - - if( (sd = script_rid2sd(st)) == NULL ) - return 0; - - if( data_isstring(data) ) - { - const char *name = conv_str(st,data); - struct item_data *itd = itemdb_searchname(name); - if( itd == NULL ) - { - ShowError("buildin_rentitem: Nonexistant item %s requested.\n", name); - return 1; - } - nameid = itd->nameid; - } - else if( data_isint(data) ) - { - nameid = conv_num(st,data); - if( nameid <= 0 || !itemdb_exists(nameid) ) - { - ShowError("buildin_rentitem: Nonexistant item %d requested.\n", nameid); - return 1; - } - } - else - { - ShowError("buildin_rentitem: invalid data type for argument #1 (%d).\n", data->type); - return 1; - } - - seconds = script_getnum(st,3); - memset(&it, 0, sizeof(it)); - it.nameid = nameid; - it.identify = 1; - it.expire_time = (unsigned int)(time(NULL) + seconds); - - if( (flag = pc_additem(sd, &it, 1, LOG_TYPE_SCRIPT)) ) - { - clif_additem(sd, 0, 0, flag); - return 1; - } - - return 0; -} - -/*========================================== - * gets an item with someone's name inscribed [Skotlex] - * getinscribeditem item_num, character_name - * Returned Qty is always 1, only works on equip-able - * equipment - *------------------------------------------*/ -BUILDIN_FUNC(getnameditem) -{ - int nameid; - struct item item_tmp; - TBL_PC *sd, *tsd; - struct script_data *data; - - sd = script_rid2sd(st); - if (sd == NULL) - { //Player not attached! - script_pushint(st,0); - return 0; - } - - data=script_getdata(st,2); - get_val(st,data); - if( data_isstring(data) ){ - const char *name=conv_str(st,data); - struct item_data *item_data = itemdb_searchname(name); - if( item_data == NULL) - { //Failed - script_pushint(st,0); - return 0; - } - nameid = item_data->nameid; - }else - nameid = conv_num(st,data); - - if(!itemdb_exists(nameid)/* || itemdb_isstackable(nameid)*/) - { //Even though named stackable items "could" be risky, they are required for certain quests. - script_pushint(st,0); - return 0; - } - - data=script_getdata(st,3); - get_val(st,data); - if( data_isstring(data) ) //Char Name - tsd=map_nick2sd(conv_str(st,data)); - else //Char Id was given - tsd=map_charid2sd(conv_num(st,data)); - - if( tsd == NULL ) - { //Failed - script_pushint(st,0); - return 0; - } - - memset(&item_tmp,0,sizeof(item_tmp)); - item_tmp.nameid=nameid; - item_tmp.amount=1; - item_tmp.identify=1; - item_tmp.card[0]=CARD0_CREATE; //we don't use 255! because for example SIGNED WEAPON shouldn't get TOP10 BS Fame bonus [Lupus] - item_tmp.card[2]=tsd->status.char_id; - item_tmp.card[3]=tsd->status.char_id >> 16; - if(pc_additem(sd,&item_tmp,1,LOG_TYPE_SCRIPT)) { - script_pushint(st,0); - return 0; //Failed to add item, we will not drop if they don't fit - } - - script_pushint(st,1); - return 0; -} - -/*========================================== - * gets a random item ID from an item group [Skotlex] - * groupranditem group_num - *------------------------------------------*/ -BUILDIN_FUNC(grouprandomitem) -{ - int group; - - group = script_getnum(st,2); - script_pushint(st,itemdb_searchrandomid(group)); - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -BUILDIN_FUNC(makeitem) -{ - int nameid,amount,flag = 0; - int x,y,m; - const char *mapname; - struct item item_tmp; - struct script_data *data; - - data=script_getdata(st,2); - get_val(st,data); - if( data_isstring(data) ){ - const char *name=conv_str(st,data); - struct item_data *item_data = itemdb_searchname(name); - if( item_data ) - nameid=item_data->nameid; - else - nameid=UNKNOWN_ITEM_ID; - }else - nameid=conv_num(st,data); - - amount=script_getnum(st,3); - mapname =script_getstr(st,4); - x =script_getnum(st,5); - y =script_getnum(st,6); - - if(strcmp(mapname,"this")==0) - { - TBL_PC *sd; - sd = script_rid2sd(st); - if (!sd) return 0; //Failed... - m=sd->bl.m; - } else - m=map_mapname2mapid(mapname); - - if(nameid<0) { - nameid = -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_isidentified(nameid); - - map_addflooritem(&item_tmp,amount,m,x,y,0,0,0,0); - } - - return 0; -} - - -/// Counts / deletes the current item given by idx. -/// Used by buildin_delitem_search -/// Relies on all input data being already fully valid. -static void buildin_delitem_delete(struct map_session_data* sd, int idx, int* amount, bool delete_items) -{ - int delamount; - struct item* inv = &sd->status.inventory[idx]; - - delamount = ( amount[0] < inv->amount ) ? amount[0] : inv->amount; - - if( delete_items ) - { - if( sd->inventory_data[idx]->type == IT_PETEGG && inv->card[0] == CARD0_PET ) - {// delete associated pet - intif_delete_petdata(MakeDWord(inv->card[1], inv->card[2])); - } - pc_delitem(sd, idx, delamount, 0, 0, LOG_TYPE_SCRIPT); - } - - amount[0]-= delamount; -} - - -/// Searches for item(s) and checks, if there is enough of them. -/// Used by delitem and delitem2 -/// Relies on all input data being already fully valid. -/// @param exact_match will also match item attributes and cards, not just name id -/// @return true when all items could be deleted, false when there were not enough items to delete -static bool buildin_delitem_search(struct map_session_data* sd, struct item* it, bool exact_match) -{ - bool delete_items = false; - int i, amount, important; - struct item* inv; - - // prefer always non-equipped items - it->equip = 0; - - // when searching for nameid only, prefer additionally - if( !exact_match ) - { - // non-refined items - it->refine = 0; - // card-less items - memset(it->card, 0, sizeof(it->card)); - } - - for(;;) - { - amount = it->amount; - important = 0; - - // 1st pass -- less important items / exact match - for( i = 0; amount && i < ARRAYLENGTH(sd->status.inventory); i++ ) - { - inv = &sd->status.inventory[i]; - - if( !inv->nameid || !sd->inventory_data[i] || inv->nameid != it->nameid ) - {// wrong/invalid item - continue; - } - - if( inv->equip != it->equip || inv->refine != it->refine ) - {// not matching attributes - important++; - continue; - } - - if( exact_match ) - { - if( inv->identify != it->identify || inv->attribute != it->attribute || memcmp(inv->card, it->card, sizeof(inv->card)) ) - {// not matching exact attributes - continue; - } - } - else - { - if( sd->inventory_data[i]->type == IT_PETEGG ) - { - if( inv->card[0] == CARD0_PET && CheckForCharServer() ) - {// pet which cannot be deleted - continue; - } - } - else if( memcmp(inv->card, it->card, sizeof(inv->card)) ) - {// named/carded item - important++; - continue; - } - } - - // count / delete item - buildin_delitem_delete(sd, i, &amount, delete_items); - } - - // 2nd pass -- any matching item - if( amount == 0 || important == 0 ) - {// either everything was already consumed or no items were skipped - ; - } - else for( i = 0; amount && i < ARRAYLENGTH(sd->status.inventory); i++ ) - { - inv = &sd->status.inventory[i]; - - if( !inv->nameid || !sd->inventory_data[i] || inv->nameid != it->nameid ) - {// wrong/invalid item - continue; - } - - if( sd->inventory_data[i]->type == IT_PETEGG && inv->card[0] == CARD0_PET && CheckForCharServer() ) - {// pet which cannot be deleted - continue; - } - - if( exact_match ) - { - if( inv->refine != it->refine || inv->identify != it->identify || inv->attribute != it->attribute || memcmp(inv->card, it->card, sizeof(inv->card)) ) - {// not matching attributes - continue; - } - } - - // count / delete item - buildin_delitem_delete(sd, i, &amount, delete_items); - } - - if( amount ) - {// not enough items - return false; - } - else if( delete_items ) - {// we are done with the work - return true; - } - else - {// get rid of the items now - delete_items = true; - } - } -} - - -/// Deletes items from the target/attached player. -/// Prioritizes ordinary items. -/// -/// delitem <item id>,<amount>{,<account id>} -/// delitem "<item name>",<amount>{,<account id>} -BUILDIN_FUNC(delitem) -{ - TBL_PC *sd; - struct item it; - struct script_data *data; - - if( script_hasdata(st,4) ) - { - int account_id = script_getnum(st,4); - sd = map_id2sd(account_id); // <account id> - if( sd == NULL ) - { - ShowError("script:delitem: player not found (AID=%d).\n", account_id); - st->state = END; - return 1; - } - } - else - { - sd = script_rid2sd(st);// attached player - if( sd == NULL ) - return 0; - } - - data = script_getdata(st,2); - get_val(st,data); - if( data_isstring(data) ) - { - const char* item_name = conv_str(st,data); - struct item_data* id = itemdb_searchname(item_name); - if( id == NULL ) - { - ShowError("script:delitem: unknown item \"%s\".\n", item_name); - st->state = END; - return 1; - } - it.nameid = id->nameid;// "<item name>" - } - else - { - it.nameid = conv_num(st,data);// <item id> - if( !itemdb_exists( it.nameid ) ) - { - ShowError("script:delitem: unknown item \"%d\".\n", it.nameid); - st->state = END; - return 1; - } - } - - it.amount=script_getnum(st,3); - - if( it.amount <= 0 ) - return 0;// nothing to do - - if( buildin_delitem_search(sd, &it, false) ) - {// success - return 0; - } - - ShowError("script:delitem: failed to delete %d items (AID=%d item_id=%d).\n", it.amount, sd->status.account_id, it.nameid); - st->state = END; - clif_scriptclose(sd, st->oid); - return 1; -} - -/// Deletes items from the target/attached player. -/// -/// delitem2 <item id>,<amount>,<identify>,<refine>,<attribute>,<card1>,<card2>,<card3>,<card4>{,<account ID>} -/// delitem2 "<Item name>",<amount>,<identify>,<refine>,<attribute>,<card1>,<card2>,<card3>,<card4>{,<account ID>} -BUILDIN_FUNC(delitem2) -{ - TBL_PC *sd; - struct item it; - struct script_data *data; - - if( script_hasdata(st,11) ) - { - int account_id = script_getnum(st,11); - sd = map_id2sd(account_id); // <account id> - if( sd == NULL ) - { - ShowError("script:delitem2: player not found (AID=%d).\n", account_id); - st->state = END; - return 1; - } - } - else - { - sd = script_rid2sd(st);// attached player - if( sd == NULL ) - return 0; - } - - data = script_getdata(st,2); - get_val(st,data); - if( data_isstring(data) ) - { - const char* item_name = conv_str(st,data); - struct item_data* id = itemdb_searchname(item_name); - if( id == NULL ) - { - ShowError("script:delitem2: unknown item \"%s\".\n", item_name); - st->state = END; - return 1; - } - it.nameid = id->nameid;// "<item name>" - } - else - { - it.nameid = conv_num(st,data);// <item id> - if( !itemdb_exists( it.nameid ) ) - { - ShowError("script:delitem: unknown item \"%d\".\n", it.nameid); - st->state = END; - return 1; - } - } - - it.amount=script_getnum(st,3); - it.identify=script_getnum(st,4); - it.refine=script_getnum(st,5); - it.attribute=script_getnum(st,6); - it.card[0]=(short)script_getnum(st,7); - it.card[1]=(short)script_getnum(st,8); - it.card[2]=(short)script_getnum(st,9); - it.card[3]=(short)script_getnum(st,10); - - if( it.amount <= 0 ) - return 0;// nothing to do - - if( buildin_delitem_search(sd, &it, true) ) - {// success - return 0; - } - - ShowError("script:delitem2: failed to delete %d items (AID=%d item_id=%d).\n", it.amount, sd->status.account_id, it.nameid); - st->state = END; - clif_scriptclose(sd, st->oid); - return 1; -} - -/*========================================== - * Enables/Disables use of items while in an NPC [Skotlex] - *------------------------------------------*/ -BUILDIN_FUNC(enableitemuse) -{ - TBL_PC *sd; - sd=script_rid2sd(st); - if (sd) - sd->npc_item_flag = st->oid; - return 0; -} - -BUILDIN_FUNC(disableitemuse) -{ - TBL_PC *sd; - sd=script_rid2sd(st); - if (sd) - sd->npc_item_flag = 0; - return 0; -} - -/*========================================== - * return the basic stats of sd - * chk pc_readparam for available type - *------------------------------------------*/ -BUILDIN_FUNC(readparam) -{ - int type; - TBL_PC *sd; - - type=script_getnum(st,2); - if( script_hasdata(st,3) ) - sd=map_nick2sd(script_getstr(st,3)); - else - sd=script_rid2sd(st); - - if(sd==NULL){ - script_pushint(st,-1); - return 0; - } - - script_pushint(st,pc_readparam(sd,type)); - - return 0; -} - -/*========================================== - * Return charid identification - * return by @num : - * 0 : char_id - * 1 : party_id - * 2 : guild_id - * 3 : account_id - * 4 : bg_id - *------------------------------------------*/ -BUILDIN_FUNC(getcharid) -{ - int num; - TBL_PC *sd; - - num = script_getnum(st,2); - if( script_hasdata(st,3) ) - sd=map_nick2sd(script_getstr(st,3)); - else - sd=script_rid2sd(st); - - if(sd==NULL){ - script_pushint(st,0); //return 0, according docs - return 0; - } - - switch( num ) { - case 0: script_pushint(st,sd->status.char_id); break; - case 1: script_pushint(st,sd->status.party_id); break; - case 2: script_pushint(st,sd->status.guild_id); break; - case 3: script_pushint(st,sd->status.account_id); break; - case 4: script_pushint(st,sd->bg_id); break; - default: - ShowError("buildin_getcharid: invalid parameter (%d).\n", num); - script_pushint(st,0); - break; - } - - return 0; -} -/*========================================== - * returns the GID of an NPC - *------------------------------------------*/ -BUILDIN_FUNC(getnpcid) -{ - int num = script_getnum(st,2); - struct npc_data* nd = NULL; - - if( script_hasdata(st,3) ) - {// unique npc name - if( ( nd = npc_name2id(script_getstr(st,3)) ) == NULL ) - { - ShowError("buildin_getnpcid: No such NPC '%s'.\n", script_getstr(st,3)); - script_pushint(st,0); - return 1; - } - } - - switch (num) { - case 0: - script_pushint(st,nd ? nd->bl.id : st->oid); - break; - default: - ShowError("buildin_getnpcid: invalid parameter (%d).\n", num); - script_pushint(st,0); - return 1; - } - - return 0; -} - -/*========================================== - * Return the name of the party_id - * null if not found - *------------------------------------------*/ -BUILDIN_FUNC(getpartyname) -{ - int party_id; - struct party_data* p; - - party_id = script_getnum(st,2); - - if( ( p = party_search(party_id) ) != NULL ) - { - script_pushstrcopy(st,p->party.name); - } - else - { - script_pushconststr(st,"null"); - } - return 0; -} - -/*========================================== - * Get the information of the members of a party by type - * @party_id, @type - * return by @type : - * - : nom des membres - * 1 : char_id des membres - * 2 : account_id des membres - *------------------------------------------*/ -BUILDIN_FUNC(getpartymember) -{ - struct party_data *p; - int i,j=0,type=0; - - p=party_search(script_getnum(st,2)); - - if( script_hasdata(st,3) ) - type=script_getnum(st,3); - - if(p!=NULL){ - for(i=0;i<MAX_PARTY;i++){ - if(p->party.member[i].account_id){ - switch (type) { - case 2: - mapreg_setreg(reference_uid(add_str("$@partymemberaid"), j),p->party.member[i].account_id); - break; - case 1: - mapreg_setreg(reference_uid(add_str("$@partymembercid"), j),p->party.member[i].char_id); - break; - default: - mapreg_setregstr(reference_uid(add_str("$@partymembername$"), j),p->party.member[i].name); - } - j++; - } - } - } - mapreg_setreg(add_str("$@partymembercount"),j); - - return 0; -} - -/*========================================== - * Retrieves party leader. if flag is specified, - * return some of the leader data. Otherwise, return name. - *------------------------------------------*/ -BUILDIN_FUNC(getpartyleader) -{ - int party_id, type = 0, i=0; - struct party_data *p; - - party_id=script_getnum(st,2); - if( script_hasdata(st,3) ) - type=script_getnum(st,3); - - p=party_search(party_id); - - if (p) //Search leader - for(i = 0; i < MAX_PARTY && !p->party.member[i].leader; i++); - - if (!p || i == MAX_PARTY) { //leader not found - if (type) - script_pushint(st,-1); - else - script_pushconststr(st,"null"); - return 0; - } - - switch (type) { - case 1: script_pushint(st,p->party.member[i].account_id); break; - case 2: script_pushint(st,p->party.member[i].char_id); break; - case 3: script_pushint(st,p->party.member[i].class_); break; - case 4: script_pushstrcopy(st,mapindex_id2name(p->party.member[i].map)); break; - case 5: script_pushint(st,p->party.member[i].lv); break; - default: script_pushstrcopy(st,p->party.member[i].name); break; - } - return 0; -} - -/*========================================== - * Return the name of the @guild_id - * null if not found - *------------------------------------------*/ -BUILDIN_FUNC(getguildname) -{ - int guild_id; - struct guild* g; - - guild_id = script_getnum(st,2); - - if( ( g = guild_search(guild_id) ) != NULL ) - { - script_pushstrcopy(st,g->name); - } - else - { - script_pushconststr(st,"null"); - } - return 0; -} - -/*========================================== - * Return the name of the guild master of @guild_id - * null if not found - *------------------------------------------*/ -BUILDIN_FUNC(getguildmaster) -{ - int guild_id; - struct guild* g; - - guild_id = script_getnum(st,2); - - if( ( g = guild_search(guild_id) ) != NULL ) - { - script_pushstrcopy(st,g->member[0].name); - } - else - { - script_pushconststr(st,"null"); - } - return 0; -} - -BUILDIN_FUNC(getguildmasterid) -{ - int guild_id; - struct guild* g; - - guild_id = script_getnum(st,2); - - if( ( g = guild_search(guild_id) ) != NULL ) - { - script_pushint(st,g->member[0].char_id); - } - else - { - script_pushint(st,0); - } - return 0; -} - -/*========================================== - * Get char string information by type : - * Return by @type : - * 0 : char_name - * 1 : party_name or "" - * 2 : guild_name or "" - * 3 : map_name - * - : "" - *------------------------------------------*/ -BUILDIN_FUNC(strcharinfo) -{ - TBL_PC *sd; - int num; - struct guild* g; - struct party_data* p; - - sd=script_rid2sd(st); - if (!sd) { //Avoid crashing.... - script_pushconststr(st,""); - return 0; - } - num=script_getnum(st,2); - switch(num){ - case 0: - script_pushstrcopy(st,sd->status.name); - break; - case 1: - if( ( p = party_search(sd->status.party_id) ) != NULL ) - { - script_pushstrcopy(st,p->party.name); - } - else - { - script_pushconststr(st,""); - } - break; - case 2: - if( ( g = guild_search(sd->status.guild_id) ) != NULL ) - { - script_pushstrcopy(st,g->name); - } - else - { - script_pushconststr(st,""); - } - break; - case 3: - script_pushconststr(st,map[sd->bl.m].name); - break; - default: - ShowWarning("buildin_strcharinfo: unknown parameter.\n"); - script_pushconststr(st,""); - break; - } - - return 0; -} - -/*========================================== - * Get npc string information by type - * return by @type: - * 0 : name - * 1 : str# - * 2 : #str - * 3 : ::str - * 4 : map name - *------------------------------------------*/ -BUILDIN_FUNC(strnpcinfo) -{ - TBL_NPC* nd; - int num; - char *buf,*name=NULL; - - nd = map_id2nd(st->oid); - if (!nd) { - script_pushconststr(st, ""); - return 0; - } - - num = script_getnum(st,2); - switch(num){ - case 0: // display name - name = aStrdup(nd->name); - break; - case 1: // visible part of display name - if((buf = strchr(nd->name,'#')) != NULL) - { - name = aStrdup(nd->name); - name[buf - nd->name] = 0; - } else // Return the name, there is no '#' present - name = aStrdup(nd->name); - break; - case 2: // # fragment - if((buf = strchr(nd->name,'#')) != NULL) - name = aStrdup(buf+1); - break; - case 3: // unique name - name = aStrdup(nd->exname); - break; - case 4: // map name - name = aStrdup(map[nd->bl.m].name); - break; - } - - if(name) - script_pushstr(st, name); - else - script_pushconststr(st, ""); - - return 0; -} - - -// aegis->athena slot position conversion table -static unsigned int equip[] = {EQP_HEAD_TOP,EQP_ARMOR,EQP_HAND_L,EQP_HAND_R,EQP_GARMENT,EQP_SHOES,EQP_ACC_L,EQP_ACC_R,EQP_HEAD_MID,EQP_HEAD_LOW}; - -/*========================================== - * GetEquipID(Pos); Pos: 1-10 - *------------------------------------------*/ -BUILDIN_FUNC(getequipid) -{ - int i, num; - TBL_PC* sd; - struct item_data* item; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - num = script_getnum(st,2) - 1; - if( num < 0 || num >= ARRAYLENGTH(equip) ) - { - script_pushint(st,-1); - return 0; - } - - // get inventory position of item - i = pc_checkequip(sd,equip[num]); - if( i < 0 ) - { - script_pushint(st,-1); - return 0; - } - - item = sd->inventory_data[i]; - if( item != 0 ) - script_pushint(st,item->nameid); - else - script_pushint(st,0); - - return 0; -} - -/*========================================== - * Get the equipement name at pos - * return item jname or "" - *------------------------------------------*/ -BUILDIN_FUNC(getequipname) -{ - int i, num; - TBL_PC* sd; - struct item_data* item; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - num = script_getnum(st,2) - 1; - if( num < 0 || num >= ARRAYLENGTH(equip) ) - { - script_pushconststr(st,""); - return 0; - } - - // get inventory position of item - i = pc_checkequip(sd,equip[num]); - if( i < 0 ) - { - script_pushint(st,-1); - return 0; - } - - item = sd->inventory_data[i]; - if( item != 0 ) - script_pushstrcopy(st,item->jname); - else - script_pushconststr(st,""); - - return 0; -} - -/*========================================== - * getbrokenid [Valaris] - *------------------------------------------*/ -BUILDIN_FUNC(getbrokenid) -{ - int i,num,id=0,brokencounter=0; - TBL_PC *sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - num=script_getnum(st,2); - for(i=0; i<MAX_INVENTORY; i++) { - if(sd->status.inventory[i].attribute){ - brokencounter++; - if(num==brokencounter){ - id=sd->status.inventory[i].nameid; - break; - } - } - } - - script_pushint(st,id); - - return 0; -} - -/*========================================== - * repair [Valaris] - *------------------------------------------*/ -BUILDIN_FUNC(repair) -{ - int i,num; - int repaircounter=0; - TBL_PC *sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - num=script_getnum(st,2); - for(i=0; i<MAX_INVENTORY; i++) { - if(sd->status.inventory[i].attribute){ - repaircounter++; - if(num==repaircounter){ - sd->status.inventory[i].attribute=0; - clif_equiplist(sd); - clif_produceeffect(sd, 0, sd->status.inventory[i].nameid); - clif_misceffect(&sd->bl, 3); - break; - } - } - } - - return 0; -} - -/*========================================== - * repairall - *------------------------------------------*/ -BUILDIN_FUNC(repairall) -{ - int i, repaircounter = 0; - TBL_PC *sd; - - sd = script_rid2sd(st); - if(sd == NULL) - return 0; - - for(i = 0; i < MAX_INVENTORY; i++) - { - if(sd->status.inventory[i].nameid && sd->status.inventory[i].attribute) - { - sd->status.inventory[i].attribute = 0; - clif_produceeffect(sd,0,sd->status.inventory[i].nameid); - repaircounter++; - } - } - - if(repaircounter) - { - clif_misceffect(&sd->bl, 3); - clif_equiplist(sd); - } - - return 0; -} - -/*========================================== - * Chk if player have something equiped at pos - *------------------------------------------*/ -BUILDIN_FUNC(getequipisequiped) -{ - int i = -1,num; - TBL_PC *sd; - - num = script_getnum(st,2); - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - if (num > 0 && num <= ARRAYLENGTH(equip)) - i=pc_checkequip(sd,equip[num-1]); - - if(i >= 0) - script_pushint(st,1); - else - script_pushint(st,0); - return 0; -} - -/*========================================== - * Chk if the player have something equiped at pos - * if so chk if this item ain't marked not refinable or rental - * return (npc) - * 1 : true - * 0 : false - *------------------------------------------*/ -BUILDIN_FUNC(getequipisenableref) -{ - int i = -1,num; - TBL_PC *sd; - - num = script_getnum(st,2); - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - if( num > 0 && num <= ARRAYLENGTH(equip) ) - i = pc_checkequip(sd,equip[num-1]); - if( i >= 0 && sd->inventory_data[i] && !sd->inventory_data[i]->flag.no_refine && !sd->status.inventory[i].expire_time ) - script_pushint(st,1); - else - script_pushint(st,0); - - return 0; -} - -/*========================================== - * Chk if the item equiped at pos is identify (huh ?) - * return (npc) - * 1 : true - * 0 : false - *------------------------------------------*/ -BUILDIN_FUNC(getequipisidentify) -{ - int i = -1,num; - TBL_PC *sd; - - num = script_getnum(st,2); - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - if (num > 0 && num <= ARRAYLENGTH(equip)) - i=pc_checkequip(sd,equip[num-1]); - if(i >= 0) - script_pushint(st,sd->status.inventory[i].identify); - else - script_pushint(st,0); - - return 0; -} - -/*========================================== - * Get the item refined value at pos - * return (npc) - * x : refine amount - * 0 : false (not refined) - *------------------------------------------*/ -BUILDIN_FUNC(getequiprefinerycnt) -{ - int i = -1,num; - TBL_PC *sd; - - num = script_getnum(st,2); - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - if (num > 0 && num <= ARRAYLENGTH(equip)) - i=pc_checkequip(sd,equip[num-1]); - if(i >= 0) - script_pushint(st,sd->status.inventory[i].refine); - else - script_pushint(st,0); - - return 0; -} - -/*========================================== - * Get the weapon level value at pos - * (pos should normally only be EQI_HAND_L or EQI_HAND_R) - * return (npc) - * x : weapon level - * 0 : false - *------------------------------------------*/ -BUILDIN_FUNC(getequipweaponlv) -{ - int i = -1,num; - TBL_PC *sd; - - num = script_getnum(st,2); - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - if (num > 0 && num <= ARRAYLENGTH(equip)) - i=pc_checkequip(sd,equip[num-1]); - if(i >= 0 && sd->inventory_data[i]) - script_pushint(st,sd->inventory_data[i]->wlv); - else - script_pushint(st,0); - - return 0; -} - -/*========================================== - * Get the item refine chance (from refine.txt) for item at pos - * return (npc) - * x : refine chance - * 0 : false (max refine level or unequip..) - *------------------------------------------*/ -BUILDIN_FUNC(getequippercentrefinery) -{ - int i = -1,num; - TBL_PC *sd; - - num = script_getnum(st,2); - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - if (num > 0 && num <= ARRAYLENGTH(equip)) - i=pc_checkequip(sd,equip[num-1]); - if(i >= 0 && sd->status.inventory[i].nameid && sd->status.inventory[i].refine < MAX_REFINE) - script_pushint(st,status_get_refine_chance(itemdb_wlv(sd->status.inventory[i].nameid), (int)sd->status.inventory[i].refine)); - else - script_pushint(st,0); - - return 0; -} - -/*========================================== - * Refine +1 item at pos and log and display refine - *------------------------------------------*/ -BUILDIN_FUNC(successrefitem) -{ - int i=-1,num,ep; - TBL_PC *sd; - - num = script_getnum(st,2); - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - if (num > 0 && num <= ARRAYLENGTH(equip)) - i=pc_checkequip(sd,equip[num-1]); - if(i >= 0) { - ep=sd->status.inventory[i].equip; - - //Logs items, got from (N)PC scripts [Lupus] - log_pick_pc(sd, LOG_TYPE_SCRIPT, -1, &sd->status.inventory[i]); - - sd->status.inventory[i].refine++; - pc_unequipitem(sd,i,2); // status calc will happen in pc_equipitem() below - - clif_refine(sd->fd,0,i,sd->status.inventory[i].refine); - clif_delitem(sd,i,1,3); - - //Logs items, got from (N)PC scripts [Lupus] - log_pick_pc(sd, LOG_TYPE_SCRIPT, 1, &sd->status.inventory[i]); - - clif_additem(sd,i,1,0); - pc_equipitem(sd,i,ep); - clif_misceffect(&sd->bl,3); - if(sd->status.inventory[i].refine == MAX_REFINE && - sd->status.inventory[i].card[0] == CARD0_FORGE && - sd->status.char_id == (int)MakeDWord(sd->status.inventory[i].card[2],sd->status.inventory[i].card[3]) - ){ // Fame point system [DracoRPG] - switch (sd->inventory_data[i]->wlv){ - case 1: - pc_addfame(sd,1); // Success to refine to +10 a lv1 weapon you forged = +1 fame point - break; - case 2: - pc_addfame(sd,25); // Success to refine to +10 a lv2 weapon you forged = +25 fame point - break; - case 3: - pc_addfame(sd,1000); // Success to refine to +10 a lv3 weapon you forged = +1000 fame point - break; - } - } - } - - return 0; -} - -/*========================================== - * Show a failed Refine +1 attempt - *------------------------------------------*/ -BUILDIN_FUNC(failedrefitem) -{ - int i=-1,num; - TBL_PC *sd; - - num = script_getnum(st,2); - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - if (num > 0 && num <= ARRAYLENGTH(equip)) - i=pc_checkequip(sd,equip[num-1]); - if(i >= 0) { - sd->status.inventory[i].refine = 0; - pc_unequipitem(sd,i,3); //recalculate bonus - clif_refine(sd->fd,1,i,sd->status.inventory[i].refine); //notify client of failure - - pc_delitem(sd,i,1,0,2,LOG_TYPE_SCRIPT); - - clif_misceffect(&sd->bl,2); // display failure effect - } - - return 0; -} - -/*========================================== - * Downgrades an Equipment Part by -1 . [Masao] - *------------------------------------------*/ -BUILDIN_FUNC(downrefitem) -{ - int i = -1,num,ep; - TBL_PC *sd; - - num = script_getnum(st,2); - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - if (num > 0 && num <= ARRAYLENGTH(equip)) - i = pc_checkequip(sd,equip[num-1]); - if(i >= 0) { - ep = sd->status.inventory[i].equip; - - //Logs items, got from (N)PC scripts [Lupus] - log_pick_pc(sd, LOG_TYPE_SCRIPT, -1, &sd->status.inventory[i]); - - sd->status.inventory[i].refine++; - pc_unequipitem(sd,i,2); // status calc will happen in pc_equipitem() below - - clif_refine(sd->fd,2,i,sd->status.inventory[i].refine = sd->status.inventory[i].refine - 2); - clif_delitem(sd,i,1,3); - - //Logs items, got from (N)PC scripts [Lupus] - log_pick_pc(sd, LOG_TYPE_SCRIPT, 1, &sd->status.inventory[i]); - - clif_additem(sd,i,1,0); - pc_equipitem(sd,i,ep); - clif_misceffect(&sd->bl,2); - } - - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -BUILDIN_FUNC(statusup) -{ - int type; - TBL_PC *sd; - - type=script_getnum(st,2); - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - pc_statusup(sd,type); - - return 0; -} -/*========================================== - * - *------------------------------------------*/ -BUILDIN_FUNC(statusup2) -{ - int type,val; - TBL_PC *sd; - - type=script_getnum(st,2); - val=script_getnum(st,3); - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - pc_statusup2(sd,type,val); - - return 0; -} - -/// See 'doc/item_bonus.txt' -/// -/// bonus <bonus type>,<val1>; -/// bonus2 <bonus type>,<val1>,<val2>; -/// bonus3 <bonus type>,<val1>,<val2>,<val3>; -/// bonus4 <bonus type>,<val1>,<val2>,<val3>,<val4>; -/// bonus5 <bonus type>,<val1>,<val2>,<val3>,<val4>,<val5>; -BUILDIN_FUNC(bonus) -{ - int type; - int val1; - int val2 = 0; - int val3 = 0; - int val4 = 0; - int val5 = 0; - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; // no player attached - - type = script_getnum(st,2); - switch( type ) { - case SP_AUTOSPELL: - case SP_AUTOSPELL_WHENHIT: - case SP_AUTOSPELL_ONSKILL: - case SP_SKILL_ATK: - case SP_SKILL_HEAL: - case SP_SKILL_HEAL2: - case SP_ADD_SKILL_BLOW: - case SP_CASTRATE: - case SP_ADDEFF_ONSKILL: - case SP_SKILL_USE_SP_RATE: - case SP_SKILL_COOLDOWN: - case SP_SKILL_FIXEDCAST: - case SP_SKILL_VARIABLECAST: - case SP_VARCASTRATE: - case SP_SKILL_USE_SP: - // these bonuses support skill names - val1 = ( script_isstring(st,3) ? skill_name2id(script_getstr(st,3)) : script_getnum(st,3) ); - break; - default: - val1 = script_getnum(st,3); - break; - } - - switch( script_lastdata(st)-2 ) { - case 1: - pc_bonus(sd, type, val1); - break; - case 2: - val2 = script_getnum(st,4); - pc_bonus2(sd, type, val1, val2); - break; - case 3: - val2 = script_getnum(st,4); - val3 = script_getnum(st,5); - pc_bonus3(sd, type, val1, val2, val3); - break; - case 4: - if( type == SP_AUTOSPELL_ONSKILL && script_isstring(st,4) ) - val2 = skill_name2id(script_getstr(st,4)); // 2nd value can be skill name - else - val2 = script_getnum(st,4); - - val3 = script_getnum(st,5); - val4 = script_getnum(st,6); - pc_bonus4(sd, type, val1, val2, val3, val4); - break; - case 5: - if( type == SP_AUTOSPELL_ONSKILL && script_isstring(st,4) ) - val2 = skill_name2id(script_getstr(st,4)); // 2nd value can be skill name - else - val2 = script_getnum(st,4); - - val3 = script_getnum(st,5); - val4 = script_getnum(st,6); - val5 = script_getnum(st,7); - pc_bonus5(sd, type, val1, val2, val3, val4, val5); - break; - default: - ShowDebug("buildin_bonus: unexpected number of arguments (%d)\n", (script_lastdata(st) - 1)); - break; - } - - return 0; -} - -BUILDIN_FUNC(autobonus) -{ - unsigned int dur; - short rate; - short atk_type = 0; - TBL_PC* sd; - const char *bonus_script, *other_script = NULL; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; // no player attached - - if( sd->state.autobonus&sd->status.inventory[current_equip_item_index].equip ) - return 0; - - rate = script_getnum(st,3); - dur = script_getnum(st,4); - bonus_script = script_getstr(st,2); - if( !rate || !dur || !bonus_script ) - return 0; - - if( script_hasdata(st,5) ) - atk_type = script_getnum(st,5); - if( script_hasdata(st,6) ) - other_script = script_getstr(st,6); - - if( pc_addautobonus(sd->autobonus,ARRAYLENGTH(sd->autobonus), - bonus_script,rate,dur,atk_type,other_script,sd->status.inventory[current_equip_item_index].equip,false) ) - { - script_add_autobonus(bonus_script); - if( other_script ) - script_add_autobonus(other_script); - } - - return 0; -} - -BUILDIN_FUNC(autobonus2) -{ - unsigned int dur; - short rate; - short atk_type = 0; - TBL_PC* sd; - const char *bonus_script, *other_script = NULL; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; // no player attached - - if( sd->state.autobonus&sd->status.inventory[current_equip_item_index].equip ) - return 0; - - rate = script_getnum(st,3); - dur = script_getnum(st,4); - bonus_script = script_getstr(st,2); - if( !rate || !dur || !bonus_script ) - return 0; - - if( script_hasdata(st,5) ) - atk_type = script_getnum(st,5); - if( script_hasdata(st,6) ) - other_script = script_getstr(st,6); - - if( pc_addautobonus(sd->autobonus2,ARRAYLENGTH(sd->autobonus2), - bonus_script,rate,dur,atk_type,other_script,sd->status.inventory[current_equip_item_index].equip,false) ) - { - script_add_autobonus(bonus_script); - if( other_script ) - script_add_autobonus(other_script); - } - - return 0; -} - -BUILDIN_FUNC(autobonus3) -{ - unsigned int dur; - short rate,atk_type; - TBL_PC* sd; - const char *bonus_script, *other_script = NULL; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; // no player attached - - if( sd->state.autobonus&sd->status.inventory[current_equip_item_index].equip ) - return 0; - - rate = script_getnum(st,3); - dur = script_getnum(st,4); - atk_type = ( script_isstring(st,5) ? skill_name2id(script_getstr(st,5)) : script_getnum(st,5) ); - bonus_script = script_getstr(st,2); - if( !rate || !dur || !atk_type || !bonus_script ) - return 0; - - if( script_hasdata(st,6) ) - other_script = script_getstr(st,6); - - if( pc_addautobonus(sd->autobonus3,ARRAYLENGTH(sd->autobonus3), - bonus_script,rate,dur,atk_type,other_script,sd->status.inventory[current_equip_item_index].equip,true) ) - { - script_add_autobonus(bonus_script); - if( other_script ) - script_add_autobonus(other_script); - } - - return 0; -} - -/// Changes the level of a player skill. -/// <flag> defaults to 1 -/// <flag>=0 : set the level of the skill -/// <flag>=1 : set the temporary level of the skill -/// <flag>=2 : add to the level of the skill -/// -/// skill <skill id>,<level>,<flag> -/// skill <skill id>,<level> -/// skill "<skill name>",<level>,<flag> -/// skill "<skill name>",<level> -BUILDIN_FUNC(skill) -{ - int id; - int level; - int flag = 1; - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached, report source - - id = ( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); - level = script_getnum(st,3); - if( script_hasdata(st,4) ) - flag = script_getnum(st,4); - pc_skill(sd, id, level, flag); - - return 0; -} - -/// Changes the level of a player skill. -/// like skill, but <flag> defaults to 2 -/// -/// addtoskill <skill id>,<amount>,<flag> -/// addtoskill <skill id>,<amount> -/// addtoskill "<skill name>",<amount>,<flag> -/// addtoskill "<skill name>",<amount> -/// -/// @see skill -BUILDIN_FUNC(addtoskill) -{ - int id; - int level; - int flag = 2; - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached, report source - - id = ( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); - level = script_getnum(st,3); - if( script_hasdata(st,4) ) - flag = script_getnum(st,4); - pc_skill(sd, id, level, flag); - - return 0; -} - -/// Increases the level of a guild skill. -/// -/// guildskill <skill id>,<amount>; -/// guildskill "<skill name>",<amount>; -BUILDIN_FUNC(guildskill) -{ - int id; - int level; - TBL_PC* sd; - int i; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached, report source - - id = ( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); - level = script_getnum(st,3); - for( i=0; i < level; i++ ) - guild_skillup(sd, id); - - return 0; -} - -/// Returns the level of the player skill. -/// -/// getskilllv(<skill id>) -> <level> -/// getskilllv("<skill name>") -> <level> -BUILDIN_FUNC(getskilllv) -{ - int id; - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached, report source - - id = ( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); - script_pushint(st, pc_checkskill(sd,id)); - - return 0; -} - -/// Returns the level of the guild skill. -/// -/// getgdskilllv(<guild id>,<skill id>) -> <level> -/// getgdskilllv(<guild id>,"<skill name>") -> <level> -BUILDIN_FUNC(getgdskilllv) -{ - int guild_id; - uint16 skill_id; - struct guild* g; - - guild_id = script_getnum(st,2); - skill_id = ( script_isstring(st,3) ? skill_name2id(script_getstr(st,3)) : script_getnum(st,3) ); - g = guild_search(guild_id); - if( g == NULL ) - script_pushint(st, -1); - else - script_pushint(st, guild_checkskill(g,skill_id)); - - return 0; -} - -/// Returns the 'basic_skill_check' setting. -/// This config determines if the server checks the skill level of NV_BASIC -/// before allowing the basic actions. -/// -/// basicskillcheck() -> <bool> -BUILDIN_FUNC(basicskillcheck) -{ - script_pushint(st, battle_config.basic_skill_check); - return 0; -} - -/// Returns the GM level of the player. -/// -/// getgmlevel() -> <level> -BUILDIN_FUNC(getgmlevel) -{ - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached, report source - - script_pushint(st, pc_get_group_level(sd)); - - return 0; -} - -/// Returns the group ID of the player. -/// -/// getgroupid() -> <int> -BUILDIN_FUNC(getgroupid) -{ - TBL_PC* sd; - - sd = script_rid2sd(st); - if (sd == NULL) - return 1; // no player attached, report source - script_pushint(st, pc_get_group_id(sd)); - - return 0; -} - -/// Terminates the execution of this script instance. -/// -/// end -BUILDIN_FUNC(end) -{ - st->state = END; - return 0; -} - -/// Checks if the player has that effect state (option). -/// -/// checkoption(<option>) -> <bool> -BUILDIN_FUNC(checkoption) -{ - int option; - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached, report source - - option = script_getnum(st,2); - if( sd->sc.option&option ) - script_pushint(st, 1); - else - script_pushint(st, 0); - - return 0; -} - -/// Checks if the player is in that body state (opt1). -/// -/// checkoption1(<opt1>) -> <bool> -BUILDIN_FUNC(checkoption1) -{ - int opt1; - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached, report source - - opt1 = script_getnum(st,2); - if( sd->sc.opt1 == opt1 ) - script_pushint(st, 1); - else - script_pushint(st, 0); - - return 0; -} - -/// Checks if the player has that health state (opt2). -/// -/// checkoption2(<opt2>) -> <bool> -BUILDIN_FUNC(checkoption2) -{ - int opt2; - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached, report source - - opt2 = script_getnum(st,2); - if( sd->sc.opt2&opt2 ) - script_pushint(st, 1); - else - script_pushint(st, 0); - - return 0; -} - -/// Changes the effect state (option) of the player. -/// <flag> defaults to 1 -/// <flag>=0 : removes the option -/// <flag>=other : adds the option -/// -/// setoption <option>,<flag>; -/// setoption <option>; -BUILDIN_FUNC(setoption) -{ - int option; - int flag = 1; - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached, report source - - option = script_getnum(st,2); - if( script_hasdata(st,3) ) - flag = script_getnum(st,3); - else if( !option ){// Request to remove everything. - flag = 0; - option = OPTION_FALCON|OPTION_RIDING; -#ifndef NEW_CARTS - option |= OPTION_CART; -#endif - } - if( flag ){// Add option - if( option&OPTION_WEDDING && !battle_config.wedding_modifydisplay ) - option &= ~OPTION_WEDDING;// Do not show the wedding sprites - pc_setoption(sd, sd->sc.option|option); - } else// Remove option - pc_setoption(sd, sd->sc.option&~option); - - return 0; -} - -/// Returns if the player has a cart. -/// -/// checkcart() -> <bool> -/// -/// @author Valaris -BUILDIN_FUNC(checkcart) -{ - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached, report source - - if( pc_iscarton(sd) ) - script_pushint(st, 1); - else - script_pushint(st, 0); - - return 0; -} - -/// Sets the cart of the player. -/// <type> defaults to 1 -/// <type>=0 : removes the cart -/// <type>=1 : Normal cart -/// <type>=2 : Wooden cart -/// <type>=3 : Covered cart with flowers and ferns -/// <type>=4 : Wooden cart with a Panda doll on the back -/// <type>=5 : Normal cart with bigger wheels, a roof and a banner on the back -/// -/// setcart <type>; -/// setcart; -BUILDIN_FUNC(setcart) -{ - int type = 1; - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached, report source - - if( script_hasdata(st,2) ) - type = script_getnum(st,2); - pc_setcart(sd, type); - - return 0; -} - -/// Returns if the player has a falcon. -/// -/// checkfalcon() -> <bool> -/// -/// @author Valaris -BUILDIN_FUNC(checkfalcon) -{ - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached, report source - - if( pc_isfalcon(sd) ) - script_pushint(st, 1); - else - script_pushint(st, 0); - - return 0; -} - -/// Sets if the player has a falcon or not. -/// <flag> defaults to 1 -/// -/// setfalcon <flag>; -/// setfalcon; -BUILDIN_FUNC(setfalcon) -{ - int flag = 1; - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached, report source - - if( script_hasdata(st,2) ) - flag = script_getnum(st,2); - - pc_setfalcon(sd, flag); - - return 0; -} - -/// Returns if the player is riding. -/// -/// checkriding() -> <bool> -/// -/// @author Valaris -BUILDIN_FUNC(checkriding) -{ - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached, report source - - if( pc_isriding(sd) || pc_isridingwug(sd) || pc_isridingdragon(sd) ) - script_pushint(st, 1); - else - script_pushint(st, 0); - - return 0; -} - -/// Sets if the player is riding. -/// <flag> defaults to 1 -/// -/// setriding <flag>; -/// setriding; -BUILDIN_FUNC(setriding) -{ - int flag = 1; - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached, report source - - if( script_hasdata(st,2) ) - flag = script_getnum(st,2); - pc_setriding(sd, flag); - - return 0; -} - -/// Returns if the player has a warg. -/// -/// checkwug() -> <bool> -/// -BUILDIN_FUNC(checkwug) -{ - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached, report source - - if( pc_iswug(sd) || pc_isridingwug(sd) ) - script_pushint(st, 1); - else - script_pushint(st, 0); - - return 0; -} - -/// Returns if the player is wearing MADO Gear. -/// -/// checkmadogear() -> <bool> -/// -BUILDIN_FUNC(checkmadogear) -{ - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached, report source - - if( pc_ismadogear(sd) ) - script_pushint(st, 1); - else - script_pushint(st, 0); - - return 0; -} - -/// Sets if the player is riding MADO Gear. -/// <flag> defaults to 1 -/// -/// setmadogear <flag>; -/// setmadogear; -BUILDIN_FUNC(setmadogear) -{ - int flag = 1; - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached, report source - - if( script_hasdata(st,2) ) - flag = script_getnum(st,2); - pc_setmadogear(sd, flag); - - return 0; -} - -/// Sets the save point of the player. -/// -/// save "<map name>",<x>,<y> -/// savepoint "<map name>",<x>,<y> -BUILDIN_FUNC(savepoint) -{ - int x; - int y; - short map; - const char* str; - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached, report source - - str = script_getstr(st, 2); - x = script_getnum(st,3); - y = script_getnum(st,4); - map = mapindex_name2id(str); - if( map ) - pc_setsavepoint(sd, map, x, y); - - return 0; -} - -/*========================================== - * GetTimeTick(0: System Tick, 1: Time Second Tick) - *------------------------------------------*/ -BUILDIN_FUNC(gettimetick) /* Asgard Version */ -{ - int type; - time_t timer; - struct tm *t; - - type=script_getnum(st,2); - - switch(type){ - case 2: - //type 2:(Get the number of seconds elapsed since 00:00 hours, Jan 1, 1970 UTC - // from the system clock.) - script_pushint(st,(int)time(NULL)); - break; - case 1: - //type 1:(Second Ticks: 0-86399, 00:00:00-23:59:59) - time(&timer); - t=localtime(&timer); - script_pushint(st,((t->tm_hour)*3600+(t->tm_min)*60+t->tm_sec)); - break; - case 0: - default: - //type 0:(System Ticks) - script_pushint(st,gettick()); - break; - } - return 0; -} - -/*========================================== - * GetTime(Type); - * 1: Sec 2: Min 3: Hour - * 4: WeekDay 5: MonthDay 6: Month - * 7: Year - *------------------------------------------*/ -BUILDIN_FUNC(gettime) /* Asgard Version */ -{ - int type; - time_t timer; - struct tm *t; - - type=script_getnum(st,2); - - time(&timer); - t=localtime(&timer); - - switch(type){ - case 1://Sec(0~59) - script_pushint(st,t->tm_sec); - break; - case 2://Min(0~59) - script_pushint(st,t->tm_min); - break; - case 3://Hour(0~23) - script_pushint(st,t->tm_hour); - break; - case 4://WeekDay(0~6) - script_pushint(st,t->tm_wday); - break; - case 5://MonthDay(01~31) - script_pushint(st,t->tm_mday); - break; - case 6://Month(01~12) - script_pushint(st,t->tm_mon+1); - break; - case 7://Year(20xx) - script_pushint(st,t->tm_year+1900); - break; - case 8://Year Day(01~366) - script_pushint(st,t->tm_yday+1); - break; - default://(format error) - script_pushint(st,-1); - break; - } - return 0; -} - -/*========================================== - * GetTimeStr("TimeFMT", Length); - *------------------------------------------*/ -BUILDIN_FUNC(gettimestr) -{ - char *tmpstr; - const char *fmtstr; - int maxlen; - time_t now = time(NULL); - - fmtstr=script_getstr(st,2); - maxlen=script_getnum(st,3); - - tmpstr=(char *)aMalloc((maxlen+1)*sizeof(char)); - strftime(tmpstr,maxlen,fmtstr,localtime(&now)); - tmpstr[maxlen]='\0'; - - script_pushstr(st,tmpstr); - return 0; -} - -/*========================================== - * Open player storage - *------------------------------------------*/ -BUILDIN_FUNC(openstorage) -{ - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - storage_storageopen(sd); - return 0; -} - -BUILDIN_FUNC(guildopenstorage) -{ - TBL_PC* sd; - int ret; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - ret = storage_guild_storageopen(sd); - script_pushint(st,ret); - return 0; -} - -/*========================================== - * Make player use a skill trought item usage - *------------------------------------------*/ -/// itemskill <skill id>,<level> -/// itemskill "<skill name>",<level> -BUILDIN_FUNC(itemskill) -{ - int id; - int lv; - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL || sd->ud.skilltimer != INVALID_TIMER ) - return 0; - - id = ( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); - lv = script_getnum(st,3); - - sd->skillitem=id; - sd->skillitemlv=lv; - clif_item_skill(sd,id,lv); - return 0; -} -/*========================================== - * Attempt to create an item - *------------------------------------------*/ -BUILDIN_FUNC(produce) -{ - int trigger; - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - trigger=script_getnum(st,2); - clif_skill_produce_mix_list(sd, -1, trigger); - return 0; -} -/*========================================== - * - *------------------------------------------*/ -BUILDIN_FUNC(cooking) -{ - int trigger; - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - trigger=script_getnum(st,2); - clif_cooking_list(sd, trigger, AM_PHARMACY, 1, 1); - return 0; -} -/*========================================== - * Create a pet - *------------------------------------------*/ -BUILDIN_FUNC(makepet) -{ - TBL_PC* sd; - int id,pet_id; - - id=script_getnum(st,2); - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - 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, - (short)pet_db[pet_id].class_, (short)mob_db(pet_db[pet_id].class_)->lv, - (short)pet_db[pet_id].EggID, 0, (short)pet_db[pet_id].intimate, - 100, 0, 1, pet_db[pet_id].jname); - } - - return 0; -} -/*========================================== - * Give player exp base,job * quest_exp_rate/100 - *------------------------------------------*/ -BUILDIN_FUNC(getexp) -{ - TBL_PC* sd; - int base=0,job=0; - double bonus; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - base=script_getnum(st,2); - job =script_getnum(st,3); - if(base<0 || job<0) - return 0; - - // bonus for npc-given exp - bonus = battle_config.quest_exp_rate / 100.; - base = (int) cap_value(base * bonus, 0, INT_MAX); - job = (int) cap_value(job * bonus, 0, INT_MAX); - - pc_gainexp(sd, NULL, base, job, true); - - return 0; -} - -/*========================================== - * Gain guild exp [Celest] - *------------------------------------------*/ -BUILDIN_FUNC(guildgetexp) -{ - TBL_PC* sd; - int exp; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - exp = script_getnum(st,2); - if(exp < 0) - return 0; - if(sd && sd->status.guild_id > 0) - guild_getexp (sd, exp); - - return 0; -} - -/*========================================== - * Changes the guild master of a guild [Skotlex] - *------------------------------------------*/ -BUILDIN_FUNC(guildchangegm) -{ - TBL_PC *sd; - int guild_id; - const char *name; - - guild_id = script_getnum(st,2); - name = script_getstr(st,3); - sd=map_nick2sd(name); - - if (!sd) - script_pushint(st,0); - else - script_pushint(st,guild_gm_change(guild_id, sd)); - - return 0; -} - -/*========================================== - * Spawn a monster : - @mapn,x,y : location - @str : monster name - @class_ : mob_id - @amount : nb to spawn - @event : event to attach to mob - *------------------------------------------*/ -BUILDIN_FUNC(monster) -{ - const char* mapn = script_getstr(st,2); - int x = script_getnum(st,3); - int y = script_getnum(st,4); - const char* str = script_getstr(st,5); - int class_ = script_getnum(st,6); - int amount = script_getnum(st,7); - const char* event = ""; - unsigned int size = SZ_SMALL; - unsigned int ai = AI_NONE; - - struct map_session_data* sd; - int16 m; - - if (script_hasdata(st, 8)) - { - event = script_getstr(st, 8); - check_event(st, event); - } - - if (script_hasdata(st, 9)) - { - size = script_getnum(st, 9); - if (size > 3) - { - ShowWarning("buildin_monster: Attempted to spawn non-existing size %d for monster class %d\n", size, class_); - return 1; - } - } - - if (script_hasdata(st, 10)) - { - ai = script_getnum(st, 10); - if (ai > 4) - { - ShowWarning("buildin_monster: Attempted to spawn non-existing ai %d for monster class %d\n", ai, class_); - return 1; - } - } - - if (class_ >= 0 && !mobdb_checkid(class_)) - { - ShowWarning("buildin_monster: Attempted to spawn non-existing monster class %d\n", class_); - return 1; - } - - sd = map_id2sd(st->rid); - - if (sd && strcmp(mapn, "this") == 0) - m = sd->bl.m; - else - { - m = map_mapname2mapid(mapn); - if (map[m].flag.src4instance && st->instance_id) - { // Try to redirect to the instance map, not the src map - if ((m = instance_mapid2imapid(m, st->instance_id)) < 0) - { - ShowError("buildin_monster: Trying to spawn monster (%d) on instance map (%s) without instance attached.\n", class_, mapn); - return 1; - } - } - } - - mob_once_spawn(sd, m, x, y, str, class_, amount, event, size, ai); - return 0; -} -/*========================================== - * Request List of Monster Drops - *------------------------------------------*/ -BUILDIN_FUNC(getmobdrops) -{ - int class_ = script_getnum(st,2); - int i, j = 0; - struct mob_db *mob; - - if( !mobdb_checkid(class_) ) - { - script_pushint(st, 0); - return 0; - } - - mob = mob_db(class_); - - for( i = 0; i < MAX_MOB_DROP; i++ ) - { - if( mob->dropitem[i].nameid < 1 ) - continue; - if( itemdb_exists(mob->dropitem[i].nameid) == NULL ) - continue; - - mapreg_setreg(reference_uid(add_str("$@MobDrop_item"), j), mob->dropitem[i].nameid); - mapreg_setreg(reference_uid(add_str("$@MobDrop_rate"), j), mob->dropitem[i].p); - - j++; - } - - mapreg_setreg(add_str("$@MobDrop_count"), j); - script_pushint(st, 1); - - return 0; -} -/*========================================== - * Same as monster but randomize location in x0,x1,y0,y1 area - *------------------------------------------*/ -BUILDIN_FUNC(areamonster) -{ - const char* mapn = script_getstr(st,2); - int x0 = script_getnum(st,3); - int y0 = script_getnum(st,4); - int x1 = script_getnum(st,5); - int y1 = script_getnum(st,6); - const char* str = script_getstr(st,7); - int class_ = script_getnum(st,8); - int amount = script_getnum(st,9); - const char* event = ""; - unsigned int size = SZ_SMALL; - unsigned int ai = AI_NONE; - - struct map_session_data* sd; - int16 m; - - if (script_hasdata(st,10)) - { - event = script_getstr(st, 10); - check_event(st, event); - } - - if (script_hasdata(st, 11)) - { - size = script_getnum(st, 11); - if (size > 3) - { - ShowWarning("buildin_monster: Attempted to spawn non-existing size %d for monster class %d\n", size, class_); - return 1; - } - } - - if (script_hasdata(st, 12)) - { - ai = script_getnum(st, 12); - if (ai > 4) - { - ShowWarning("buildin_monster: Attempted to spawn non-existing ai %d for monster class %d\n", ai, class_); - return 1; - } - } - - sd = map_id2sd(st->rid); - - if (sd && strcmp(mapn, "this") == 0) - m = sd->bl.m; - else - { - m = map_mapname2mapid(mapn); - if (map[m].flag.src4instance && st->instance_id) - { // Try to redirect to the instance map, not the src map - if ((m = instance_mapid2imapid(m, st->instance_id)) < 0) - { - ShowError("buildin_areamonster: Trying to spawn monster (%d) on instance map (%s) without instance attached.\n", class_, mapn); - return 1; - } - } - } - - mob_once_spawn_area(sd, m, x0, y0, x1, y1, str, class_, amount, event, size, ai); - return 0; -} -/*========================================== - * KillMonster subcheck, verify if mob to kill ain't got an even to handle, could be force kill by allflag - *------------------------------------------*/ - static int buildin_killmonster_sub_strip(struct block_list *bl,va_list ap) -{ //same fix but with killmonster instead - stripping events from mobs. - TBL_MOB* md = (TBL_MOB*)bl; - char *event=va_arg(ap,char *); - int allflag=va_arg(ap,int); - - md->state.npc_killmonster = 1; - - if(!allflag){ - if(strcmp(event,md->npc_event)==0) - status_kill(bl); - }else{ - if(!md->spawn) - status_kill(bl); - } - md->state.npc_killmonster = 0; - return 0; -} -static int buildin_killmonster_sub(struct block_list *bl,va_list ap) -{ - TBL_MOB* md = (TBL_MOB*)bl; - char *event=va_arg(ap,char *); - int allflag=va_arg(ap,int); - - if(!allflag){ - if(strcmp(event,md->npc_event)==0) - status_kill(bl); - }else{ - if(!md->spawn) - status_kill(bl); - } - return 0; -} -BUILDIN_FUNC(killmonster) -{ - const char *mapname,*event; - int16 m,allflag=0; - mapname=script_getstr(st,2); - event=script_getstr(st,3); - if(strcmp(event,"All")==0) - allflag = 1; - else - check_event(st, event); - - if( (m=map_mapname2mapid(mapname))<0 ) - return 0; - - if( map[m].flag.src4instance && st->instance_id && (m = instance_mapid2imapid(m, st->instance_id)) < 0 ) - return 0; - - if( script_hasdata(st,4) ) { - if ( script_getnum(st,4) == 1 ) { - map_foreachinmap(buildin_killmonster_sub, m, BL_MOB, event ,allflag); - return 0; - } - } - - map_freeblock_lock(); - map_foreachinmap(buildin_killmonster_sub_strip, m, BL_MOB, event ,allflag); - map_freeblock_unlock(); - return 0; -} - -static int buildin_killmonsterall_sub_strip(struct block_list *bl,va_list ap) -{ //Strips the event from the mob if it's killed the old method. - struct mob_data *md; - - md = BL_CAST(BL_MOB, bl); - if (md->npc_event[0]) - md->npc_event[0] = 0; - - status_kill(bl); - return 0; -} -static int buildin_killmonsterall_sub(struct block_list *bl,va_list ap) -{ - status_kill(bl); - return 0; -} -BUILDIN_FUNC(killmonsterall) -{ - const char *mapname; - int16 m; - mapname=script_getstr(st,2); - - if( (m = map_mapname2mapid(mapname))<0 ) - return 0; - - if( map[m].flag.src4instance && st->instance_id && (m = instance_mapid2imapid(m, st->instance_id)) < 0 ) - return 0; - - if( script_hasdata(st,3) ) { - if ( script_getnum(st,3) == 1 ) { - map_foreachinmap(buildin_killmonsterall_sub,m,BL_MOB); - return 0; - } - } - - map_foreachinmap(buildin_killmonsterall_sub_strip,m,BL_MOB); - return 0; -} - -/*========================================== - * Creates a clone of a player. - * clone map, x, y, event, char_id, master_id, mode, flag, duration - *------------------------------------------*/ -BUILDIN_FUNC(clone) -{ - TBL_PC *sd, *msd=NULL; - int char_id,master_id=0,x,y, mode = 0, flag = 0, m; - unsigned int duration = 0; - const char *map,*event=""; - - map=script_getstr(st,2); - x=script_getnum(st,3); - y=script_getnum(st,4); - event=script_getstr(st,5); - char_id=script_getnum(st,6); - - if( script_hasdata(st,7) ) - master_id=script_getnum(st,7); - - if( script_hasdata(st,8) ) - mode=script_getnum(st,8); - - if( script_hasdata(st,9) ) - flag=script_getnum(st,9); - - if( script_hasdata(st,10) ) - duration=script_getnum(st,10); - - check_event(st, event); - - m = map_mapname2mapid(map); - if (m < 0) return 0; - - sd = map_charid2sd(char_id); - - if (master_id) { - msd = map_charid2sd(master_id); - if (msd) - master_id = msd->bl.id; - else - master_id = 0; - } - if (sd) //Return ID of newly crafted clone. - script_pushint(st,mob_clone_spawn(sd, m, x, y, event, master_id, mode, flag, 1000*duration)); - else //Failed to create clone. - script_pushint(st,0); - - return 0; -} -/*========================================== - *------------------------------------------*/ -BUILDIN_FUNC(doevent) -{ - const char* event = script_getstr(st,2); - struct map_session_data* sd; - - if( ( sd = script_rid2sd(st) ) == NULL ) - { - return 0; - } - - check_event(st, event); - npc_event(sd, event, 0); - return 0; -} -/*========================================== - *------------------------------------------*/ -BUILDIN_FUNC(donpcevent) -{ - const char* event = script_getstr(st,2); - check_event(st, event); - if( !npc_event_do(event) ) { - struct npc_data * nd = map_id2nd(st->oid); - ShowDebug("NPCEvent '%s' not found! (source: %s)\n",event,nd?nd->name:"Unknown"); - script_pushint(st, 0); - } else - script_pushint(st, 1); - return 0; -} - -/// for Aegis compatibility -/// basically a specialized 'donpcevent', with the event specified as two arguments instead of one -BUILDIN_FUNC(cmdothernpc) // Added by RoVeRT -{ - const char* npc = script_getstr(st,2); - const char* command = script_getstr(st,3); - char event[EVENT_NAME_LENGTH]; - snprintf(event, sizeof(event), "%s::OnCommand%s", npc, command); - check_event(st, event); - npc_event_do(event); - return 0; -} - -/*========================================== - *------------------------------------------*/ -BUILDIN_FUNC(addtimer) -{ - int tick = script_getnum(st,2); - const char* event = script_getstr(st, 3); - TBL_PC* sd; - - check_event(st, event); - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - pc_addeventtimer(sd,tick,event); - return 0; -} -/*========================================== - *------------------------------------------*/ -BUILDIN_FUNC(deltimer) -{ - const char *event; - TBL_PC* sd; - - event=script_getstr(st, 2); - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - check_event(st, event); - pc_deleventtimer(sd,event); - return 0; -} -/*========================================== - *------------------------------------------*/ -BUILDIN_FUNC(addtimercount) -{ - const char *event; - int tick; - TBL_PC* sd; - - event=script_getstr(st, 2); - tick=script_getnum(st,3); - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - check_event(st, event); - pc_addeventtimercount(sd,event,tick); - return 0; -} - -/*========================================== - *------------------------------------------*/ -BUILDIN_FUNC(initnpctimer) -{ - struct npc_data *nd; - int flag = 0; - - if( script_hasdata(st,3) ) - { //Two arguments: NPC name and attach flag. - nd = npc_name2id(script_getstr(st, 2)); - flag = script_getnum(st,3); - } - else if( script_hasdata(st,2) ) - { //Check if argument is numeric (flag) or string (npc name) - struct script_data *data; - data = script_getdata(st,2); - get_val(st,data); - if( data_isstring(data) ) //NPC name - nd = npc_name2id(conv_str(st, data)); - else if( data_isint(data) ) //Flag - { - nd = (struct npc_data *)map_id2bl(st->oid); - flag = conv_num(st,data); - } - else - { - ShowError("initnpctimer: invalid argument type #1 (needs be int or string)).\n"); - return 1; - } - } - else - nd = (struct npc_data *)map_id2bl(st->oid); - - if( !nd ) - return 0; - if( flag ) //Attach - { - TBL_PC* sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - nd->u.scr.rid = sd->bl.id; - } - - nd->u.scr.timertick = 0; - npc_settimerevent_tick(nd,0); - npc_timerevent_start(nd, st->rid); - return 0; -} -/*========================================== - *------------------------------------------*/ -BUILDIN_FUNC(startnpctimer) -{ - struct npc_data *nd; - int flag = 0; - - if( script_hasdata(st,3) ) - { //Two arguments: NPC name and attach flag. - nd = npc_name2id(script_getstr(st, 2)); - flag = script_getnum(st,3); - } - else if( script_hasdata(st,2) ) - { //Check if argument is numeric (flag) or string (npc name) - struct script_data *data; - data = script_getdata(st,2); - get_val(st,data); - if( data_isstring(data) ) //NPC name - nd = npc_name2id(conv_str(st, data)); - else if( data_isint(data) ) //Flag - { - nd = (struct npc_data *)map_id2bl(st->oid); - flag = conv_num(st,data); - } - else - { - ShowError("initnpctimer: invalid argument type #1 (needs be int or string)).\n"); - return 1; - } - } - else - nd=(struct npc_data *)map_id2bl(st->oid); - - if( !nd ) - return 0; - if( flag ) //Attach - { - TBL_PC* sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - nd->u.scr.rid = sd->bl.id; - } - - npc_timerevent_start(nd, st->rid); - return 0; -} -/*========================================== - *------------------------------------------*/ -BUILDIN_FUNC(stopnpctimer) -{ - struct npc_data *nd; - int flag = 0; - - if( script_hasdata(st,3) ) - { //Two arguments: NPC name and attach flag. - nd = npc_name2id(script_getstr(st, 2)); - flag = script_getnum(st,3); - } - else if( script_hasdata(st,2) ) - { //Check if argument is numeric (flag) or string (npc name) - struct script_data *data; - data = script_getdata(st,2); - get_val(st,data); - if( data_isstring(data) ) //NPC name - nd = npc_name2id(conv_str(st, data)); - else if( data_isint(data) ) //Flag - { - nd = (struct npc_data *)map_id2bl(st->oid); - flag = conv_num(st,data); - } - else - { - ShowError("initnpctimer: invalid argument type #1 (needs be int or string)).\n"); - return 1; - } - } - else - nd=(struct npc_data *)map_id2bl(st->oid); - - if( !nd ) - return 0; - if( flag ) //Detach - nd->u.scr.rid = 0; - - npc_timerevent_stop(nd); - return 0; -} -/*========================================== - *------------------------------------------*/ -BUILDIN_FUNC(getnpctimer) -{ - struct npc_data *nd; - TBL_PC *sd; - int type = script_getnum(st,2); - int val = 0; - - if( script_hasdata(st,3) ) - nd = npc_name2id(script_getstr(st,3)); - else - nd = (struct npc_data *)map_id2bl(st->oid); - - if( !nd || nd->bl.type != BL_NPC ) - { - script_pushint(st,0); - ShowError("getnpctimer: Invalid NPC.\n"); - return 1; - } - - switch( type ) - { - case 0: val = npc_gettimerevent_tick(nd); break; - case 1: - if( nd->u.scr.rid ) - { - sd = map_id2sd(nd->u.scr.rid); - if( !sd ) - { - ShowError("buildin_getnpctimer: Attached player not found!\n"); - break; - } - val = (sd->npc_timer_id != INVALID_TIMER); - } - else - val = (nd->u.scr.timerid != INVALID_TIMER); - break; - case 2: val = nd->u.scr.timeramount; break; - } - - script_pushint(st,val); - return 0; -} -/*========================================== - *------------------------------------------*/ -BUILDIN_FUNC(setnpctimer) -{ - int tick; - struct npc_data *nd; - - tick = script_getnum(st,2); - if( script_hasdata(st,3) ) - nd = npc_name2id(script_getstr(st,3)); - else - nd = (struct npc_data *)map_id2bl(st->oid); - - if( !nd || nd->bl.type != BL_NPC ) - { - script_pushint(st,1); - ShowError("setnpctimer: Invalid NPC.\n"); - return 1; - } - - npc_settimerevent_tick(nd,tick); - script_pushint(st,0); - return 0; -} - -/*========================================== - * attaches the player rid to the timer [Celest] - *------------------------------------------*/ -BUILDIN_FUNC(attachnpctimer) -{ - TBL_PC *sd; - struct npc_data *nd = (struct npc_data *)map_id2bl(st->oid); - - if( !nd || nd->bl.type != BL_NPC ) - { - script_pushint(st,1); - ShowError("setnpctimer: Invalid NPC.\n"); - return 1; - } - - if( script_hasdata(st,2) ) - sd = map_nick2sd(script_getstr(st,2)); - else - sd = script_rid2sd(st); - - if( !sd ) - { - script_pushint(st,1); - ShowWarning("attachnpctimer: Invalid player.\n"); - return 1; - } - - nd->u.scr.rid = sd->bl.id; - script_pushint(st,0); - return 0; -} - -/*========================================== - * detaches a player rid from the timer [Celest] - *------------------------------------------*/ -BUILDIN_FUNC(detachnpctimer) -{ - struct npc_data *nd; - - if( script_hasdata(st,2) ) - nd = npc_name2id(script_getstr(st,2)); - else - nd = (struct npc_data *)map_id2bl(st->oid); - - if( !nd || nd->bl.type != BL_NPC ) - { - script_pushint(st,1); - ShowError("detachnpctimer: Invalid NPC.\n"); - return 1; - } - - nd->u.scr.rid = 0; - script_pushint(st,0); - return 0; -} - -/*========================================== - * To avoid "player not attached" script errors, this function is provided, - * it checks if there is a player attached to the current script. [Skotlex] - * If no, returns 0, if yes, returns the account_id of the attached player. - *------------------------------------------*/ -BUILDIN_FUNC(playerattached) -{ - if(st->rid == 0 || map_id2sd(st->rid) == NULL) - script_pushint(st,0); - else - script_pushint(st,st->rid); - return 0; -} - -/*========================================== - *------------------------------------------*/ -BUILDIN_FUNC(announce) -{ - const char *mes = script_getstr(st,2); - int flag = script_getnum(st,3); - const char *fontColor = script_hasdata(st,4) ? script_getstr(st,4) : NULL; - int fontType = script_hasdata(st,5) ? script_getnum(st,5) : 0x190; // default fontType (FW_NORMAL) - int fontSize = script_hasdata(st,6) ? script_getnum(st,6) : 12; // default fontSize - int fontAlign = script_hasdata(st,7) ? script_getnum(st,7) : 0; // default fontAlign - int fontY = script_hasdata(st,8) ? script_getnum(st,8) : 0; // default fontY - - if (flag&0x0f) // Broadcast source or broadcast region defined - { - send_target target; - struct block_list *bl = (flag&0x08) ? map_id2bl(st->oid) : (struct block_list *)script_rid2sd(st); // If bc_npc flag is set, use NPC as broadcast source - if (bl == NULL) - return 0; - - flag &= 0x07; - target = (flag == 1) ? ALL_SAMEMAP : - (flag == 2) ? AREA : - (flag == 3) ? SELF : - ALL_CLIENT; - if (fontColor) - clif_broadcast2(bl, mes, (int)strlen(mes)+1, strtol(fontColor, (char **)NULL, 0), fontType, fontSize, fontAlign, fontY, target); - else - clif_broadcast(bl, mes, (int)strlen(mes)+1, flag&0xf0, target); - } - else - { - if (fontColor) - intif_broadcast2(mes, (int)strlen(mes)+1, strtol(fontColor, (char **)NULL, 0), fontType, fontSize, fontAlign, fontY); - else - intif_broadcast(mes, (int)strlen(mes)+1, flag&0xf0); - } - return 0; -} -/*========================================== - *------------------------------------------*/ -static int buildin_announce_sub(struct block_list *bl, va_list ap) -{ - char *mes = va_arg(ap, char *); - int len = va_arg(ap, int); - int type = va_arg(ap, int); - char *fontColor = va_arg(ap, char *); - short fontType = (short)va_arg(ap, int); - short fontSize = (short)va_arg(ap, int); - short fontAlign = (short)va_arg(ap, int); - short fontY = (short)va_arg(ap, int); - if (fontColor) - clif_broadcast2(bl, mes, len, strtol(fontColor, (char **)NULL, 0), fontType, fontSize, fontAlign, fontY, SELF); - else - clif_broadcast(bl, mes, len, type, SELF); - return 0; -} - -BUILDIN_FUNC(mapannounce) -{ - const char *mapname = script_getstr(st,2); - const char *mes = script_getstr(st,3); - int flag = script_getnum(st,4); - const char *fontColor = script_hasdata(st,5) ? script_getstr(st,5) : NULL; - int fontType = script_hasdata(st,6) ? script_getnum(st,6) : 0x190; // default fontType (FW_NORMAL) - int fontSize = script_hasdata(st,7) ? script_getnum(st,7) : 12; // default fontSize - int fontAlign = script_hasdata(st,8) ? script_getnum(st,8) : 0; // default fontAlign - int fontY = script_hasdata(st,9) ? script_getnum(st,9) : 0; // default fontY - int16 m; - - if ((m = map_mapname2mapid(mapname)) < 0) - return 0; - - map_foreachinmap(buildin_announce_sub, m, BL_PC, - mes, strlen(mes)+1, flag&0xf0, fontColor, fontType, fontSize, fontAlign, fontY); - return 0; -} -/*========================================== - *------------------------------------------*/ -BUILDIN_FUNC(areaannounce) -{ - const char *mapname = script_getstr(st,2); - int x0 = script_getnum(st,3); - int y0 = script_getnum(st,4); - int x1 = script_getnum(st,5); - int y1 = script_getnum(st,6); - const char *mes = script_getstr(st,7); - int flag = script_getnum(st,8); - const char *fontColor = script_hasdata(st,9) ? script_getstr(st,9) : NULL; - int fontType = script_hasdata(st,10) ? script_getnum(st,10) : 0x190; // default fontType (FW_NORMAL) - int fontSize = script_hasdata(st,11) ? script_getnum(st,11) : 12; // default fontSize - int fontAlign = script_hasdata(st,12) ? script_getnum(st,12) : 0; // default fontAlign - int fontY = script_hasdata(st,13) ? script_getnum(st,13) : 0; // default fontY - int16 m; - - if ((m = map_mapname2mapid(mapname)) < 0) - return 0; - - map_foreachinarea(buildin_announce_sub, m, x0, y0, x1, y1, BL_PC, - mes, strlen(mes)+1, flag&0xf0, fontColor, fontType, fontSize, fontAlign, fontY); - return 0; -} - -/*========================================== - *------------------------------------------*/ -BUILDIN_FUNC(getusers) -{ - int flag, val = 0; - struct map_session_data* sd; - struct block_list* bl = NULL; - - flag = script_getnum(st,2); - - switch(flag&0x07) - { - case 0: - if(flag&0x8) - {// npc - bl = map_id2bl(st->oid); - } - else if((sd = script_rid2sd(st))!=NULL) - {// pc - bl = &sd->bl; - } - - if(bl) - { - val = map[bl->m].users; - } - break; - case 1: - val = map_getusers(); - break; - default: - ShowWarning("buildin_getusers: Unknown type %d.\n", flag); - script_pushint(st,0); - return 1; - } - - script_pushint(st,val); - return 0; -} -/*========================================== - * Works like @WHO - displays all online users names in window - *------------------------------------------*/ -BUILDIN_FUNC(getusersname) -{ - TBL_PC *sd, *pl_sd; - int /*disp_num=1,*/ group_level = 0; - struct s_mapiterator* iter; - - sd = script_rid2sd(st); - if (!sd) return 0; - - group_level = pc_get_group_level(sd); - iter = mapit_getallusers(); - for( pl_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); pl_sd = (TBL_PC*)mapit_next(iter) ) - { - if (pc_has_permission(pl_sd, PC_PERM_HIDE_SESSION) && pc_get_group_level(pl_sd) > group_level) - continue; // skip hidden sessions - - /* Temporary fix for bugreport:1023. - * Do not uncomment unless you want thousands of 'next' buttons. - if((disp_num++)%10==0) - clif_scriptnext(sd,st->oid);*/ - clif_scriptmes(sd,st->oid,pl_sd->status.name); - } - mapit_free(iter); - - return 0; -} -/*========================================== - * getmapguildusers("mapname",guild ID) Returns the number guild members present on a map [Reddozen] - *------------------------------------------*/ -BUILDIN_FUNC(getmapguildusers) -{ - const char *str; - int16 m; - int gid; - int i=0,c=0; - struct guild *g = NULL; - str=script_getstr(st,2); - gid=script_getnum(st,3); - if ((m = map_mapname2mapid(str)) < 0) { // map id on this server (m == -1 if not in actual map-server) - script_pushint(st,-1); - return 0; - } - g = guild_search(gid); - - if (g){ - for(i = 0; i < g->max_member; i++) - { - if (g->member[i].sd && g->member[i].sd->bl.m == m) - c++; - } - } - - script_pushint(st,c); - return 0; -} -/*========================================== - *------------------------------------------*/ -BUILDIN_FUNC(getmapusers) -{ - const char *str; - int16 m; - str=script_getstr(st,2); - if( (m=map_mapname2mapid(str))< 0){ - script_pushint(st,-1); - return 0; - } - script_pushint(st,map[m].users); - return 0; -} -/*========================================== - *------------------------------------------*/ -static int buildin_getareausers_sub(struct block_list *bl,va_list ap) -{ - int *users=va_arg(ap,int *); - (*users)++; - return 0; -} -BUILDIN_FUNC(getareausers) -{ - const char *str; - int16 m,x0,y0,x1,y1,users=0; //doubt we can have more then 32k users on - str=script_getstr(st,2); - x0=script_getnum(st,3); - y0=script_getnum(st,4); - x1=script_getnum(st,5); - y1=script_getnum(st,6); - if( (m=map_mapname2mapid(str))< 0){ - script_pushint(st,-1); - return 0; - } - map_foreachinarea(buildin_getareausers_sub, - m,x0,y0,x1,y1,BL_PC,&users); - script_pushint(st,users); - return 0; -} - -/*========================================== - *------------------------------------------*/ -static 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; -} -BUILDIN_FUNC(getareadropitem) -{ - const char *str; - int16 m,x0,y0,x1,y1; - int item,amount=0; - struct script_data *data; - - str=script_getstr(st,2); - x0=script_getnum(st,3); - y0=script_getnum(st,4); - x1=script_getnum(st,5); - y1=script_getnum(st,6); - - data=script_getdata(st,7); - get_val(st,data); - if( data_isstring(data) ){ - const char *name=conv_str(st,data); - struct item_data *item_data = itemdb_searchname(name); - item=UNKNOWN_ITEM_ID; - if( item_data ) - item=item_data->nameid; - }else - item=conv_num(st,data); - - if( (m=map_mapname2mapid(str))< 0){ - script_pushint(st,-1); - return 0; - } - map_foreachinarea(buildin_getareadropitem_sub, - m,x0,y0,x1,y1,BL_ITEM,item,&amount); - script_pushint(st,amount); - return 0; -} -/*========================================== - *------------------------------------------*/ -BUILDIN_FUNC(enablenpc) -{ - const char *str; - str=script_getstr(st,2); - npc_enable(str,1); - return 0; -} -/*========================================== - *------------------------------------------*/ -BUILDIN_FUNC(disablenpc) -{ - const char *str; - str=script_getstr(st,2); - npc_enable(str,0); - return 0; -} - -/*========================================== - *------------------------------------------*/ -BUILDIN_FUNC(hideoffnpc) -{ - const char *str; - str=script_getstr(st,2); - npc_enable(str,2); - return 0; -} -/*========================================== - *------------------------------------------*/ -BUILDIN_FUNC(hideonnpc) -{ - const char *str; - str=script_getstr(st,2); - npc_enable(str,4); - return 0; -} - -/// Starts a status effect on the target unit or on the attached player. -/// -/// sc_start <effect_id>,<duration>,<val1>{,<unit_id>}; -BUILDIN_FUNC(sc_start) -{ - struct block_list* bl; - enum sc_type type; - int tick; - int val1; - int val4 = 0; - - type = (sc_type)script_getnum(st,2); - tick = script_getnum(st,3); - val1 = script_getnum(st,4); - if( script_hasdata(st,5) ) - bl = map_id2bl(script_getnum(st,5)); - else - bl = map_id2bl(st->rid); - - if( tick == 0 && val1 > 0 && type > SC_NONE && type < SC_MAX && status_sc2skill(type) != 0 ) - {// When there isn't a duration specified, try to get it from the skill_db - tick = skill_get_time(status_sc2skill(type), val1); - } - - if( potion_flag == 1 && potion_target ) - { //skill.c set the flags before running the script, this must be a potion-pitched effect. - bl = map_id2bl(potion_target); - tick /= 2;// Thrown potions only last half. - val4 = 1;// Mark that this was a thrown sc_effect - } - - if( bl ) - status_change_start(bl, type, 10000, val1, 0, 0, val4, tick, 2); - - return 0; -} - -/// Starts a status effect on the target unit or on the attached player. -/// -/// sc_start2 <effect_id>,<duration>,<val1>,<percent chance>{,<unit_id>}; -BUILDIN_FUNC(sc_start2) -{ - struct block_list* bl; - enum sc_type type; - int tick; - int val1; - int val4 = 0; - int rate; - - type = (sc_type)script_getnum(st,2); - tick = script_getnum(st,3); - val1 = script_getnum(st,4); - rate = script_getnum(st,5); - if( script_hasdata(st,6) ) - bl = map_id2bl(script_getnum(st,6)); - else - bl = map_id2bl(st->rid); - - if( tick == 0 && val1 > 0 && type > SC_NONE && type < SC_MAX && status_sc2skill(type) != 0 ) - {// When there isn't a duration specified, try to get it from the skill_db - tick = skill_get_time(status_sc2skill(type), val1); - } - - if( potion_flag == 1 && potion_target ) - { //skill.c set the flags before running the script, this must be a potion-pitched effect. - bl = map_id2bl(potion_target); - tick /= 2;// Thrown potions only last half. - val4 = 1;// Mark that this was a thrown sc_effect - } - - if( bl ) - status_change_start(bl, type, rate, val1, 0, 0, val4, tick, 2); - - return 0; -} - -/// Starts a status effect on the target unit or on the attached player. -/// -/// sc_start4 <effect_id>,<duration>,<val1>,<val2>,<val3>,<val4>{,<unit_id>}; -BUILDIN_FUNC(sc_start4) -{ - struct block_list* bl; - enum sc_type type; - int tick; - int val1; - int val2; - int val3; - int val4; - - type = (sc_type)script_getnum(st,2); - tick = script_getnum(st,3); - val1 = script_getnum(st,4); - val2 = script_getnum(st,5); - val3 = script_getnum(st,6); - val4 = script_getnum(st,7); - if( script_hasdata(st,8) ) - bl = map_id2bl(script_getnum(st,8)); - else - bl = map_id2bl(st->rid); - - if( tick == 0 && val1 > 0 && type > SC_NONE && type < SC_MAX && status_sc2skill(type) != 0 ) - {// When there isn't a duration specified, try to get it from the skill_db - tick = skill_get_time(status_sc2skill(type), val1); - } - - if( potion_flag == 1 && potion_target ) - { //skill.c set the flags before running the script, this must be a potion-pitched effect. - bl = map_id2bl(potion_target); - tick /= 2;// Thrown potions only last half. - } - - if( bl ) - status_change_start(bl, type, 10000, val1, val2, val3, val4, tick, 2); - - return 0; -} - -/// Ends one or all status effects on the target unit or on the attached player. -/// -/// sc_end <effect_id>{,<unit_id>}; -BUILDIN_FUNC(sc_end) -{ - struct block_list* bl; - int type; - - type = script_getnum(st, 2); - if (script_hasdata(st, 3)) - bl = map_id2bl(script_getnum(st, 3)); - else - bl = map_id2bl(st->rid); - - if (potion_flag == 1 && potion_target) //##TODO how does this work [FlavioJS] - bl = map_id2bl(potion_target); - - if (!bl) - return 0; - - if (type >= 0 && type < SC_MAX) - { - struct status_change *sc = status_get_sc(bl); - struct status_change_entry *sce = sc ? sc->data[type] : NULL; - - if (!sce) - return 0; - - - switch (type) - { - case SC_WEIGHT50: - case SC_WEIGHT90: - case SC_NOCHAT: - case SC_PUSH_CART: - return 0; - - default: - break; - } - - //This should help status_change_end force disabling the SC in case it has no limit. - sce->val1 = sce->val2 = sce->val3 = sce->val4 = 0; - status_change_end(bl, (sc_type)type, INVALID_TIMER); - } - else - status_change_clear(bl, 3); // remove all effects - - return 0; -} - -/*========================================== - * @FIXME atm will return reduced tick, 0 immune, 1 no tick - *------------------------------------------*/ -BUILDIN_FUNC(getscrate) -{ - struct block_list *bl; - int type,rate; - - type=script_getnum(st,2); - rate=script_getnum(st,3); - if( script_hasdata(st,4) ) //get for the bl assigned - bl = map_id2bl(script_getnum(st,4)); - else - bl = map_id2bl(st->rid); - - if (bl) - rate = status_get_sc_def(bl, (sc_type)type, 10000, 10000, 0); - - script_pushint(st,rate); - return 0; -} - -/*========================================== - * getstatus <type>{, <info>}; - *------------------------------------------*/ -BUILDIN_FUNC(getstatus) -{ - int id, type; - struct map_session_data* sd = script_rid2sd(st); - - if( sd == NULL ) - {// no player attached - return 0; - } - - id = script_getnum(st, 2); - type = script_hasdata(st, 3) ? script_getnum(st, 3) : 0; - - if( id <= SC_NONE || id >= SC_MAX ) - {// invalid status type given - ShowWarning("script.c:getstatus: Invalid status type given (%d).\n", id); - return 0; - } - - if( sd->sc.count == 0 || !sd->sc.data[id] ) - {// no status is active - script_pushint(st, 0); - return 0; - } - - switch( type ) - { - case 1: script_pushint(st, sd->sc.data[id]->val1); break; - case 2: script_pushint(st, sd->sc.data[id]->val2); break; - case 3: script_pushint(st, sd->sc.data[id]->val3); break; - case 4: script_pushint(st, sd->sc.data[id]->val4); break; - case 5: - { - struct TimerData* timer = (struct TimerData*)get_timer(sd->sc.data[id]->timer); - - if( timer ) - {// return the amount of time remaining - script_pushint(st, timer->tick - gettick()); - } - } - break; - default: script_pushint(st, 1); break; - } - - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -BUILDIN_FUNC(debugmes) -{ - const char *str; - str=script_getstr(st,2); - ShowDebug("script debug : %d %d : %s\n",st->rid,st->oid,str); - return 0; -} - -/*========================================== - *------------------------------------------*/ -BUILDIN_FUNC(catchpet) -{ - int pet_id; - TBL_PC *sd; - - pet_id= script_getnum(st,2); - sd=script_rid2sd(st); - if( sd == NULL ) - return 0; - - pet_catch_process1(sd,pet_id); - return 0; -} - -/*========================================== - * [orn] - *------------------------------------------*/ -BUILDIN_FUNC(homunculus_evolution) -{ - TBL_PC *sd; - - sd=script_rid2sd(st); - if( sd == NULL ) - return 0; - - if(merc_is_hom_active(sd->hd)) - { - if (sd->hd->homunculus.intimacy > 91000) - merc_hom_evolution(sd->hd); - else - clif_emotion(&sd->hd->bl, E_SWT); - } - return 0; -} - -/*========================================== - * [Xantara] - *------------------------------------------*/ -BUILDIN_FUNC(homunculus_mutate) -{ - int homun_id, m_class, m_id; - TBL_PC *sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - if(script_hasdata(st,2)) - homun_id = script_getnum(st,2); - else - homun_id = 6048 + (rnd() % 4); - - if(merc_is_hom_active(sd->hd)) { - m_class = hom_class2mapid(sd->hd->homunculus.class_); - m_id = hom_class2mapid(homun_id); - - if ( m_class != -1 && m_id != -1 && m_class&HOM_EVO && m_id&HOM_S && sd->hd->homunculus.level >= 99 ) - hom_mutate(sd->hd, homun_id); - else - clif_emotion(&sd->hd->bl, E_SWT); - } - return 0; -} - -// [Zephyrus] -BUILDIN_FUNC(homunculus_shuffle) -{ - TBL_PC *sd; - - sd=script_rid2sd(st); - if( sd == NULL ) - return 0; - - if(merc_is_hom_active(sd->hd)) - merc_hom_shuffle(sd->hd); - - return 0; -} - -//These two functions bring the eA MAPID_* class functionality to scripts. -BUILDIN_FUNC(eaclass) -{ - int class_; - if( script_hasdata(st,2) ) - class_ = script_getnum(st,2); - else { - TBL_PC *sd; - sd=script_rid2sd(st); - if (!sd) { - script_pushint(st,-1); - return 0; - } - class_ = sd->status.class_; - } - script_pushint(st,pc_jobid2mapid(class_)); - return 0; -} - -BUILDIN_FUNC(roclass) -{ - int class_ =script_getnum(st,2); - int sex; - if( script_hasdata(st,3) ) - sex = script_getnum(st,3); - else { - TBL_PC *sd; - if (st->rid && (sd=script_rid2sd(st))) - sex = sd->status.sex; - else - sex = 1; //Just use male when not found. - } - script_pushint(st,pc_mapid2jobid(class_, sex)); - return 0; -} - -/*========================================== - * Tells client to open a hatching window, used for pet incubator - *------------------------------------------*/ -BUILDIN_FUNC(birthpet) -{ - TBL_PC *sd; - sd=script_rid2sd(st); - if( sd == NULL ) - return 0; - - if( sd->status.pet_id ) - {// do not send egg list, when you already have a pet - return 0; - } - - clif_sendegg(sd); - return 0; -} - -/*========================================== - * Added - AppleGirl For Advanced Classes, (Updated for Cleaner Script Purposes) - * @type - * 1 : make like after rebirth - * 2 : blvl,jlvl=1, skillpoint=0 - * 3 : don't reset skill, blvl=1 - * 4 : jlvl=0 - *------------------------------------------*/ -BUILDIN_FUNC(resetlvl) -{ - TBL_PC *sd; - - int type=script_getnum(st,2); - - sd=script_rid2sd(st); - if( sd == NULL ) - return 0; - - pc_resetlvl(sd,type); - return 0; -} -/*========================================== - * Reset a player status point - *------------------------------------------*/ -BUILDIN_FUNC(resetstatus) -{ - TBL_PC *sd; - sd=script_rid2sd(st); - pc_resetstate(sd); - return 0; -} - -/*========================================== - * script command resetskill - *------------------------------------------*/ -BUILDIN_FUNC(resetskill) -{ - TBL_PC *sd; - sd=script_rid2sd(st); - pc_resetskill(sd,1); - return 0; -} - -/*========================================== - * Counts total amount of skill points. - *------------------------------------------*/ -BUILDIN_FUNC(skillpointcount) -{ - TBL_PC *sd; - sd=script_rid2sd(st); - script_pushint(st,sd->status.skill_point + pc_resetskill(sd,2)); - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -BUILDIN_FUNC(changebase) -{ - TBL_PC *sd=NULL; - int vclass; - - if( script_hasdata(st,3) ) - sd=map_id2sd(script_getnum(st,3)); - else - sd=script_rid2sd(st); - - if(sd == NULL) - return 0; - - vclass = script_getnum(st,2); - if(vclass == JOB_WEDDING) - { - if (!battle_config.wedding_modifydisplay || //Do not show the wedding sprites - sd->class_&JOBL_BABY //Baby classes screw up when showing wedding sprites. [Skotlex] They don't seem to anymore. - ) - return 0; - } - - if(!sd->disguise && vclass != sd->vd.class_) { - status_set_viewdata(&sd->bl, vclass); - //Updated client view. Base, Weapon and Cloth Colors. - clif_changelook(&sd->bl,LOOK_BASE,sd->vd.class_); - clif_changelook(&sd->bl,LOOK_WEAPON,sd->status.weapon); - if (sd->vd.cloth_color) - clif_changelook(&sd->bl,LOOK_CLOTHES_COLOR,sd->vd.cloth_color); - clif_skillinfoblock(sd); - } - - return 0; -} - -/*========================================== - * Unequip all item and request for a changesex to char-serv - *------------------------------------------*/ -BUILDIN_FUNC(changesex) -{ - int i; - TBL_PC *sd = NULL; - sd = script_rid2sd(st); - - pc_resetskill(sd,4); - // to avoid any problem with equipment and invalid sex, equipment is unequiped. - for( i=0; i<EQI_MAX; i++ ) - if( sd->equip_index[i] >= 0 ) pc_unequipitem(sd, sd->equip_index[i], 3); - chrif_changesex(sd); - return 0; -} - -/*========================================== - * Works like 'announce' but outputs in the common chat window - *------------------------------------------*/ -BUILDIN_FUNC(globalmes) -{ - struct block_list *bl = map_id2bl(st->oid); - struct npc_data *nd = (struct npc_data *)bl; - const char *name=NULL,*mes; - - mes=script_getstr(st,2); - if(mes==NULL) return 0; - - if(script_hasdata(st,3)){ // npc name to display - name=script_getstr(st,3); - } else { - name=nd->name; //use current npc name - } - - npc_globalmessage(name,mes); // broadcast to all players connected - - return 0; -} - -///////////////////////////////////////////////////////////////////// -// NPC waiting room (chat room) -// - -/// Creates a waiting room (chat room) for this npc. -/// -/// waitingroom "<title>",<limit>{,"<event>"{,<trigger>{,<zeny>{,<minlvl>{,<maxlvl>}}}}}; -BUILDIN_FUNC(waitingroom) -{ - struct npc_data* nd; - int pub = 1; - const char* title = script_getstr(st, 2); - int limit = script_getnum(st, 3); - const char* ev = script_hasdata(st,4) ? script_getstr(st,4) : ""; - int trigger = script_hasdata(st,5) ? script_getnum(st,5) : limit; - int zeny = script_hasdata(st,6) ? script_getnum(st,6) : 0; - int minLvl = script_hasdata(st,7) ? script_getnum(st,7) : 1; - int maxLvl = script_hasdata(st,8) ? script_getnum(st,8) : MAX_LEVEL; - - nd = (struct npc_data *)map_id2bl(st->oid); - if( nd != NULL ) - chat_createnpcchat(nd, title, limit, pub, trigger, ev, zeny, minLvl, maxLvl); - - return 0; -} - -/// Removes the waiting room of the current or target npc. -/// -/// delwaitingroom "<npc_name>"; -/// delwaitingroom; -BUILDIN_FUNC(delwaitingroom) -{ - struct npc_data* nd; - if( script_hasdata(st,2) ) - nd = npc_name2id(script_getstr(st, 2)); - else - nd = (struct npc_data *)map_id2bl(st->oid); - if( nd != NULL ) - chat_deletenpcchat(nd); - return 0; -} - -/// Kicks all the players from the waiting room of the current or target npc. -/// -/// kickwaitingroomall "<npc_name>"; -/// kickwaitingroomall; -BUILDIN_FUNC(waitingroomkickall) -{ - struct npc_data* nd; - struct chat_data* cd; - - if( script_hasdata(st,2) ) - nd = npc_name2id(script_getstr(st,2)); - else - nd = (struct npc_data *)map_id2bl(st->oid); - - if( nd != NULL && (cd=(struct chat_data *)map_id2bl(nd->chat_id)) != NULL ) - chat_npckickall(cd); - return 0; -} - -/// Enables the waiting room event of the current or target npc. -/// -/// enablewaitingroomevent "<npc_name>"; -/// enablewaitingroomevent; -BUILDIN_FUNC(enablewaitingroomevent) -{ - struct npc_data* nd; - struct chat_data* cd; - - if( script_hasdata(st,2) ) - nd = npc_name2id(script_getstr(st, 2)); - else - nd = (struct npc_data *)map_id2bl(st->oid); - - if( nd != NULL && (cd=(struct chat_data *)map_id2bl(nd->chat_id)) != NULL ) - chat_enableevent(cd); - return 0; -} - -/// Disables the waiting room event of the current or target npc. -/// -/// disablewaitingroomevent "<npc_name>"; -/// disablewaitingroomevent; -BUILDIN_FUNC(disablewaitingroomevent) -{ - struct npc_data *nd; - struct chat_data *cd; - - if( script_hasdata(st,2) ) - nd = npc_name2id(script_getstr(st, 2)); - else - nd = (struct npc_data *)map_id2bl(st->oid); - - if( nd != NULL && (cd=(struct chat_data *)map_id2bl(nd->chat_id)) != NULL ) - chat_disableevent(cd); - return 0; -} - -/// Returns info on the waiting room of the current or target npc. -/// Returns -1 if the type unknown -/// <type>=0 : current number of users -/// <type>=1 : maximum number of users allowed -/// <type>=2 : the number of users that trigger the event -/// <type>=3 : if the trigger is disabled -/// <type>=4 : the title of the waiting room -/// <type>=5 : the password of the waiting room -/// <type>=16 : the name of the waiting room event -/// <type>=32 : if the waiting room is full -/// <type>=33 : if there are enough users to trigger the event -/// -/// getwaitingroomstate(<type>,"<npc_name>") -> <info> -/// getwaitingroomstate(<type>) -> <info> -BUILDIN_FUNC(getwaitingroomstate) -{ - struct npc_data *nd; - struct chat_data *cd; - int type; - - type = script_getnum(st,2); - if( script_hasdata(st,3) ) - nd = npc_name2id(script_getstr(st, 3)); - else - nd = (struct npc_data *)map_id2bl(st->oid); - - if( nd == NULL || (cd=(struct chat_data *)map_id2bl(nd->chat_id)) == NULL ) - { - script_pushint(st, -1); - return 0; - } - - switch(type) - { - case 0: script_pushint(st, cd->users); break; - case 1: script_pushint(st, cd->limit); break; - case 2: script_pushint(st, cd->trigger&0x7f); break; - case 3: script_pushint(st, ((cd->trigger&0x80)!=0)); break; - case 4: script_pushstrcopy(st, cd->title); break; - case 5: script_pushstrcopy(st, cd->pass); break; - case 16: script_pushstrcopy(st, cd->npc_event);break; - case 32: script_pushint(st, (cd->users >= cd->limit)); break; - case 33: script_pushint(st, (cd->users >= cd->trigger)); break; - default: script_pushint(st, -1); break; - } - return 0; -} - -/// Warps the trigger or target amount of players to the target map and position. -/// Players are automatically removed from the waiting room. -/// Those waiting the longest will get warped first. -/// The target map can be "Random" for a random position in the current map, -/// and "SavePoint" for the savepoint map+position. -/// The map flag noteleport of the current map is only considered when teleporting to the savepoint. -/// -/// The id's of the teleported players are put into the array $@warpwaitingpc[] -/// The total number of teleported players is put into $@warpwaitingpcnum -/// -/// warpwaitingpc "<map name>",<x>,<y>,<number of players>; -/// warpwaitingpc "<map name>",<x>,<y>; -BUILDIN_FUNC(warpwaitingpc) -{ - int x; - int y; - int i; - int n; - const char* map_name; - struct npc_data* nd; - struct chat_data* cd; - TBL_PC* sd; - - nd = (struct npc_data *)map_id2bl(st->oid); - if( nd == NULL || (cd=(struct chat_data *)map_id2bl(nd->chat_id)) == NULL ) - return 0; - - map_name = script_getstr(st,2); - x = script_getnum(st,3); - y = script_getnum(st,4); - n = cd->trigger&0x7f; - - if( script_hasdata(st,5) ) - n = script_getnum(st,5); - - for( i = 0; i < n && cd->users > 0; i++ ) - { - sd = cd->usersd[0]; - - if( strcmp(map_name,"SavePoint") == 0 && map[sd->bl.m].flag.noteleport ) - {// can't teleport on this map - break; - } - - if( cd->zeny ) - {// fee set - if( (uint32)sd->status.zeny < cd->zeny ) - {// no zeny to cover set fee - break; - } - pc_payzeny(sd, cd->zeny, LOG_TYPE_NPC, NULL); - } - - mapreg_setreg(reference_uid(add_str("$@warpwaitingpc"), i), sd->bl.id); - - if( strcmp(map_name,"Random") == 0 ) - pc_randomwarp(sd,CLR_TELEPORT); - else if( strcmp(map_name,"SavePoint") == 0 ) - pc_setpos(sd, sd->status.save_point.map, sd->status.save_point.x, sd->status.save_point.y, CLR_TELEPORT); - else - pc_setpos(sd, mapindex_name2id(map_name), x, y, CLR_OUTSIGHT); - } - mapreg_setreg(add_str("$@warpwaitingpcnum"), i); - return 0; -} - -///////////////////////////////////////////////////////////////////// -// ... -// - -/// Detaches a character from a script. -/// -/// @param st Script state to detach the character from. -static void script_detach_rid(struct script_state* st) -{ - if(st->rid) - { - script_detach_state(st, false); - st->rid = 0; - } -} - -/*========================================== - * Attach sd char id to script and detach current one if any - *------------------------------------------*/ -BUILDIN_FUNC(attachrid) -{ - int rid = script_getnum(st,2); - struct map_session_data* sd; - - if ((sd = map_id2sd(rid))!=NULL) { - script_detach_rid(st); - - st->rid = rid; - script_attach_state(st); - script_pushint(st,1); - } else - script_pushint(st,0); - return 0; -} -/*========================================== - * Detach script to rid - *------------------------------------------*/ -BUILDIN_FUNC(detachrid) -{ - script_detach_rid(st); - return 0; -} -/*========================================== - * Chk if account connected, (and charid from account if specified) - *------------------------------------------*/ -BUILDIN_FUNC(isloggedin) -{ - TBL_PC* sd = map_id2sd(script_getnum(st,2)); - if (script_hasdata(st,3) && sd && - sd->status.char_id != script_getnum(st,3)) - sd = NULL; - push_val(st->stack,C_INT,sd!=NULL); - return 0; -} - - -/*========================================== - * - *------------------------------------------*/ -BUILDIN_FUNC(setmapflagnosave) -{ - int16 m,x,y; - unsigned short mapindex; - const char *str,*str2; - - str=script_getstr(st,2); - str2=script_getstr(st,3); - x=script_getnum(st,4); - y=script_getnum(st,5); - m = map_mapname2mapid(str); - mapindex = mapindex_name2id(str2); - - if(m >= 0 && mapindex) { - map[m].flag.nosave=1; - map[m].save.map=mapindex; - map[m].save.x=x; - map[m].save.y=y; - } - - return 0; -} - -BUILDIN_FUNC(getmapflag) -{ - int16 m,i; - const char *str; - - str=script_getstr(st,2); - i=script_getnum(st,3); - - m = map_mapname2mapid(str); - if(m >= 0) { - switch(i) { - case MF_NOMEMO: script_pushint(st,map[m].flag.nomemo); break; - case MF_NOTELEPORT: script_pushint(st,map[m].flag.noteleport); break; - case MF_NOSAVE: script_pushint(st,map[m].flag.nosave); break; - case MF_NOBRANCH: script_pushint(st,map[m].flag.nobranch); break; - case MF_NOPENALTY: script_pushint(st,map[m].flag.noexppenalty); break; - case MF_NOZENYPENALTY: script_pushint(st,map[m].flag.nozenypenalty); break; - case MF_PVP: script_pushint(st,map[m].flag.pvp); break; - case MF_PVP_NOPARTY: script_pushint(st,map[m].flag.pvp_noparty); break; - case MF_PVP_NOGUILD: script_pushint(st,map[m].flag.pvp_noguild); break; - case MF_GVG: script_pushint(st,map[m].flag.gvg); break; - case MF_GVG_NOPARTY: script_pushint(st,map[m].flag.gvg_noparty); break; - case MF_NOTRADE: script_pushint(st,map[m].flag.notrade); break; - case MF_NOSKILL: script_pushint(st,map[m].flag.noskill); break; - case MF_NOWARP: script_pushint(st,map[m].flag.nowarp); break; - case MF_PARTYLOCK: script_pushint(st,map[m].flag.partylock); break; - case MF_NOICEWALL: script_pushint(st,map[m].flag.noicewall); break; - case MF_SNOW: script_pushint(st,map[m].flag.snow); break; - case MF_FOG: script_pushint(st,map[m].flag.fog); break; - case MF_SAKURA: script_pushint(st,map[m].flag.sakura); break; - case MF_LEAVES: script_pushint(st,map[m].flag.leaves); break; - /** - * No longer available, keeping here just in case it's back someday. [Ind] - **/ - //case MF_RAIN: script_pushint(st,map[m].flag.rain); break; - case MF_NOGO: script_pushint(st,map[m].flag.nogo); break; - case MF_CLOUDS: script_pushint(st,map[m].flag.clouds); break; - case MF_CLOUDS2: script_pushint(st,map[m].flag.clouds2); break; - case MF_FIREWORKS: script_pushint(st,map[m].flag.fireworks); break; - case MF_GVG_CASTLE: script_pushint(st,map[m].flag.gvg_castle); break; - case MF_GVG_DUNGEON: script_pushint(st,map[m].flag.gvg_dungeon); break; - case MF_NIGHTENABLED: script_pushint(st,map[m].flag.nightenabled); break; - case MF_NOBASEEXP: script_pushint(st,map[m].flag.nobaseexp); break; - case MF_NOJOBEXP: script_pushint(st,map[m].flag.nojobexp); break; - case MF_NOMOBLOOT: script_pushint(st,map[m].flag.nomobloot); break; - case MF_NOMVPLOOT: script_pushint(st,map[m].flag.nomvploot); break; - case MF_NORETURN: script_pushint(st,map[m].flag.noreturn); break; - case MF_NOWARPTO: script_pushint(st,map[m].flag.nowarpto); break; - case MF_NIGHTMAREDROP: script_pushint(st,map[m].flag.pvp_nightmaredrop); break; - case MF_RESTRICTED: script_pushint(st,map[m].flag.restricted); break; - case MF_NOCOMMAND: script_pushint(st,map[m].nocommand); break; - case MF_NODROP: script_pushint(st,map[m].flag.nodrop); break; - case MF_JEXP: script_pushint(st,map[m].jexp); break; - case MF_BEXP: script_pushint(st,map[m].bexp); break; - case MF_NOVENDING: script_pushint(st,map[m].flag.novending); break; - case MF_LOADEVENT: script_pushint(st,map[m].flag.loadevent); break; - case MF_NOCHAT: script_pushint(st,map[m].flag.nochat); break; - case MF_NOEXPPENALTY: script_pushint(st,map[m].flag.noexppenalty ); break; - case MF_GUILDLOCK: script_pushint(st,map[m].flag.guildlock); break; - case MF_TOWN: script_pushint(st,map[m].flag.town); break; - case MF_AUTOTRADE: script_pushint(st,map[m].flag.autotrade); break; - case MF_ALLOWKS: script_pushint(st,map[m].flag.allowks); break; - case MF_MONSTER_NOTELEPORT: script_pushint(st,map[m].flag.monster_noteleport); break; - case MF_PVP_NOCALCRANK: script_pushint(st,map[m].flag.pvp_nocalcrank); break; - case MF_BATTLEGROUND: script_pushint(st,map[m].flag.battleground); break; - case MF_RESET: script_pushint(st,map[m].flag.reset); break; - } - } - - return 0; -} -/* pvp timer handling */ -static int script_mapflag_pvp_sub(struct block_list *bl,va_list ap) { - TBL_PC* sd = (TBL_PC*)bl; - if (sd->pvp_timer == INVALID_TIMER) { - 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; - sd->pvp_won = 0; - sd->pvp_lost = 0; - } - clif_map_property(sd, MAPPROPERTY_FREEPVPZONE); - return 0; -} -BUILDIN_FUNC(setmapflag) -{ - int16 m,i; - const char *str; - int val=0; - - str=script_getstr(st,2); - i=script_getnum(st,3); - if(script_hasdata(st,4)){ - val=script_getnum(st,4); - } - 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_NOSAVE: map[m].flag.nosave = 1; break; - case MF_NOBRANCH: map[m].flag.nobranch = 1; break; - case MF_NOPENALTY: map[m].flag.noexppenalty = 1; map[m].flag.nozenypenalty = 1; break; - case MF_NOZENYPENALTY: map[m].flag.nozenypenalty = 1; break; - case MF_PVP: - map[m].flag.pvp = 1; - if( !battle_config.pk_mode ) { - map_foreachinmap(script_mapflag_pvp_sub,m,BL_PC); - } - 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: - map[m].flag.gvg = 1; - clif_map_property_mapall(m, MAPPROPERTY_AGITZONE); - break; - case MF_GVG_NOPARTY: map[m].flag.gvg_noparty = 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_PARTYLOCK: map[m].flag.partylock = 1; break; - case MF_NOICEWALL: map[m].flag.noicewall = 1; break; - case MF_SNOW: map[m].flag.snow = 1; break; - case MF_FOG: map[m].flag.fog = 1; break; - case MF_SAKURA: map[m].flag.sakura = 1; break; - case MF_LEAVES: map[m].flag.leaves = 1; break; - /** - * No longer available, keeping here just in case it's back someday. [Ind] - **/ - //case MF_RAIN: map[m].flag.rain = 1; break; - case MF_NOGO: map[m].flag.nogo = 1; break; - case MF_CLOUDS: map[m].flag.clouds = 1; break; - case MF_CLOUDS2: map[m].flag.clouds2 = 1; break; - case MF_FIREWORKS: map[m].flag.fireworks = 1; break; - case MF_GVG_CASTLE: map[m].flag.gvg_castle = 1; break; - case MF_GVG_DUNGEON: map[m].flag.gvg_dungeon = 1; break; - case MF_NIGHTENABLED: map[m].flag.nightenabled = 1; break; - case MF_NOBASEEXP: map[m].flag.nobaseexp = 1; break; - case MF_NOJOBEXP: map[m].flag.nojobexp = 1; break; - case MF_NOMOBLOOT: map[m].flag.nomobloot = 1; break; - case MF_NOMVPLOOT: map[m].flag.nomvploot = 1; break; - case MF_NORETURN: map[m].flag.noreturn = 1; break; - case MF_NOWARPTO: map[m].flag.nowarpto = 1; break; - case MF_NIGHTMAREDROP: map[m].flag.pvp_nightmaredrop = 1; break; - case MF_RESTRICTED: - map[m].zone |= 1<<(val+1); - map[m].flag.restricted=1; - break; - case MF_NOCOMMAND: map[m].nocommand = (val <= 0) ? 100 : val; break; - case MF_NODROP: map[m].flag.nodrop = 1; break; - case MF_JEXP: map[m].jexp = (val <= 0) ? 100 : val; break; - case MF_BEXP: map[m].bexp = (val <= 0) ? 100 : val; break; - case MF_NOVENDING: map[m].flag.novending = 1; break; - case MF_LOADEVENT: map[m].flag.loadevent = 1; break; - case MF_NOCHAT: map[m].flag.nochat = 1; break; - case MF_NOEXPPENALTY: map[m].flag.noexppenalty = 1; break; - case MF_GUILDLOCK: map[m].flag.guildlock = 1; break; - case MF_TOWN: map[m].flag.town = 1; break; - case MF_AUTOTRADE: map[m].flag.autotrade = 1; break; - case MF_ALLOWKS: map[m].flag.allowks = 1; break; - case MF_MONSTER_NOTELEPORT: map[m].flag.monster_noteleport = 1; break; - case MF_PVP_NOCALCRANK: map[m].flag.pvp_nocalcrank = 1; break; - case MF_BATTLEGROUND: map[m].flag.battleground = (val <= 0 || val > 2) ? 1 : val; break; - case MF_RESET: map[m].flag.reset = 1; break; - } - } - - return 0; -} - -BUILDIN_FUNC(removemapflag) -{ - int16 m,i; - const char *str; - int val=0; - - str=script_getstr(st,2); - i=script_getnum(st,3); - if(script_hasdata(st,4)){ - val=script_getnum(st,4); - } - 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.noexppenalty = 0; map[m].flag.nozenypenalty = 0; break; - case MF_NOZENYPENALTY: map[m].flag.nozenypenalty = 0; break; - case MF_PVP: - map[m].flag.pvp = 0; - clif_map_property_mapall(m, MAPPROPERTY_NOTHING); - 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: - map[m].flag.gvg = 0; - clif_map_property_mapall(m, MAPPROPERTY_NOTHING); - break; - case MF_GVG_NOPARTY: map[m].flag.gvg_noparty = 0; break; - case MF_NOTRADE: map[m].flag.notrade = 0; break; - case MF_NOSKILL: map[m].flag.noskill = 0; break; - case MF_NOWARP: map[m].flag.nowarp = 0; break; - case MF_PARTYLOCK: map[m].flag.partylock = 0; break; - case MF_NOICEWALL: map[m].flag.noicewall = 0; break; - case MF_SNOW: map[m].flag.snow = 0; break; - case MF_FOG: map[m].flag.fog = 0; break; - case MF_SAKURA: map[m].flag.sakura = 0; break; - case MF_LEAVES: map[m].flag.leaves = 0; break; - /** - * No longer available, keeping here just in case it's back someday. [Ind] - **/ - //case MF_RAIN: map[m].flag.rain = 0; break; - case MF_NOGO: map[m].flag.nogo = 0; break; - case MF_CLOUDS: map[m].flag.clouds = 0; break; - case MF_CLOUDS2: map[m].flag.clouds2 = 0; break; - case MF_FIREWORKS: map[m].flag.fireworks = 0; break; - case MF_GVG_CASTLE: map[m].flag.gvg_castle = 0; break; - case MF_GVG_DUNGEON: map[m].flag.gvg_dungeon = 0; break; - case MF_NIGHTENABLED: map[m].flag.nightenabled = 0; break; - case MF_NOBASEEXP: map[m].flag.nobaseexp = 0; break; - case MF_NOJOBEXP: map[m].flag.nojobexp = 0; break; - case MF_NOMOBLOOT: map[m].flag.nomobloot = 0; break; - case MF_NOMVPLOOT: map[m].flag.nomvploot = 0; break; - case MF_NORETURN: map[m].flag.noreturn = 0; break; - case MF_NOWARPTO: map[m].flag.nowarpto = 0; break; - case MF_NIGHTMAREDROP: map[m].flag.pvp_nightmaredrop = 0; break; - case MF_RESTRICTED: - map[m].zone ^= 1<<(val+1); - if (map[m].zone == 0){ - map[m].flag.restricted=0; - } - break; - case MF_NOCOMMAND: map[m].nocommand = 0; break; - case MF_NODROP: map[m].flag.nodrop = 0; break; - case MF_JEXP: map[m].jexp = 0; break; - case MF_BEXP: map[m].bexp = 0; break; - case MF_NOVENDING: map[m].flag.novending = 0; break; - case MF_LOADEVENT: map[m].flag.loadevent = 0; break; - case MF_NOCHAT: map[m].flag.nochat = 0; break; - case MF_NOEXPPENALTY: map[m].flag.noexppenalty = 0; break; - case MF_GUILDLOCK: map[m].flag.guildlock = 0; break; - case MF_TOWN: map[m].flag.town = 0; break; - case MF_AUTOTRADE: map[m].flag.autotrade = 0; break; - case MF_ALLOWKS: map[m].flag.allowks = 0; break; - case MF_MONSTER_NOTELEPORT: map[m].flag.monster_noteleport = 0; break; - case MF_PVP_NOCALCRANK: map[m].flag.pvp_nocalcrank = 0; break; - case MF_BATTLEGROUND: map[m].flag.battleground = 0; break; - case MF_RESET: map[m].flag.reset = 0; break; - } - } - - return 0; -} - -BUILDIN_FUNC(pvpon) -{ - int16 m; - const char *str; - TBL_PC* sd = NULL; - struct s_mapiterator* iter; - - str = script_getstr(st,2); - m = map_mapname2mapid(str); - if( m < 0 || map[m].flag.pvp ) - return 0; // nothing to do - - map[m].flag.pvp = 1; - clif_map_property_mapall(m, MAPPROPERTY_FREEPVPZONE); - - if(battle_config.pk_mode) // disable ranking functions if pk_mode is on [Valaris] - return 0; - - iter = mapit_getallusers(); - for( sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC*)mapit_next(iter) ) - { - if( sd->bl.m != m || sd->pvp_timer != INVALID_TIMER ) - continue; // not applicable - - 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; - sd->pvp_won = 0; - sd->pvp_lost = 0; - } - mapit_free(iter); - - return 0; -} - -static int buildin_pvpoff_sub(struct block_list *bl,va_list ap) -{ - TBL_PC* sd = (TBL_PC*)bl; - clif_pvpset(sd, 0, 0, 2); - if (sd->pvp_timer != INVALID_TIMER) { - delete_timer(sd->pvp_timer, pc_calc_pvprank_timer); - sd->pvp_timer = INVALID_TIMER; - } - return 0; -} - -BUILDIN_FUNC(pvpoff) -{ - int16 m; - const char *str; - - str=script_getstr(st,2); - m = map_mapname2mapid(str); - if(m < 0 || !map[m].flag.pvp) - return 0; //fixed Lupus - - map[m].flag.pvp = 0; - clif_map_property_mapall(m, MAPPROPERTY_NOTHING); - - if(battle_config.pk_mode) // disable ranking options if pk_mode is on [Valaris] - return 0; - - map_foreachinmap(buildin_pvpoff_sub, m, BL_PC); - return 0; -} - -BUILDIN_FUNC(gvgon) -{ - int16 m; - const char *str; - - str=script_getstr(st,2); - m = map_mapname2mapid(str); - if(m >= 0 && !map[m].flag.gvg) { - map[m].flag.gvg = 1; - clif_map_property_mapall(m, MAPPROPERTY_AGITZONE); - } - - return 0; -} -BUILDIN_FUNC(gvgoff) -{ - int16 m; - const char *str; - - str=script_getstr(st,2); - m = map_mapname2mapid(str); - if(m >= 0 && map[m].flag.gvg) { - map[m].flag.gvg = 0; - clif_map_property_mapall(m, MAPPROPERTY_NOTHING); - } - - return 0; -} -/*========================================== - * Shows an emoticon on top of the player/npc - * emotion emotion#, <target: 0 - NPC, 1 - PC>, <NPC/PC name> - *------------------------------------------*/ -//Optional second parameter added by [Skotlex] -BUILDIN_FUNC(emotion) -{ - int type; - int player=0; - - type=script_getnum(st,2); - if(type < 0 || type > 100) - return 0; - - if( script_hasdata(st,3) ) - player=script_getnum(st,3); - - if (player) { - TBL_PC *sd = NULL; - if( script_hasdata(st,4) ) - sd = map_nick2sd(script_getstr(st,4)); - else - sd = script_rid2sd(st); - if (sd) - clif_emotion(&sd->bl,type); - } else - if( script_hasdata(st,4) ) - { - TBL_NPC *nd = npc_name2id(script_getstr(st,4)); - if(nd) - clif_emotion(&nd->bl,type); - } - else - clif_emotion(map_id2bl(st->oid),type); - return 0; -} - -static int buildin_maprespawnguildid_sub_pc(struct map_session_data* sd, va_list ap) -{ - int16 m=va_arg(ap,int); - int g_id=va_arg(ap,int); - int flag=va_arg(ap,int); - - if(!sd || sd->bl.m != m) - return 0; - if( - (sd->status.guild_id == g_id && flag&1) || //Warp out owners - (sd->status.guild_id != g_id && flag&2) || //Warp out outsiders - (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,CLR_TELEPORT); - return 1; -} - -static int buildin_maprespawnguildid_sub_mob(struct block_list *bl,va_list ap) -{ - struct mob_data *md=(struct mob_data *)bl; - - if(!md->guardian_data && md->class_ != MOBID_EMPERIUM) - status_kill(bl); - - return 0; -} - -BUILDIN_FUNC(maprespawnguildid) -{ - const char *mapname=script_getstr(st,2); - int g_id=script_getnum(st,3); - int flag=script_getnum(st,4); - - int16 m=map_mapname2mapid(mapname); - - if(m == -1) - return 0; - - //Catch ALL players (in case some are 'between maps' on execution time) - map_foreachpc(buildin_maprespawnguildid_sub_pc,m,g_id,flag); - if (flag&4) //Remove script mobs. - map_foreachinmap(buildin_maprespawnguildid_sub_mob,m,BL_MOB); - return 0; -} - -BUILDIN_FUNC(agitstart) -{ - if(agit_flag==1) return 0; // Agit already Start. - agit_flag=1; - guild_agit_start(); - return 0; -} - -BUILDIN_FUNC(agitend) -{ - if(agit_flag==0) return 0; // Agit already End. - agit_flag=0; - guild_agit_end(); - return 0; -} - -BUILDIN_FUNC(agitstart2) -{ - if(agit2_flag==1) return 0; // Agit2 already Start. - agit2_flag=1; - guild_agit2_start(); - return 0; -} - -BUILDIN_FUNC(agitend2) -{ - if(agit2_flag==0) return 0; // Agit2 already End. - agit2_flag=0; - guild_agit2_end(); - return 0; -} - -/*========================================== - * Returns whether woe is on or off. // choice script - *------------------------------------------*/ -BUILDIN_FUNC(agitcheck) -{ - script_pushint(st,agit_flag); - return 0; -} - -/*========================================== - * Returns whether woese is on or off. // choice script - *------------------------------------------*/ -BUILDIN_FUNC(agitcheck2) -{ - script_pushint(st,agit2_flag); - return 0; -} - -/// Sets the guild_id of this npc. -/// -/// flagemblem <guild_id>; -BUILDIN_FUNC(flagemblem) -{ - TBL_NPC* nd; - int g_id = script_getnum(st,2); - - if(g_id < 0) return 0; - - nd = (TBL_NPC*)map_id2nd(st->oid); - if( nd == NULL ) { - ShowError("script:flagemblem: npc %d not found\n", st->oid); - } else if( nd->subtype != SCRIPT ) { - ShowError("script:flagemblem: unexpected subtype %d for npc %d '%s'\n", nd->subtype, st->oid, nd->exname); - } else { - bool changed = ( nd->u.scr.guild_id != g_id )?true:false; - nd->u.scr.guild_id = g_id; - clif_guild_emblem_area(&nd->bl); - /* guild flag caching */ - if( g_id ) /* adding a id */ - guild_flag_add(nd); - else if( changed ) /* removing a flag */ - guild_flag_remove(nd); - } - return 0; -} - -BUILDIN_FUNC(getcastlename) -{ - const char* mapname = mapindex_getmapname(script_getstr(st,2),NULL); - struct guild_castle* gc = guild_mapname2gc(mapname); - const char* name = (gc) ? gc->castle_name : ""; - script_pushstrcopy(st,name); - return 0; -} - -BUILDIN_FUNC(getcastledata) -{ - const char *mapname = mapindex_getmapname(script_getstr(st,2),NULL); - int index = script_getnum(st,3); - struct guild_castle *gc = guild_mapname2gc(mapname); - - if (gc == NULL) { - script_pushint(st,0); - ShowWarning("buildin_setcastledata: guild castle for map '%s' not found\n", mapname); - return 1; - } - - switch (index) { - case 1: - script_pushint(st,gc->guild_id); break; - case 2: - script_pushint(st,gc->economy); break; - case 3: - script_pushint(st,gc->defense); break; - case 4: - script_pushint(st,gc->triggerE); break; - case 5: - script_pushint(st,gc->triggerD); break; - case 6: - script_pushint(st,gc->nextTime); break; - case 7: - script_pushint(st,gc->payTime); break; - case 8: - script_pushint(st,gc->createTime); break; - case 9: - script_pushint(st,gc->visibleC); break; - default: - if (index > 9 && index <= 9+MAX_GUARDIANS) { - script_pushint(st,gc->guardian[index-10].visible); - break; - } - script_pushint(st,0); - ShowWarning("buildin_setcastledata: index = '%d' is out of allowed range\n", index); - return 1; - } - return 0; -} - -BUILDIN_FUNC(setcastledata) -{ - const char *mapname = mapindex_getmapname(script_getstr(st,2),NULL); - int index = script_getnum(st,3); - int value = script_getnum(st,4); - struct guild_castle *gc = guild_mapname2gc(mapname); - - if (gc == NULL) { - ShowWarning("buildin_setcastledata: guild castle for map '%s' not found\n", mapname); - return 1; - } - - if (index <= 0 || index > 9+MAX_GUARDIANS) { - ShowWarning("buildin_setcastledata: index = '%d' is out of allowed range\n", index); - return 1; - } - - guild_castledatasave(gc->castle_id, index, value); - return 0; -} - -/* ===================================================================== - * ---------------------------------------------------------------------*/ -BUILDIN_FUNC(requestguildinfo) -{ - int guild_id=script_getnum(st,2); - const char *event=NULL; - - if( script_hasdata(st,3) ){ - event=script_getstr(st,3); - check_event(st, event); - } - - if(guild_id>0) - guild_npc_request_info(guild_id,event); - return 0; -} - -/// Returns the number of cards that have been compounded onto the specified equipped item. -/// getequipcardcnt(<equipment slot>); -BUILDIN_FUNC(getequipcardcnt) -{ - int i=-1,j,num; - TBL_PC *sd; - int count; - - num=script_getnum(st,2); - sd=script_rid2sd(st); - if (num > 0 && num <= ARRAYLENGTH(equip)) - i=pc_checkequip(sd,equip[num-1]); - - if (i < 0 || !sd->inventory_data[i]) { - script_pushint(st,0); - return 0; - } - - if(itemdb_isspecial(sd->status.inventory[i].card[0])) - { - script_pushint(st,0); - return 0; - } - - count = 0; - for( j = 0; j < sd->inventory_data[i]->slot; j++ ) - if( sd->status.inventory[i].card[j] && itemdb_type(sd->status.inventory[i].card[j]) == IT_CARD ) - count++; - - script_pushint(st,count); - return 0; -} - -/// Removes all cards from the item found in the specified equipment slot of the invoking character, -/// and give them to the character. If any cards were removed in this manner, it will also show a success effect. -/// successremovecards <slot>; -BUILDIN_FUNC(successremovecards) { - int i=-1,j,c,cardflag=0; - - TBL_PC* sd = script_rid2sd(st); - int num = script_getnum(st,2); - - if (num > 0 && num <= ARRAYLENGTH(equip)) - i=pc_checkequip(sd,equip[num-1]); - - if (i < 0 || !sd->inventory_data[i]) { - return 0; - } - - if(itemdb_isspecial(sd->status.inventory[i].card[0])) - return 0; - - for( c = sd->inventory_data[i]->slot - 1; c >= 0; --c ) { - if( sd->status.inventory[i].card[c] && itemdb_type(sd->status.inventory[i].card[c]) == IT_CARD ) {// extract this card from the item - int flag; - struct item item_tmp; - memset(&item_tmp,0,sizeof(item_tmp)); - cardflag = 1; - item_tmp.nameid = sd->status.inventory[i].card[c]; - item_tmp.identify = 1; - - if((flag=pc_additem(sd,&item_tmp,1,LOG_TYPE_SCRIPT))){ // get back the cart in inventory - clif_additem(sd,0,0,flag); - map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); - } - } - } - - if(cardflag == 1) {//if card was remove remplace item with no card - int flag; - struct item item_tmp; - memset(&item_tmp,0,sizeof(item_tmp)); - - item_tmp.nameid = sd->status.inventory[i].nameid; - item_tmp.identify = 1; - item_tmp.refine = sd->status.inventory[i].refine; - item_tmp.attribute = sd->status.inventory[i].attribute; - item_tmp.expire_time = sd->status.inventory[i].expire_time; - - for (j = sd->inventory_data[i]->slot; j < MAX_SLOTS; j++) - item_tmp.card[j]=sd->status.inventory[i].card[j]; - - pc_delitem(sd,i,1,0,3,LOG_TYPE_SCRIPT); - if((flag=pc_additem(sd,&item_tmp,1,LOG_TYPE_SCRIPT))){ //chk if can be spawn in inventory otherwise put on floor - clif_additem(sd,0,0,flag); - map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); - } - - clif_misceffect(&sd->bl,3); - } - return 0; -} - -/// Removes all cards from the item found in the specified equipment slot of the invoking character. -/// failedremovecards <slot>, <type>; -/// <type>=0 : will destroy both the item and the cards. -/// <type>=1 : will keep the item, but destroy the cards. -/// <type>=2 : will keep the cards, but destroy the item. -/// <type>=? : will just display the failure effect. -BUILDIN_FUNC(failedremovecards) { - int i=-1,j,c,cardflag=0; - - TBL_PC* sd = script_rid2sd(st); - int num = script_getnum(st,2); - int typefail = script_getnum(st,3); - - if (num > 0 && num <= ARRAYLENGTH(equip)) - i=pc_checkequip(sd,equip[num-1]); - - if (i < 0 || !sd->inventory_data[i]) - return 0; - - if(itemdb_isspecial(sd->status.inventory[i].card[0])) - return 0; - - for( c = sd->inventory_data[i]->slot - 1; c >= 0; --c ) { - if( sd->status.inventory[i].card[c] && itemdb_type(sd->status.inventory[i].card[c]) == IT_CARD ) { - cardflag = 1; - - if(typefail == 2) {// add cards to inventory, clear - int flag; - struct item item_tmp; - - memset(&item_tmp,0,sizeof(item_tmp)); - - item_tmp.nameid = sd->status.inventory[i].card[c]; - item_tmp.identify = 1; - - if((flag=pc_additem(sd,&item_tmp,1,LOG_TYPE_SCRIPT))){ - clif_additem(sd,0,0,flag); - map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); - } - } - } - } - - if(cardflag == 1) { - if(typefail == 0 || typefail == 2){ // destroy the item - pc_delitem(sd,i,1,0,2,LOG_TYPE_SCRIPT); - } - if(typefail == 1){ // destroy the card - int flag; - struct item item_tmp; - - memset(&item_tmp,0,sizeof(item_tmp)); - - item_tmp.nameid = sd->status.inventory[i].nameid; - item_tmp.identify = 1; - item_tmp.refine = sd->status.inventory[i].refine; - item_tmp.attribute = sd->status.inventory[i].attribute; - item_tmp.expire_time = sd->status.inventory[i].expire_time; - - for (j = sd->inventory_data[i]->slot; j < MAX_SLOTS; j++) - item_tmp.card[j]=sd->status.inventory[i].card[j]; - - pc_delitem(sd,i,1,0,2,LOG_TYPE_SCRIPT); - - if((flag=pc_additem(sd,&item_tmp,1,LOG_TYPE_SCRIPT))){ - clif_additem(sd,0,0,flag); - map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); - } - } - clif_misceffect(&sd->bl,2); - } - - return 0; -} - -/* ================================================================ - * mapwarp "<from map>","<to map>",<x>,<y>,<type>,<ID for Type>; - * type: 0=everyone, 1=guild, 2=party; [Reddozen] - * improved by [Lance] - * ================================================================*/ -BUILDIN_FUNC(mapwarp) // Added by RoVeRT -{ - int x,y,m,check_val=0,check_ID=0,i=0; - struct guild *g = NULL; - struct party_data *p = NULL; - const char *str; - const char *mapname; - unsigned int index; - mapname=script_getstr(st,2); - str=script_getstr(st,3); - x=script_getnum(st,4); - y=script_getnum(st,5); - if(script_hasdata(st,7)){ - check_val=script_getnum(st,6); - check_ID=script_getnum(st,7); - } - - if((m=map_mapname2mapid(mapname))< 0) - return 0; - - if(!(index=mapindex_name2id(str))) - return 0; - - switch(check_val){ - case 1: - g = guild_search(check_ID); - if (g){ - for( i=0; i < g->max_member; i++) - { - if(g->member[i].sd && g->member[i].sd->bl.m==m){ - pc_setpos(g->member[i].sd,index,x,y,CLR_TELEPORT); - } - } - } - break; - case 2: - p = party_search(check_ID); - if(p){ - for(i=0;i<MAX_PARTY; i++){ - if(p->data[i].sd && p->data[i].sd->bl.m == m){ - pc_setpos(p->data[i].sd,index,x,y,CLR_TELEPORT); - } - } - } - break; - default: - map_foreachinmap(buildin_areawarp_sub,m,BL_PC,index,x,y,0,0); - break; - } - - return 0; -} - -static int buildin_mobcount_sub(struct block_list *bl,va_list ap) // Added by RoVeRT -{ - char *event=va_arg(ap,char *); - struct mob_data *md = ((struct mob_data *)bl); - if( md->status.hp > 0 && (!event || strcmp(event,md->npc_event) == 0) ) - return 1; - return 0; -} - -BUILDIN_FUNC(mobcount) // Added by RoVeRT -{ - const char *mapname,*event; - int16 m; - mapname=script_getstr(st,2); - event=script_getstr(st,3); - - if( strcmp(event, "all") == 0 ) - event = NULL; - else - check_event(st, event); - - if( strcmp(mapname, "this") == 0 ) { - struct map_session_data *sd = script_rid2sd(st); - if( sd ) - m = sd->bl.m; - else { - script_pushint(st,-1); - return 0; - } - } - else if( (m = map_mapname2mapid(mapname)) < 0 ) { - script_pushint(st,-1); - return 0; - } - - if( map[m].flag.src4instance && map[m].instance_id == 0 && st->instance_id && (m = instance_mapid2imapid(m, st->instance_id)) < 0 ) - { - script_pushint(st,-1); - return 0; - } - - script_pushint(st,map_foreachinmap(buildin_mobcount_sub, m, BL_MOB, event)); - - return 0; -} - -BUILDIN_FUNC(marriage) -{ - const char *partner=script_getstr(st,2); - TBL_PC *sd=script_rid2sd(st); - TBL_PC *p_sd=map_nick2sd(partner); - - if(sd==NULL || p_sd==NULL || pc_marriage(sd,p_sd) < 0){ - script_pushint(st,0); - return 0; - } - script_pushint(st,1); - return 0; -} -BUILDIN_FUNC(wedding_effect) -{ - TBL_PC *sd=script_rid2sd(st); - struct block_list *bl; - - if(sd==NULL) { - bl=map_id2bl(st->oid); - } else - bl=&sd->bl; - clif_wedding_effect(bl); - return 0; -} -BUILDIN_FUNC(divorce) -{ - TBL_PC *sd=script_rid2sd(st); - if(sd==NULL || pc_divorce(sd) < 0){ - script_pushint(st,0); - return 0; - } - script_pushint(st,1); - return 0; -} - -BUILDIN_FUNC(ispartneron) -{ - TBL_PC *sd=script_rid2sd(st); - - if(sd==NULL || !pc_ismarried(sd) || - map_charid2sd(sd->status.partner_id) == NULL) { - script_pushint(st,0); - return 0; - } - - script_pushint(st,1); - return 0; -} - -BUILDIN_FUNC(getpartnerid) -{ - TBL_PC *sd=script_rid2sd(st); - if (sd == NULL) { - script_pushint(st,0); - return 0; - } - - script_pushint(st,sd->status.partner_id); - return 0; -} - -BUILDIN_FUNC(getchildid) -{ - TBL_PC *sd=script_rid2sd(st); - if (sd == NULL) { - script_pushint(st,0); - return 0; - } - - script_pushint(st,sd->status.child); - return 0; -} - -BUILDIN_FUNC(getmotherid) -{ - TBL_PC *sd=script_rid2sd(st); - if (sd == NULL) { - script_pushint(st,0); - return 0; - } - - script_pushint(st,sd->status.mother); - return 0; -} - -BUILDIN_FUNC(getfatherid) -{ - TBL_PC *sd=script_rid2sd(st); - if (sd == NULL) { - script_pushint(st,0); - return 0; - } - - script_pushint(st,sd->status.father); - return 0; -} - -BUILDIN_FUNC(warppartner) -{ - int x,y; - unsigned short mapindex; - const char *str; - TBL_PC *sd=script_rid2sd(st); - TBL_PC *p_sd=NULL; - - if(sd==NULL || !pc_ismarried(sd) || - (p_sd=map_charid2sd(sd->status.partner_id)) == NULL) { - script_pushint(st,0); - return 0; - } - - str=script_getstr(st,2); - x=script_getnum(st,3); - y=script_getnum(st,4); - - mapindex = mapindex_name2id(str); - if (mapindex) { - pc_setpos(p_sd,mapindex,x,y,CLR_OUTSIGHT); - script_pushint(st,1); - } else - script_pushint(st,0); - return 0; -} - -/*================================================ - * Script for Displaying MOB Information [Valaris] - *------------------------------------------------*/ -BUILDIN_FUNC(strmobinfo) -{ - - int num=script_getnum(st,2); - int class_=script_getnum(st,3); - - if(!mobdb_checkid(class_)) - { - if (num < 3) //requested a string - script_pushconststr(st,""); - else - script_pushint(st,0); - return 0; - } - - switch (num) { - case 1: script_pushstrcopy(st,mob_db(class_)->name); break; - case 2: script_pushstrcopy(st,mob_db(class_)->jname); break; - case 3: script_pushint(st,mob_db(class_)->lv); break; - case 4: script_pushint(st,mob_db(class_)->status.max_hp); break; - case 5: script_pushint(st,mob_db(class_)->status.max_sp); break; - case 6: script_pushint(st,mob_db(class_)->base_exp); break; - case 7: script_pushint(st,mob_db(class_)->job_exp); break; - default: - script_pushint(st,0); - break; - } - return 0; -} - -/*========================================== - * Summon guardians [Valaris] - * guardian("<map name>",<x>,<y>,"<name to show>",<mob id>{,"<event label>"}{,<guardian index>}) -> <id> - *------------------------------------------*/ -BUILDIN_FUNC(guardian) -{ - int class_=0,x=0,y=0,guardian=0; - const char *str,*map,*evt=""; - struct script_data *data; - bool has_index = false; - - map =script_getstr(st,2); - x =script_getnum(st,3); - y =script_getnum(st,4); - str =script_getstr(st,5); - class_=script_getnum(st,6); - - if( script_hasdata(st,8) ) - {// "<event label>",<guardian index> - evt=script_getstr(st,7); - guardian=script_getnum(st,8); - has_index = true; - } else if( script_hasdata(st,7) ){ - data=script_getdata(st,7); - get_val(st,data); - if( data_isstring(data) ) - {// "<event label>" - evt=script_getstr(st,7); - } else if( data_isint(data) ) - {// <guardian index> - guardian=script_getnum(st,7); - has_index = true; - } else { - ShowError("script:guardian: invalid data type for argument #6 (from 1)\n"); - script_reportdata(data); - return 1; - } - } - - check_event(st, evt); - script_pushint(st, mob_spawn_guardian(map,x,y,str,class_,evt,guardian,has_index)); - - return 0; -} -/*========================================== - * Invisible Walls [Zephyrus] - *------------------------------------------*/ -BUILDIN_FUNC(setwall) -{ - const char *map, *name; - int x, y, m, size, dir; - bool shootable; - - map = script_getstr(st,2); - x = script_getnum(st,3); - y = script_getnum(st,4); - size = script_getnum(st,5); - dir = script_getnum(st,6); - shootable = script_getnum(st,7); - name = script_getstr(st,8); - - if( (m = map_mapname2mapid(map)) < 0 ) - return 0; // Invalid Map - - map_iwall_set(m, x, y, size, dir, shootable, name); - return 0; -} -BUILDIN_FUNC(delwall) -{ - const char *name = script_getstr(st,2); - map_iwall_remove(name); - - return 0; -} - -/// Retrieves various information about the specified guardian. -/// -/// guardianinfo("<map_name>", <index>, <type>) -> <value> -/// type: 0 - whether it is deployed or not -/// 1 - maximum hp -/// 2 - current hp -/// -BUILDIN_FUNC(guardianinfo) -{ - const char* mapname = mapindex_getmapname(script_getstr(st,2),NULL); - int id = script_getnum(st,3); - int type = script_getnum(st,4); - - struct guild_castle* gc = guild_mapname2gc(mapname); - struct mob_data* gd; - - if( gc == NULL || id < 0 || id >= MAX_GUARDIANS ) - { - script_pushint(st,-1); - return 0; - } - - if( type == 0 ) - script_pushint(st, gc->guardian[id].visible); - else - if( !gc->guardian[id].visible ) - script_pushint(st,-1); - else - if( (gd = map_id2md(gc->guardian[id].id)) == NULL ) - script_pushint(st,-1); - else - { - if ( type == 1 ) script_pushint(st,gd->status.max_hp); - else if( type == 2 ) script_pushint(st,gd->status.hp); - else - script_pushint(st,-1); - } - - return 0; -} - -/*========================================== - * Get the item name by item_id or null - *------------------------------------------*/ -BUILDIN_FUNC(getitemname) -{ - int item_id=0; - struct item_data *i_data; - char *item_name; - struct script_data *data; - - data=script_getdata(st,2); - get_val(st,data); - - if( data_isstring(data) ){ - const char *name=conv_str(st,data); - struct item_data *item_data = itemdb_searchname(name); - if( item_data ) - item_id=item_data->nameid; - }else - item_id=conv_num(st,data); - - i_data = itemdb_exists(item_id); - if (i_data == NULL) - { - script_pushconststr(st,"null"); - return 0; - } - item_name=(char *)aMalloc(ITEM_NAME_LENGTH*sizeof(char)); - - memcpy(item_name, i_data->jname, ITEM_NAME_LENGTH); - script_pushstr(st,item_name); - return 0; -} -/*========================================== - * Returns number of slots an item has. [Skotlex] - *------------------------------------------*/ -BUILDIN_FUNC(getitemslots) -{ - int item_id; - struct item_data *i_data; - - item_id=script_getnum(st,2); - - i_data = itemdb_exists(item_id); - - if (i_data) - script_pushint(st,i_data->slot); - else - script_pushint(st,-1); - return 0; -} - -// TODO: add matk here if needed/once we get rid of RENEWAL - -/*========================================== - * Returns some values of an item [Lupus] - * Price, Weight, etc... - getiteminfo(itemID,n), where n - 0 value_buy; - 1 value_sell; - 2 type; - 3 maxchance = Max drop chance of this item e.g. 1 = 0.01% , etc.. - if = 0, then monsters don't drop it at all (rare or a quest item) - if = -1, then this item is sold in NPC shops only - 4 sex; - 5 equip; - 6 weight; - 7 atk; - 8 def; - 9 range; - 10 slot; - 11 look; - 12 elv; - 13 wlv; - 14 view id - *------------------------------------------*/ -BUILDIN_FUNC(getiteminfo) -{ - int item_id,n; - int *item_arr; - struct item_data *i_data; - - item_id = script_getnum(st,2); - n = script_getnum(st,3); - i_data = itemdb_exists(item_id); - - if (i_data && n>=0 && n<=14) { - item_arr = (int*)&i_data->value_buy; - script_pushint(st,item_arr[n]); - } else - script_pushint(st,-1); - return 0; -} - -/*========================================== - * Set some values of an item [Lupus] - * Price, Weight, etc... - setiteminfo(itemID,n,Value), where n - 0 value_buy; - 1 value_sell; - 2 type; - 3 maxchance = Max drop chance of this item e.g. 1 = 0.01% , etc.. - if = 0, then monsters don't drop it at all (rare or a quest item) - if = -1, then this item is sold in NPC shops only - 4 sex; - 5 equip; - 6 weight; - 7 atk; - 8 def; - 9 range; - 10 slot; - 11 look; - 12 elv; - 13 wlv; - 14 view id - * Returns Value or -1 if the wrong field's been set - *------------------------------------------*/ -BUILDIN_FUNC(setiteminfo) -{ - int item_id,n,value; - int *item_arr; - struct item_data *i_data; - - item_id = script_getnum(st,2); - n = script_getnum(st,3); - value = script_getnum(st,4); - i_data = itemdb_exists(item_id); - - if (i_data && n>=0 && n<=14) { - item_arr = (int*)&i_data->value_buy; - item_arr[n] = value; - script_pushint(st,value); - } else - script_pushint(st,-1); - return 0; -} - -/*========================================== - * Returns value from equipped item slot n [Lupus] - getequipcardid(num,slot) - where - num = eqip position slot - slot = 0,1,2,3 (Card Slot N) - - This func returns CARD ID, 255,254,-255 (for card 0, if the item is produced) - it's useful when you want to check item cards or if it's signed - Useful for such quests as "Sign this refined item with players name" etc - Hat[0] +4 -> Player's Hat[0] +4 - *------------------------------------------*/ -BUILDIN_FUNC(getequipcardid) -{ - int i=-1,num,slot; - TBL_PC *sd; - - num=script_getnum(st,2); - slot=script_getnum(st,3); - sd=script_rid2sd(st); - if (num > 0 && num <= ARRAYLENGTH(equip)) - i=pc_checkequip(sd,equip[num-1]); - if(i >= 0 && slot>=0 && slot<4) - script_pushint(st,sd->status.inventory[i].card[slot]); - else - script_pushint(st,0); - - return 0; -} - -/*========================================== - * petskillbonus [Valaris] //Rewritten by [Skotlex] - *------------------------------------------*/ -BUILDIN_FUNC(petskillbonus) -{ - struct pet_data *pd; - - TBL_PC *sd=script_rid2sd(st); - - if(sd==NULL || sd->pd==NULL) - return 0; - - pd=sd->pd; - if (pd->bonus) - { //Clear previous bonus - if (pd->bonus->timer != INVALID_TIMER) - delete_timer(pd->bonus->timer, pet_skill_bonus_timer); - } else //init - pd->bonus = (struct pet_bonus *) aMalloc(sizeof(struct pet_bonus)); - - pd->bonus->type=script_getnum(st,2); - pd->bonus->val=script_getnum(st,3); - pd->bonus->duration=script_getnum(st,4); - pd->bonus->delay=script_getnum(st,5); - - if (pd->state.skillbonus == 1) - pd->state.skillbonus=0; // waiting state - - // wait for timer to start - if (battle_config.pet_equip_required && pd->pet.equip == 0) - pd->bonus->timer = INVALID_TIMER; - else - pd->bonus->timer = add_timer(gettick()+pd->bonus->delay*1000, pet_skill_bonus_timer, sd->bl.id, 0); - - return 0; -} - -/*========================================== - * pet looting [Valaris] //Rewritten by [Skotlex] - *------------------------------------------*/ -BUILDIN_FUNC(petloot) -{ - int max; - struct pet_data *pd; - TBL_PC *sd=script_rid2sd(st); - - if(sd==NULL || sd->pd==NULL) - return 0; - - max=script_getnum(st,2); - - if(max < 1) - max = 1; //Let'em loot at least 1 item. - else if (max > MAX_PETLOOT_SIZE) - max = MAX_PETLOOT_SIZE; - - pd = sd->pd; - if (pd->loot != NULL) - { //Release whatever was there already and reallocate memory - pet_lootitem_drop(pd, pd->msd); - aFree(pd->loot->item); - } - else - pd->loot = (struct pet_loot *)aMalloc(sizeof(struct pet_loot)); - - pd->loot->item = (struct item *)aCalloc(max,sizeof(struct item)); - - pd->loot->max=max; - pd->loot->count = 0; - pd->loot->weight = 0; - - return 0; -} -/*========================================== - * Set arrays with info of all sd inventory : - * @inventorylist_id, @inventorylist_amount, @inventorylist_equip, - * @inventorylist_refine, @inventorylist_identify, @inventorylist_attribute, - * @inventorylist_card(0..3), @inventorylist_expire - * @inventorylist_count = scalar - *------------------------------------------*/ -BUILDIN_FUNC(getinventorylist) -{ - TBL_PC *sd=script_rid2sd(st); - char card_var[NAME_LENGTH]; - - int i,j=0,k; - 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,reference_uid(add_str("@inventorylist_id"), j),sd->status.inventory[i].nameid); - pc_setreg(sd,reference_uid(add_str("@inventorylist_amount"), j),sd->status.inventory[i].amount); - pc_setreg(sd,reference_uid(add_str("@inventorylist_equip"), j),sd->status.inventory[i].equip); - pc_setreg(sd,reference_uid(add_str("@inventorylist_refine"), j),sd->status.inventory[i].refine); - pc_setreg(sd,reference_uid(add_str("@inventorylist_identify"), j),sd->status.inventory[i].identify); - pc_setreg(sd,reference_uid(add_str("@inventorylist_attribute"), j),sd->status.inventory[i].attribute); - for (k = 0; k < MAX_SLOTS; k++) - { - sprintf(card_var, "@inventorylist_card%d",k+1); - pc_setreg(sd,reference_uid(add_str(card_var), j),sd->status.inventory[i].card[k]); - } - pc_setreg(sd,reference_uid(add_str("@inventorylist_expire"), j),sd->status.inventory[i].expire_time); - j++; - } - } - pc_setreg(sd,add_str("@inventorylist_count"),j); - return 0; -} - -BUILDIN_FUNC(getskilllist) -{ - TBL_PC *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,reference_uid(add_str("@skilllist_id"), j),sd->status.skill[i].id); - pc_setreg(sd,reference_uid(add_str("@skilllist_lv"), j),sd->status.skill[i].lv); - pc_setreg(sd,reference_uid(add_str("@skilllist_flag"), j),sd->status.skill[i].flag); - j++; - } - } - pc_setreg(sd,add_str("@skilllist_count"),j); - return 0; -} - -BUILDIN_FUNC(clearitem) -{ - TBL_PC *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, 0, LOG_TYPE_SCRIPT); - } - } - return 0; -} - -/*========================================== - * Disguise Player (returns Mob/NPC ID if success, 0 on fail) - *------------------------------------------*/ -BUILDIN_FUNC(disguise) -{ - int id; - TBL_PC* sd = script_rid2sd(st); - if (sd == NULL) return 0; - - id = script_getnum(st,2); - - if (mobdb_checkid(id) || npcdb_checkid(id)) { - pc_disguise(sd, id); - script_pushint(st,id); - } else - script_pushint(st,0); - - return 0; -} - -/*========================================== - * Undisguise Player (returns 1 if success, 0 on fail) - *------------------------------------------*/ -BUILDIN_FUNC(undisguise) -{ - TBL_PC* sd = script_rid2sd(st); - if (sd == NULL) return 0; - - if (sd->disguise) { - pc_disguise(sd, 0); - script_pushint(st,0); - } else { - script_pushint(st,1); - } - return 0; -} - -/*========================================== - * Transform a bl to another _class, - * @type unused - *------------------------------------------*/ -BUILDIN_FUNC(classchange) -{ - int _class,type; - struct block_list *bl=map_id2bl(st->oid); - - if(bl==NULL) return 0; - - _class=script_getnum(st,2); - type=script_getnum(st,3); - clif_class_change(bl,_class,type); - return 0; -} - -/*========================================== - * Display an effect - *------------------------------------------*/ -BUILDIN_FUNC(misceffect) -{ - int type; - - type=script_getnum(st,2); - if(st->oid && st->oid != fake_nd->bl.id) { - struct block_list *bl = map_id2bl(st->oid); - if (bl) - clif_specialeffect(bl,type,AREA); - } else{ - TBL_PC *sd=script_rid2sd(st); - if(sd) - clif_specialeffect(&sd->bl,type,AREA); - } - return 0; -} -/*========================================== - * Play a BGM on a single client [Rikter/Yommy] - *------------------------------------------*/ -BUILDIN_FUNC(playBGM) -{ - const char* name; - struct map_session_data* sd; - - if( ( sd = script_rid2sd(st) ) != NULL ) - { - name = script_getstr(st,2); - - clif_playBGM(sd, name); - } - - return 0; -} - -static int playBGM_sub(struct block_list* bl,va_list ap) -{ - const char* name = va_arg(ap,const char*); - - clif_playBGM(BL_CAST(BL_PC, bl), name); - - return 0; -} - -static int playBGM_foreachpc_sub(struct map_session_data* sd, va_list args) -{ - const char* name = va_arg(args, const char*); - - clif_playBGM(sd, name); - return 0; -} - -/*========================================== - * Play a BGM on multiple client [Rikter/Yommy] - *------------------------------------------*/ -BUILDIN_FUNC(playBGMall) -{ - const char* name; - - name = script_getstr(st,2); - - if( script_hasdata(st,7) ) - {// specified part of map - const char* map = script_getstr(st,3); - int x0 = script_getnum(st,4); - int y0 = script_getnum(st,5); - int x1 = script_getnum(st,6); - int y1 = script_getnum(st,7); - - map_foreachinarea(playBGM_sub, map_mapname2mapid(map), x0, y0, x1, y1, BL_PC, name); - } - else if( script_hasdata(st,3) ) - {// entire map - const char* map = script_getstr(st,3); - - map_foreachinmap(playBGM_sub, map_mapname2mapid(map), BL_PC, name); - } - else - {// entire server - map_foreachpc(&playBGM_foreachpc_sub, name); - } - - return 0; -} - -/*========================================== - * Play a .wav sound for sd - *------------------------------------------*/ -BUILDIN_FUNC(soundeffect) -{ - TBL_PC* sd = script_rid2sd(st); - const char* name = script_getstr(st,2); - int type = script_getnum(st,3); - - if(sd) - { - clif_soundeffect(sd,&sd->bl,name,type); - } - return 0; -} - -int soundeffect_sub(struct block_list* bl,va_list ap) -{ - char* name = va_arg(ap,char*); - int type = va_arg(ap,int); - - clif_soundeffect((TBL_PC *)bl, bl, name, type); - - return 0; -} - -/*========================================== - * Play a sound effect (.wav) on multiple clients - * soundeffectall "<filepath>",<type>{,"<map name>"}{,<x0>,<y0>,<x1>,<y1>}; - *------------------------------------------*/ -BUILDIN_FUNC(soundeffectall) -{ - struct block_list* bl; - const char* name; - int type; - - bl = (st->rid) ? &(script_rid2sd(st)->bl) : map_id2bl(st->oid); - if (!bl) - return 0; - - name = script_getstr(st,2); - type = script_getnum(st,3); - - //FIXME: enumerating map squares (map_foreach) is slower than enumerating the list of online players (map_foreachpc?) [ultramage] - - if(!script_hasdata(st,4)) - { // area around - clif_soundeffectall(bl, name, type, AREA); - } - else - if(!script_hasdata(st,5)) - { // entire map - const char* map = script_getstr(st,4); - map_foreachinmap(soundeffect_sub, map_mapname2mapid(map), BL_PC, name, type); - } - else - if(script_hasdata(st,8)) - { // specified part of map - const char* map = script_getstr(st,4); - int x0 = script_getnum(st,5); - int y0 = script_getnum(st,6); - int x1 = script_getnum(st,7); - int y1 = script_getnum(st,8); - map_foreachinarea(soundeffect_sub, map_mapname2mapid(map), x0, y0, x1, y1, BL_PC, name, type); - } - else - { - ShowError("buildin_soundeffectall: insufficient arguments for specific area broadcast.\n"); - } - - return 0; -} -/*========================================== - * pet status recovery [Valaris] / Rewritten by [Skotlex] - *------------------------------------------*/ -BUILDIN_FUNC(petrecovery) -{ - struct pet_data *pd; - TBL_PC *sd=script_rid2sd(st); - - if(sd==NULL || sd->pd==NULL) - return 0; - - pd=sd->pd; - - if (pd->recovery) - { //Halt previous bonus - if (pd->recovery->timer != INVALID_TIMER) - delete_timer(pd->recovery->timer, pet_recovery_timer); - } else //Init - pd->recovery = (struct pet_recovery *)aMalloc(sizeof(struct pet_recovery)); - - pd->recovery->type = (sc_type)script_getnum(st,2); - pd->recovery->delay = script_getnum(st,3); - pd->recovery->timer = INVALID_TIMER; - - return 0; -} - -/*========================================== - * pet healing [Valaris] //Rewritten by [Skotlex] - *------------------------------------------*/ -BUILDIN_FUNC(petheal) -{ - struct pet_data *pd; - TBL_PC *sd=script_rid2sd(st); - - if(sd==NULL || sd->pd==NULL) - return 0; - - pd=sd->pd; - if (pd->s_skill) - { //Clear previous skill - if (pd->s_skill->timer != INVALID_TIMER) - { - if (pd->s_skill->id) - delete_timer(pd->s_skill->timer, pet_skill_support_timer); - else - delete_timer(pd->s_skill->timer, pet_heal_timer); - } - } else //init memory - pd->s_skill = (struct pet_skill_support *) aMalloc(sizeof(struct pet_skill_support)); - - pd->s_skill->id=0; //This id identifies that it IS petheal rather than pet_skillsupport - //Use the lv as the amount to heal - pd->s_skill->lv=script_getnum(st,2); - pd->s_skill->delay=script_getnum(st,3); - pd->s_skill->hp=script_getnum(st,4); - pd->s_skill->sp=script_getnum(st,5); - - //Use delay as initial offset to avoid skill/heal exploits - if (battle_config.pet_equip_required && pd->pet.equip == 0) - pd->s_skill->timer = INVALID_TIMER; - else - pd->s_skill->timer = add_timer(gettick()+pd->s_skill->delay*1000,pet_heal_timer,sd->bl.id,0); - - return 0; -} - -/*========================================== - * pet attack skills [Valaris] //Rewritten by [Skotlex] - *------------------------------------------*/ -/// petskillattack <skill id>,<level>,<rate>,<bonusrate> -/// petskillattack "<skill name>",<level>,<rate>,<bonusrate> -BUILDIN_FUNC(petskillattack) -{ - struct pet_data *pd; - TBL_PC *sd=script_rid2sd(st); - - if(sd==NULL || sd->pd==NULL) - return 0; - - pd=sd->pd; - if (pd->a_skill == NULL) - pd->a_skill = (struct pet_skill_attack *)aMalloc(sizeof(struct pet_skill_attack)); - - pd->a_skill->id=( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); - pd->a_skill->lv=script_getnum(st,3); - pd->a_skill->div_ = 0; - pd->a_skill->rate=script_getnum(st,4); - pd->a_skill->bonusrate=script_getnum(st,5); - - return 0; -} - -/*========================================== - * pet attack skills [Valaris] - *------------------------------------------*/ -/// petskillattack2 <skill id>,<level>,<div>,<rate>,<bonusrate> -/// petskillattack2 "<skill name>",<level>,<div>,<rate>,<bonusrate> -BUILDIN_FUNC(petskillattack2) -{ - struct pet_data *pd; - TBL_PC *sd=script_rid2sd(st); - - if(sd==NULL || sd->pd==NULL) - return 0; - - pd=sd->pd; - if (pd->a_skill == NULL) - pd->a_skill = (struct pet_skill_attack *)aMalloc(sizeof(struct pet_skill_attack)); - - pd->a_skill->id=( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); - pd->a_skill->lv=script_getnum(st,3); - pd->a_skill->div_ = script_getnum(st,4); - pd->a_skill->rate=script_getnum(st,5); - pd->a_skill->bonusrate=script_getnum(st,6); - - return 0; -} - -/*========================================== - * pet support skills [Skotlex] - *------------------------------------------*/ -/// petskillsupport <skill id>,<level>,<delay>,<hp>,<sp> -/// petskillsupport "<skill name>",<level>,<delay>,<hp>,<sp> -BUILDIN_FUNC(petskillsupport) -{ - struct pet_data *pd; - TBL_PC *sd=script_rid2sd(st); - - if(sd==NULL || sd->pd==NULL) - return 0; - - pd=sd->pd; - if (pd->s_skill) - { //Clear previous skill - if (pd->s_skill->timer != INVALID_TIMER) - { - if (pd->s_skill->id) - delete_timer(pd->s_skill->timer, pet_skill_support_timer); - else - delete_timer(pd->s_skill->timer, pet_heal_timer); - } - } else //init memory - pd->s_skill = (struct pet_skill_support *) aMalloc(sizeof(struct pet_skill_support)); - - pd->s_skill->id=( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); - pd->s_skill->lv=script_getnum(st,3); - pd->s_skill->delay=script_getnum(st,4); - pd->s_skill->hp=script_getnum(st,5); - pd->s_skill->sp=script_getnum(st,6); - - //Use delay as initial offset to avoid skill/heal exploits - if (battle_config.pet_equip_required && pd->pet.equip == 0) - pd->s_skill->timer = INVALID_TIMER; - else - pd->s_skill->timer = add_timer(gettick()+pd->s_skill->delay*1000,pet_skill_support_timer,sd->bl.id,0); - - return 0; -} - -/*========================================== - * Scripted skill effects [Celest] - *------------------------------------------*/ -/// skilleffect <skill id>,<level> -/// skilleffect "<skill name>",<level> -BUILDIN_FUNC(skilleffect) -{ - TBL_PC *sd; - - uint16 skill_id=( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); - uint16 skill_lv=script_getnum(st,3); - sd=script_rid2sd(st); - - clif_skill_nodamage(&sd->bl,&sd->bl,skill_id,skill_lv,1); - - return 0; -} - -/*========================================== - * NPC skill effects [Valaris] - *------------------------------------------*/ -/// npcskilleffect <skill id>,<level>,<x>,<y> -/// npcskilleffect "<skill name>",<level>,<x>,<y> -BUILDIN_FUNC(npcskilleffect) -{ - struct block_list *bl= map_id2bl(st->oid); - - uint16 skill_id=( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); - uint16 skill_lv=script_getnum(st,3); - int x=script_getnum(st,4); - int y=script_getnum(st,5); - - if (bl) - clif_skill_poseffect(bl,skill_id,skill_lv,x,y,gettick()); - - return 0; -} - -/*========================================== - * Special effects [Valaris] - *------------------------------------------*/ -BUILDIN_FUNC(specialeffect) -{ - struct block_list *bl=map_id2bl(st->oid); - int type = script_getnum(st,2); - enum send_target target = script_hasdata(st,3) ? (send_target)script_getnum(st,3) : AREA; - - if(bl==NULL) - return 0; - - if( script_hasdata(st,4) ) - { - TBL_NPC *nd = npc_name2id(script_getstr(st,4)); - if(nd) - clif_specialeffect(&nd->bl, type, target); - } - else - { - if (target == SELF) { - TBL_PC *sd=script_rid2sd(st); - if (sd) - clif_specialeffect_single(bl,type,sd->fd); - } else { - clif_specialeffect(bl, type, target); - } - } - - return 0; -} - -BUILDIN_FUNC(specialeffect2) -{ - TBL_PC *sd=script_rid2sd(st); - int type = script_getnum(st,2); - enum send_target target = script_hasdata(st,3) ? (send_target)script_getnum(st,3) : AREA; - - if( script_hasdata(st,4) ) - sd = map_nick2sd(script_getstr(st,4)); - - if (sd) - clif_specialeffect(&sd->bl, type, target); - - return 0; -} - -/*========================================== - * Nude [Valaris] - *------------------------------------------*/ -BUILDIN_FUNC(nude) -{ - TBL_PC *sd = script_rid2sd(st); - int i, calcflag = 0; - - if( sd == NULL ) - return 0; - - for( i = 0 ; i < EQI_MAX; i++ ) { - if( sd->equip_index[ i ] >= 0 ) { - if( !calcflag ) - calcflag = 1; - pc_unequipitem( sd , sd->equip_index[ i ] , 2); - } - } - - if( calcflag ) - status_calc_pc(sd,0); - - return 0; -} - -/*========================================== - * gmcommand [MouseJstr] - *------------------------------------------*/ -BUILDIN_FUNC(atcommand) -{ - TBL_PC dummy_sd; - TBL_PC* sd; - int fd; - const char* cmd; - - cmd = script_getstr(st,2); - - if (st->rid) { - sd = script_rid2sd(st); - fd = sd->fd; - } else { //Use a dummy character. - sd = &dummy_sd; - fd = 0; - - memset(&dummy_sd, 0, sizeof(TBL_PC)); - if (st->oid) - { - struct block_list* bl = map_id2bl(st->oid); - memcpy(&dummy_sd.bl, bl, sizeof(struct block_list)); - if (bl->type == BL_NPC) - safestrncpy(dummy_sd.status.name, ((TBL_NPC*)bl)->name, NAME_LENGTH); - } - } - - if (!is_atcommand(fd, sd, cmd, 0)) { - ShowWarning("script: buildin_atcommand: failed to execute command '%s'\n", cmd); - script_reportsrc(st); - return 1; - } - - return 0; -} - -/*========================================== - * Displays a message for the player only (like system messages like "you got an apple" ) - *------------------------------------------*/ -BUILDIN_FUNC(dispbottom) -{ - TBL_PC *sd=script_rid2sd(st); - const char *message; - message=script_getstr(st,2); - if(sd) - clif_disp_onlyself(sd,message,(int)strlen(message)); - return 0; -} - -/*========================================== - * All The Players Full Recovery - * (HP/SP full restore and resurrect if need) - *------------------------------------------*/ -BUILDIN_FUNC(recovery) -{ - TBL_PC* sd; - struct s_mapiterator* iter; - - iter = mapit_getallusers(); - for( sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC*)mapit_next(iter) ) - { - if(pc_isdead(sd)) - status_revive(&sd->bl, 100, 100); - else - status_percent_heal(&sd->bl, 100, 100); - clif_displaymessage(sd->fd,msg_txt(680)); - } - mapit_free(iter); - return 0; -} -/*========================================== - * Get your pet info: getpetinfo(n) - * n -> 0:pet_id 1:pet_class 2:pet_name - * 3:friendly 4:hungry, 5: rename flag. - *------------------------------------------*/ -BUILDIN_FUNC(getpetinfo) -{ - TBL_PC *sd=script_rid2sd(st); - TBL_PET *pd; - int type=script_getnum(st,2); - - if(!sd || !sd->pd) { - if (type == 2) - script_pushconststr(st,"null"); - else - script_pushint(st,0); - return 0; - } - pd = sd->pd; - switch(type){ - case 0: script_pushint(st,pd->pet.pet_id); break; - case 1: script_pushint(st,pd->pet.class_); break; - case 2: script_pushstrcopy(st,pd->pet.name); break; - case 3: script_pushint(st,pd->pet.intimate); break; - case 4: script_pushint(st,pd->pet.hungry); break; - case 5: script_pushint(st,pd->pet.rename_flag); break; - default: - script_pushint(st,0); - break; - } - return 0; -} - -/*========================================== - * Get your homunculus info: gethominfo(n) - * n -> 0:hom_id 1:class 2:name - * 3:friendly 4:hungry, 5: rename flag. - * 6: level - *------------------------------------------*/ -BUILDIN_FUNC(gethominfo) -{ - TBL_PC *sd=script_rid2sd(st); - TBL_HOM *hd; - int type=script_getnum(st,2); - - hd = sd?sd->hd:NULL; - if(!merc_is_hom_active(hd)) - { - if (type == 2) - script_pushconststr(st,"null"); - else - script_pushint(st,0); - return 0; - } - - switch(type){ - case 0: script_pushint(st,hd->homunculus.hom_id); break; - case 1: script_pushint(st,hd->homunculus.class_); break; - case 2: script_pushstrcopy(st,hd->homunculus.name); break; - case 3: script_pushint(st,hd->homunculus.intimacy); break; - case 4: script_pushint(st,hd->homunculus.hunger); break; - case 5: script_pushint(st,hd->homunculus.rename_flag); break; - case 6: script_pushint(st,hd->homunculus.level); break; - default: - script_pushint(st,0); - break; - } - return 0; -} - -/// Retrieves information about character's mercenary -/// getmercinfo <type>[,<char id>]; -BUILDIN_FUNC(getmercinfo) -{ - int type, char_id; - struct map_session_data* sd; - struct mercenary_data* md; - - type = script_getnum(st,2); - - if( script_hasdata(st,3) ) - { - char_id = script_getnum(st,3); - - if( ( sd = map_charid2sd(char_id) ) == NULL ) - { - ShowError("buildin_getmercinfo: No such character (char_id=%d).\n", char_id); - script_pushnil(st); - return 1; - } - } - else - { - if( ( sd = script_rid2sd(st) ) == NULL ) - { - script_pushnil(st); - return 0; - } - } - - md = ( sd->status.mer_id && sd->md ) ? sd->md : NULL; - - switch( type ) - { - case 0: script_pushint(st,md ? md->mercenary.mercenary_id : 0); break; - case 1: script_pushint(st,md ? md->mercenary.class_ : 0); break; - case 2: - if( md ) - script_pushstrcopy(st,md->db->name); - else - script_pushconststr(st,""); - break; - case 3: script_pushint(st,md ? mercenary_get_faith(md) : 0); break; - case 4: script_pushint(st,md ? mercenary_get_calls(md) : 0); break; - case 5: script_pushint(st,md ? md->mercenary.kill_count : 0); break; - case 6: script_pushint(st,md ? mercenary_get_lifetime(md) : 0); break; - case 7: script_pushint(st,md ? md->db->lv : 0); break; - default: - ShowError("buildin_getmercinfo: Invalid type %d (char_id=%d).\n", type, sd->status.char_id); - script_pushnil(st); - return 1; - } - - return 0; -} - -/*========================================== - * Shows wether your inventory(and equips) contain - selected card or not. - checkequipedcard(4001); - *------------------------------------------*/ -BUILDIN_FUNC(checkequipedcard) -{ - TBL_PC *sd=script_rid2sd(st); - - if(sd){ - int n,i,c=0; - c=script_getnum(st,2); - - for(i=0;i<MAX_INVENTORY;i++){ - if(sd->status.inventory[i].nameid > 0 && sd->status.inventory[i].amount && sd->inventory_data[i]){ - if (itemdb_isspecial(sd->status.inventory[i].card[0])) - continue; - for(n=0;n<sd->inventory_data[i]->slot;n++){ - if(sd->status.inventory[i].card[n]==c){ - script_pushint(st,1); - return 0; - } - } - } - } - } - script_pushint(st,0); - return 0; -} - -BUILDIN_FUNC(jump_zero) -{ - int sel; - sel=script_getnum(st,2); - if(!sel) { - int pos; - if( !data_islabel(script_getdata(st,3)) ){ - ShowError("script: jump_zero: not label !\n"); - st->state=END; - return 1; - } - - pos=script_getnum(st,3); - st->pos=pos; - st->state=GOTO; - } - return 0; -} - -/*========================================== - * movenpc [MouseJstr] - *------------------------------------------*/ -BUILDIN_FUNC(movenpc) -{ - TBL_NPC *nd = NULL; - const char *npc; - int x,y; - - npc = script_getstr(st,2); - x = script_getnum(st,3); - y = script_getnum(st,4); - - if ((nd = npc_name2id(npc)) == NULL) - return -1; - - if (script_hasdata(st,5)) - nd->ud.dir = script_getnum(st,5) % 8; - npc_movenpc(nd, x, y); - return 0; -} - -/*========================================== - * message [MouseJstr] - *------------------------------------------*/ -BUILDIN_FUNC(message) -{ - const char *msg,*player; - TBL_PC *pl_sd = NULL; - - player = script_getstr(st,2); - msg = script_getstr(st,3); - - if((pl_sd=map_nick2sd((char *) player)) == NULL) - return 0; - clif_displaymessage(pl_sd->fd, msg); - - return 0; -} - -/*========================================== - * npctalk (sends message to surrounding area) - *------------------------------------------*/ -BUILDIN_FUNC(npctalk) -{ - const char* str; - char name[NAME_LENGTH], message[256]; - - struct npc_data* nd = (struct npc_data *)map_id2bl(st->oid); - str = script_getstr(st,2); - - if(nd) - { - safestrncpy(name, nd->name, sizeof(name)); - strtok(name, "#"); // discard extra name identifier if present - safesnprintf(message, sizeof(message), "%s : %s", name, str); - clif_message(&nd->bl, message); - } - - return 0; -} - -// change npc walkspeed [Valaris] -BUILDIN_FUNC(npcspeed) -{ - struct npc_data* nd; - int speed; - - speed = script_getnum(st,2); - nd =(struct npc_data *)map_id2bl(st->oid); - - if( nd ) - { - nd->speed = speed; - nd->ud.state.speed_changed = 1; - } - - return 0; -} -// make an npc walk to a position [Valaris] -BUILDIN_FUNC(npcwalkto) -{ - struct npc_data *nd=(struct npc_data *)map_id2bl(st->oid); - int x=0,y=0; - - x=script_getnum(st,2); - y=script_getnum(st,3); - - if(nd) { - unit_walktoxy(&nd->bl,x,y,0); - } - - return 0; -} -// stop an npc's movement [Valaris] -BUILDIN_FUNC(npcstop) -{ - struct npc_data *nd=(struct npc_data *)map_id2bl(st->oid); - - if(nd) { - unit_stop_walking(&nd->bl,1|4); - } - - return 0; -} - - -/*========================================== - * getlook char info. getlook(arg) - *------------------------------------------*/ -BUILDIN_FUNC(getlook) -{ - int type,val; - TBL_PC *sd; - sd=script_rid2sd(st); - - type=script_getnum(st,2); - val=-1; - switch(type) { - case LOOK_HAIR: val=sd->status.hair; break; //1 - case LOOK_WEAPON: val=sd->status.weapon; break; //2 - case LOOK_HEAD_BOTTOM: val=sd->status.head_bottom; break; //3 - case LOOK_HEAD_TOP: val=sd->status.head_top; break; //4 - case LOOK_HEAD_MID: val=sd->status.head_mid; break; //5 - case LOOK_HAIR_COLOR: val=sd->status.hair_color; break; //6 - case LOOK_CLOTHES_COLOR: val=sd->status.clothes_color; break; //7 - case LOOK_SHIELD: val=sd->status.shield; break; //8 - case LOOK_SHOES: break; //9 - } - - script_pushint(st,val); - return 0; -} - -/*========================================== - * get char save point. argument: 0- map name, 1- x, 2- y - *------------------------------------------*/ -BUILDIN_FUNC(getsavepoint) -{ - TBL_PC* sd; - int type; - - sd = script_rid2sd(st); - if (sd == NULL) { - script_pushint(st,0); - return 0; - } - - type = script_getnum(st,2); - - switch(type) { - case 0: script_pushstrcopy(st,mapindex_id2name(sd->status.save_point.map)); break; - case 1: script_pushint(st,sd->status.save_point.x); break; - case 2: script_pushint(st,sd->status.save_point.y); break; - default: - script_pushint(st,0); - break; - } - return 0; -} - -/*========================================== - * Get position for char/npc/pet/mob objects. Added by Lorky - * - * int getMapXY(MapName$,MapX,MapY,type,[CharName$]); - * where type: - * MapName$ - String variable for output map name - * MapX - Integer variable for output coord X - * MapY - Integer variable for output coord Y - * type - type of object - * 0 - Character coord - * 1 - NPC coord - * 2 - Pet coord - * 3 - Mob coord (not released) - * 4 - Homun coord - * 5 - Mercenary coord - * 6 - Elemental coord - * CharName$ - Name object. If miss or "this" the current object - * - * Return: - * 0 - success - * -1 - some error, MapName$,MapX,MapY contains unknown value. - *------------------------------------------*/ -BUILDIN_FUNC(getmapxy) -{ - struct block_list *bl = NULL; - TBL_PC *sd=NULL; - - int num; - const char *name; - char prefix; - - int x,y,type; - char mapname[MAP_NAME_LENGTH]; - - if( !data_isreference(script_getdata(st,2)) ){ - ShowWarning("script: buildin_getmapxy: not mapname variable\n"); - script_pushint(st,-1); - return 1; - } - if( !data_isreference(script_getdata(st,3)) ){ - ShowWarning("script: buildin_getmapxy: not mapx variable\n"); - script_pushint(st,-1); - return 1; - } - if( !data_isreference(script_getdata(st,4)) ){ - ShowWarning("script: buildin_getmapxy: not mapy variable\n"); - script_pushint(st,-1); - return 1; - } - - // Possible needly check function parameters on C_STR,C_INT,C_INT - type=script_getnum(st,5); - - switch (type){ - case 0: //Get Character Position - if( script_hasdata(st,6) ) - sd=map_nick2sd(script_getstr(st,6)); - else - sd=script_rid2sd(st); - - if (sd) - bl = &sd->bl; - break; - case 1: //Get NPC Position - if( script_hasdata(st,6) ) - { - struct npc_data *nd; - nd=npc_name2id(script_getstr(st,6)); - if (nd) - bl = &nd->bl; - } else //In case the origin is not an npc? - bl=map_id2bl(st->oid); - break; - case 2: //Get Pet Position - if(script_hasdata(st,6)) - sd=map_nick2sd(script_getstr(st,6)); - else - sd=script_rid2sd(st); - - if (sd && sd->pd) - bl = &sd->pd->bl; - break; - case 3: //Get Mob Position - break; //Not supported? - case 4: //Get Homun Position - if(script_hasdata(st,6)) - sd=map_nick2sd(script_getstr(st,6)); - else - sd=script_rid2sd(st); - - if (sd && sd->hd) - bl = &sd->hd->bl; - break; - case 5: //Get Mercenary Position - if(script_hasdata(st,6)) - sd=map_nick2sd(script_getstr(st,6)); - else - sd=script_rid2sd(st); - - if (sd && sd->md) - bl = &sd->md->bl; - break; - case 6: //Get Elemental Position - if(script_hasdata(st,6)) - sd=map_nick2sd(script_getstr(st,6)); - else - sd=script_rid2sd(st); - - if (sd && sd->ed) - bl = &sd->ed->bl; - break; - default: - ShowWarning("script: buildin_getmapxy: Invalid type %d\n", type); - script_pushint(st,-1); - return 1; - } - if (!bl) { //No object found. - script_pushint(st,-1); - return 0; - } - - x= bl->x; - y= bl->y; - safestrncpy(mapname, map[bl->m].name, MAP_NAME_LENGTH); - - //Set MapName$ - num=st->stack->stack_data[st->start+2].u.num; - name=get_str(num&0x00ffffff); - prefix=*name; - - if(not_server_variable(prefix)) - sd=script_rid2sd(st); - else - sd=NULL; - set_reg(st,sd,num,name,(void*)mapname,script_getref(st,2)); - - //Set MapX - num=st->stack->stack_data[st->start+3].u.num; - name=get_str(num&0x00ffffff); - prefix=*name; - - if(not_server_variable(prefix)) - sd=script_rid2sd(st); - else - sd=NULL; - set_reg(st,sd,num,name,(void*)__64BPRTSIZE(x),script_getref(st,3)); - - //Set MapY - num=st->stack->stack_data[st->start+4].u.num; - name=get_str(num&0x00ffffff); - prefix=*name; - - if(not_server_variable(prefix)) - sd=script_rid2sd(st); - else - sd=NULL; - set_reg(st,sd,num,name,(void*)__64BPRTSIZE(y),script_getref(st,4)); - - //Return Success value - script_pushint(st,0); - return 0; -} - -/*========================================== - * Allows player to write NPC logs (i.e. Bank NPC, etc) [Lupus] - *------------------------------------------*/ -BUILDIN_FUNC(logmes) -{ - const char *str; - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 1; - - str = script_getstr(st,2); - log_npc(sd,str); - return 0; -} - -BUILDIN_FUNC(summon) -{ - int _class, timeout=0; - const char *str,*event=""; - TBL_PC *sd; - struct mob_data *md; - int tick = gettick(); - - sd=script_rid2sd(st); - if (!sd) return 0; - - str =script_getstr(st,2); - _class=script_getnum(st,3); - if( script_hasdata(st,4) ) - timeout=script_getnum(st,4); - if( script_hasdata(st,5) ){ - event=script_getstr(st,5); - check_event(st, event); - } - - clif_skill_poseffect(&sd->bl,AM_CALLHOMUN,1,sd->bl.x,sd->bl.y,tick); - - md = mob_once_spawn_sub(&sd->bl, sd->bl.m, sd->bl.x, sd->bl.y, str, _class, event, SZ_SMALL, AI_NONE); - if (md) { - md->master_id=sd->bl.id; - md->special_state.ai = AI_ATTACK; - if( md->deletetimer != INVALID_TIMER ) - delete_timer(md->deletetimer, mob_timer_delete); - md->deletetimer = add_timer(tick+(timeout>0?timeout*1000:60000),mob_timer_delete,md->bl.id,0); - mob_spawn (md); //Now it is ready for spawning. - clif_specialeffect(&md->bl,344,AREA); - sc_start4(&md->bl, SC_MODECHANGE, 100, 1, 0, MD_AGGRESSIVE, 0, 60000); - } - return 0; -} - -/*========================================== - * Checks whether it is daytime/nighttime - *------------------------------------------*/ -BUILDIN_FUNC(isnight) -{ - script_pushint(st,(night_flag == 1)); - return 0; -} - -BUILDIN_FUNC(isday) -{ - script_pushint(st,(night_flag == 0)); - return 0; -} - -/*================================================ - * Check how many items/cards in the list are - * equipped - used for 2/15's cards patch [celest] - *------------------------------------------------*/ -BUILDIN_FUNC(isequippedcnt) -{ - TBL_PC *sd; - int i, j, k, id = 1; - int ret = 0; - - sd = script_rid2sd(st); - if (!sd) { //If the player is not attached it is a script error anyway... but better prevent the map server from crashing... - script_pushint(st,0); - return 0; - } - - for (i=0; id!=0; i++) { - FETCH (i+2, id) else id = 0; - if (id <= 0) - continue; - - for (j=0; j<EQI_MAX; j++) { - int index; - index = sd->equip_index[j]; - if(index < 0) continue; - if(j == EQI_HAND_R && sd->equip_index[EQI_HAND_L] == index) continue; - if(j == EQI_HEAD_MID && sd->equip_index[EQI_HEAD_LOW] == index) continue; - if(j == EQI_HEAD_TOP && (sd->equip_index[EQI_HEAD_MID] == index || sd->equip_index[EQI_HEAD_LOW] == index)) continue; - - if(!sd->inventory_data[index]) - continue; - - if (itemdb_type(id) != IT_CARD) { //No card. Count amount in inventory. - if (sd->inventory_data[index]->nameid == id) - ret+= sd->status.inventory[index].amount; - } else { //Count cards. - if (itemdb_isspecial(sd->status.inventory[index].card[0])) - continue; //No cards - for(k=0; k<sd->inventory_data[index]->slot; k++) { - if (sd->status.inventory[index].card[k] == id) - ret++; //[Lupus] - } - } - } - } - - script_pushint(st,ret); - return 0; -} - -/*================================================ - * Check whether another card has been - * equipped - used for 2/15's cards patch [celest] - * -- Items checked cannot be reused in another - * card set to prevent exploits - *------------------------------------------------*/ -BUILDIN_FUNC(isequipped) -{ - TBL_PC *sd; - int i, j, k, id = 1; - int index, flag; - int ret = -1; - //Original hash to reverse it when full check fails. - unsigned int setitem_hash = 0, setitem_hash2 = 0; - - sd = script_rid2sd(st); - - if (!sd) { //If the player is not attached it is a script error anyway... but better prevent the map server from crashing... - script_pushint(st,0); - return 0; - } - - setitem_hash = sd->bonus.setitem_hash; - setitem_hash2 = sd->bonus.setitem_hash2; - for (i=0; id!=0; i++) { - FETCH (i+2, id) else id = 0; - if (id <= 0) - continue; - flag = 0; - for (j=0; j<EQI_MAX; j++) { - index = sd->equip_index[j]; - if(index < 0) continue; - if(j == EQI_HAND_R && sd->equip_index[EQI_HAND_L] == index) continue; - if(j == EQI_HEAD_MID && sd->equip_index[EQI_HEAD_LOW] == index) continue; - if(j == EQI_HEAD_TOP && (sd->equip_index[EQI_HEAD_MID] == index || sd->equip_index[EQI_HEAD_LOW] == index)) continue; - - if(!sd->inventory_data[index]) - continue; - - if (itemdb_type(id) != IT_CARD) { - if (sd->inventory_data[index]->nameid != id) - continue; - flag = 1; - break; - } else { //Cards - if (sd->inventory_data[index]->slot == 0 || - itemdb_isspecial(sd->status.inventory[index].card[0])) - continue; - - for (k = 0; k < sd->inventory_data[index]->slot; k++) - { //New hash system which should support up to 4 slots on any equipment. [Skotlex] - unsigned int hash = 0; - if (sd->status.inventory[index].card[k] != id) - continue; - - hash = 1<<((j<5?j:j-5)*4 + k); - // check if card is already used by another set - if ( ( j < 5 ? sd->bonus.setitem_hash : sd->bonus.setitem_hash2 ) & hash) - continue; - - // We have found a match - flag = 1; - // Set hash so this card cannot be used by another - if (j<5) - sd->bonus.setitem_hash |= hash; - else - sd->bonus.setitem_hash2 |= hash; - break; - } - } - if (flag) break; //Card found - } - if (ret == -1) - ret = flag; - else - ret &= flag; - if (!ret) break; - } - if (!ret) {//When check fails, restore original hash values. [Skotlex] - sd->bonus.setitem_hash = setitem_hash; - sd->bonus.setitem_hash2 = setitem_hash2; - } - script_pushint(st,ret); - return 0; -} - -/*================================================ - * Check how many given inserted cards in the CURRENT - * weapon - used for 2/15's cards patch [Lupus] - *------------------------------------------------*/ -BUILDIN_FUNC(cardscnt) -{ - TBL_PC *sd; - int i, k, id = 1; - int ret = 0; - int index; - - sd = script_rid2sd(st); - - for (i=0; id!=0; i++) { - FETCH (i+2, id) else id = 0; - if (id <= 0) - continue; - - index = current_equip_item_index; //we get CURRENT WEAPON inventory index from status.c [Lupus] - if(index < 0) continue; - - if(!sd->inventory_data[index]) - continue; - - if(itemdb_type(id) != IT_CARD) { - if (sd->inventory_data[index]->nameid == id) - ret+= sd->status.inventory[index].amount; - } else { - if (itemdb_isspecial(sd->status.inventory[index].card[0])) - continue; - for(k=0; k<sd->inventory_data[index]->slot; k++) { - if (sd->status.inventory[index].card[k] == id) - ret++; - } - } - } - script_pushint(st,ret); -// script_pushint(st,current_equip_item_index); - return 0; -} - -/*======================================================= - * Returns the refined number of the current item, or an - * item with inventory index specified - *-------------------------------------------------------*/ -BUILDIN_FUNC(getrefine) -{ - TBL_PC *sd; - if ((sd = script_rid2sd(st))!= NULL) - script_pushint(st,sd->status.inventory[current_equip_item_index].refine); - else - script_pushint(st,0); - return 0; -} - -/*======================================================= - * Day/Night controls - *-------------------------------------------------------*/ -BUILDIN_FUNC(night) -{ - if (night_flag != 1) map_night_timer(night_timer_tid, 0, 0, 1); - return 0; -} -BUILDIN_FUNC(day) -{ - if (night_flag != 0) map_day_timer(day_timer_tid, 0, 0, 1); - return 0; -} - -//======================================================= -// Unequip [Spectre] -//------------------------------------------------------- -BUILDIN_FUNC(unequip) -{ - int i; - size_t num; - TBL_PC *sd; - - num = script_getnum(st,2); - sd = script_rid2sd(st); - if( sd != NULL && num >= 1 && num <= ARRAYLENGTH(equip) ) - { - i = pc_checkequip(sd,equip[num-1]); - if (i >= 0) - pc_unequipitem(sd,i,1|2); - } - return 0; -} - -BUILDIN_FUNC(equip) -{ - int nameid=0,i; - TBL_PC *sd; - struct item_data *item_data; - - sd = script_rid2sd(st); - - nameid=script_getnum(st,2); - if((item_data = itemdb_exists(nameid)) == NULL) - { - ShowError("wrong item ID : equipitem(%i)\n",nameid); - return 1; - } - ARR_FIND( 0, MAX_INVENTORY, i, sd->status.inventory[i].nameid == nameid ); - if( i < MAX_INVENTORY ) - pc_equipitem(sd,i,item_data->equip); - - return 0; -} - -BUILDIN_FUNC(autoequip) -{ - int nameid, flag; - struct item_data *item_data; - nameid=script_getnum(st,2); - flag=script_getnum(st,3); - - if( ( item_data = itemdb_exists(nameid) ) == NULL ) - { - ShowError("buildin_autoequip: Invalid item '%d'.\n", nameid); - return 1; - } - - if( !itemdb_isequip2(item_data) ) - { - ShowError("buildin_autoequip: Item '%d' cannot be equipped.\n", nameid); - return 1; - } - - item_data->flag.autoequip = flag>0?1:0; - return 0; -} - -BUILDIN_FUNC(setbattleflag) -{ - const char *flag, *value; - - flag = script_getstr(st,2); - value = script_getstr(st,3); // HACK: Retrieve number as string (auto-converted) for battle_set_value - - if (battle_set_value(flag, value) == 0) - ShowWarning("buildin_setbattleflag: unknown battle_config flag '%s'\n",flag); - else - ShowInfo("buildin_setbattleflag: battle_config flag '%s' is now set to '%s'.\n",flag,value); - - return 0; -} - -BUILDIN_FUNC(getbattleflag) -{ - const char *flag; - flag = script_getstr(st,2); - script_pushint(st,battle_get_value(flag)); - return 0; -} - -//======================================================= -// strlen [Valaris] -//------------------------------------------------------- -BUILDIN_FUNC(getstrlen) -{ - - const char *str = script_getstr(st,2); - int len = (str) ? (int)strlen(str) : 0; - - script_pushint(st,len); - return 0; -} - -//======================================================= -// isalpha [Valaris] -//------------------------------------------------------- -BUILDIN_FUNC(charisalpha) -{ - const char *str=script_getstr(st,2); - int pos=script_getnum(st,3); - - int val = ( str && pos >= 0 && (unsigned int)pos < strlen(str) ) ? ISALPHA( str[pos] ) != 0 : 0; - - script_pushint(st,val); - return 0; -} - -//======================================================= -// charisupper <str>, <index> -//------------------------------------------------------- -BUILDIN_FUNC(charisupper) -{ - const char *str = script_getstr(st,2); - int pos = script_getnum(st,3); - - int val = ( str && pos >= 0 && (unsigned int)pos < strlen(str) ) ? ISUPPER( str[pos] ) : 0; - - script_pushint(st,val); - return 0; -} - -//======================================================= -// charislower <str>, <index> -//------------------------------------------------------- -BUILDIN_FUNC(charislower) -{ - const char *str = script_getstr(st,2); - int pos = script_getnum(st,3); - - int val = ( str && pos >= 0 && (unsigned int)pos < strlen(str) ) ? ISLOWER( str[pos] ) : 0; - - script_pushint(st,val); - return 0; -} - -//======================================================= -// charat <str>, <index> -//------------------------------------------------------- -BUILDIN_FUNC(charat) { - const char *str = script_getstr(st,2); - int pos = script_getnum(st,3); - - if( pos >= 0 && (unsigned int)pos < strlen(str) ) { - char output[2]; - output[0] = str[pos]; - output[1] = '\0'; - script_pushstrcopy(st, output); - } else - script_pushconststr(st, ""); - return 0; -} - -//======================================================= -// setchar <string>, <char>, <index> -//------------------------------------------------------- -BUILDIN_FUNC(setchar) -{ - const char *str = script_getstr(st,2); - const char *c = script_getstr(st,3); - int index = script_getnum(st,4); - char *output = aStrdup(str); - - if(index >= 0 && index < strlen(output)) - output[index] = *c; - - script_pushstr(st, output); - return 0; -} - -//======================================================= -// insertchar <string>, <char>, <index> -//------------------------------------------------------- -BUILDIN_FUNC(insertchar) -{ - const char *str = script_getstr(st,2); - const char *c = script_getstr(st,3); - int index = script_getnum(st,4); - char *output; - size_t len = strlen(str); - - if(index < 0) - index = 0; - else if(index > len) - index = len; - - output = (char*)aMalloc(len + 2); - - memcpy(output, str, index); - output[index] = c[0]; - memcpy(&output[index+1], &str[index], len - index); - output[len+1] = '\0'; - - script_pushstr(st, output); - return 0; -} - -//======================================================= -// delchar <string>, <index> -//------------------------------------------------------- -BUILDIN_FUNC(delchar) -{ - const char *str = script_getstr(st,2); - int index = script_getnum(st,3); - char *output; - size_t len = strlen(str); - - if(index < 0 || index > len) { - //return original - output = aStrdup(str); - script_pushstr(st, output); - return 0; - } - - output = (char*)aMalloc(len); - - memcpy(output, str, index); - memcpy(&output[index], &str[index+1], len - index); - - script_pushstr(st, output); - return 0; -} - -//======================================================= -// strtoupper <str> -//------------------------------------------------------- -BUILDIN_FUNC(strtoupper) -{ - const char *str = script_getstr(st,2); - char *output = aStrdup(str); - char *cursor = output; - - while (*cursor != '\0') { - *cursor = TOUPPER(*cursor); - cursor++; - } - - script_pushstr(st, output); - return 0; -} - -//======================================================= -// strtolower <str> -//------------------------------------------------------- -BUILDIN_FUNC(strtolower) -{ - const char *str = script_getstr(st,2); - char *output = aStrdup(str); - char *cursor = output; - - while (*cursor != '\0') { - *cursor = TOLOWER(*cursor); - cursor++; - } - - script_pushstr(st, output); - return 0; -} - -//======================================================= -// substr <str>, <start>, <end> -//------------------------------------------------------- -BUILDIN_FUNC(substr) -{ - const char *str = script_getstr(st,2); - char *output; - int start = script_getnum(st,3); - int end = script_getnum(st,4); - - int len = 0; - - if(start >= 0 && end < strlen(str) && start <= end) { - len = end - start + 1; - output = (char*)aMalloc(len + 1); - memcpy(output, &str[start], len); - } else - output = (char*)aMalloc(1); - - output[len] = '\0'; - - script_pushstr(st, output); - return 0; -} - -//======================================================= -// explode <dest_string_array>, <str>, <delimiter> -// Note: delimiter is limited to 1 char -//------------------------------------------------------- -BUILDIN_FUNC(explode) -{ - struct script_data* data = script_getdata(st, 2); - const char *str = script_getstr(st,3); - const char delimiter = script_getstr(st, 4)[0]; - int32 id; - size_t len = strlen(str); - int i = 0, j = 0; - int start; - - - char *temp; - const char* name; - - TBL_PC* sd = NULL; - - temp = (char*)aMalloc(len + 1); - - if( !data_isreference(data) ) - { - ShowError("script:explode: not a variable\n"); - script_reportdata(data); - st->state = END; - return 1;// not a variable - } - - id = reference_getid(data); - start = reference_getindex(data); - name = reference_getname(data); - - if( not_array_variable(*name) ) - { - ShowError("script:explode: illegal scope\n"); - script_reportdata(data); - st->state = END; - return 1;// not supported - } - - if( !is_string_variable(name) ) - { - ShowError("script:explode: not string array\n"); - script_reportdata(data); - st->state = END; - return 1;// data type mismatch - } - - if( not_server_variable(*name) ) - { - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached - } - - while(str[i] != '\0') { - if(str[i] == delimiter && start < SCRIPT_MAX_ARRAYSIZE-1) { //break at delimiter but ignore after reaching last array index - temp[j] = '\0'; - set_reg(st, sd, reference_uid(id, start++), name, (void*)temp, reference_getref(data)); - j = 0; - ++i; - } else { - temp[j++] = str[i++]; - } - } - //set last string - temp[j] = '\0'; - set_reg(st, sd, reference_uid(id, start), name, (void*)temp, reference_getref(data)); - - aFree(temp); - return 0; -} - -//======================================================= -// implode <string_array> -// implode <string_array>, <glue> -//------------------------------------------------------- -BUILDIN_FUNC(implode) -{ - struct script_data* data = script_getdata(st, 2); - const char *glue = NULL, *name, *temp; - int32 glue_len = 0, array_size, id; - size_t len = 0; - int i, k = 0; - - TBL_PC* sd = NULL; - - char *output; - - if( !data_isreference(data) ) - { - ShowError("script:implode: not a variable\n"); - script_reportdata(data); - st->state = END; - return 1;// not a variable - } - - id = reference_getid(data); - name = reference_getname(data); - - if( not_array_variable(*name) ) - { - ShowError("script:implode: illegal scope\n"); - script_reportdata(data); - st->state = END; - return 1;// not supported - } - - if( !is_string_variable(name) ) - { - ShowError("script:implode: not string array\n"); - script_reportdata(data); - st->state = END; - return 1;// data type mismatch - } - - if( not_server_variable(*name) ) - { - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached - } - - //count chars - array_size = getarraysize(st, id, reference_getindex(data), is_string_variable(name), reference_getref(data)) - 1; - - if(array_size == -1) //empty array check (AmsTaff) - { - ShowWarning("script:implode: array length = 0\n"); - output = (char*)aMalloc(sizeof(char)*5); - sprintf(output,"%s","NULL"); - } else { - for(i = 0; i <= array_size; ++i) { - temp = (char*) get_val2(st, reference_uid(id, i), reference_getref(data)); - len += strlen(temp); - script_removetop(st, -1, 0); - } - - //allocate mem - if( script_hasdata(st,3) ) { - glue = script_getstr(st,3); - glue_len = strlen(glue); - len += glue_len * (array_size); - } - output = (char*)aMalloc(len + 1); - - //build output - for(i = 0; i < array_size; ++i) { - temp = (char*) get_val2(st, reference_uid(id, i), reference_getref(data)); - len = strlen(temp); - memcpy(&output[k], temp, len); - k += len; - if(glue_len != 0) { - memcpy(&output[k], glue, glue_len); - k += glue_len; - } - script_removetop(st, -1, 0); - } - temp = (char*) get_val2(st, reference_uid(id, array_size), reference_getref(data)); - len = strlen(temp); - memcpy(&output[k], temp, len); - k += len; - script_removetop(st, -1, 0); - - output[k] = '\0'; - } - - script_pushstr(st, output); - return 0; -} - -//======================================================= -// sprintf(<format>, ...); -// Implements C sprintf, except format %n. The resulting string is -// returned, instead of being saved in variable by reference. -//------------------------------------------------------- -BUILDIN_FUNC(sprintf) -{ - unsigned int len, argc = 0, arg = 0, buf2_len = 0; - const char* format; - char* p; - char* q; - char* buf = NULL; - char* buf2 = NULL; - struct script_data* data; - StringBuf final_buf; - - // Fetch init data - format = script_getstr(st, 2); - argc = script_lastdata(st)-2; - len = strlen(format); - - // Skip parsing, where no parsing is required. - if(len==0){ - script_pushconststr(st,""); - return 0; - } - - // Pessimistic alloc - CREATE(buf, char, len+1); - - // Need not be parsed, just solve stuff like %%. - if(argc==0){ - memcpy(buf,format,len+1); - script_pushstrcopy(st, buf); - aFree(buf); - return 0; - } - - safestrncpy(buf, format, len+1); - - // Issue sprintf for each parameter - StringBuf_Init(&final_buf); - q = buf; - while((p = strchr(q, '%'))!=NULL){ - if(p!=q){ - len = p-q+1; - if(buf2_len<len){ - RECREATE(buf2, char, len); - buf2_len = len; - } - safestrncpy(buf2, q, len); - StringBuf_AppendStr(&final_buf, buf2); - q = p; - } - p = q+1; - if(*p=='%'){ // %% - StringBuf_AppendStr(&final_buf, "%"); - q+=2; - continue; - } - if(*p=='n'){ // %n - ShowWarning("buildin_sprintf: Format %%n not supported! Skipping...\n"); - script_reportsrc(st); - q+=2; - continue; - } - if(arg>=argc){ - ShowError("buildin_sprintf: Not enough arguments passed!\n"); - if(buf) aFree(buf); - if(buf2) aFree(buf2); - StringBuf_Destroy(&final_buf); - script_pushconststr(st,""); - return 1; - } - if((p = strchr(q+1, '%'))==NULL){ - p = strchr(q, 0); // EOS - } - len = p-q+1; - if(buf2_len<len){ - RECREATE(buf2, char, len); - buf2_len = len; - } - safestrncpy(buf2, q, len); - q = p; - - // Note: This assumes the passed value being the correct - // type to the current format specifier. If not, the server - // probably crashes or returns anything else, than expected, - // but it would behave in normal code the same way so it's - // the scripter's responsibility. - data = script_getdata(st, arg+3); - if(data_isstring(data)){ // String - StringBuf_Printf(&final_buf, buf2, script_getstr(st, arg+3)); - }else if(data_isint(data)){ // Number - StringBuf_Printf(&final_buf, buf2, script_getnum(st, arg+3)); - }else if(data_isreference(data)){ // Variable - char* name = reference_getname(data); - if(name[strlen(name)-1]=='$'){ // var Str - StringBuf_Printf(&final_buf, buf2, script_getstr(st, arg+3)); - }else{ // var Int - StringBuf_Printf(&final_buf, buf2, script_getnum(st, arg+3)); - } - }else{ // Unsupported type - ShowError("buildin_sprintf: Unknown argument type!\n"); - if(buf) aFree(buf); - if(buf2) aFree(buf2); - StringBuf_Destroy(&final_buf); - script_pushconststr(st,""); - return 1; - } - arg++; - } - - // Append anything left - if(*q){ - StringBuf_AppendStr(&final_buf, q); - } - - // Passed more, than needed - if(arg<argc){ - ShowWarning("buildin_sprintf: Unused arguments passed.\n"); - script_reportsrc(st); - } - - script_pushstrcopy(st, StringBuf_Value(&final_buf)); - - if(buf) aFree(buf); - if(buf2) aFree(buf2); - StringBuf_Destroy(&final_buf); - - return 0; -} - -//======================================================= -// sscanf(<str>, <format>, ...); -// Implements C sscanf. -//------------------------------------------------------- -BUILDIN_FUNC(sscanf){ - unsigned int argc, arg = 0, len; - struct script_data* data; - struct map_session_data* sd = NULL; - const char* str; - const char* format; - const char* p; - const char* q; - char* buf = NULL; - char* buf_p; - char* ref_str = NULL; - int ref_int; - - // Get data - str = script_getstr(st, 2); - format = script_getstr(st, 3); - argc = script_lastdata(st)-3; - - len = strlen(format); - CREATE(buf, char, len*2+1); - - // Issue sscanf for each parameter - *buf = 0; - q = format; - while((p = strchr(q, '%'))){ - if(p!=q){ - strncat(buf, q, (size_t)(p-q)); - q = p; - } - p = q+1; - if(*p=='*' || *p=='%'){ // Skip - strncat(buf, q, 2); - q+=2; - continue; - } - if(arg>=argc){ - ShowError("buildin_sscanf: Not enough arguments passed!\n"); - script_pushint(st, -1); - if(buf) aFree(buf); - if(ref_str) aFree(ref_str); - return 1; - } - if((p = strchr(q+1, '%'))==NULL){ - p = strchr(q, 0); // EOS - } - len = p-q; - strncat(buf, q, len); - q = p; - - // Validate output - data = script_getdata(st, arg+4); - if(!data_isreference(data) || !reference_tovariable(data)){ - ShowError("buildin_sscanf: Target argument is not a variable!\n"); - script_pushint(st, -1); - if(buf) aFree(buf); - if(ref_str) aFree(ref_str); - return 1; - } - buf_p = reference_getname(data); - if(not_server_variable(*buf_p) && (sd = script_rid2sd(st))==NULL){ - script_pushint(st, -1); - if(buf) aFree(buf); - if(ref_str) aFree(ref_str); - return 0; - } - - // Save value if any - if(buf_p[strlen(buf_p)-1]=='$'){ // String - if(ref_str==NULL){ - CREATE(ref_str, char, strlen(str)+1); - } - if(sscanf(str, buf, ref_str)==0){ - break; - } - set_reg(st, sd, add_str(buf_p), buf_p, (void *)(ref_str), reference_getref(data)); - }else{ // Number - if(sscanf(str, buf, &ref_int)==0){ - break; - } - set_reg(st, sd, add_str(buf_p), buf_p, (void *)__64BPRTSIZE(ref_int), reference_getref(data)); - } - arg++; - - // Disable used format (%... -> %*...) - buf_p = strchr(buf, 0); - memmove(buf_p-len+2, buf_p-len+1, len); - *(buf_p-len+1) = '*'; - } - - script_pushint(st, arg); - if(buf) aFree(buf); - if(ref_str) aFree(ref_str); - - return 0; -} - -//======================================================= -// strpos(<haystack>, <needle>) -// strpos(<haystack>, <needle>, <offset>) -// -// Implements PHP style strpos. Adapted from code from -// http://www.daniweb.com/code/snippet313.html, Dave Sinkula -//------------------------------------------------------- -BUILDIN_FUNC(strpos) { - const char *haystack = script_getstr(st,2); - const char *needle = script_getstr(st,3); - int i; - size_t len; - - if( script_hasdata(st,4) ) - i = script_getnum(st,4); - else - i = 0; - - if (needle[0] == '\0') { - script_pushint(st, -1); - return 0; - } - - len = strlen(haystack); - for ( ; i < len; ++i ) { - if ( haystack[i] == *needle ) { - // matched starting char -- loop through remaining chars - const char *h, *n; - for ( h = &haystack[i], n = needle; *h && *n; ++h, ++n ) { - if ( *h != *n ) { - break; - } - } - if ( !*n ) { // matched all of 'needle' to null termination - script_pushint(st, i); - return 0; - } - } - } - script_pushint(st, -1); - return 0; -} - -//=============================================================== -// replacestr <input>, <search>, <replace>{, <usecase>{, <count>}} -// -// Note: Finds all instances of <search> in <input> and replaces -// with <replace>. If specified will only replace as many -// instances as specified in <count>. By default will be case -// sensitive. -//--------------------------------------------------------------- -BUILDIN_FUNC(replacestr) -{ - const char *input = script_getstr(st, 2); - const char *find = script_getstr(st, 3); - const char *replace = script_getstr(st, 4); - size_t inputlen = strlen(input); - size_t findlen = strlen(find); - struct StringBuf output; - bool usecase = true; - - int count = 0; - int numFinds = 0; - int i = 0, f = 0; - - if(findlen == 0) { - ShowError("script:replacestr: Invalid search length.\n"); - st->state = END; - return 1; - } - - if(script_hasdata(st, 5)) { - if( !script_isstring(st,5) ) - usecase = script_getnum(st, 5) != 0; - else { - ShowError("script:replacestr: Invalid usecase value. Expected int got string\n"); - st->state = END; - return 1; - } - } - - if(script_hasdata(st, 6)) { - count = script_getnum(st, 6); - if(count == 0) { - ShowError("script:replacestr: Invalid count value. Expected int got string\n"); - st->state = END; - return 1; - } - } - - StringBuf_Init(&output); - - for(; i < inputlen; i++) { - if(count && count == numFinds) { //found enough, stop looking - break; - } - - for(f = 0; f <= findlen; f++) { - if(f == findlen) { //complete match - numFinds++; - StringBuf_AppendStr(&output, replace); - - i += findlen - 1; - break; - } else { - if(usecase) { - if((i + f) > inputlen || input[i + f] != find[f]) { - StringBuf_Printf(&output, "%c", input[i]); - break; - } - } else { - if(((i + f) > inputlen || input[i + f] != find[f]) && TOUPPER(input[i+f]) != TOUPPER(find[f])) { - StringBuf_Printf(&output, "%c", input[i]); - break; - } - } - } - } - } - - //append excess after enough found - if(i < inputlen) - StringBuf_AppendStr(&output, &(input[i])); - - script_pushstrcopy(st, StringBuf_Value(&output)); - StringBuf_Destroy(&output); - return 0; -} - -//======================================================== -// countstr <input>, <search>{, <usecase>} -// -// Note: Counts the number of times <search> occurs in -// <input>. By default will be case sensitive. -//-------------------------------------------------------- -BUILDIN_FUNC(countstr) -{ - const char *input = script_getstr(st, 2); - const char *find = script_getstr(st, 3); - size_t inputlen = strlen(input); - size_t findlen = strlen(find); - bool usecase = true; - - int numFinds = 0; - int i = 0, f = 0; - - if(findlen == 0) { - ShowError("script:countstr: Invalid search length.\n"); - st->state = END; - return 1; - } - - if(script_hasdata(st, 4)) { - if( !script_isstring(st,4) ) - usecase = script_getnum(st, 4) != 0; - else { - ShowError("script:countstr: Invalid usecase value. Expected int got string\n"); - st->state = END; - return 1; - } - } - - for(; i < inputlen; i++) { - for(f = 0; f <= findlen; f++) { - if(f == findlen) { //complete match - numFinds++; - i += findlen - 1; - break; - } else { - if(usecase) { - if((i + f) > inputlen || input[i + f] != find[f]) { - break; - } - } else { - if(((i + f) > inputlen || input[i + f] != find[f]) && TOUPPER(input[i+f]) != TOUPPER(find[f])) { - break; - } - } - } - } - } - script_pushint(st, numFinds); - return 0; -} - - -/// Changes the display name and/or display class of the npc. -/// Returns 0 is successful, 1 if the npc does not exist. -/// -/// setnpcdisplay("<npc name>", "<new display name>", <new class id>, <new size>) -> <int> -/// setnpcdisplay("<npc name>", "<new display name>", <new class id>) -> <int> -/// setnpcdisplay("<npc name>", "<new display name>") -> <int> -/// setnpcdisplay("<npc name>", <new class id>) -> <int> -BUILDIN_FUNC(setnpcdisplay) -{ - const char* name; - const char* newname = NULL; - int class_ = -1, size = -1; - struct script_data* data; - struct npc_data* nd; - - name = script_getstr(st,2); - data = script_getdata(st,3); - - if( script_hasdata(st,4) ) - class_ = script_getnum(st,4); - if( script_hasdata(st,5) ) - size = script_getnum(st,5); - - get_val(st, data); - if( data_isstring(data) ) - newname = conv_str(st,data); - else if( data_isint(data) ) - class_ = conv_num(st,data); - else - { - ShowError("script:setnpcdisplay: expected a string or number\n"); - script_reportdata(data); - return 1; - } - - nd = npc_name2id(name); - if( nd == NULL ) - {// not found - script_pushint(st,1); - return 0; - } - - // update npc - if( newname ) - npc_setdisplayname(nd, newname); - - if( size != -1 && size != (int)nd->size ) - nd->size = size; - else - size = -1; - - if( class_ != -1 && nd->class_ != class_ ) - npc_setclass(nd, class_); - else if( size != -1 ) - { // Required to update the visual size - clif_clearunit_area(&nd->bl, CLR_OUTSIGHT); - clif_spawn(&nd->bl); - } - - script_pushint(st,0); - return 0; -} - -BUILDIN_FUNC(atoi) -{ - const char *value; - value = script_getstr(st,2); - script_pushint(st,atoi(value)); - return 0; -} - -// case-insensitive substring search [lordalfa] -BUILDIN_FUNC(compare) -{ - const char *message; - const char *cmpstring; - message = script_getstr(st,2); - cmpstring = script_getstr(st,3); - script_pushint(st,(stristr(message,cmpstring) != NULL)); - return 0; -} - -// [zBuffer] List of mathematics commands ---> -BUILDIN_FUNC(sqrt) -{ - double i, a; - i = script_getnum(st,2); - a = sqrt(i); - script_pushint(st,(int)a); - return 0; -} - -BUILDIN_FUNC(pow) -{ - double i, a, b; - a = script_getnum(st,2); - b = script_getnum(st,3); - i = pow(a,b); - script_pushint(st,(int)i); - return 0; -} - -BUILDIN_FUNC(distance) -{ - int x0, y0, x1, y1; - - x0 = script_getnum(st,2); - y0 = script_getnum(st,3); - x1 = script_getnum(st,4); - y1 = script_getnum(st,5); - - script_pushint(st,distance_xy(x0,y0,x1,y1)); - return 0; -} - -// <--- [zBuffer] List of mathematics commands - -BUILDIN_FUNC(md5) -{ - const char *tmpstr; - char *md5str; - - tmpstr = script_getstr(st,2); - md5str = (char *)aMalloc((32+1)*sizeof(char)); - MD5_String(tmpstr, md5str); - script_pushstr(st, md5str); - return 0; -} - -// [zBuffer] List of dynamic var commands ---> - -BUILDIN_FUNC(setd) -{ - TBL_PC *sd=NULL; - char varname[100]; - const char *buffer; - int elem; - buffer = script_getstr(st, 2); - - if(sscanf(buffer, "%99[^[][%d]", varname, &elem) < 2) - elem = 0; - - if( not_server_variable(*varname) ) - { - sd = script_rid2sd(st); - if( sd == NULL ) - { - ShowError("script:setd: no player attached for player variable '%s'\n", buffer); - return 0; - } - } - - if( is_string_variable(varname) ) { - setd_sub(st, sd, varname, elem, (void *)script_getstr(st, 3), NULL); - } else { - setd_sub(st, sd, varname, elem, (void *)__64BPRTSIZE(script_getnum(st, 3)), NULL); - } - - return 0; -} - -int buildin_query_sql_sub(struct script_state* st, Sql* handle) -{ - int i, j; - TBL_PC* sd = NULL; - const char* query; - struct script_data* data; - const char* name; - int max_rows = SCRIPT_MAX_ARRAYSIZE; // maximum number of rows - int num_vars; - int num_cols; - - // check target variables - for( i = 3; script_hasdata(st,i); ++i ) { - data = script_getdata(st, i); - if( data_isreference(data) ) { // it's a variable - name = reference_getname(data); - if( not_server_variable(*name) && sd == NULL ) { // requires a player - sd = script_rid2sd(st); - if( sd == NULL ) { // no player attached - script_reportdata(data); - st->state = END; - return 1; - } - } - if( not_array_variable(*name) ) - max_rows = 1;// not an array, limit to one row - } else { - ShowError("script:query_sql: not a variable\n"); - script_reportdata(data); - st->state = END; - return 1; - } - } - num_vars = i - 3; - - // Execute the query - query = script_getstr(st,2); - - if( SQL_ERROR == Sql_QueryStr(handle, query) ) { - Sql_ShowDebug(handle); - script_pushint(st, 0); - return 1; - } - - if( Sql_NumRows(handle) == 0 ) { // No data received - Sql_FreeResult(handle); - script_pushint(st, 0); - return 0; - } - - // Count the number of columns to store - num_cols = Sql_NumColumns(handle); - if( num_vars < num_cols ) { - ShowWarning("script:query_sql: Too many columns, discarding last %u columns.\n", (unsigned int)(num_cols-num_vars)); - script_reportsrc(st); - } else if( num_vars > num_cols ) { - ShowWarning("script:query_sql: Too many variables (%u extra).\n", (unsigned int)(num_vars-num_cols)); - script_reportsrc(st); - } - - // Store data - for( i = 0; i < max_rows && SQL_SUCCESS == Sql_NextRow(handle); ++i ) { - for( j = 0; j < num_vars; ++j ) { - char* str = NULL; - - if( j < num_cols ) - Sql_GetData(handle, j, &str, NULL); - - data = script_getdata(st, j+3); - name = reference_getname(data); - if( is_string_variable(name) ) - setd_sub(st, sd, name, i, (void *)(str?str:""), reference_getref(data)); - else - setd_sub(st, sd, name, i, (void *)__64BPRTSIZE((str?atoi(str):0)), reference_getref(data)); - } - } - if( i == max_rows && max_rows < Sql_NumRows(handle) ) { - ShowWarning("script:query_sql: Only %d/%u rows have been stored.\n", max_rows, (unsigned int)Sql_NumRows(handle)); - script_reportsrc(st); - } - - // Free data - Sql_FreeResult(handle); - script_pushint(st, i); - - return 0; -} - -BUILDIN_FUNC(query_sql) { -#ifdef BETA_THREAD_TEST - if( st->state != RERUNLINE ) { - queryThread_add(st,false); - - st->state = RERUNLINE;/* will continue when the query is finished running. */ - } else - st->state = RUN; - - return 0; -#else - return buildin_query_sql_sub(st, mmysql_handle); -#endif -} - -BUILDIN_FUNC(query_logsql) { - if( !log_config.sql_logs ) {// logmysql_handle == NULL - ShowWarning("buildin_query_logsql: SQL logs are disabled, query '%s' will not be executed.\n", script_getstr(st,2)); - script_pushint(st,-1); - return 1; - } -#ifdef BETA_THREAD_TEST - if( st->state != RERUNLINE ) { - queryThread_add(st,true); - - st->state = RERUNLINE;/* will continue when the query is finished running. */ - } else - st->state = RUN; - - return 0; -#else - return buildin_query_sql_sub(st, logmysql_handle); -#endif -} - -//Allows escaping of a given string. -BUILDIN_FUNC(escape_sql) -{ - const char *str; - char *esc_str; - size_t len; - - str = script_getstr(st,2); - len = strlen(str); - esc_str = (char*)aMalloc(len*2+1); - Sql_EscapeStringLen(mmysql_handle, esc_str, str, len); - script_pushstr(st, esc_str); - return 0; -} - -BUILDIN_FUNC(getd) -{ - char varname[100]; - const char *buffer; - int elem; - - buffer = script_getstr(st, 2); - - if(sscanf(buffer, "%[^[][%d]", varname, &elem) < 2) - elem = 0; - - // Push the 'pointer' so it's more flexible [Lance] - push_val(st->stack, C_NAME, reference_uid(add_str(varname), elem)); - - return 0; -} - -// <--- [zBuffer] List of dynamic var commands -// Pet stat [Lance] -BUILDIN_FUNC(petstat) -{ - TBL_PC *sd = NULL; - struct pet_data *pd; - int flag = script_getnum(st,2); - sd = script_rid2sd(st); - if(!sd || !sd->status.pet_id || !sd->pd){ - if(flag == 2) - script_pushconststr(st, ""); - else - script_pushint(st,0); - return 0; - } - pd = sd->pd; - switch(flag){ - case 1: script_pushint(st,(int)pd->pet.class_); break; - case 2: script_pushstrcopy(st, pd->pet.name); break; - case 3: script_pushint(st,(int)pd->pet.level); break; - case 4: script_pushint(st,(int)pd->pet.hungry); break; - case 5: script_pushint(st,(int)pd->pet.intimate); break; - default: - script_pushint(st,0); - break; - } - return 0; -} - -BUILDIN_FUNC(callshop) -{ - TBL_PC *sd = NULL; - struct npc_data *nd; - const char *shopname; - int flag = 0; - sd = script_rid2sd(st); - if (!sd) { - script_pushint(st,0); - return 0; - } - shopname = script_getstr(st, 2); - if( script_hasdata(st,3) ) - flag = script_getnum(st,3); - nd = npc_name2id(shopname); - if( !nd || nd->bl.type != BL_NPC || (nd->subtype != SHOP && nd->subtype != CASHSHOP) ) - { - ShowError("buildin_callshop: Shop [%s] not found (or NPC is not shop type)\n", shopname); - script_pushint(st,0); - return 1; - } - - if( nd->subtype == SHOP ) - { - // flag the user as using a valid script call for opening the shop (for floating NPCs) - sd->state.callshop = 1; - - switch( flag ) - { - case 1: npc_buysellsel(sd,nd->bl.id,0); break; //Buy window - case 2: npc_buysellsel(sd,nd->bl.id,1); break; //Sell window - default: clif_npcbuysell(sd,nd->bl.id); break; //Show menu - } - } - else - clif_cashshop_show(sd, nd); - - sd->npc_shopid = nd->bl.id; - script_pushint(st,1); - return 0; -} - -BUILDIN_FUNC(npcshopitem) -{ - const char* npcname = script_getstr(st, 2); - struct npc_data* nd = npc_name2id(npcname); - int n, i; - int amount; - - if( !nd || ( nd->subtype != SHOP && nd->subtype != CASHSHOP ) ) - { //Not found. - script_pushint(st,0); - return 0; - } - - // get the count of new entries - amount = (script_lastdata(st)-2)/2; - - // generate new shop item list - RECREATE(nd->u.shop.shop_item, struct npc_item_list, amount); - for( n = 0, i = 3; n < amount; n++, i+=2 ) - { - nd->u.shop.shop_item[n].nameid = script_getnum(st,i); - nd->u.shop.shop_item[n].value = script_getnum(st,i+1); - } - nd->u.shop.count = n; - - script_pushint(st,1); - return 0; -} - -BUILDIN_FUNC(npcshopadditem) -{ - const char* npcname = script_getstr(st,2); - struct npc_data* nd = npc_name2id(npcname); - int n, i; - int amount; - - if( !nd || ( nd->subtype != SHOP && nd->subtype != CASHSHOP ) ) - { //Not found. - script_pushint(st,0); - return 0; - } - - // get the count of new entries - amount = (script_lastdata(st)-2)/2; - - // append new items to existing shop item list - RECREATE(nd->u.shop.shop_item, struct npc_item_list, nd->u.shop.count+amount); - for( n = nd->u.shop.count, i = 3; n < nd->u.shop.count+amount; n++, i+=2 ) - { - nd->u.shop.shop_item[n].nameid = script_getnum(st,i); - nd->u.shop.shop_item[n].value = script_getnum(st,i+1); - } - nd->u.shop.count = n; - - script_pushint(st,1); - return 0; -} - -BUILDIN_FUNC(npcshopdelitem) -{ - const char* npcname = script_getstr(st,2); - struct npc_data* nd = npc_name2id(npcname); - unsigned int nameid; - int n, i; - int amount; - int size; - - if( !nd || ( nd->subtype != SHOP && nd->subtype != CASHSHOP ) ) - { //Not found. - script_pushint(st,0); - return 0; - } - - amount = script_lastdata(st)-2; - size = nd->u.shop.count; - - // remove specified items from the shop item list - for( i = 3; i < 3 + amount; i++ ) - { - nameid = script_getnum(st,i); - - ARR_FIND( 0, size, n, nd->u.shop.shop_item[n].nameid == nameid ); - if( n < size ) - { - memmove(&nd->u.shop.shop_item[n], &nd->u.shop.shop_item[n+1], sizeof(nd->u.shop.shop_item[0])*(size-n)); - size--; - } - } - - RECREATE(nd->u.shop.shop_item, struct npc_item_list, size); - nd->u.shop.count = size; - - script_pushint(st,1); - return 0; -} - -//Sets a script to attach to a shop npc. -BUILDIN_FUNC(npcshopattach) -{ - const char* npcname = script_getstr(st,2); - struct npc_data* nd = npc_name2id(npcname); - int flag = 1; - - if( script_hasdata(st,3) ) - flag = script_getnum(st,3); - - if( !nd || nd->subtype != SHOP ) - { //Not found. - script_pushint(st,0); - return 0; - } - - if (flag) - nd->master_nd = ((struct npc_data *)map_id2bl(st->oid)); - else - nd->master_nd = NULL; - - script_pushint(st,1); - return 0; -} - -/*========================================== - * Returns some values of an item [Lupus] - * Price, Weight, etc... - setitemscript(itemID,"{new item bonus script}",[n]); - Where n: - 0 - script - 1 - Equip script - 2 - Unequip script - *------------------------------------------*/ -BUILDIN_FUNC(setitemscript) -{ - int item_id,n=0; - const char *script; - struct item_data *i_data; - struct script_code **dstscript; - - item_id = script_getnum(st,2); - script = script_getstr(st,3); - if( script_hasdata(st,4) ) - n=script_getnum(st,4); - i_data = itemdb_exists(item_id); - - if (!i_data || script==NULL || ( script[0] && script[0]!='{' )) { - script_pushint(st,0); - return 0; - } - switch (n) { - case 2: - dstscript = &i_data->unequip_script; - break; - case 1: - dstscript = &i_data->equip_script; - break; - default: - dstscript = &i_data->script; - break; - } - if(*dstscript) - script_free_code(*dstscript); - - *dstscript = script[0] ? parse_script(script, "script_setitemscript", 0, 0) : NULL; - script_pushint(st,1); - return 0; -} - -/* Work In Progress [Lupus] -BUILDIN_FUNC(addmonsterdrop) -{ - int class_,item_id,chance; - class_=script_getnum(st,2); - item_id=script_getnum(st,3); - chance=script_getnum(st,4); - if(class_>1000 && item_id>500 && chance>0) { - script_pushint(st,1); - } else { - script_pushint(st,0); - } -} - -BUILDIN_FUNC(delmonsterdrop) -{ - int class_,item_id; - class_=script_getnum(st,2); - item_id=script_getnum(st,3); - if(class_>1000 && item_id>500) { - script_pushint(st,1); - } else { - script_pushint(st,0); - } -} -*/ - -/*========================================== - * Returns some values of a monster [Lupus] - * Name, Level, race, size, etc... - getmonsterinfo(monsterID,queryIndex); - *------------------------------------------*/ -BUILDIN_FUNC(getmonsterinfo) -{ - struct mob_db *mob; - int mob_id; - - mob_id = script_getnum(st,2); - if (!mobdb_checkid(mob_id)) { - ShowError("buildin_getmonsterinfo: Wrong Monster ID: %i\n", mob_id); - if ( !script_getnum(st,3) ) //requested a string - script_pushconststr(st,"null"); - else - script_pushint(st,-1); - return -1; - } - mob = mob_db(mob_id); - switch ( script_getnum(st,3) ) { - case 0: script_pushstrcopy(st,mob->jname); break; - case 1: script_pushint(st,mob->lv); break; - case 2: script_pushint(st,mob->status.max_hp); break; - case 3: script_pushint(st,mob->base_exp); break; - case 4: script_pushint(st,mob->job_exp); break; - case 5: script_pushint(st,mob->status.rhw.atk); break; - case 6: script_pushint(st,mob->status.rhw.atk2); break; - case 7: script_pushint(st,mob->status.def); break; - case 8: script_pushint(st,mob->status.mdef); break; - case 9: script_pushint(st,mob->status.str); break; - case 10: script_pushint(st,mob->status.agi); break; - case 11: script_pushint(st,mob->status.vit); break; - case 12: script_pushint(st,mob->status.int_); break; - case 13: script_pushint(st,mob->status.dex); break; - case 14: script_pushint(st,mob->status.luk); break; - case 15: script_pushint(st,mob->status.rhw.range); break; - case 16: script_pushint(st,mob->range2); break; - case 17: script_pushint(st,mob->range3); break; - case 18: script_pushint(st,mob->status.size); break; - case 19: script_pushint(st,mob->status.race); break; - case 20: script_pushint(st,mob->status.def_ele); break; - case 21: script_pushint(st,mob->status.mode); break; - case 22: script_pushint(st,mob->mexp); break; - default: script_pushint(st,-1); //wrong Index - } - return 0; -} - -BUILDIN_FUNC(checkvending) // check vending [Nab4] -{ - TBL_PC *sd = NULL; - - if(script_hasdata(st,2)) - sd = map_nick2sd(script_getstr(st,2)); - else - sd = script_rid2sd(st); - - if(sd) - script_pushint(st, sd->state.autotrade ? 2 : sd->state.vending); - else - script_pushint(st,0); - - return 0; -} - - -BUILDIN_FUNC(checkchatting) // check chatting [Marka] -{ - TBL_PC *sd = NULL; - - if(script_hasdata(st,2)) - sd = map_nick2sd(script_getstr(st,2)); - else - sd = script_rid2sd(st); - - if(sd) - script_pushint(st,(sd->chatID != 0)); - else - script_pushint(st,0); - - return 0; -} - -BUILDIN_FUNC(searchitem) -{ - struct script_data* data = script_getdata(st, 2); - const char *itemname = script_getstr(st,3); - struct item_data *items[MAX_SEARCH]; - int count; - - char* name; - int32 start; - int32 id; - int32 i; - TBL_PC* sd = NULL; - - if ((items[0] = itemdb_exists(atoi(itemname)))) - count = 1; - else { - count = itemdb_searchname_array(items, ARRAYLENGTH(items), itemname); - if (count > MAX_SEARCH) count = MAX_SEARCH; - } - - if (!count) { - script_pushint(st, 0); - return 0; - } - - if( !data_isreference(data) ) - { - ShowError("script:searchitem: not a variable\n"); - script_reportdata(data); - st->state = END; - return 1;// not a variable - } - - id = reference_getid(data); - start = reference_getindex(data); - name = reference_getname(data); - if( not_array_variable(*name) ) - { - ShowError("script:searchitem: illegal scope\n"); - script_reportdata(data); - st->state = END; - return 1;// not supported - } - - if( not_server_variable(*name) ) - { - sd = script_rid2sd(st); - if( sd == NULL ) - return 0;// no player attached - } - - if( is_string_variable(name) ) - {// string array - ShowError("script:searchitem: not an integer array reference\n"); - script_reportdata(data); - st->state = END; - return 1;// not supported - } - - for( i = 0; i < count; ++start, ++i ) - {// Set array - void* v = (void*)__64BPRTSIZE((int)items[i]->nameid); - set_reg(st, sd, reference_uid(id, start), name, v, reference_getref(data)); - } - - script_pushint(st, count); - return 0; -} - -int axtoi(const char *hexStg) -{ - int n = 0; // position in string - int16 m = 0; // position in digit[] to shift - int count; // loop index - int intValue = 0; // integer value of hex string - int digit[11]; // hold values to convert - while (n < 10) { - if (hexStg[n]=='\0') - break; - if (hexStg[n] > 0x29 && hexStg[n] < 0x40 ) //if 0 to 9 - digit[n] = hexStg[n] & 0x0f; //convert to int - else if (hexStg[n] >='a' && hexStg[n] <= 'f') //if a to f - digit[n] = (hexStg[n] & 0x0f) + 9; //convert to int - else if (hexStg[n] >='A' && hexStg[n] <= 'F') //if A to F - digit[n] = (hexStg[n] & 0x0f) + 9; //convert to int - else break; - n++; - } - count = n; - m = n - 1; - n = 0; - while(n < count) { - // digit[n] is value of hex digit at position n - // (m << 2) is the number of positions to shift - // OR the bits into return value - intValue = intValue | (digit[n] << (m << 2)); - m--; // adjust the position to set - n++; // next digit to process - } - return (intValue); -} - -// [Lance] Hex string to integer converter -BUILDIN_FUNC(axtoi) -{ - const char *hex = script_getstr(st,2); - script_pushint(st,axtoi(hex)); - return 0; -} - -// [zBuffer] List of player cont commands ---> -BUILDIN_FUNC(rid2name) -{ - struct block_list *bl = NULL; - int rid = script_getnum(st,2); - if((bl = map_id2bl(rid))) - { - switch(bl->type) { - case BL_MOB: script_pushstrcopy(st,((TBL_MOB*)bl)->name); break; - case BL_PC: script_pushstrcopy(st,((TBL_PC*)bl)->status.name); break; - case BL_NPC: script_pushstrcopy(st,((TBL_NPC*)bl)->exname); break; - case BL_PET: script_pushstrcopy(st,((TBL_PET*)bl)->pet.name); break; - case BL_HOM: script_pushstrcopy(st,((TBL_HOM*)bl)->homunculus.name); break; - case BL_MER: script_pushstrcopy(st,((TBL_MER*)bl)->db->name); break; - default: - ShowError("buildin_rid2name: BL type unknown.\n"); - script_pushconststr(st,""); - break; - } - } else { - ShowError("buildin_rid2name: invalid RID\n"); - script_pushconststr(st,"(null)"); - } - return 0; -} - -BUILDIN_FUNC(pcblockmove) -{ - int id, flag; - TBL_PC *sd = NULL; - - id = script_getnum(st,2); - flag = script_getnum(st,3); - - if(id) - sd = map_id2sd(id); - else - sd = script_rid2sd(st); - - if(sd) - sd->state.blockedmove = flag > 0; - - return 0; -} - -BUILDIN_FUNC(pcfollow) -{ - int id, targetid; - TBL_PC *sd = NULL; - - - id = script_getnum(st,2); - targetid = script_getnum(st,3); - - if(id) - sd = map_id2sd(id); - else - sd = script_rid2sd(st); - - if(sd) - pc_follow(sd, targetid); - - return 0; -} - -BUILDIN_FUNC(pcstopfollow) -{ - int id; - TBL_PC *sd = NULL; - - - id = script_getnum(st,2); - - if(id) - sd = map_id2sd(id); - else - sd = script_rid2sd(st); - - if(sd) - pc_stop_following(sd); - - return 0; -} -// <--- [zBuffer] List of player cont commands -// [zBuffer] List of mob control commands ---> -//## TODO always return if the request/whatever was successfull [FlavioJS] - -/// Makes the unit walk to target position or map -/// Returns if it was successfull -/// -/// unitwalk(<unit_id>,<x>,<y>) -> <bool> -/// unitwalk(<unit_id>,<map_id>) -> <bool> -BUILDIN_FUNC(unitwalk) -{ - struct block_list* bl; - - bl = map_id2bl(script_getnum(st,2)); - if( bl == NULL ) - { - script_pushint(st, 0); - } - else if( script_hasdata(st,4) ) - { - int x = script_getnum(st,3); - int y = script_getnum(st,4); - script_pushint(st, unit_walktoxy(bl,x,y,0));// We'll use harder calculations. - } - else - { - int map_id = script_getnum(st,3); - script_pushint(st, unit_walktobl(bl,map_id2bl(map_id),65025,1)); - } - - return 0; -} - -/// Kills the unit -/// -/// unitkill <unit_id>; -BUILDIN_FUNC(unitkill) -{ - struct block_list* bl = map_id2bl(script_getnum(st,2)); - if( bl != NULL ) - status_kill(bl); - - return 0; -} - -/// Warps the unit to the target position in the target map -/// Returns if it was successfull -/// -/// unitwarp(<unit_id>,"<map name>",<x>,<y>) -> <bool> -BUILDIN_FUNC(unitwarp) -{ - int unit_id; - int map; - short x; - short y; - struct block_list* bl; - const char *mapname; - - unit_id = script_getnum(st,2); - mapname = script_getstr(st, 3); - x = (short)script_getnum(st,4); - y = (short)script_getnum(st,5); - - if (!unit_id) //Warp the script's runner - bl = map_id2bl(st->rid); - else - bl = map_id2bl(unit_id); - - if( strcmp(mapname,"this") == 0 ) - map = bl?bl->m:-1; - else - map = map_mapname2mapid(mapname); - - if( map >= 0 && bl != NULL ) - script_pushint(st, unit_warp(bl,map,x,y,CLR_OUTSIGHT)); - else - script_pushint(st, 0); - - return 0; -} - -/// Makes the unit attack the target. -/// If the unit is a player and <action type> is not 0, it does a continuous -/// attack instead of a single attack. -/// Returns if the request was successfull. -/// -/// unitattack(<unit_id>,"<target name>"{,<action type>}) -> <bool> -/// unitattack(<unit_id>,<target_id>{,<action type>}) -> <bool> -BUILDIN_FUNC(unitattack) -{ - struct block_list* unit_bl; - struct block_list* target_bl = NULL; - struct script_data* data; - int actiontype = 0; - - // get unit - unit_bl = map_id2bl(script_getnum(st,2)); - if( unit_bl == NULL ) { - script_pushint(st, 0); - return 0; - } - - data = script_getdata(st, 3); - get_val(st, data); - if( data_isstring(data) ) - { - TBL_PC* sd = map_nick2sd(conv_str(st, data)); - if( sd != NULL ) - target_bl = &sd->bl; - } else - target_bl = map_id2bl(conv_num(st, data)); - // request the attack - if( target_bl == NULL ) - { - script_pushint(st, 0); - return 0; - } - - // get actiontype - if( script_hasdata(st,4) ) - actiontype = script_getnum(st,4); - - switch( unit_bl->type ) - { - case BL_PC: - clif_parse_ActionRequest_sub(((TBL_PC *)unit_bl), actiontype > 0 ? 0x07 : 0x00, target_bl->id, gettick()); - script_pushint(st, 1); - return 0; - case BL_MOB: - ((TBL_MOB *)unit_bl)->target_id = target_bl->id; - break; - case BL_PET: - ((TBL_PET *)unit_bl)->target_id = target_bl->id; - break; - default: - ShowError("script:unitattack: unsupported source unit type %d\n", unit_bl->type); - script_pushint(st, 0); - return 1; - } - script_pushint(st, unit_walktobl(unit_bl, target_bl, 65025, 2)); - return 0; -} - -/// Makes the unit stop attacking and moving -/// -/// unitstop <unit_id>; -BUILDIN_FUNC(unitstop) -{ - int unit_id; - struct block_list* bl; - - unit_id = script_getnum(st,2); - - bl = map_id2bl(unit_id); - if( bl != NULL ) - { - unit_stop_attack(bl); - unit_stop_walking(bl,4); - if( bl->type == BL_MOB ) - ((TBL_MOB*)bl)->target_id = 0; - } - - return 0; -} - -/// Makes the unit say the message -/// -/// unittalk <unit_id>,"<message>"; -BUILDIN_FUNC(unittalk) -{ - int unit_id; - const char* message; - struct block_list* bl; - - unit_id = script_getnum(st,2); - message = script_getstr(st, 3); - - bl = map_id2bl(unit_id); - if( bl != NULL ) - { - struct StringBuf sbuf; - StringBuf_Init(&sbuf); - StringBuf_Printf(&sbuf, "%s : %s", status_get_name(bl), message); - clif_message(bl, StringBuf_Value(&sbuf)); - if( bl->type == BL_PC ) - clif_displaymessage(((TBL_PC*)bl)->fd, StringBuf_Value(&sbuf)); - StringBuf_Destroy(&sbuf); - } - - return 0; -} - -/// Makes the unit do an emotion -/// -/// unitemote <unit_id>,<emotion>; -/// -/// @see e_* in const.txt -BUILDIN_FUNC(unitemote) -{ - int unit_id; - int emotion; - struct block_list* bl; - - unit_id = script_getnum(st,2); - emotion = script_getnum(st,3); - bl = map_id2bl(unit_id); - if( bl != NULL ) - clif_emotion(bl, emotion); - - return 0; -} - -/// Makes the unit cast the skill on the target or self if no target is specified -/// -/// unitskilluseid <unit_id>,<skill_id>,<skill_lv>{,<target_id>}; -/// unitskilluseid <unit_id>,"<skill name>",<skill_lv>{,<target_id>}; -BUILDIN_FUNC(unitskilluseid) -{ - int unit_id; - uint16 skill_id; - uint16 skill_lv; - int target_id; - struct block_list* bl; - - unit_id = script_getnum(st,2); - skill_id = ( script_isstring(st,3) ? skill_name2id(script_getstr(st,3)) : script_getnum(st,3) ); - skill_lv = script_getnum(st,4); - target_id = ( script_hasdata(st,5) ? script_getnum(st,5) : unit_id ); - - bl = map_id2bl(unit_id); - if( bl != NULL ) - unit_skilluse_id(bl, target_id, skill_id, skill_lv); - - return 0; -} - -/// Makes the unit cast the skill on the target position. -/// -/// unitskillusepos <unit_id>,<skill_id>,<skill_lv>,<target_x>,<target_y>; -/// unitskillusepos <unit_id>,"<skill name>",<skill_lv>,<target_x>,<target_y>; -BUILDIN_FUNC(unitskillusepos) -{ - int unit_id; - uint16 skill_id; - uint16 skill_lv; - int skill_x; - int skill_y; - struct block_list* bl; - - unit_id = script_getnum(st,2); - skill_id = ( script_isstring(st,3) ? skill_name2id(script_getstr(st,3)) : script_getnum(st,3) ); - skill_lv = script_getnum(st,4); - skill_x = script_getnum(st,5); - skill_y = script_getnum(st,6); - - bl = map_id2bl(unit_id); - if( bl != NULL ) - unit_skilluse_pos(bl, skill_x, skill_y, skill_id, skill_lv); - - return 0; -} - -// <--- [zBuffer] List of mob control commands - -/// Pauses the execution of the script, detaching the player -/// -/// sleep <mili seconds>; -BUILDIN_FUNC(sleep) -{ - int ticks; - - ticks = script_getnum(st,2); - - // detach the player - script_detach_rid(st); - - if( ticks <= 0 ) - {// do nothing - } - else if( st->sleep.tick == 0 ) - {// sleep for the target amount of time - st->state = RERUNLINE; - st->sleep.tick = ticks; - } - else - {// sleep time is over - st->state = RUN; - st->sleep.tick = 0; - } - return 0; -} - -/// Pauses the execution of the script, keeping the player attached -/// Returns if a player is still attached -/// -/// sleep2(<mili secconds>) -> <bool> -BUILDIN_FUNC(sleep2) -{ - int ticks; - - ticks = script_getnum(st,2); - - if( ticks <= 0 ) - {// do nothing - script_pushint(st, (map_id2sd(st->rid)!=NULL)); - } - else if( !st->sleep.tick ) - {// sleep for the target amount of time - st->state = RERUNLINE; - st->sleep.tick = ticks; - } - else - {// sleep time is over - st->state = RUN; - st->sleep.tick = 0; - script_pushint(st, (map_id2sd(st->rid)!=NULL)); - } - return 0; -} - -/// Awakes all the sleep timers of the target npc -/// -/// awake "<npc name>"; -BUILDIN_FUNC(awake) -{ - struct npc_data* nd; - struct linkdb_node *node = (struct linkdb_node *)sleep_db; - - nd = npc_name2id(script_getstr(st, 2)); - if( nd == NULL ) { - ShowError("awake: NPC \"%s\" not found\n", script_getstr(st, 2)); - return 1; - } - - while( node ) - { - if( (int)__64BPRTSIZE(node->key) == nd->bl.id ) - {// sleep timer for the npc - struct script_state* tst = (struct script_state*)node->data; - TBL_PC* sd = map_id2sd(tst->rid); - - if( tst->sleep.timer == INVALID_TIMER ) - {// already awake ??? - node = node->next; - continue; - } - if( (sd && sd->status.char_id != tst->sleep.charid) || (tst->rid && !sd)) - {// char not online anymore / another char of the same account is online - Cancel execution - tst->state = END; - tst->rid = 0; - } - - delete_timer(tst->sleep.timer, run_script_timer); - node = script_erase_sleepdb(node); - tst->sleep.timer = INVALID_TIMER; - if(tst->state != RERUNLINE) - tst->sleep.tick = 0; - run_script_main(tst); - } - else - { - node = node->next; - } - } - return 0; -} - -/// Returns a reference to a variable of the target NPC. -/// Returns 0 if an error occurs. -/// -/// getvariableofnpc(<variable>, "<npc name>") -> <reference> -BUILDIN_FUNC(getvariableofnpc) -{ - struct script_data* data; - const char* name; - struct npc_data* nd; - - data = script_getdata(st,2); - if( !data_isreference(data) ) - {// Not a reference (aka varaible name) - ShowError("script:getvariableofnpc: not a variable\n"); - script_reportdata(data); - script_pushnil(st); - st->state = END; - return 1; - } - - name = reference_getname(data); - if( *name != '.' || name[1] == '@' ) - {// not a npc variable - ShowError("script:getvariableofnpc: invalid scope (not npc variable)\n"); - script_reportdata(data); - script_pushnil(st); - st->state = END; - return 1; - } - - nd = npc_name2id(script_getstr(st,3)); - if( nd == NULL || nd->subtype != SCRIPT || nd->u.scr.script == NULL ) - {// NPC not found or has no script - ShowError("script:getvariableofnpc: can't find npc %s\n", script_getstr(st,3)); - script_pushnil(st); - st->state = END; - return 1; - } - - push_val2(st->stack, C_NAME, reference_getuid(data), &nd->u.scr.script->script_vars ); - return 0; -} - -/// Opens a warp portal. -/// Has no "portal opening" effect/sound, it opens the portal immediately. -/// -/// warpportal <source x>,<source y>,"<target map>",<target x>,<target y>; -/// -/// @author blackhole89 -BUILDIN_FUNC(warpportal) -{ - int spx; - int spy; - unsigned short mapindex; - int tpx; - int tpy; - struct skill_unit_group* group; - struct block_list* bl; - - bl = map_id2bl(st->oid); - if( bl == NULL ) - { - ShowError("script:warpportal: npc is needed\n"); - return 1; - } - - spx = script_getnum(st,2); - spy = script_getnum(st,3); - mapindex = mapindex_name2id(script_getstr(st, 4)); - tpx = script_getnum(st,5); - tpy = script_getnum(st,6); - - if( mapindex == 0 ) - return 0;// map not found - - group = skill_unitsetting(bl, AL_WARP, 4, spx, spy, 0); - if( group == NULL ) - return 0;// failed - group->val2 = (tpx<<16) | tpy; - group->val3 = mapindex; - - return 0; -} - -BUILDIN_FUNC(openmail) -{ - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - mail_openmail(sd); - - return 0; -} - -BUILDIN_FUNC(openauction) -{ - TBL_PC* sd; - - sd = script_rid2sd(st); - if( sd == NULL ) - return 0; - - clif_Auction_openwindow(sd); - - return 0; -} - -/// Retrieves the value of the specified flag of the specified cell. -/// -/// checkcell("<map name>",<x>,<y>,<type>) -> <bool> -/// -/// @see cell_chk* constants in const.txt for the types -BUILDIN_FUNC(checkcell) -{ - int16 m = map_mapname2mapid(script_getstr(st,2)); - int16 x = script_getnum(st,3); - int16 y = script_getnum(st,4); - cell_chk type = (cell_chk)script_getnum(st,5); - - script_pushint(st, map_getcell(m, x, y, type)); - - return 0; -} - -/// Modifies flags of cells in the specified area. -/// -/// setcell "<map name>",<x1>,<y1>,<x2>,<y2>,<type>,<flag>; -/// -/// @see cell_* constants in const.txt for the types -BUILDIN_FUNC(setcell) -{ - int16 m = map_mapname2mapid(script_getstr(st,2)); - int16 x1 = script_getnum(st,3); - int16 y1 = script_getnum(st,4); - int16 x2 = script_getnum(st,5); - int16 y2 = script_getnum(st,6); - cell_t type = (cell_t)script_getnum(st,7); - bool flag = (bool)script_getnum(st,8); - - int x,y; - - if( x1 > x2 ) swap(x1,x2); - if( y1 > y2 ) swap(y1,y2); - - for( y = y1; y <= y2; ++y ) - for( x = x1; x <= x2; ++x ) - map_setcell(m, x, y, type, flag); - - return 0; -} - -/*========================================== - * Mercenary Commands - *------------------------------------------*/ -BUILDIN_FUNC(mercenary_create) -{ - struct map_session_data *sd; - int class_, contract_time; - - if( (sd = script_rid2sd(st)) == NULL || sd->md || sd->status.mer_id != 0 ) - return 0; - - class_ = script_getnum(st,2); - - if( !merc_class(class_) ) - return 0; - - contract_time = script_getnum(st,3); - merc_create(sd, class_, contract_time); - return 0; -} - -BUILDIN_FUNC(mercenary_heal) -{ - struct map_session_data *sd = script_rid2sd(st); - int hp, sp; - - if( sd == NULL || sd->md == NULL ) - return 0; - hp = script_getnum(st,2); - sp = script_getnum(st,3); - - status_heal(&sd->md->bl, hp, sp, 0); - return 0; -} - -BUILDIN_FUNC(mercenary_sc_start) -{ - struct map_session_data *sd = script_rid2sd(st); - enum sc_type type; - int tick, val1; - - if( sd == NULL || sd->md == NULL ) - return 0; - - type = (sc_type)script_getnum(st,2); - tick = script_getnum(st,3); - val1 = script_getnum(st,4); - - status_change_start(&sd->md->bl, type, 10000, val1, 0, 0, 0, tick, 2); - return 0; -} - -BUILDIN_FUNC(mercenary_get_calls) -{ - struct map_session_data *sd = script_rid2sd(st); - int guild; - - if( sd == NULL ) - return 0; - - guild = script_getnum(st,2); - switch( guild ) - { - case ARCH_MERC_GUILD: - script_pushint(st,sd->status.arch_calls); - break; - case SPEAR_MERC_GUILD: - script_pushint(st,sd->status.spear_calls); - break; - case SWORD_MERC_GUILD: - script_pushint(st,sd->status.sword_calls); - break; - default: - script_pushint(st,0); - break; - } - - return 0; -} - -BUILDIN_FUNC(mercenary_set_calls) -{ - struct map_session_data *sd = script_rid2sd(st); - int guild, value, *calls; - - if( sd == NULL ) - return 0; - - guild = script_getnum(st,2); - value = script_getnum(st,3); - - switch( guild ) - { - case ARCH_MERC_GUILD: - calls = &sd->status.arch_calls; - break; - case SPEAR_MERC_GUILD: - calls = &sd->status.spear_calls; - break; - case SWORD_MERC_GUILD: - calls = &sd->status.sword_calls; - break; - default: - return 0; // Invalid Guild - } - - *calls += value; - *calls = cap_value(*calls, 0, INT_MAX); - - return 0; -} - -BUILDIN_FUNC(mercenary_get_faith) -{ - struct map_session_data *sd = script_rid2sd(st); - int guild; - - if( sd == NULL ) - return 0; - - guild = script_getnum(st,2); - switch( guild ) - { - case ARCH_MERC_GUILD: - script_pushint(st,sd->status.arch_faith); - break; - case SPEAR_MERC_GUILD: - script_pushint(st,sd->status.spear_faith); - break; - case SWORD_MERC_GUILD: - script_pushint(st,sd->status.sword_faith); - break; - default: - script_pushint(st,0); - break; - } - - return 0; -} - -BUILDIN_FUNC(mercenary_set_faith) -{ - struct map_session_data *sd = script_rid2sd(st); - int guild, value, *calls; - - if( sd == NULL ) - return 0; - - guild = script_getnum(st,2); - value = script_getnum(st,3); - - switch( guild ) - { - case ARCH_MERC_GUILD: - calls = &sd->status.arch_faith; - break; - case SPEAR_MERC_GUILD: - calls = &sd->status.spear_faith; - break; - case SWORD_MERC_GUILD: - calls = &sd->status.sword_faith; - break; - default: - return 0; // Invalid Guild - } - - *calls += value; - *calls = cap_value(*calls, 0, INT_MAX); - if( mercenary_get_guild(sd->md) == guild ) - clif_mercenary_updatestatus(sd,SP_MERCFAITH); - - return 0; -} - -/*------------------------------------------ - * Book Reading - *------------------------------------------*/ -BUILDIN_FUNC(readbook) -{ - struct map_session_data *sd; - int book_id, page; - - if( (sd = script_rid2sd(st)) == NULL ) - return 0; - - book_id = script_getnum(st,2); - page = script_getnum(st,3); - - clif_readbook(sd->fd, book_id, page); - return 0; -} - -/****************** -Questlog script commands -*******************/ - -BUILDIN_FUNC(setquest) -{ - struct map_session_data *sd = script_rid2sd(st); - nullpo_ret(sd); - - quest_add(sd, script_getnum(st, 2)); - return 0; -} - -BUILDIN_FUNC(erasequest) -{ - struct map_session_data *sd = script_rid2sd(st); - nullpo_ret(sd); - - quest_delete(sd, script_getnum(st, 2)); - return 0; -} - -BUILDIN_FUNC(completequest) -{ - struct map_session_data *sd = script_rid2sd(st); - nullpo_ret(sd); - - quest_update_status(sd, script_getnum(st, 2), Q_COMPLETE); - return 0; -} - -BUILDIN_FUNC(changequest) -{ - struct map_session_data *sd = script_rid2sd(st); - nullpo_ret(sd); - - quest_change(sd, script_getnum(st, 2),script_getnum(st, 3)); - return 0; -} - -BUILDIN_FUNC(checkquest) -{ - struct map_session_data *sd = script_rid2sd(st); - quest_check_type type = HAVEQUEST; - - nullpo_ret(sd); - - if( script_hasdata(st, 3) ) - type = (quest_check_type)script_getnum(st, 3); - - script_pushint(st, quest_check(sd, script_getnum(st, 2), type)); - - return 0; -} - -BUILDIN_FUNC(showevent) -{ - TBL_PC *sd = script_rid2sd(st); - struct npc_data *nd = map_id2nd(st->oid); - int state, color; - - if( sd == NULL || nd == NULL ) - return 0; - state = script_getnum(st, 2); - color = script_getnum(st, 3); - - if( color < 0 || color > 3 ) - color = 0; // set default color - - clif_quest_show_event(sd, &nd->bl, state, color); - return 0; -} - -/*========================================== - * BattleGround System - *------------------------------------------*/ -BUILDIN_FUNC(waitingroom2bg) -{ - struct npc_data *nd; - struct chat_data *cd; - const char *map_name, *ev = "", *dev = ""; - int x, y, i, mapindex = 0, bg_id, n; - struct map_session_data *sd; - - if( script_hasdata(st,7) ) - nd = npc_name2id(script_getstr(st,7)); - else - nd = (struct npc_data *)map_id2bl(st->oid); - - if( nd == NULL || (cd = (struct chat_data *)map_id2bl(nd->chat_id)) == NULL ) - { - script_pushint(st,0); - return 0; - } - - map_name = script_getstr(st,2); - if( strcmp(map_name,"-") != 0 ) - { - mapindex = mapindex_name2id(map_name); - if( mapindex == 0 ) - { // Invalid Map - script_pushint(st,0); - return 0; - } - } - - x = script_getnum(st,3); - y = script_getnum(st,4); - ev = script_getstr(st,5); // Logout Event - dev = script_getstr(st,6); // Die Event - - if( (bg_id = bg_create(mapindex, x, y, ev, dev)) == 0 ) - { // Creation failed - script_pushint(st,0); - return 0; - } - - n = cd->users; - for( i = 0; i < n && i < MAX_BG_MEMBERS; i++ ) - { - if( (sd = cd->usersd[i]) != NULL && bg_team_join(bg_id, sd) ) - mapreg_setreg(reference_uid(add_str("$@arenamembers"), i), sd->bl.id); - else - mapreg_setreg(reference_uid(add_str("$@arenamembers"), i), 0); - } - - mapreg_setreg(add_str("$@arenamembersnum"), i); - script_pushint(st,bg_id); - return 0; -} - -BUILDIN_FUNC(waitingroom2bg_single) -{ - const char* map_name; - struct npc_data *nd; - struct chat_data *cd; - struct map_session_data *sd; - int x, y, mapindex, bg_id; - - bg_id = script_getnum(st,2); - map_name = script_getstr(st,3); - if( (mapindex = mapindex_name2id(map_name)) == 0 ) - return 0; // Invalid Map - - x = script_getnum(st,4); - y = script_getnum(st,5); - nd = npc_name2id(script_getstr(st,6)); - - if( nd == NULL || (cd = (struct chat_data *)map_id2bl(nd->chat_id)) == NULL || cd->users <= 0 ) - return 0; - - if( (sd = cd->usersd[0]) == NULL ) - return 0; - - if( bg_team_join(bg_id, sd) ) - { - pc_setpos(sd, mapindex, x, y, CLR_TELEPORT); - script_pushint(st,1); - } - else - script_pushint(st,0); - - return 0; -} - -BUILDIN_FUNC(bg_team_setxy) -{ - struct battleground_data *bg; - int bg_id; - - bg_id = script_getnum(st,2); - if( (bg = bg_team_search(bg_id)) == NULL ) - return 0; - - bg->x = script_getnum(st,3); - bg->y = script_getnum(st,4); - return 0; -} - -BUILDIN_FUNC(bg_warp) -{ - int x, y, mapindex, bg_id; - const char* map_name; - - bg_id = script_getnum(st,2); - map_name = script_getstr(st,3); - if( (mapindex = mapindex_name2id(map_name)) == 0 ) - return 0; // Invalid Map - x = script_getnum(st,4); - y = script_getnum(st,5); - bg_team_warp(bg_id, mapindex, x, y); - return 0; -} - -BUILDIN_FUNC(bg_monster) -{ - int class_ = 0, x = 0, y = 0, bg_id = 0; - const char *str,*map, *evt=""; - - bg_id = script_getnum(st,2); - map = script_getstr(st,3); - x = script_getnum(st,4); - y = script_getnum(st,5); - str = script_getstr(st,6); - class_ = script_getnum(st,7); - if( script_hasdata(st,8) ) evt = script_getstr(st,8); - check_event(st, evt); - script_pushint(st, mob_spawn_bg(map,x,y,str,class_,evt,bg_id)); - return 0; -} - -BUILDIN_FUNC(bg_monster_set_team) -{ - struct mob_data *md; - struct block_list *mbl; - int id = script_getnum(st,2), - bg_id = script_getnum(st,3); - - if( (mbl = map_id2bl(id)) == NULL || mbl->type != BL_MOB ) - return 0; - md = (TBL_MOB *)mbl; - md->bg_id = bg_id; - - mob_stop_attack(md); - mob_stop_walking(md, 0); - md->target_id = md->attacked_id = 0; - clif_charnameack(0, &md->bl); - - return 0; -} - -BUILDIN_FUNC(bg_leave) -{ - struct map_session_data *sd = script_rid2sd(st); - if( sd == NULL || !sd->bg_id ) - return 0; - - bg_team_leave(sd,0); - return 0; -} - -BUILDIN_FUNC(bg_destroy) -{ - int bg_id = script_getnum(st,2); - bg_team_delete(bg_id); - return 0; -} - -BUILDIN_FUNC(bg_getareausers) -{ - const char *str; - int16 m, x0, y0, x1, y1; - int bg_id; - int i = 0, c = 0; - struct battleground_data *bg = NULL; - struct map_session_data *sd; - - bg_id = script_getnum(st,2); - str = script_getstr(st,3); - - if( (bg = bg_team_search(bg_id)) == NULL || (m = map_mapname2mapid(str)) < 0 ) - { - script_pushint(st,0); - return 0; - } - - x0 = script_getnum(st,4); - y0 = script_getnum(st,5); - x1 = script_getnum(st,6); - y1 = script_getnum(st,7); - - for( i = 0; i < MAX_BG_MEMBERS; i++ ) - { - if( (sd = bg->members[i].sd) == NULL ) - continue; - if( sd->bl.m != m || sd->bl.x < x0 || sd->bl.y < y0 || sd->bl.x > x1 || sd->bl.y > y1 ) - continue; - c++; - } - - script_pushint(st,c); - return 0; -} - -BUILDIN_FUNC(bg_updatescore) -{ - const char *str; - int16 m; - - str = script_getstr(st,2); - if( (m = map_mapname2mapid(str)) < 0 ) - return 0; - - map[m].bgscore_lion = script_getnum(st,3); - map[m].bgscore_eagle = script_getnum(st,4); - - clif_bg_updatescore(m); - return 0; -} - -BUILDIN_FUNC(bg_get_data) -{ - struct battleground_data *bg; - int bg_id = script_getnum(st,2), - type = script_getnum(st,3); - - if( (bg = bg_team_search(bg_id)) == NULL ) - { - script_pushint(st,0); - return 0; - } - - switch( type ) - { - case 0: script_pushint(st, bg->count); break; - default: - ShowError("script:bg_get_data: unknown data identifier %d\n", type); - break; - } - - return 0; -} - -/*========================================== - * Instancing Script Commands - *------------------------------------------*/ - -BUILDIN_FUNC(instance_create) -{ - const char *name; - int party_id, res; - - name = script_getstr(st, 2); - party_id = script_getnum(st, 3); - - res = instance_create(party_id, name); - if( res == -4 ) // Already exists - { - script_pushint(st, -1); - return 0; - } - else if( res < 0 ) - { - const char *err; - switch(res) - { - case -3: err = "No free instances"; break; - case -2: err = "Invalid party ID"; break; - case -1: err = "Invalid type"; break; - default: err = "Unknown"; break; - } - ShowError("buildin_instance_create: %s [%d].\n", err, res); - script_pushint(st, -2); - return 0; - } - - script_pushint(st, res); - return 0; -} - -BUILDIN_FUNC(instance_destroy) -{ - int instance_id; - struct map_session_data *sd; - struct party_data *p; - - if( script_hasdata(st, 2) ) - instance_id = script_getnum(st, 2); - else if( st->instance_id ) - instance_id = st->instance_id; - else if( (sd = script_rid2sd(st)) != NULL && sd->status.party_id && (p = party_search(sd->status.party_id)) != NULL && p->instance_id ) - instance_id = p->instance_id; - else return 0; - - if( instance_id <= 0 || instance_id >= MAX_INSTANCE ) - { - ShowError("buildin_instance_destroy: Trying to destroy invalid instance %d.\n", instance_id); - return 0; - } - - instance_destroy(instance_id); - return 0; -} - -BUILDIN_FUNC(instance_attachmap) -{ - const char *name; - int16 m; - int instance_id; - bool usebasename = false; - - name = script_getstr(st,2); - instance_id = script_getnum(st,3); - if( script_hasdata(st,4) && script_getnum(st,4) > 0) - usebasename = true; - - if( (m = instance_add_map(name, instance_id, usebasename)) < 0 ) // [Saithis] - { - ShowError("buildin_instance_attachmap: instance creation failed (%s): %d\n", name, m); - script_pushconststr(st, ""); - return 0; - } - script_pushconststr(st, map[m].name); - - return 0; -} - -BUILDIN_FUNC(instance_detachmap) -{ - struct map_session_data *sd; - struct party_data *p; - const char *str; - int16 m; - int instance_id; - - str = script_getstr(st, 2); - if( script_hasdata(st, 3) ) - instance_id = script_getnum(st, 3); - else if( st->instance_id ) - instance_id = st->instance_id; - else if( (sd = script_rid2sd(st)) != NULL && sd->status.party_id && (p = party_search(sd->status.party_id)) != NULL && p->instance_id ) - instance_id = p->instance_id; - else return 0; - - if( (m = map_mapname2mapid(str)) < 0 || (m = instance_map2imap(m,instance_id)) < 0 ) - { - ShowError("buildin_instance_detachmap: Trying to detach invalid map %s\n", str); - return 0; - } - - instance_del_map(m); - return 0; -} - -BUILDIN_FUNC(instance_attach) -{ - int instance_id; - - instance_id = script_getnum(st, 2); - if( instance_id <= 0 || instance_id >= MAX_INSTANCE ) - return 0; - - st->instance_id = instance_id; - return 0; -} - -BUILDIN_FUNC(instance_id) -{ - int type, instance_id; - struct map_session_data *sd; - struct party_data *p; - - if( script_hasdata(st, 2) ) - { - type = script_getnum(st, 2); - if( type == 0 ) - instance_id = st->instance_id; - else if( type == 1 && (sd = script_rid2sd(st)) != NULL && sd->status.party_id && (p = party_search(sd->status.party_id)) != NULL ) - instance_id = p->instance_id; - else - instance_id = 0; - } - else - instance_id = st->instance_id; - - script_pushint(st, instance_id); - return 0; -} - -BUILDIN_FUNC(instance_set_timeout) -{ - int progress_timeout, idle_timeout; - int instance_id; - struct map_session_data *sd; - struct party_data *p; - - progress_timeout = script_getnum(st, 2); - idle_timeout = script_getnum(st, 3); - - if( script_hasdata(st, 4) ) - instance_id = script_getnum(st, 4); - else if( st->instance_id ) - instance_id = st->instance_id; - else if( (sd = script_rid2sd(st)) != NULL && sd->status.party_id && (p = party_search(sd->status.party_id)) != NULL && p->instance_id ) - instance_id = p->instance_id; - else return 0; - - if( instance_id > 0 ) - instance_set_timeout(instance_id, progress_timeout, idle_timeout); - - return 0; -} - -BUILDIN_FUNC(instance_init) -{ - int instance_id = script_getnum(st, 2); - - if( instance[instance_id].state != INSTANCE_IDLE ) - { - ShowError("instance_init: instance already initialized.\n"); - return 0; - } - - instance_init(instance_id); - return 0; -} - -BUILDIN_FUNC(instance_announce) -{ - int instance_id = script_getnum(st,2); - const char *mes = script_getstr(st,3); - int flag = script_getnum(st,4); - const char *fontColor = script_hasdata(st,5) ? script_getstr(st,5) : NULL; - int fontType = script_hasdata(st,6) ? script_getnum(st,6) : 0x190; // default fontType (FW_NORMAL) - int fontSize = script_hasdata(st,7) ? script_getnum(st,7) : 12; // default fontSize - int fontAlign = script_hasdata(st,8) ? script_getnum(st,8) : 0; // default fontAlign - int fontY = script_hasdata(st,9) ? script_getnum(st,9) : 0; // default fontY - - int i; - struct map_session_data *sd; - struct party_data *p; - - if( instance_id == 0 ) - { - if( st->instance_id ) - instance_id = st->instance_id; - else if( (sd = script_rid2sd(st)) != NULL && sd->status.party_id && (p = party_search(sd->status.party_id)) != NULL && p->instance_id ) - instance_id = p->instance_id; - else return 0; - } - - if( instance_id <= 0 || instance_id >= MAX_INSTANCE ) - return 0; - - for( i = 0; i < instance[instance_id].num_map; i++ ) - map_foreachinmap(buildin_announce_sub, instance[instance_id].map[i], BL_PC, - mes, strlen(mes)+1, flag&0xf0, fontColor, fontType, fontSize, fontAlign, fontY); - - return 0; -} - -BUILDIN_FUNC(instance_npcname) -{ - const char *str; - int instance_id = 0; - - struct map_session_data *sd; - struct party_data *p; - struct npc_data *nd; - - str = script_getstr(st, 2); - if( script_hasdata(st, 3) ) - instance_id = script_getnum(st, 3); - else if( st->instance_id ) - instance_id = st->instance_id; - else if( (sd = script_rid2sd(st)) != NULL && sd->status.party_id && (p = party_search(sd->status.party_id)) != NULL && p->instance_id ) - instance_id = p->instance_id; - - if( instance_id && (nd = npc_name2id(str)) != NULL ) - { - static char npcname[NAME_LENGTH]; - snprintf(npcname, sizeof(npcname), "dup_%d_%d", instance_id, nd->bl.id); - script_pushconststr(st,npcname); - } - else - { - ShowError("script:instance_npcname: invalid instance NPC (instance_id: %d, NPC name: \"%s\".)\n", instance_id, str); - st->state = END; - return 1; - } - - return 0; -} - -BUILDIN_FUNC(has_instance) -{ - struct map_session_data *sd; - struct party_data *p; - const char *str; - int16 m; - int instance_id = 0; - - str = script_getstr(st, 2); - if( script_hasdata(st, 3) ) - instance_id = script_getnum(st, 3); - else if( st->instance_id ) - instance_id = st->instance_id; - else if( (sd = script_rid2sd(st)) != NULL && sd->status.party_id && (p = party_search(sd->status.party_id)) != NULL && p->instance_id ) - instance_id = p->instance_id; - - if( !instance_id || (m = map_mapname2mapid(str)) < 0 || (m = instance_map2imap(m, instance_id)) < 0 ) - { - script_pushconststr(st, ""); - return 0; - } - - script_pushconststr(st, map[m].name); - return 0; -} - -BUILDIN_FUNC(instance_warpall) -{ - struct map_session_data *pl_sd; - int16 m, i; - int instance_id; - const char *mapn; - int x, y; - unsigned short mapindex; - struct party_data *p = NULL; - - mapn = script_getstr(st,2); - x = script_getnum(st,3); - y = script_getnum(st,4); - if( script_hasdata(st,5) ) - instance_id = script_getnum(st,5); - else if( st->instance_id ) - instance_id = st->instance_id; - else if( (pl_sd = script_rid2sd(st)) != NULL && pl_sd->status.party_id && (p = party_search(pl_sd->status.party_id)) != NULL && p->instance_id ) - instance_id = p->instance_id; - else return 0; - - if( (m = map_mapname2mapid(mapn)) < 0 || (map[m].flag.src4instance && (m = instance_mapid2imapid(m, instance_id)) < 0) ) - return 0; - - if( !(p = party_search(instance[instance_id].party_id)) ) - return 0; - - mapindex = map_id2index(m); - for( i = 0; i < MAX_PARTY; i++ ) - if( (pl_sd = p->data[i].sd) && map[pl_sd->bl.m].instance_id == st->instance_id ) pc_setpos(pl_sd,mapindex,x,y,CLR_TELEPORT); - - return 0; -} - -/*========================================== - * instance_check_party [malufett] - * Values: - * party_id : Party ID of the invoking character. [Required Parameter] - * amount : Amount of needed Partymembers for the Instance. [Optional Parameter] - * min : Minimum Level needed to join the Instance. [Optional Parameter] - * max : Maxium Level allowed to join the Instance. [Optional Parameter] - * Example: instance_check_party (getcharid(1){,amount}{,min}{,max}); - * Example 2: instance_check_party (getcharid(1),1,1,99); - *------------------------------------------*/ -BUILDIN_FUNC(instance_check_party) -{ - struct map_session_data *pl_sd; - int amount, min, max, i, party_id, c = 0; - struct party_data *p = NULL; - - amount = script_hasdata(st,3) ? script_getnum(st,3) : 1; // Amount of needed Partymembers for the Instance. - min = script_hasdata(st,4) ? script_getnum(st,4) : 1; // Minimum Level needed to join the Instance. - max = script_hasdata(st,5) ? script_getnum(st,5) : MAX_LEVEL; // Maxium Level allowed to join the Instance. - - if( min < 1 || min > MAX_LEVEL){ - ShowError("instance_check_party: Invalid min level, %d\n", min); - return 0; - }else if( max < 1 || max > MAX_LEVEL){ - ShowError("instance_check_party: Invalid max level, %d\n", max); - return 0; - } - - if( script_hasdata(st,2) ) - party_id = script_getnum(st,2); - else return 0; - - if( !(p = party_search(party_id)) ){ - script_pushint(st, 0); // Returns false if party does not exist. - return 0; - } - - for( i = 0; i < MAX_PARTY; i++ ) - if( (pl_sd = p->data[i].sd) ) - if(map_id2bl(pl_sd->bl.id)){ - if(pl_sd->status.base_level < min){ - script_pushint(st, 0); - return 0; - }else if(pl_sd->status.base_level > max){ - script_pushint(st, 0); - return 0; - } - c++; - } - - if(c < amount){ - script_pushint(st, 0); // Not enough Members in the Party to join Instance. - }else - script_pushint(st, 1); - - return 0; -} - -/*========================================== - * Custom Fonts - *------------------------------------------*/ -BUILDIN_FUNC(setfont) -{ - struct map_session_data *sd = script_rid2sd(st); - int font = script_getnum(st,2); - if( sd == NULL ) - return 0; - - if( sd->user_font != font ) - sd->user_font = font; - else - sd->user_font = 0; - - clif_font(sd); - return 0; -} - -static int buildin_mobuseskill_sub(struct block_list *bl,va_list ap) -{ - TBL_MOB* md = (TBL_MOB*)bl; - struct block_list *tbl; - int mobid = va_arg(ap,int); - uint16 skill_id = va_arg(ap,int); - uint16 skill_lv = va_arg(ap,int); - int casttime = va_arg(ap,int); - int cancel = va_arg(ap,int); - int emotion = va_arg(ap,int); - int target = va_arg(ap,int); - - if( md->class_ != mobid ) - return 0; - - // 0:self, 1:target, 2:master, default:random - switch( target ) - { - case 0: tbl = map_id2bl(md->bl.id); break; - case 1: tbl = map_id2bl(md->target_id); break; - case 2: tbl = map_id2bl(md->master_id); break; - default:tbl = battle_getenemy(&md->bl, DEFAULT_ENEMY_TYPE(md),skill_get_range2(&md->bl, skill_id, skill_lv)); break; - } - - if( !tbl ) - return 0; - - if( md->ud.skilltimer != INVALID_TIMER ) // Cancel the casting skill. - unit_skillcastcancel(bl,0); - - if( skill_get_casttype(skill_id) == CAST_GROUND ) - unit_skilluse_pos2(&md->bl, tbl->x, tbl->y, skill_id, skill_lv, casttime, cancel); - else - unit_skilluse_id2(&md->bl, tbl->id, skill_id, skill_lv, casttime, cancel); - - clif_emotion(&md->bl, emotion); - - return 0; -} -/*========================================== - * areamobuseskill "Map Name",<x>,<y>,<range>,<Mob ID>,"Skill Name"/<Skill ID>,<Skill Lv>,<Cast Time>,<Cancelable>,<Emotion>,<Target Type>; - *------------------------------------------*/ -BUILDIN_FUNC(areamobuseskill) -{ - struct block_list center; - int16 m; - int range,mobid,skill_id,skill_lv,casttime,emotion,target,cancel; - - if( (m = map_mapname2mapid(script_getstr(st,2))) < 0 ) - { - ShowError("areamobuseskill: invalid map name.\n"); - return 0; - } - - if( map[m].flag.src4instance && st->instance_id && (m = instance_mapid2imapid(m, st->instance_id)) < 0 ) - return 0; - - center.m = m; - center.x = script_getnum(st,3); - center.y = script_getnum(st,4); - range = script_getnum(st,5); - mobid = script_getnum(st,6); - skill_id = ( script_isstring(st,7) ? skill_name2id(script_getstr(st,7)) : script_getnum(st,7) ); - if( (skill_lv = script_getnum(st,8)) > battle_config.mob_max_skilllvl ) - skill_lv = battle_config.mob_max_skilllvl; - - casttime = script_getnum(st,9); - cancel = script_getnum(st,10); - emotion = script_getnum(st,11); - target = script_getnum(st,12); - - map_foreachinrange(buildin_mobuseskill_sub, ¢er, range, BL_MOB, mobid, skill_id, skill_lv, casttime, cancel, emotion, target); - return 0; -} - - -BUILDIN_FUNC(progressbar) -{ - struct map_session_data * sd = script_rid2sd(st); - const char * color; - unsigned int second; - - if( !st || !sd ) - return 0; - - st->state = STOP; - - color = script_getstr(st,2); - second = script_getnum(st,3); - - sd->progressbar.npc_id = st->oid; - sd->progressbar.timeout = gettick() + second*1000; - - clif_progressbar(sd, strtol(color, (char **)NULL, 0), second); - return 0; -} - -BUILDIN_FUNC(pushpc) -{ - uint8 dir; - int cells, dx, dy; - struct map_session_data* sd; - - if((sd = script_rid2sd(st))==NULL) - { - return 0; - } - - dir = script_getnum(st,2); - cells = script_getnum(st,3); - - if(dir>7) - { - ShowWarning("buildin_pushpc: Invalid direction %d specified.\n", dir); - script_reportsrc(st); - - dir%= 8; // trim spin-over - } - - if(!cells) - {// zero distance - return 0; - } - else if(cells<0) - {// pushing backwards - dir = (dir+4)%8; // turn around - cells = -cells; - } - - dx = dirx[dir]; - dy = diry[dir]; - - unit_blown(&sd->bl, dx, dy, cells, 0); - return 0; -} - - -/// Invokes buying store preparation window -/// buyingstore <slots>; -BUILDIN_FUNC(buyingstore) -{ - struct map_session_data* sd; - - if( ( sd = script_rid2sd(st) ) == NULL ) - { - return 0; - } - - buyingstore_setup(sd, script_getnum(st,2)); - return 0; -} - - -/// Invokes search store info window -/// searchstores <uses>,<effect>; -BUILDIN_FUNC(searchstores) -{ - unsigned short effect; - unsigned int uses; - struct map_session_data* sd; - - if( ( sd = script_rid2sd(st) ) == NULL ) - { - return 0; - } - - uses = script_getnum(st,2); - effect = script_getnum(st,3); - - if( !uses ) - { - ShowError("buildin_searchstores: Amount of uses cannot be zero.\n"); - return 1; - } - - if( effect > 1 ) - { - ShowError("buildin_searchstores: Invalid effect id %hu, specified.\n", effect); - return 1; - } - - searchstore_open(sd, uses, effect); - return 0; -} -/// Displays a number as large digital clock. -/// showdigit <value>[,<type>]; -BUILDIN_FUNC(showdigit) -{ - unsigned int type = 0; - int value; - struct map_session_data* sd; - - if( ( sd = script_rid2sd(st) ) == NULL ) - { - return 0; - } - - value = script_getnum(st,2); - - if( script_hasdata(st,3) ) - { - type = script_getnum(st,3); - - if( type > 3 ) - { - ShowError("buildin_showdigit: Invalid type %u.\n", type); - return 1; - } - } - - clif_showdigit(sd, (unsigned char)type, value); - return 0; -} -/** - * Rune Knight - **/ -BUILDIN_FUNC(makerune) { - TBL_PC* sd; - if( (sd = script_rid2sd(st)) == NULL ) - return 0; - clif_skill_produce_mix_list(sd,RK_RUNEMASTERY,24); - sd->itemid = script_getnum(st,2); - return 0; -} -/** - * checkdragon() returns 1 if mounting a dragon or 0 otherwise. - **/ -BUILDIN_FUNC(checkdragon) { - TBL_PC* sd; - if( (sd = script_rid2sd(st)) == NULL ) - return 0; - if( pc_isridingdragon(sd) ) - script_pushint(st,1); - else - script_pushint(st,0); - return 0; -} -/** - * setdragon({optional Color}) returns 1 on success or 0 otherwise - * - Toggles the dragon on a RK if he can mount; - * @param Color - when not provided uses the green dragon; - * - 1 : Green Dragon - * - 2 : Brown Dragon - * - 3 : Gray Dragon - * - 4 : Blue Dragon - * - 5 : Red Dragon - **/ -BUILDIN_FUNC(setdragon) { - TBL_PC* sd; - int color = script_hasdata(st,2) ? script_getnum(st,2) : 0; - unsigned int option = OPTION_DRAGON1; - if( (sd = script_rid2sd(st)) == NULL ) - return 0; - if( !pc_checkskill(sd,RK_DRAGONTRAINING) || (sd->class_&MAPID_THIRDMASK) != MAPID_RUNE_KNIGHT ) - script_pushint(st,0);//Doesn't have the skill or it's not a Rune Knight - else if ( pc_isridingdragon(sd) ) {//Is mounted; release - pc_setoption(sd, sd->sc.option&~OPTION_DRAGON); - script_pushint(st,1); - } else {//Not mounted; Mount now. - if( color ) { - option = ( color == 1 ? OPTION_DRAGON1 : - color == 2 ? OPTION_DRAGON2 : - color == 3 ? OPTION_DRAGON3 : - color == 4 ? OPTION_DRAGON4 : - color == 5 ? OPTION_DRAGON5 : 0); - if( !option ) { - ShowWarning("script_setdragon: Unknown Color %d used; changing to green (1)\n",color); - option = OPTION_DRAGON1; - } - } - pc_setoption(sd, sd->sc.option|option); - script_pushint(st,1); - } - return 0; -} - -/** - * ismounting() returns 1 if mounting a new mount or 0 otherwise - **/ -BUILDIN_FUNC(ismounting) { - TBL_PC* sd; - if( (sd = script_rid2sd(st)) == NULL ) - return 0; - if( sd->sc.option&OPTION_MOUNTING ) - script_pushint(st,1); - else - script_pushint(st,0); - return 0; -} - -/** - * setmounting() returns 1 on success or 0 otherwise - * - Toggles new mounts on a player when he can mount - * - Will fail if the player is mounting a non-new mount, e.g. dragon, peco, wug, etc. - * - Will unmount the player is he is already mounting - **/ -BUILDIN_FUNC(setmounting) { - TBL_PC* sd; - if( (sd = script_rid2sd(st)) == NULL ) - return 0; - if( sd->sc.option&(OPTION_WUGRIDER|OPTION_RIDING|OPTION_DRAGON|OPTION_MADOGEAR) ) - script_pushint(st,0);//can't mount with one of these - else { - if( sd->sc.option&OPTION_MOUNTING ) - pc_setoption(sd, sd->sc.option&~OPTION_MOUNTING);//release mount - else - pc_setoption(sd, sd->sc.option|OPTION_MOUNTING);//mount - script_pushint(st,1);//in both cases, return 1. - } - return 0; -} -/** - * Retrieves quantity of arguments provided to callfunc/callsub. - * getargcount() -> amount of arguments received in a function - **/ -BUILDIN_FUNC(getargcount) { - struct script_retinfo* ri; - - if( st->stack->defsp < 1 || st->stack->stack_data[st->stack->defsp - 1].type != C_RETINFO ) { - ShowError("script:getargcount: used out of function or callsub label!\n"); - st->state = END; - return 1; - } - ri = st->stack->stack_data[st->stack->defsp - 1].u.ri; - - script_pushint(st, ri->nargs); - - return 0; -} -/** - * getcharip(<account ID>/<character ID>/<character name>) - **/ -BUILDIN_FUNC(getcharip) -{ - struct map_session_data* sd = NULL; - int id = 0; - - /* check if a character name is specified */ - if( script_hasdata(st, 2) ) - { - if (script_isstring(st, 2)) - sd = map_nick2sd(script_getstr(st, 2)); - else if (script_isint(st, 2) || script_getnum(st, 2)) - { - id = script_getnum(st, 2); - sd = (map_id2sd(id) ? map_id2sd(id) : map_charid2sd(id)); - } - } - else - sd = script_rid2sd(st); - - /* check for sd and IP */ - if (!sd || !session[sd->fd]->client_addr) - { - script_pushconststr(st, ""); - return 0; - } - - /* return the client ip_addr converted for output */ - if (sd && sd->fd && session[sd->fd]) - { - /* initiliaze */ - const char *ip_addr = NULL; - uint32 ip; - - /* set ip, ip_addr and convert to ip and push str */ - ip = session[sd->fd]->client_addr; - ip_addr = ip2str(ip, NULL); - script_pushstrcopy(st, ip_addr); - } - - return 0; -} -/** - * is_function(<function name>) -> 1 if function exists, 0 otherwise - **/ -BUILDIN_FUNC(is_function) { - const char* str = script_getstr(st,2); - - if( strdb_exists(userfunc_db, str) ) - script_pushint(st,1); - else - script_pushint(st,0); - - return 0; -} -/** - * get_revision() -> retrieves the current svn revision (if available) - **/ -BUILDIN_FUNC(get_revision) { - const char * revision; - - if ( (revision = get_svn_revision()) != 0 ) - script_pushint(st,atoi(revision)); - else - script_pushint(st,-1);//unknown - - return 0; -} -/** - * freeloop(<toggle>) -> toggles this script instance's looping-check ability - **/ -BUILDIN_FUNC(freeloop) { - - if( script_getnum(st,2) ) - st->freeloop = 1; - else - st->freeloop = 0; - - script_pushint(st, st->freeloop); - - return 0; -} - -/** - * @commands (script based) - **/ -BUILDIN_FUNC(bindatcmd) { - const char* atcmd; - const char* eventName; - int i, level = 0, level2 = 0; - bool create = false; - - atcmd = script_getstr(st,2); - eventName = script_getstr(st,3); - - if( *atcmd == atcommand_symbol || *atcmd == charcommand_symbol ) - atcmd++; - - if( script_hasdata(st,4) ) level = script_getnum(st,4); - if( script_hasdata(st,5) ) level2 = script_getnum(st,5); - - if( atcmd_binding_count == 0 ) { - CREATE(atcmd_binding,struct atcmd_binding_data*,1); - - create = true; - } else { - ARR_FIND(0, atcmd_binding_count, i, strcmp(atcmd_binding[i]->command,atcmd) == 0); - if( i < atcmd_binding_count ) {/* update existent entry */ - safestrncpy(atcmd_binding[i]->npc_event, eventName, 50); - atcmd_binding[i]->level = level; - atcmd_binding[i]->level2 = level2; - } else - create = true; - } - - if( create ) { - i = atcmd_binding_count; - - if( atcmd_binding_count++ != 0 ) - RECREATE(atcmd_binding,struct atcmd_binding_data*,atcmd_binding_count); - - CREATE(atcmd_binding[i],struct atcmd_binding_data,1); - - safestrncpy(atcmd_binding[i]->command, atcmd, 50); - safestrncpy(atcmd_binding[i]->npc_event, eventName, 50); - atcmd_binding[i]->level = level; - atcmd_binding[i]->level2 = level2; - } - - return 0; -} - -BUILDIN_FUNC(unbindatcmd) { - const char* atcmd; - int i = 0; - - atcmd = script_getstr(st, 2); - - if( *atcmd == atcommand_symbol || *atcmd == charcommand_symbol ) - atcmd++; - - if( atcmd_binding_count == 0 ) { - script_pushint(st, 0); - return 0; - } - - ARR_FIND(0, atcmd_binding_count, i, strcmp(atcmd_binding[i]->command, atcmd) == 0); - if( i < atcmd_binding_count ) { - int cursor = 0; - aFree(atcmd_binding[i]); - atcmd_binding[i] = NULL; - /* compact the list now that we freed a slot somewhere */ - for( i = 0, cursor = 0; i < atcmd_binding_count; i++ ) { - if( atcmd_binding[i] == NULL ) - continue; - - if( cursor != i ) { - memmove(&atcmd_binding[cursor], &atcmd_binding[i], sizeof(struct atcmd_binding_data*)); - } - - cursor++; - } - - if( (atcmd_binding_count = cursor) == 0 ) - aFree(atcmd_binding); - - script_pushint(st, 1); - } else - script_pushint(st, 0);/* not found */ - - return 0; -} - -BUILDIN_FUNC(useatcmd) -{ - TBL_PC dummy_sd; - TBL_PC* sd; - int fd; - const char* cmd; - - cmd = script_getstr(st,2); - - if( st->rid ) - { - sd = script_rid2sd(st); - fd = sd->fd; - } - else - { // Use a dummy character. - sd = &dummy_sd; - fd = 0; - - memset(&dummy_sd, 0, sizeof(TBL_PC)); - if( st->oid ) - { - struct block_list* bl = map_id2bl(st->oid); - memcpy(&dummy_sd.bl, bl, sizeof(struct block_list)); - if( bl->type == BL_NPC ) - safestrncpy(dummy_sd.status.name, ((TBL_NPC*)bl)->name, NAME_LENGTH); - } - } - - // compatibility with previous implementation (deprecated!) - if( cmd[0] != atcommand_symbol ) - { - cmd += strlen(sd->status.name); - while( *cmd != atcommand_symbol && *cmd != 0 ) - cmd++; - } - - is_atcommand(fd, sd, cmd, 1); - return 0; -} - -BUILDIN_FUNC(checkre) -{ - int num; - - num=script_getnum(st,2); - switch(num){ - case 0: - #ifdef RENEWAL - script_pushint(st, 1); - #else - script_pushint(st, 0); - #endif - break; - case 1: - #ifdef RENEWAL_CAST - script_pushint(st, 1); - #else - script_pushint(st, 0); - #endif - break; - case 2: - #ifdef RENEWAL_DROP - script_pushint(st, 1); - #else - script_pushint(st, 0); - #endif - break; - case 3: - #ifdef RENEWAL_EXP - script_pushint(st, 1); - #else - script_pushint(st, 0); - #endif - break; - case 4: - #ifdef RENEWAL_LVDMG - script_pushint(st, 1); - #else - script_pushint(st, 0); - #endif - break; - case 5: - #ifdef RENEWAL_EDP - script_pushint(st, 1); - #else - script_pushint(st, 0); - #endif - break; - case 6: - #ifdef RENEWAL_ASPD - script_pushint(st, 1); - #else - script_pushint(st, 0); - #endif - break; - default: - ShowWarning("buildin_checkre: unknown parameter.\n"); - break; - } - return 0; -} - -/* getrandgroupitem <group_id>,<quantity> */ -BUILDIN_FUNC(getrandgroupitem) { - TBL_PC* sd; - int i, get_count = 0, flag, nameid, group = script_getnum(st, 2), qty = script_getnum(st,3); - struct item item_tmp; - - if( !( sd = script_rid2sd(st) ) ) - return 0; - - if( qty <= 0 ) { - ShowError("getrandgroupitem: qty is <= 0!\n"); - return 1; - } - if( (nameid = itemdb_searchrandomid(group)) == UNKNOWN_ITEM_ID ) { - return 1;/* itemdb_searchrandomid will already scream a error */ - } - - memset(&item_tmp,0,sizeof(item_tmp)); - - item_tmp.nameid = nameid; - item_tmp.identify = itemdb_isidentified(nameid); - - //Check if it's stackable. - if (!itemdb_isstackable(nameid)) - get_count = 1; - else - get_count = qty; - - for (i = 0; i < qty; i += get_count) { - // if not pet egg - if (!pet_create_egg(sd, nameid)) { - if ((flag = pc_additem(sd, &item_tmp, get_count, LOG_TYPE_SCRIPT))) { - clif_additem(sd, 0, 0, flag); - if( pc_candrop(sd,&item_tmp) ) - map_addflooritem(&item_tmp,get_count,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); - } - } - } - - return 0; -} - -/* cleanmap <map_name>; - * cleanarea <map_name>, <x0>, <y0>, <x1>, <y1>; */ -static int atcommand_cleanfloor_sub(struct block_list *bl, va_list ap) -{ - nullpo_ret(bl); - map_clearflooritem(bl); - - return 0; -} - -BUILDIN_FUNC(cleanmap) -{ - const char *map; - int16 m = -1; - int16 x0 = 0, y0 = 0, x1 = 0, y1 = 0; - - map = script_getstr(st, 2); - m = map_mapname2mapid(map); - if (!m) - return 1; - - if ((script_lastdata(st) - 2) < 4) { - map_foreachinmap(atcommand_cleanfloor_sub, m, BL_ITEM); - } else { - x0 = script_getnum(st, 3); - y0 = script_getnum(st, 4); - x1 = script_getnum(st, 5); - y1 = script_getnum(st, 6); - if (x0 > 0 && y0 > 0 && x1 > 0 && y1 > 0) { - map_foreachinarea(atcommand_cleanfloor_sub, m, x0, y0, x1, y1, BL_ITEM); - } else { - ShowError("cleanarea: invalid coordinate defined!\n"); - return 1; - } - } - - return 0; -} -/* Cast a skill on the attached player. - * npcskill <skill id>, <skill lvl>, <stat point>, <NPC level>; - * npcskill "<skill name>", <skill lvl>, <stat point>, <NPC level>; */ -BUILDIN_FUNC(npcskill) -{ - uint16 skill_id; - unsigned short skill_level; - unsigned int stat_point; - unsigned int npc_level; - struct npc_data *nd; - struct map_session_data *sd; - - skill_id = script_isstring(st, 2) ? skill_name2id(script_getstr(st, 2)) : script_getnum(st, 2); - skill_level = script_getnum(st, 3); - stat_point = script_getnum(st, 4); - npc_level = script_getnum(st, 5); - sd = script_rid2sd(st); - nd = (struct npc_data *)map_id2bl(sd->npc_id); - - if (stat_point > battle_config.max_third_parameter) { - ShowError("npcskill: stat point exceeded maximum of %d.\n",battle_config.max_third_parameter ); - return 1; - } - if (npc_level > MAX_LEVEL) { - ShowError("npcskill: level exceeded maximum of %d.\n", MAX_LEVEL); - return 1; - } - if (sd == NULL || nd == NULL) { //ain't possible, but I don't trust people. - return 1; - } - - nd->level = npc_level; - nd->stat_point = stat_point; - - if (!nd->status.hp) { - status_calc_npc(nd, true); - } else { - status_calc_npc(nd, false); - } - - if (skill_get_inf(skill_id)&INF_GROUND_SKILL) { - unit_skilluse_pos(&nd->bl, sd->bl.x, sd->bl.y, skill_id, skill_level); - } else { - unit_skilluse_id(&nd->bl, sd->bl.id, skill_id, skill_level); - } - - return 0; -} - -// declarations that were supposed to be exported from npc_chat.c -#ifdef PCRE_SUPPORT -BUILDIN_FUNC(defpattern); -BUILDIN_FUNC(activatepset); -BUILDIN_FUNC(deactivatepset); -BUILDIN_FUNC(deletepset); -#endif - -/// script command definitions -/// for an explanation on args, see add_buildin_func -struct script_function buildin_func[] = { - // NPC interaction - BUILDIN_DEF(mes,"s*"), - BUILDIN_DEF(next,""), - BUILDIN_DEF(close,""), - BUILDIN_DEF(close2,""), - BUILDIN_DEF(menu,"sl*"), - BUILDIN_DEF(select,"s*"), //for future jA script compatibility - BUILDIN_DEF(prompt,"s*"), - // - BUILDIN_DEF(goto,"l"), - BUILDIN_DEF(callsub,"l*"), - BUILDIN_DEF(callfunc,"s*"), - BUILDIN_DEF(return,"?"), - BUILDIN_DEF(getarg,"i?"), - BUILDIN_DEF(jobchange,"i?"), - BUILDIN_DEF(jobname,"i"), - BUILDIN_DEF(input,"r??"), - BUILDIN_DEF(warp,"sii"), - BUILDIN_DEF(areawarp,"siiiisii??"), - BUILDIN_DEF(warpchar,"siii"), // [LuzZza] - BUILDIN_DEF(warpparty,"siii?"), // [Fredzilla] [Paradox924X] - BUILDIN_DEF(warpguild,"siii"), // [Fredzilla] - BUILDIN_DEF(setlook,"ii"), - BUILDIN_DEF(changelook,"ii"), // Simulates but don't Store it - BUILDIN_DEF(set,"rv"), - BUILDIN_DEF(setarray,"rv*"), - BUILDIN_DEF(cleararray,"rvi"), - BUILDIN_DEF(copyarray,"rri"), - BUILDIN_DEF(getarraysize,"r"), - BUILDIN_DEF(deletearray,"r?"), - BUILDIN_DEF(getelementofarray,"ri"), - BUILDIN_DEF(getitem,"vi?"), - BUILDIN_DEF(rentitem,"vi"), - BUILDIN_DEF(getitem2,"viiiiiiii?"), - BUILDIN_DEF(getnameditem,"vv"), - BUILDIN_DEF2(grouprandomitem,"groupranditem","i"), - BUILDIN_DEF(makeitem,"visii"), - BUILDIN_DEF(delitem,"vi?"), - BUILDIN_DEF(delitem2,"viiiiiiii?"), - BUILDIN_DEF2(enableitemuse,"enable_items",""), - BUILDIN_DEF2(disableitemuse,"disable_items",""), - BUILDIN_DEF(cutin,"si"), - BUILDIN_DEF(viewpoint,"iiiii"), - BUILDIN_DEF(heal,"ii"), - BUILDIN_DEF(itemheal,"ii"), - BUILDIN_DEF(percentheal,"ii"), - BUILDIN_DEF(rand,"i?"), - BUILDIN_DEF(countitem,"v"), - BUILDIN_DEF(countitem2,"viiiiiii"), - BUILDIN_DEF(checkweight,"vi*"), - BUILDIN_DEF(checkweight2,"rr"), - BUILDIN_DEF(readparam,"i?"), - BUILDIN_DEF(getcharid,"i?"), - BUILDIN_DEF(getnpcid,"i?"), - BUILDIN_DEF(getpartyname,"i"), - BUILDIN_DEF(getpartymember,"i?"), - BUILDIN_DEF(getpartyleader,"i?"), - BUILDIN_DEF(getguildname,"i"), - BUILDIN_DEF(getguildmaster,"i"), - BUILDIN_DEF(getguildmasterid,"i"), - BUILDIN_DEF(strcharinfo,"i"), - BUILDIN_DEF(strnpcinfo,"i"), - BUILDIN_DEF(getequipid,"i"), - BUILDIN_DEF(getequipname,"i"), - BUILDIN_DEF(getbrokenid,"i"), // [Valaris] - BUILDIN_DEF(repair,"i"), // [Valaris] - BUILDIN_DEF(repairall,""), - BUILDIN_DEF(getequipisequiped,"i"), - BUILDIN_DEF(getequipisenableref,"i"), - BUILDIN_DEF(getequipisidentify,"i"), - BUILDIN_DEF(getequiprefinerycnt,"i"), - BUILDIN_DEF(getequipweaponlv,"i"), - BUILDIN_DEF(getequippercentrefinery,"i"), - BUILDIN_DEF(successrefitem,"i"), - BUILDIN_DEF(failedrefitem,"i"), - BUILDIN_DEF(downrefitem,"i"), - BUILDIN_DEF(statusup,"i"), - BUILDIN_DEF(statusup2,"ii"), - BUILDIN_DEF(bonus,"iv"), - BUILDIN_DEF2(bonus,"bonus2","ivi"), - BUILDIN_DEF2(bonus,"bonus3","ivii"), - BUILDIN_DEF2(bonus,"bonus4","ivvii"), - BUILDIN_DEF2(bonus,"bonus5","ivviii"), - BUILDIN_DEF(autobonus,"sii??"), - BUILDIN_DEF(autobonus2,"sii??"), - BUILDIN_DEF(autobonus3,"siiv?"), - BUILDIN_DEF(skill,"vi?"), - BUILDIN_DEF(addtoskill,"vi?"), // [Valaris] - BUILDIN_DEF(guildskill,"vi"), - BUILDIN_DEF(getskilllv,"v"), - BUILDIN_DEF(getgdskilllv,"iv"), - BUILDIN_DEF(basicskillcheck,""), - BUILDIN_DEF(getgmlevel,""), - BUILDIN_DEF(getgroupid,""), - BUILDIN_DEF(end,""), - BUILDIN_DEF(checkoption,"i"), - BUILDIN_DEF(setoption,"i?"), - BUILDIN_DEF(setcart,"?"), - BUILDIN_DEF(checkcart,""), - BUILDIN_DEF(setfalcon,"?"), - BUILDIN_DEF(checkfalcon,""), - BUILDIN_DEF(setriding,"?"), - BUILDIN_DEF(checkriding,""), - BUILDIN_DEF(checkwug,""), - BUILDIN_DEF(checkmadogear,""), - BUILDIN_DEF(setmadogear,"?"), - BUILDIN_DEF2(savepoint,"save","sii"), - BUILDIN_DEF(savepoint,"sii"), - BUILDIN_DEF(gettimetick,"i"), - BUILDIN_DEF(gettime,"i"), - BUILDIN_DEF(gettimestr,"si"), - BUILDIN_DEF(openstorage,""), - BUILDIN_DEF(guildopenstorage,""), - BUILDIN_DEF(itemskill,"vi"), - BUILDIN_DEF(produce,"i"), - BUILDIN_DEF(cooking,"i"), - BUILDIN_DEF(monster,"siisii???"), - BUILDIN_DEF(getmobdrops,"i"), - BUILDIN_DEF(areamonster,"siiiisii???"), - BUILDIN_DEF(killmonster,"ss?"), - BUILDIN_DEF(killmonsterall,"s?"), - BUILDIN_DEF(clone,"siisi????"), - BUILDIN_DEF(doevent,"s"), - BUILDIN_DEF(donpcevent,"s"), - BUILDIN_DEF(cmdothernpc,"ss"), - BUILDIN_DEF(addtimer,"is"), - BUILDIN_DEF(deltimer,"s"), - BUILDIN_DEF(addtimercount,"si"), - BUILDIN_DEF(initnpctimer,"??"), - BUILDIN_DEF(stopnpctimer,"??"), - BUILDIN_DEF(startnpctimer,"??"), - BUILDIN_DEF(setnpctimer,"i?"), - BUILDIN_DEF(getnpctimer,"i?"), - BUILDIN_DEF(attachnpctimer,"?"), // attached the player id to the npc timer [Celest] - BUILDIN_DEF(detachnpctimer,"?"), // detached the player id from the npc timer [Celest] - BUILDIN_DEF(playerattached,""), // returns id of the current attached player. [Skotlex] - BUILDIN_DEF(announce,"si?????"), - BUILDIN_DEF(mapannounce,"ssi?????"), - BUILDIN_DEF(areaannounce,"siiiisi?????"), - BUILDIN_DEF(getusers,"i"), - BUILDIN_DEF(getmapguildusers,"si"), - BUILDIN_DEF(getmapusers,"s"), - BUILDIN_DEF(getareausers,"siiii"), - BUILDIN_DEF(getareadropitem,"siiiiv"), - BUILDIN_DEF(enablenpc,"s"), - BUILDIN_DEF(disablenpc,"s"), - BUILDIN_DEF(hideoffnpc,"s"), - BUILDIN_DEF(hideonnpc,"s"), - BUILDIN_DEF(sc_start,"iii?"), - BUILDIN_DEF(sc_start2,"iiii?"), - BUILDIN_DEF(sc_start4,"iiiiii?"), - BUILDIN_DEF(sc_end,"i?"), - BUILDIN_DEF(getstatus, "i?"), - BUILDIN_DEF(getscrate,"ii?"), - BUILDIN_DEF(debugmes,"s"), - BUILDIN_DEF2(catchpet,"pet","i"), - BUILDIN_DEF2(birthpet,"bpet",""), - BUILDIN_DEF(resetlvl,"i"), - BUILDIN_DEF(resetstatus,""), - BUILDIN_DEF(resetskill,""), - BUILDIN_DEF(skillpointcount,""), - BUILDIN_DEF(changebase,"i?"), - BUILDIN_DEF(changesex,""), - BUILDIN_DEF(waitingroom,"si?????"), - BUILDIN_DEF(delwaitingroom,"?"), - BUILDIN_DEF2(waitingroomkickall,"kickwaitingroomall","?"), - BUILDIN_DEF(enablewaitingroomevent,"?"), - BUILDIN_DEF(disablewaitingroomevent,"?"), - BUILDIN_DEF2(enablewaitingroomevent,"enablearena",""), // Added by RoVeRT - BUILDIN_DEF2(disablewaitingroomevent,"disablearena",""), // Added by RoVeRT - BUILDIN_DEF(getwaitingroomstate,"i?"), - BUILDIN_DEF(warpwaitingpc,"sii?"), - BUILDIN_DEF(attachrid,"i"), - BUILDIN_DEF(detachrid,""), - BUILDIN_DEF(isloggedin,"i?"), - BUILDIN_DEF(setmapflagnosave,"ssii"), - BUILDIN_DEF(getmapflag,"si"), - BUILDIN_DEF(setmapflag,"si?"), - BUILDIN_DEF(removemapflag,"si?"), - BUILDIN_DEF(pvpon,"s"), - BUILDIN_DEF(pvpoff,"s"), - BUILDIN_DEF(gvgon,"s"), - BUILDIN_DEF(gvgoff,"s"), - BUILDIN_DEF(emotion,"i??"), - BUILDIN_DEF(maprespawnguildid,"sii"), - BUILDIN_DEF(agitstart,""), // <Agit> - BUILDIN_DEF(agitend,""), - BUILDIN_DEF(agitcheck,""), // <Agitcheck> - BUILDIN_DEF(flagemblem,"i"), // Flag Emblem - BUILDIN_DEF(getcastlename,"s"), - BUILDIN_DEF(getcastledata,"si"), - BUILDIN_DEF(setcastledata,"sii"), - BUILDIN_DEF(requestguildinfo,"i?"), - BUILDIN_DEF(getequipcardcnt,"i"), - BUILDIN_DEF(successremovecards,"i"), - BUILDIN_DEF(failedremovecards,"ii"), - BUILDIN_DEF(marriage,"s"), - BUILDIN_DEF2(wedding_effect,"wedding",""), - BUILDIN_DEF(divorce,""), - BUILDIN_DEF(ispartneron,""), - BUILDIN_DEF(getpartnerid,""), - BUILDIN_DEF(getchildid,""), - BUILDIN_DEF(getmotherid,""), - BUILDIN_DEF(getfatherid,""), - BUILDIN_DEF(warppartner,"sii"), - BUILDIN_DEF(getitemname,"v"), - BUILDIN_DEF(getitemslots,"i"), - BUILDIN_DEF(makepet,"i"), - BUILDIN_DEF(getexp,"ii"), - BUILDIN_DEF(getinventorylist,""), - BUILDIN_DEF(getskilllist,""), - BUILDIN_DEF(clearitem,""), - BUILDIN_DEF(classchange,"ii"), - BUILDIN_DEF(misceffect,"i"), - BUILDIN_DEF(playBGM,"s"), - BUILDIN_DEF(playBGMall,"s?????"), - BUILDIN_DEF(soundeffect,"si"), - BUILDIN_DEF(soundeffectall,"si?????"), // SoundEffectAll [Codemaster] - BUILDIN_DEF(strmobinfo,"ii"), // display mob data [Valaris] - BUILDIN_DEF(guardian,"siisi??"), // summon guardians - BUILDIN_DEF(guardianinfo,"sii"), // display guardian data [Valaris] - BUILDIN_DEF(petskillbonus,"iiii"), // [Valaris] - BUILDIN_DEF(petrecovery,"ii"), // [Valaris] - BUILDIN_DEF(petloot,"i"), // [Valaris] - BUILDIN_DEF(petheal,"iiii"), // [Valaris] - BUILDIN_DEF(petskillattack,"viii"), // [Skotlex] - BUILDIN_DEF(petskillattack2,"viiii"), // [Valaris] - BUILDIN_DEF(petskillsupport,"viiii"), // [Skotlex] - BUILDIN_DEF(skilleffect,"vi"), // skill effect [Celest] - BUILDIN_DEF(npcskilleffect,"viii"), // npc skill effect [Valaris] - BUILDIN_DEF(specialeffect,"i??"), // npc skill effect [Valaris] - BUILDIN_DEF(specialeffect2,"i??"), // skill effect on players[Valaris] - BUILDIN_DEF(nude,""), // nude command [Valaris] - BUILDIN_DEF(mapwarp,"ssii??"), // Added by RoVeRT - BUILDIN_DEF(atcommand,"s"), // [MouseJstr] - BUILDIN_DEF2(atcommand,"charcommand","s"), // [MouseJstr] - BUILDIN_DEF(movenpc,"sii?"), // [MouseJstr] - BUILDIN_DEF(message,"ss"), // [MouseJstr] - BUILDIN_DEF(npctalk,"s"), // [Valaris] - BUILDIN_DEF(mobcount,"ss"), - BUILDIN_DEF(getlook,"i"), - BUILDIN_DEF(getsavepoint,"i"), - BUILDIN_DEF(npcspeed,"i"), // [Valaris] - BUILDIN_DEF(npcwalkto,"ii"), // [Valaris] - BUILDIN_DEF(npcstop,""), // [Valaris] - BUILDIN_DEF(getmapxy,"rrri?"), //by Lorky [Lupus] - BUILDIN_DEF(checkoption1,"i"), - BUILDIN_DEF(checkoption2,"i"), - BUILDIN_DEF(guildgetexp,"i"), - BUILDIN_DEF(guildchangegm,"is"), - BUILDIN_DEF(logmes,"s"), //this command actls as MES but rints info into LOG file either SQL/TXT [Lupus] - BUILDIN_DEF(summon,"si??"), // summons a slave monster [Celest] - BUILDIN_DEF(isnight,""), // check whether it is night time [Celest] - BUILDIN_DEF(isday,""), // check whether it is day time [Celest] - BUILDIN_DEF(isequipped,"i*"), // check whether another item/card has been equipped [Celest] - BUILDIN_DEF(isequippedcnt,"i*"), // check how many items/cards are being equipped [Celest] - BUILDIN_DEF(cardscnt,"i*"), // check how many items/cards are being equipped in the same arm [Lupus] - BUILDIN_DEF(getrefine,""), // returns the refined number of the current item, or an item with index specified [celest] - BUILDIN_DEF(night,""), // sets the server to night time - BUILDIN_DEF(day,""), // sets the server to day time -#ifdef PCRE_SUPPORT - BUILDIN_DEF(defpattern,"iss"), // Define pattern to listen for [MouseJstr] - BUILDIN_DEF(activatepset,"i"), // Activate a pattern set [MouseJstr] - BUILDIN_DEF(deactivatepset,"i"), // Deactive a pattern set [MouseJstr] - BUILDIN_DEF(deletepset,"i"), // Delete a pattern set [MouseJstr] -#endif - BUILDIN_DEF(dispbottom,"s"), //added from jA [Lupus] - BUILDIN_DEF(getusersname,""), - BUILDIN_DEF(recovery,""), - BUILDIN_DEF(getpetinfo,"i"), - BUILDIN_DEF(gethominfo,"i"), - BUILDIN_DEF(getmercinfo,"i?"), - BUILDIN_DEF(checkequipedcard,"i"), - BUILDIN_DEF(jump_zero,"il"), //for future jA script compatibility - BUILDIN_DEF(globalmes,"s?"), //end jA addition - BUILDIN_DEF(unequip,"i"), // unequip command [Spectre] - BUILDIN_DEF(getstrlen,"s"), //strlen [Valaris] - BUILDIN_DEF(charisalpha,"si"), //isalpha [Valaris] - BUILDIN_DEF(charat,"si"), - BUILDIN_DEF(setchar,"ssi"), - BUILDIN_DEF(insertchar,"ssi"), - BUILDIN_DEF(delchar,"si"), - BUILDIN_DEF(strtoupper,"s"), - BUILDIN_DEF(strtolower,"s"), - BUILDIN_DEF(charisupper, "si"), - BUILDIN_DEF(charislower, "si"), - BUILDIN_DEF(substr,"sii"), - BUILDIN_DEF(explode, "rss"), - BUILDIN_DEF(implode, "r?"), - BUILDIN_DEF(sprintf,"s*"), // [Mirei] - BUILDIN_DEF(sscanf,"ss*"), // [Mirei] - BUILDIN_DEF(strpos,"ss?"), - BUILDIN_DEF(replacestr,"sss??"), - BUILDIN_DEF(countstr,"ss?"), - BUILDIN_DEF(setnpcdisplay,"sv??"), - BUILDIN_DEF(compare,"ss"), // Lordalfa - To bring strstr to scripting Engine. - BUILDIN_DEF(getiteminfo,"ii"), //[Lupus] returns Items Buy / sell Price, etc info - BUILDIN_DEF(setiteminfo,"iii"), //[Lupus] set Items Buy / sell Price, etc info - BUILDIN_DEF(getequipcardid,"ii"), //[Lupus] returns CARD ID or other info from CARD slot N of equipped item - // [zBuffer] List of mathematics commands ---> - BUILDIN_DEF(sqrt,"i"), - BUILDIN_DEF(pow,"ii"), - BUILDIN_DEF(distance,"iiii"), - // <--- [zBuffer] List of mathematics commands - BUILDIN_DEF(md5,"s"), - // [zBuffer] List of dynamic var commands ---> - BUILDIN_DEF(getd,"s"), - BUILDIN_DEF(setd,"sv"), - // <--- [zBuffer] List of dynamic var commands - BUILDIN_DEF(petstat,"i"), - BUILDIN_DEF(callshop,"s?"), // [Skotlex] - BUILDIN_DEF(npcshopitem,"sii*"), // [Lance] - BUILDIN_DEF(npcshopadditem,"sii*"), - BUILDIN_DEF(npcshopdelitem,"si*"), - BUILDIN_DEF(npcshopattach,"s?"), - BUILDIN_DEF(equip,"i"), - BUILDIN_DEF(autoequip,"ii"), - BUILDIN_DEF(setbattleflag,"si"), - BUILDIN_DEF(getbattleflag,"s"), - BUILDIN_DEF(setitemscript,"is?"), //Set NEW item bonus script. Lupus - BUILDIN_DEF(disguise,"i"), //disguise player. Lupus - BUILDIN_DEF(undisguise,""), //undisguise player. Lupus - BUILDIN_DEF(getmonsterinfo,"ii"), //Lupus - BUILDIN_DEF(axtoi,"s"), - BUILDIN_DEF(query_sql,"s*"), - BUILDIN_DEF(query_logsql,"s*"), - BUILDIN_DEF(escape_sql,"v"), - BUILDIN_DEF(atoi,"s"), - // [zBuffer] List of player cont commands ---> - BUILDIN_DEF(rid2name,"i"), - BUILDIN_DEF(pcfollow,"ii"), - BUILDIN_DEF(pcstopfollow,"i"), - BUILDIN_DEF(pcblockmove,"ii"), - // <--- [zBuffer] List of player cont commands - // [zBuffer] List of mob control commands ---> - BUILDIN_DEF(unitwalk,"ii?"), - BUILDIN_DEF(unitkill,"i"), - BUILDIN_DEF(unitwarp,"isii"), - BUILDIN_DEF(unitattack,"iv?"), - BUILDIN_DEF(unitstop,"i"), - BUILDIN_DEF(unittalk,"is"), - BUILDIN_DEF(unitemote,"ii"), - BUILDIN_DEF(unitskilluseid,"ivi?"), // originally by Qamera [Celest] - BUILDIN_DEF(unitskillusepos,"iviii"), // [Celest] -// <--- [zBuffer] List of mob control commands - BUILDIN_DEF(sleep,"i"), - BUILDIN_DEF(sleep2,"i"), - BUILDIN_DEF(awake,"s"), - BUILDIN_DEF(getvariableofnpc,"rs"), - BUILDIN_DEF(warpportal,"iisii"), - BUILDIN_DEF2(homunculus_evolution,"homevolution",""), //[orn] - BUILDIN_DEF2(homunculus_mutate,"hommutate","?"), - BUILDIN_DEF2(homunculus_shuffle,"homshuffle",""), //[Zephyrus] - BUILDIN_DEF(eaclass,"?"), //[Skotlex] - BUILDIN_DEF(roclass,"i?"), //[Skotlex] - BUILDIN_DEF(checkvending,"?"), - BUILDIN_DEF(checkchatting,"?"), - BUILDIN_DEF(openmail,""), - BUILDIN_DEF(openauction,""), - BUILDIN_DEF(checkcell,"siii"), - BUILDIN_DEF(setcell,"siiiiii"), - BUILDIN_DEF(setwall,"siiiiis"), - BUILDIN_DEF(delwall,"s"), - BUILDIN_DEF(searchitem,"rs"), - BUILDIN_DEF(mercenary_create,"ii"), - BUILDIN_DEF(mercenary_heal,"ii"), - BUILDIN_DEF(mercenary_sc_start,"iii"), - BUILDIN_DEF(mercenary_get_calls,"i"), - BUILDIN_DEF(mercenary_get_faith,"i"), - BUILDIN_DEF(mercenary_set_calls,"ii"), - BUILDIN_DEF(mercenary_set_faith,"ii"), - BUILDIN_DEF(readbook,"ii"), - BUILDIN_DEF(setfont,"i"), - BUILDIN_DEF(areamobuseskill,"siiiiviiiii"), - BUILDIN_DEF(progressbar,"si"), - BUILDIN_DEF(pushpc,"ii"), - BUILDIN_DEF(buyingstore,"i"), - BUILDIN_DEF(searchstores,"ii"), - BUILDIN_DEF(showdigit,"i?"), - // WoE SE - BUILDIN_DEF(agitstart2,""), - BUILDIN_DEF(agitend2,""), - BUILDIN_DEF(agitcheck2,""), - // BattleGround - BUILDIN_DEF(waitingroom2bg,"siiss?"), - BUILDIN_DEF(waitingroom2bg_single,"isiis"), - BUILDIN_DEF(bg_team_setxy,"iii"), - BUILDIN_DEF(bg_warp,"isii"), - BUILDIN_DEF(bg_monster,"isiisi?"), - BUILDIN_DEF(bg_monster_set_team,"ii"), - BUILDIN_DEF(bg_leave,""), - BUILDIN_DEF(bg_destroy,"i"), - BUILDIN_DEF(areapercentheal,"siiiiii"), - BUILDIN_DEF(bg_get_data,"ii"), - BUILDIN_DEF(bg_getareausers,"isiiii"), - BUILDIN_DEF(bg_updatescore,"sii"), - - // Instancing - BUILDIN_DEF(instance_create,"si"), - BUILDIN_DEF(instance_destroy,"?"), - BUILDIN_DEF(instance_attachmap,"si?"), - BUILDIN_DEF(instance_detachmap,"s?"), - BUILDIN_DEF(instance_attach,"i"), - BUILDIN_DEF(instance_id,"?"), - BUILDIN_DEF(instance_set_timeout,"ii?"), - BUILDIN_DEF(instance_init,"i"), - BUILDIN_DEF(instance_announce,"isi?????"), - BUILDIN_DEF(instance_npcname,"s?"), - BUILDIN_DEF(has_instance,"s?"), - BUILDIN_DEF(instance_warpall,"sii?"), - BUILDIN_DEF(instance_check_party,"i???"), - /** - * 3rd-related - **/ - BUILDIN_DEF(makerune,"i"), - BUILDIN_DEF(checkdragon,""),//[Ind] - BUILDIN_DEF(setdragon,"?"),//[Ind] - BUILDIN_DEF(ismounting,""),//[Ind] - BUILDIN_DEF(setmounting,""),//[Ind] - BUILDIN_DEF(checkre,"i"), - /** - * rAthena and beyond! - **/ - BUILDIN_DEF(getargcount,""), - BUILDIN_DEF(getcharip,"?"), - BUILDIN_DEF(is_function,"s"), - BUILDIN_DEF(get_revision,""), - BUILDIN_DEF(freeloop,"i"), - BUILDIN_DEF(getrandgroupitem,"ii"), - BUILDIN_DEF(cleanmap,"s"), - BUILDIN_DEF2(cleanmap,"cleanarea","siiii"), - BUILDIN_DEF(npcskill,"viii"), - /** - * @commands (script based) - **/ - BUILDIN_DEF(bindatcmd, "ss??"), - BUILDIN_DEF(unbindatcmd, "s"), - BUILDIN_DEF(useatcmd, "s"), - - //Quest Log System [Inkfish] - BUILDIN_DEF(setquest, "i"), - BUILDIN_DEF(erasequest, "i"), - BUILDIN_DEF(completequest, "i"), - BUILDIN_DEF(checkquest, "i?"), - BUILDIN_DEF(changequest, "ii"), - BUILDIN_DEF(showevent, "ii"), - {NULL,NULL,NULL}, -}; +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +//#define DEBUG_DISP +//#define DEBUG_DISASM +//#define DEBUG_RUN +//#define DEBUG_HASH +//#define DEBUG_DUMP_STACK + +#include "../common/cbasetypes.h" +#include "../common/malloc.h" +#include "../common/md5calc.h" +#include "../common/nullpo.h" +#include "../common/random.h" +#include "../common/showmsg.h" +#include "../common/socket.h" // usage: getcharip +#include "../common/strlib.h" +#include "../common/timer.h" +#include "../common/utils.h" + +#include "map.h" +#include "path.h" +#include "clif.h" +#include "chrif.h" +#include "itemdb.h" +#include "pc.h" +#include "status.h" +#include "storage.h" +#include "mob.h" +#include "npc.h" +#include "pet.h" +#include "mapreg.h" +#include "homunculus.h" +#include "instance.h" +#include "mercenary.h" +#include "intif.h" +#include "skill.h" +#include "status.h" +#include "chat.h" +#include "battle.h" +#include "battleground.h" +#include "party.h" +#include "guild.h" +#include "atcommand.h" +#include "log.h" +#include "unit.h" +#include "pet.h" +#include "mail.h" +#include "script.h" +#include "quest.h" +#include "elemental.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> +#ifndef WIN32 + #include <sys/time.h> +#endif +#include <time.h> +#include <setjmp.h> +#include <errno.h> + +#ifdef BETA_THREAD_TEST + #include "../common/atomic.h" + #include "../common/spinlock.h" + #include "../common/thread.h" + #include "../common/mutex.h" +#endif + + +/////////////////////////////////////////////////////////////////////////////// +//## TODO possible enhancements: [FlavioJS] +// - 'callfunc' supporting labels in the current npc "::LabelName" +// - 'callfunc' supporting labels in other npcs "NpcName::LabelName" +// - 'function FuncName;' function declarations reverting to global functions +// if local label isn't found +// - join callfunc and callsub's functionality +// - remove dynamic allocation in add_word() +// - remove GETVALUE / SETVALUE +// - clean up the set_reg / set_val / setd_sub mess +// - detect invalid label references at parse-time + +// +// struct script_state* st; +// + +/// Returns the script_data at the target index +#define script_getdata(st,i) ( &((st)->stack->stack_data[(st)->start + (i)]) ) +/// Returns if the stack contains data at the target index +#define script_hasdata(st,i) ( (st)->end > (st)->start + (i) ) +/// Returns the index of the last data in the stack +#define script_lastdata(st) ( (st)->end - (st)->start - 1 ) +/// Pushes an int into the stack +#define script_pushint(st,val) push_val((st)->stack, C_INT, (val)) +/// Pushes a string into the stack (script engine frees it automatically) +#define script_pushstr(st,val) push_str((st)->stack, C_STR, (val)) +/// Pushes a copy of a string into the stack +#define script_pushstrcopy(st,val) push_str((st)->stack, C_STR, aStrdup(val)) +/// Pushes a constant string into the stack (must never change or be freed) +#define script_pushconststr(st,val) push_str((st)->stack, C_CONSTSTR, (val)) +/// Pushes a nil into the stack +#define script_pushnil(st) push_val((st)->stack, C_NOP, 0) +/// Pushes a copy of the data in the target index +#define script_pushcopy(st,i) push_copy((st)->stack, (st)->start + (i)) + +#define script_isstring(st,i) data_isstring(script_getdata(st,i)) +#define script_isint(st,i) data_isint(script_getdata(st,i)) + +#define script_getnum(st,val) conv_num(st, script_getdata(st,val)) +#define script_getstr(st,val) conv_str(st, script_getdata(st,val)) +#define script_getref(st,val) ( script_getdata(st,val)->ref ) + +// Note: "top" functions/defines use indexes relative to the top of the stack +// -1 is the index of the data at the top + +/// Returns the script_data at the target index relative to the top of the stack +#define script_getdatatop(st,i) ( &((st)->stack->stack_data[(st)->stack->sp + (i)]) ) +/// Pushes a copy of the data in the target index relative to the top of the stack +#define script_pushcopytop(st,i) push_copy((st)->stack, (st)->stack->sp + (i)) +/// Removes the range of values [start,end[ relative to the top of the stack +#define script_removetop(st,start,end) ( pop_stack((st), ((st)->stack->sp + (start)), (st)->stack->sp + (end)) ) + +// +// struct script_data* data; +// + +/// Returns if the script data is a string +#define data_isstring(data) ( (data)->type == C_STR || (data)->type == C_CONSTSTR ) +/// Returns if the script data is an int +#define data_isint(data) ( (data)->type == C_INT ) +/// Returns if the script data is a reference +#define data_isreference(data) ( (data)->type == C_NAME ) +/// Returns if the script data is a label +#define data_islabel(data) ( (data)->type == C_POS ) +/// Returns if the script data is an internal script function label +#define data_isfunclabel(data) ( (data)->type == C_USERFUNC_POS ) + +/// Returns if this is a reference to a constant +#define reference_toconstant(data) ( str_data[reference_getid(data)].type == C_INT ) +/// Returns if this a reference to a param +#define reference_toparam(data) ( str_data[reference_getid(data)].type == C_PARAM ) +/// Returns if this a reference to a variable +//##TODO confirm it's C_NAME [FlavioJS] +#define reference_tovariable(data) ( str_data[reference_getid(data)].type == C_NAME ) +/// Returns the unique id of the reference (id and index) +#define reference_getuid(data) ( (data)->u.num ) +/// Returns the id of the reference +#define reference_getid(data) ( (int32)(reference_getuid(data) & 0x00ffffff) ) +/// Returns the array index of the reference +#define reference_getindex(data) ( (int32)(((uint32)(reference_getuid(data) & 0xff000000)) >> 24) ) +/// Returns the name of the reference +#define reference_getname(data) ( str_buf + str_data[reference_getid(data)].str ) +/// Returns the linked list of uid-value pairs of the reference (can be NULL) +#define reference_getref(data) ( (data)->ref ) +/// Returns the value of the constant +#define reference_getconstant(data) ( str_data[reference_getid(data)].val ) +/// Returns the type of param +#define reference_getparamtype(data) ( str_data[reference_getid(data)].val ) + +/// Composes the uid of a reference from the id and the index +#define reference_uid(id,idx) ( (int32)((((uint32)(id)) & 0x00ffffff) | (((uint32)(idx)) << 24)) ) + +#define not_server_variable(prefix) ( (prefix) != '$' && (prefix) != '.' && (prefix) != '\'') +#define not_array_variable(prefix) ( (prefix) != '$' && (prefix) != '@' && (prefix) != '.' && (prefix) != '\'' ) +#define is_string_variable(name) ( (name)[strlen(name) - 1] == '$' ) + +#define FETCH(n, t) \ + if( script_hasdata(st,n) ) \ + (t)=script_getnum(st,n); + +/// Maximum amount of elements in script arrays +#define SCRIPT_MAX_ARRAYSIZE 128 + +#define SCRIPT_BLOCK_SIZE 512 +enum { LABEL_NEXTLINE=1,LABEL_START }; + +/// temporary buffer for passing around compiled bytecode +/// @see add_scriptb, set_label, parse_script +static unsigned char* script_buf = NULL; +static int script_pos = 0, script_size = 0; + +static inline int GETVALUE(const unsigned char* buf, int i) +{ + return (int)MakeDWord(MakeWord(buf[i], buf[i+1]), MakeWord(buf[i+2], 0)); +} +static inline void SETVALUE(unsigned char* buf, int i, int n) +{ + buf[i] = GetByte(n, 0); + buf[i+1] = GetByte(n, 1); + buf[i+2] = GetByte(n, 2); +} + +// String buffer structures. +// str_data stores string information +static struct str_data_struct { + enum c_op type; + int str; + int backpatch; + int label; + int (*func)(struct script_state *st); + int val; + int next; +} *str_data = NULL; +static int str_data_size = 0; // size of the data +static int str_num = LABEL_START; // next id to be assigned + +// str_buf holds the strings themselves +static char *str_buf; +static int str_size = 0; // size of the buffer +static int str_pos = 0; // next position to be assigned + + +// Using a prime number for SCRIPT_HASH_SIZE should give better distributions +#define SCRIPT_HASH_SIZE 1021 +int str_hash[SCRIPT_HASH_SIZE]; +// Specifies which string hashing method to use +//#define SCRIPT_HASH_DJB2 +//#define SCRIPT_HASH_SDBM +#define SCRIPT_HASH_ELF + +static DBMap* scriptlabel_db=NULL; // const char* label_name -> int script_pos +static DBMap* userfunc_db=NULL; // const char* func_name -> struct script_code* +static int parse_options=0; +DBMap* script_get_label_db(void){ return scriptlabel_db; } +DBMap* script_get_userfunc_db(void){ return userfunc_db; } + +// important buildin function references for usage in scripts +static int buildin_set_ref = 0; +static int buildin_callsub_ref = 0; +static int buildin_callfunc_ref = 0; +static int buildin_getelementofarray_ref = 0; + +// Caches compiled autoscript item code. +// Note: This is not cleared when reloading itemdb. +static DBMap* autobonus_db=NULL; // char* script -> char* bytecode + +struct Script_Config script_config = { + 1, // warn_func_mismatch_argtypes + 1, 65535, 2048, //warn_func_mismatch_paramnum/check_cmdcount/check_gotocount + 0, INT_MAX, // input_min_value/input_max_value + "OnPCDieEvent", //die_event_name + "OnPCKillEvent", //kill_pc_event_name + "OnNPCKillEvent", //kill_mob_event_name + "OnPCLoginEvent", //login_event_name + "OnPCLogoutEvent", //logout_event_name + "OnPCLoadMapEvent", //loadmap_event_name + "OnPCBaseLvUpEvent", //baselvup_event_name + "OnPCJobLvUpEvent", //joblvup_event_name + "OnTouch_", //ontouch_name (runs on first visible char to enter area, picks another char if the first char leaves) + "OnTouch", //ontouch2_name (run whenever a char walks into the OnTouch area) +}; + +static jmp_buf error_jump; +static char* error_msg; +static const char* error_pos; +static int error_report; // if the error should produce output + +// for advanced scripting support ( nested if, switch, while, for, do-while, function, etc ) +// [Eoe / jA 1080, 1081, 1094, 1164] +enum curly_type { + TYPE_NULL = 0, + TYPE_IF, + TYPE_SWITCH, + TYPE_WHILE, + TYPE_FOR, + TYPE_DO, + TYPE_USERFUNC, + TYPE_ARGLIST // function argument list +}; + +enum e_arglist +{ + ARGLIST_UNDEFINED = 0, + ARGLIST_NO_PAREN = 1, + ARGLIST_PAREN = 2, +}; + +static struct { + struct { + enum curly_type type; + int index; + int count; + int flag; + struct linkdb_node *case_label; + } curly[256]; // Information right parenthesis + int curly_count; // The number of right brackets + int index; // Number of the syntax used in the script +} syntax; + +const char* parse_curly_close(const char* p); +const char* parse_syntax_close(const char* p); +const char* parse_syntax_close_sub(const char* p,int* flag); +const char* parse_syntax(const char* p); +static int parse_syntax_for_flag = 0; + +extern int current_equip_item_index; //for New CARDS Scripts. It contains Inventory Index of the EQUIP_SCRIPT caller item. [Lupus] +int potion_flag=0; //For use on Alchemist improved potions/Potion Pitcher. [Skotlex] +int potion_hp=0, potion_per_hp=0, potion_sp=0, potion_per_sp=0; +int potion_target=0; + + +c_op get_com(unsigned char *script,int *pos); +int get_num(unsigned char *script,int *pos); + +typedef struct script_function { + int (*func)(struct script_state *st); + const char *name; + const char *arg; +} script_function; + +extern script_function buildin_func[]; + +static struct linkdb_node* sleep_db;// int oid -> struct script_state* + +#ifdef BETA_THREAD_TEST +/** + * MySQL Query Slave + **/ +static SPIN_LOCK queryThreadLock; +static rAthread queryThread = NULL; +static ramutex queryThreadMutex = NULL; +static racond queryThreadCond = NULL; +static volatile int32 queryThreadTerminate = 0; + +struct queryThreadEntry { + bool ok; + bool type; /* main db or log db? */ + struct script_state *st; +}; + +/* Ladies and Gentleman the Manager! */ +struct { + struct queryThreadEntry **entry;/* array of structs */ + int count; + int timer;/* used to receive processed entries */ +} queryThreadData; +#endif + +/*========================================== + * (Only those needed) local declaration prototype + *------------------------------------------*/ +const char* parse_subexpr(const char* p,int limit); +int run_func(struct script_state *st); + +enum { + MF_NOMEMO, //0 + MF_NOTELEPORT, + MF_NOSAVE, + MF_NOBRANCH, + MF_NOPENALTY, + MF_NOZENYPENALTY, + MF_PVP, + MF_PVP_NOPARTY, + MF_PVP_NOGUILD, + MF_GVG, + MF_GVG_NOPARTY, //10 + MF_NOTRADE, + MF_NOSKILL, + MF_NOWARP, + MF_PARTYLOCK, + MF_NOICEWALL, + MF_SNOW, + MF_FOG, + MF_SAKURA, + MF_LEAVES, + /** + * No longer available, keeping here just in case it's back someday. [Ind] + **/ + //MF_RAIN, //20 + // 21 free + MF_NOGO = 22, + MF_CLOUDS, + MF_CLOUDS2, + MF_FIREWORKS, + MF_GVG_CASTLE, + MF_GVG_DUNGEON, + MF_NIGHTENABLED, + MF_NOBASEEXP, + MF_NOJOBEXP, //30 + MF_NOMOBLOOT, + MF_NOMVPLOOT, + MF_NORETURN, + MF_NOWARPTO, + MF_NIGHTMAREDROP, + MF_RESTRICTED, + MF_NOCOMMAND, + MF_NODROP, + MF_JEXP, + MF_BEXP, //40 + MF_NOVENDING, + MF_LOADEVENT, + MF_NOCHAT, + MF_NOEXPPENALTY, + MF_GUILDLOCK, + MF_TOWN, + MF_AUTOTRADE, + MF_ALLOWKS, + MF_MONSTER_NOTELEPORT, + MF_PVP_NOCALCRANK, //50 + MF_BATTLEGROUND, + MF_RESET +}; + +const char* script_op2name(int op) +{ +#define RETURN_OP_NAME(type) case type: return #type + switch( op ) + { + RETURN_OP_NAME(C_NOP); + RETURN_OP_NAME(C_POS); + RETURN_OP_NAME(C_INT); + RETURN_OP_NAME(C_PARAM); + RETURN_OP_NAME(C_FUNC); + RETURN_OP_NAME(C_STR); + RETURN_OP_NAME(C_CONSTSTR); + RETURN_OP_NAME(C_ARG); + RETURN_OP_NAME(C_NAME); + RETURN_OP_NAME(C_EOL); + RETURN_OP_NAME(C_RETINFO); + RETURN_OP_NAME(C_USERFUNC); + RETURN_OP_NAME(C_USERFUNC_POS); + + // operators + RETURN_OP_NAME(C_OP3); + RETURN_OP_NAME(C_LOR); + RETURN_OP_NAME(C_LAND); + RETURN_OP_NAME(C_LE); + RETURN_OP_NAME(C_LT); + RETURN_OP_NAME(C_GE); + RETURN_OP_NAME(C_GT); + RETURN_OP_NAME(C_EQ); + RETURN_OP_NAME(C_NE); + RETURN_OP_NAME(C_XOR); + RETURN_OP_NAME(C_OR); + RETURN_OP_NAME(C_AND); + RETURN_OP_NAME(C_ADD); + RETURN_OP_NAME(C_SUB); + RETURN_OP_NAME(C_MUL); + RETURN_OP_NAME(C_DIV); + RETURN_OP_NAME(C_MOD); + RETURN_OP_NAME(C_NEG); + RETURN_OP_NAME(C_LNOT); + RETURN_OP_NAME(C_NOT); + RETURN_OP_NAME(C_R_SHIFT); + RETURN_OP_NAME(C_L_SHIFT); + + default: + ShowDebug("script_op2name: unexpected op=%d\n", op); + return "???"; + } +#undef RETURN_OP_NAME +} + +#ifdef DEBUG_DUMP_STACK +static void script_dump_stack(struct script_state* st) +{ + int i; + ShowMessage("\tstart = %d\n", st->start); + ShowMessage("\tend = %d\n", st->end); + ShowMessage("\tdefsp = %d\n", st->stack->defsp); + ShowMessage("\tsp = %d\n", st->stack->sp); + for( i = 0; i < st->stack->sp; ++i ) + { + struct script_data* data = &st->stack->stack_data[i]; + ShowMessage("\t[%d] %s", i, script_op2name(data->type)); + switch( data->type ) + { + case C_INT: + case C_POS: + ShowMessage(" %d\n", data->u.num); + break; + + case C_STR: + case C_CONSTSTR: + ShowMessage(" \"%s\"\n", data->u.str); + break; + + case C_NAME: + ShowMessage(" \"%s\" (id=%d ref=%p subtype=%s)\n", reference_getname(data), data->u.num, data->ref, script_op2name(str_data[data->u.num].type)); + break; + + case C_RETINFO: + { + struct script_retinfo* ri = data->u.ri; + ShowMessage(" %p {var_function=%p, script=%p, pos=%d, nargs=%d, defsp=%d}\n", ri, ri->var_function, ri->script, ri->pos, ri->nargs, ri->defsp); + } + break; + default: + ShowMessage("\n"); + break; + } + } +} +#endif + +/// Reports on the console the src of a script error. +static void script_reportsrc(struct script_state *st) +{ + struct block_list* bl; + + if( st->oid == 0 ) + return; //Can't report source. + + bl = map_id2bl(st->oid); + if( bl == NULL ) + return; + + switch( bl->type ) + { + case BL_NPC: + if( bl->m >= 0 ) + ShowDebug("Source (NPC): %s at %s (%d,%d)\n", ((struct npc_data *)bl)->name, map[bl->m].name, bl->x, bl->y); + else + ShowDebug("Source (NPC): %s (invisible/not on a map)\n", ((struct npc_data *)bl)->name); + break; + default: + if( bl->m >= 0 ) + ShowDebug("Source (Non-NPC type %d): name %s at %s (%d,%d)\n", bl->type, status_get_name(bl), map[bl->m].name, bl->x, bl->y); + else + ShowDebug("Source (Non-NPC type %d): name %s (invisible/not on a map)\n", bl->type, status_get_name(bl)); + break; + } +} + +/// Reports on the console information about the script data. +static void script_reportdata(struct script_data* data) +{ + if( data == NULL ) + return; + switch( data->type ) + { + case C_NOP:// no value + ShowDebug("Data: nothing (nil)\n"); + break; + case C_INT:// number + ShowDebug("Data: number value=%d\n", data->u.num); + break; + case C_STR: + case C_CONSTSTR:// string + if( data->u.str ) + { + ShowDebug("Data: string value=\"%s\"\n", data->u.str); + } + else + { + ShowDebug("Data: string value=NULL\n"); + } + break; + case C_NAME:// reference + if( reference_tovariable(data) ) + {// variable + const char* name = reference_getname(data); + if( not_array_variable(*name) ) + ShowDebug("Data: variable name='%s'\n", name); + else + ShowDebug("Data: variable name='%s' index=%d\n", name, reference_getindex(data)); + } + else if( reference_toconstant(data) ) + {// constant + ShowDebug("Data: constant name='%s' value=%d\n", reference_getname(data), reference_getconstant(data)); + } + else if( reference_toparam(data) ) + {// param + ShowDebug("Data: param name='%s' type=%d\n", reference_getname(data), reference_getparamtype(data)); + } + else + {// ??? + ShowDebug("Data: reference name='%s' type=%s\n", reference_getname(data), script_op2name(data->type)); + ShowDebug("Please report this!!! - str_data.type=%s\n", script_op2name(str_data[reference_getid(data)].type)); + } + break; + case C_POS:// label + ShowDebug("Data: label pos=%d\n", data->u.num); + break; + default: + ShowDebug("Data: %s\n", script_op2name(data->type)); + break; + } +} + + +/// Reports on the console information about the current built-in function. +static void script_reportfunc(struct script_state* st) +{ + int i, params, id; + struct script_data* data; + + if( !script_hasdata(st,0) ) + {// no stack + return; + } + + data = script_getdata(st,0); + + if( !data_isreference(data) || str_data[reference_getid(data)].type != C_FUNC ) + {// script currently not executing a built-in function or corrupt stack + return; + } + + id = reference_getid(data); + params = script_lastdata(st)-1; + + if( params > 0 ) + { + ShowDebug("Function: %s (%d parameter%s):\n", get_str(id), params, ( params == 1 ) ? "" : "s"); + + for( i = 2; i <= script_lastdata(st); i++ ) + { + script_reportdata(script_getdata(st,i)); + } + } + else + { + ShowDebug("Function: %s (no parameters)\n", get_str(id)); + } +} + + +/*========================================== + * Output error message + *------------------------------------------*/ +static void disp_error_message2(const char *mes,const char *pos,int report) +{ + error_msg = aStrdup(mes); + error_pos = pos; + error_report = report; + longjmp( error_jump, 1 ); +} +#define disp_error_message(mes,pos) disp_error_message2(mes,pos,1) + +/// Checks event parameter validity +static void check_event(struct script_state *st, const char *evt) +{ + if( evt && evt[0] && !stristr(evt, "::On") ) + { + ShowWarning("NPC event parameter deprecated! Please use 'NPCNAME::OnEVENT' instead of '%s'.\n", evt); + script_reportsrc(st); + } +} + +/*========================================== + * Hashes the input string + *------------------------------------------*/ +static unsigned int calc_hash(const char* p) +{ + unsigned int h; + +#if defined(SCRIPT_HASH_DJB2) + h = 5381; + while( *p ) // hash*33 + c + h = ( h << 5 ) + h + ((unsigned char)TOLOWER(*p++)); +#elif defined(SCRIPT_HASH_SDBM) + h = 0; + while( *p ) // hash*65599 + c + h = ( h << 6 ) + ( h << 16 ) - h + ((unsigned char)TOLOWER(*p++)); +#elif defined(SCRIPT_HASH_ELF) // UNIX ELF hash + h = 0; + while( *p ){ + unsigned int g; + h = ( h << 4 ) + ((unsigned char)TOLOWER(*p++)); + g = h & 0xF0000000; + if( g ) + { + h ^= g >> 24; + h &= ~g; + } + } +#else // athena hash + h = 0; + while( *p ) + h = ( h << 1 ) + ( h >> 3 ) + ( h >> 5 ) + ( h >> 8 ) + (unsigned char)TOLOWER(*p++); +#endif + + return h % SCRIPT_HASH_SIZE; +} + + +/*========================================== + * str_data manipulation functions + *------------------------------------------*/ + +/// Looks up string using the provided id. +const char* get_str(int id) +{ + Assert( id >= LABEL_START && id < str_size ); + return str_buf+str_data[id].str; +} + +/// Returns the uid of the string, or -1. +static int search_str(const char* p) +{ + int i; + + for( i = str_hash[calc_hash(p)]; i != 0; i = str_data[i].next ) + if( strcasecmp(get_str(i),p) == 0 ) + return i; + + return -1; +} + +/// Stores a copy of the string and returns its id. +/// If an identical string is already present, returns its id instead. +int add_str(const char* p) +{ + int i, h; + int len; + + h = calc_hash(p); + + if( str_hash[h] == 0 ) + {// empty bucket, add new node here + str_hash[h] = str_num; + } + else + {// scan for end of list, or occurence of identical string + for( i = str_hash[h]; ; i = str_data[i].next ) + { + if( strcasecmp(get_str(i),p) == 0 ) + return i; // string already in list + if( str_data[i].next == 0 ) + break; // reached the end + } + + // append node to end of list + str_data[i].next = str_num; + } + + // grow list if neccessary + if( str_num >= str_data_size ) + { + str_data_size += 128; + RECREATE(str_data,struct str_data_struct,str_data_size); + memset(str_data + (str_data_size - 128), '\0', 128); + } + + len=(int)strlen(p); + + // grow string buffer if neccessary + while( str_pos+len+1 >= str_size ) + { + str_size += 256; + RECREATE(str_buf,char,str_size); + memset(str_buf + (str_size - 256), '\0', 256); + } + + safestrncpy(str_buf+str_pos, p, len+1); + 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 += len+1; + + return str_num++; +} + + +/// Appends 1 byte to the script buffer. +static void add_scriptb(int a) +{ + if( script_pos+1 >= script_size ) + { + script_size += SCRIPT_BLOCK_SIZE; + RECREATE(script_buf,unsigned char,script_size); + } + script_buf[script_pos++] = (uint8)(a); +} + +/// Appends a c_op value to the script buffer. +/// The value is variable-length encoded into 8-bit blocks. +/// The encoding scheme is ( 01?????? )* 00??????, LSB first. +/// All blocks but the last hold 7 bits of data, topmost bit is always 1 (carries). +static void add_scriptc(int a) +{ + while( a >= 0x40 ) + { + add_scriptb((a&0x3f)|0x40); + a = (a - 0x40) >> 6; + } + + add_scriptb(a); +} + +/// Appends an integer value to the script buffer. +/// The value is variable-length encoded into 8-bit blocks. +/// The encoding scheme is ( 11?????? )* 10??????, LSB first. +/// All blocks but the last hold 7 bits of data, topmost bit is always 1 (carries). +static void add_scripti(int a) +{ + while( a >= 0x40 ) + { + add_scriptb((a&0x3f)|0xc0); + a = (a - 0x40) >> 6; + } + add_scriptb(a|0x80); +} + +/// Appends a str_data object (label/function/variable/integer) to the script buffer. + +/// +/// @param l The id of the str_data entry +// Maximum up to 16M +static void add_scriptl(int l) +{ + int backpatch = str_data[l].backpatch; + + switch(str_data[l].type){ + case C_POS: + case C_USERFUNC_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: + case C_USERFUNC: + // Embedded data backpatch there is a possibility of label + 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(abs(str_data[l].val)); + if( str_data[l].val < 0 ) //Notice that this is negative, from jA (Rayce) + add_scriptc(C_NEG); + break; + default: // assume C_NAME + add_scriptc(C_NAME); + add_scriptb(l); + add_scriptb(l>>8); + add_scriptb(l>>16); + break; + } +} + +/*========================================== + * Resolve the label + *------------------------------------------*/ +void set_label(int l,int pos, const char* script_pos) +{ + int i,next; + + if(str_data[l].type==C_INT || str_data[l].type==C_PARAM || str_data[l].type==C_FUNC) + { //Prevent overwriting constants values, parameters and built-in functions [Skotlex] + disp_error_message("set_label: invalid label name",script_pos); + return; + } + if(str_data[l].label!=-1){ + disp_error_message("set_label: dup label ",script_pos); + return; + } + str_data[l].type=(str_data[l].type == C_USERFUNC ? C_USERFUNC_POS : C_POS); + str_data[l].label=pos; + for(i=str_data[l].backpatch;i>=0 && i!=0x00ffffff;){ + next=GETVALUE(script_buf,i); + script_buf[i-1]=(str_data[l].type == C_USERFUNC ? C_USERFUNC_POS : C_POS); + SETVALUE(script_buf,i,pos); + i=next; + } +} + +/// Skips spaces and/or comments. +const char* skip_space(const char* p) +{ + if( p == NULL ) + return NULL; + for(;;) + { + while( ISSPACE(*p) ) + ++p; + if( *p == '/' && p[1] == '/' ) + {// line comment + while(*p && *p!='\n') + ++p; + } + else if( *p == '/' && p[1] == '*' ) + {// block comment + p += 2; + for(;;) + { + if( *p == '\0' ) + return p;//disp_error_message("script:skip_space: end of file while parsing block comment. expected "CL_BOLD"*/"CL_NORM, p); + if( *p == '*' && p[1] == '/' ) + {// end of block comment + p += 2; + break; + } + ++p; + } + } + else + break; + } + return p; +} + +/// Skips a word. +/// A word consists of undercores and/or alfanumeric characters, +/// and valid variable prefixes/postfixes. +static +const char* skip_word(const char* p) +{ + // prefix + switch( *p ) + { + case '@':// temporary char variable + ++p; break; + case '#':// account variable + p += ( p[1] == '#' ? 2 : 1 ); break; + case '\'':// instance variable + ++p; break; + case '.':// npc variable + p += ( p[1] == '@' ? 2 : 1 ); break; + case '$':// global variable + p += ( p[1] == '@' ? 2 : 1 ); break; + } + + while( ISALNUM(*p) || *p == '_' ) + ++p; + + // postfix + if( *p == '$' )// string + p++; + + return p; +} + +/// Adds a word to str_data. +/// @see skip_word +/// @see add_str +static +int add_word(const char* p) +{ + char* word; + int len; + int i; + + // Check for a word + len = skip_word(p) - p; + if( len == 0 ) + disp_error_message("script:add_word: invalid word. A word consists of undercores and/or alfanumeric characters, and valid variable prefixes/postfixes.", p); + + // Duplicate the word + word = (char*)aMalloc(len+1); + memcpy(word, p, len); + word[len] = 0; + + // add the word + i = add_str(word); + aFree(word); + return i; +} + +/// Parses a function call. +/// The argument list can have parenthesis or not. +/// The number of arguments is checked. +static +const char* parse_callfunc(const char* p, int require_paren, int is_custom) +{ + const char* p2; + const char* arg=NULL; + int func; + + func = add_word(p); + if( str_data[func].type == C_FUNC ){ + // buildin function + add_scriptl(func); + add_scriptc(C_ARG); + arg = buildin_func[str_data[func].val].arg; + } else if( str_data[func].type == C_USERFUNC || str_data[func].type == C_USERFUNC_POS ){ + // script defined function + add_scriptl(buildin_callsub_ref); + add_scriptc(C_ARG); + add_scriptl(func); + arg = buildin_func[str_data[buildin_callsub_ref].val].arg; + if( *arg == 0 ) + disp_error_message("parse_callfunc: callsub has no arguments, please review it's definition",p); + if( *arg != '*' ) + ++arg; // count func as argument + } else { +#ifdef SCRIPT_CALLFUNC_CHECK + const char* name = get_str(func); + if( !is_custom && strdb_get(userfunc_db, name) == NULL ) { +#endif + disp_error_message("parse_line: expect command, missing function name or calling undeclared function",p); +#ifdef SCRIPT_CALLFUNC_CHECK + } else {; + add_scriptl(buildin_callfunc_ref); + add_scriptc(C_ARG); + add_scriptc(C_STR); + while( *name ) add_scriptb(*name ++); + add_scriptb(0); + arg = buildin_func[str_data[buildin_callfunc_ref].val].arg; + if( *arg != '*' ) ++ arg; + } +#endif + } + + p = skip_word(p); + p = skip_space(p); + syntax.curly[syntax.curly_count].type = TYPE_ARGLIST; + syntax.curly[syntax.curly_count].count = 0; + if( *p == ';' ) + {// <func name> ';' + syntax.curly[syntax.curly_count].flag = ARGLIST_NO_PAREN; + } else if( *p == '(' && *(p2=skip_space(p+1)) == ')' ) + {// <func name> '(' ')' + syntax.curly[syntax.curly_count].flag = ARGLIST_PAREN; + p = p2; + /* + } else if( 0 && require_paren && *p != '(' ) + {// <func name> + syntax.curly[syntax.curly_count].flag = ARGLIST_NO_PAREN; + */ + } else + {// <func name> <arg list> + if( require_paren ){ + if( *p != '(' ) + disp_error_message("need '('",p); + ++p; // skip '(' + syntax.curly[syntax.curly_count].flag = ARGLIST_PAREN; + } else if( *p == '(' ){ + syntax.curly[syntax.curly_count].flag = ARGLIST_UNDEFINED; + } else { + syntax.curly[syntax.curly_count].flag = ARGLIST_NO_PAREN; + } + ++syntax.curly_count; + while( *arg ) { + p2=parse_subexpr(p,-1); + if( p == p2 ) + break; // not an argument + if( *arg != '*' ) + ++arg; // next argument + + p=skip_space(p2); + if( *arg == 0 || *p != ',' ) + break; // no more arguments + ++p; // skip comma + } + --syntax.curly_count; + } + if( *arg && *arg != '?' && *arg != '*' ) + disp_error_message2("parse_callfunc: not enough arguments, expected ','", p, script_config.warn_func_mismatch_paramnum); + if( syntax.curly[syntax.curly_count].type != TYPE_ARGLIST ) + disp_error_message("parse_callfunc: DEBUG last curly is not an argument list",p); + if( syntax.curly[syntax.curly_count].flag == ARGLIST_PAREN ){ + if( *p != ')' ) + disp_error_message("parse_callfunc: expected ')' to close argument list",p); + ++p; + } + add_scriptc(C_FUNC); + return p; +} + +/// Processes end of logical script line. +/// @param first When true, only fix up scheduling data is initialized +/// @param p Script position for error reporting in set_label +static void parse_nextline(bool first, const char* p) +{ + if( !first ) + { + add_scriptc(C_EOL); // mark end of line for stack cleanup + set_label(LABEL_NEXTLINE, script_pos, p); // fix up '-' labels + } + + // initialize data for new '-' label fix up scheduling + str_data[LABEL_NEXTLINE].type = C_NOP; + str_data[LABEL_NEXTLINE].backpatch = -1; + str_data[LABEL_NEXTLINE].label = -1; +} + +/// Parse a variable assignment using the direct equals operator +/// @param p script position where the function should run from +/// @return NULL if not a variable assignment, the new position otherwise +const char* parse_variable(const char* p) { + int i, j, word; + c_op type = C_NOP; + const char *p2 = NULL; + const char *var = p; + + // skip the variable where applicable + p = skip_word(p); + p = skip_space(p); + + if( p == NULL ) {// end of the line or invalid buffer + return NULL; + } + + if( *p == '[' ) {// array variable so process the array as appropriate + for( p2 = p, i = 0, j = 1; p; ++ i ) { + if( *p ++ == ']' && --(j) == 0 ) break; + if( *p == '[' ) ++ j; + } + + if( !(p = skip_space(p)) ) {// end of line or invalid characters remaining + disp_error_message("Missing right expression or closing bracket for variable.", p); + } + } + + if( type == C_NOP && + !( ( p[0] == '=' && p[1] != '=' && (type = C_EQ) ) // = + || ( p[0] == '+' && p[1] == '=' && (type = C_ADD) ) // += + || ( p[0] == '-' && p[1] == '=' && (type = C_SUB) ) // -= + || ( p[0] == '^' && p[1] == '=' && (type = C_XOR) ) // ^= + || ( p[0] == '|' && p[1] == '=' && (type = C_OR ) ) // |= + || ( p[0] == '&' && p[1] == '=' && (type = C_AND) ) // &= + || ( p[0] == '*' && p[1] == '=' && (type = C_MUL) ) // *= + || ( p[0] == '/' && p[1] == '=' && (type = C_DIV) ) // /= + || ( p[0] == '%' && p[1] == '=' && (type = C_MOD) ) // %= + || ( p[0] == '~' && p[1] == '=' && (type = C_NOT) ) // ~= + || ( p[0] == '+' && p[1] == '+' && (type = C_ADD_PP) ) // ++ + || ( p[0] == '-' && p[1] == '-' && (type = C_SUB_PP) ) // -- + || ( p[0] == '<' && p[1] == '<' && p[2] == '=' && (type = C_L_SHIFT) ) // <<= + || ( p[0] == '>' && p[1] == '>' && p[2] == '=' && (type = C_R_SHIFT) ) // >>= + ) ) + {// failed to find a matching operator combination so invalid + return NULL; + } + + switch( type ) { + case C_EQ: {// incremental modifier + p = skip_space( &p[1] ); + } + break; + + case C_L_SHIFT: + case C_R_SHIFT: {// left or right shift modifier + p = skip_space( &p[3] ); + } + break; + + default: {// normal incremental command + p = skip_space( &p[2] ); + } + } + + if( p == NULL ) {// end of line or invalid buffer + return NULL; + } + + // push the set function onto the stack + add_scriptl(buildin_set_ref); + add_scriptc(C_ARG); + + // always append parenthesis to avoid errors + syntax.curly[syntax.curly_count].type = TYPE_ARGLIST; + syntax.curly[syntax.curly_count].count = 0; + syntax.curly[syntax.curly_count].flag = ARGLIST_PAREN; + + // increment the total curly count for the position in the script + ++ syntax.curly_count; + + // parse the variable currently being modified + word = add_word(var); + + if( str_data[word].type == C_FUNC || str_data[word].type == C_USERFUNC || str_data[word].type == C_USERFUNC_POS ) + {// cannot assign a variable which exists as a function or label + disp_error_message("Cannot modify a variable which has the same name as a function or label.", p); + } + + if( p2 ) {// process the variable index + const char* p3 = NULL; + + // push the getelementofarray method into the stack + add_scriptl(buildin_getelementofarray_ref); + add_scriptc(C_ARG); + add_scriptl(word); + + // process the sub-expression for this assignment + p3 = parse_subexpr(p2 + 1, 1); + p3 = skip_space(p3); + + if( *p3 != ']' ) {// closing parenthesis is required for this script + disp_error_message("Missing closing ']' parenthesis for the variable assignment.", p3); + } + + // push the closing function stack operator onto the stack + add_scriptc(C_FUNC); + p3 ++; + } else {// simply push the variable or value onto the stack + add_scriptl(word); + } + + if( type != C_EQ ) + add_scriptc(C_REF); + + if( type == C_ADD_PP || type == C_SUB_PP ) {// incremental operator for the method + add_scripti(1); + add_scriptc(type == C_ADD_PP ? C_ADD : C_SUB); + } else {// process the value as an expression + p = parse_subexpr(p, -1); + + if( type != C_EQ ) + {// push the type of modifier onto the stack + add_scriptc(type); + } + } + + // decrement the curly count for the position within the script + -- syntax.curly_count; + + // close the script by appending the function operator + add_scriptc(C_FUNC); + + // push the buffer from the method + return p; +} + +/*========================================== + * Analysis section + *------------------------------------------*/ +const char* parse_simpleexpr(const char *p) +{ + int i; + p=skip_space(p); + + if(*p==';' || *p==',') + disp_error_message("parse_simpleexpr: unexpected expr end",p); + if(*p=='('){ + if( (i=syntax.curly_count-1) >= 0 && syntax.curly[i].type == TYPE_ARGLIST ) + ++syntax.curly[i].count; + p=parse_subexpr(p+1,-1); + p=skip_space(p); + if( (i=syntax.curly_count-1) >= 0 && syntax.curly[i].type == TYPE_ARGLIST && + syntax.curly[i].flag == ARGLIST_UNDEFINED && --syntax.curly[i].count == 0 + ){ + if( *p == ',' ){ + syntax.curly[i].flag = ARGLIST_PAREN; + return p; + } else + syntax.curly[i].flag = ARGLIST_NO_PAREN; + } + if( *p != ')' ) + disp_error_message("parse_simpleexpr: unmatch ')'",p); + ++p; + } else if(ISDIGIT(*p) || ((*p=='-' || *p=='+') && ISDIGIT(p[1]))){ + char *np; + while(*p == '0' && ISDIGIT(p[1])) p++; + i=strtoul(p,&np,0); + add_scripti(i); + p=np; + } else if(*p=='"'){ + add_scriptc(C_STR); + p++; + while( *p && *p != '"' ){ + if( (unsigned char)p[-1] <= 0x7e && *p == '\\' ) + { + char buf[8]; + size_t len = skip_escaped_c(p) - p; + size_t n = sv_unescape_c(buf, p, len); + if( n != 1 ) + ShowDebug("parse_simpleexpr: unexpected length %d after unescape (\"%.*s\" -> %.*s)\n", (int)n, (int)len, p, (int)n, buf); + p += len; + add_scriptb(*buf); + continue; + } + else if( *p == '\n' ) + disp_error_message("parse_simpleexpr: unexpected newline @ string",p); + add_scriptb(*p++); + } + if(!*p) + disp_error_message("parse_simpleexpr: unexpected eof @ string",p); + add_scriptb(0); + p++; //'"' + } else { + int l; + const char* pv; + + // label , register , function etc + if(skip_word(p)==p) + disp_error_message("parse_simpleexpr: unexpected character",p); + + l=add_word(p); + if( str_data[l].type == C_FUNC || str_data[l].type == C_USERFUNC || str_data[l].type == C_USERFUNC_POS) + return parse_callfunc(p,1,0); +#ifdef SCRIPT_CALLFUNC_CHECK + else { + const char* name = get_str(l); + if( strdb_get(userfunc_db,name) != NULL ) { + return parse_callfunc(p,1,1); + } + } +#endif + + if( (pv = parse_variable(p)) ) + {// successfully processed a variable assignment + return pv; + } + + p=skip_word(p); + if( *p == '[' ){ + // array(name[i] => getelementofarray(name,i) ) + add_scriptl(buildin_getelementofarray_ref); + add_scriptc(C_ARG); + add_scriptl(l); + + p=parse_subexpr(p+1,-1); + p=skip_space(p); + if( *p != ']' ) + disp_error_message("parse_simpleexpr: unmatch ']'",p); + ++p; + add_scriptc(C_FUNC); + }else + add_scriptl(l); + + } + + return p; +} + +/*========================================== + * Analysis of the expression + *------------------------------------------*/ +const char* parse_subexpr(const char* p,int limit) +{ + int op,opl,len; + const char* tmpp; + + p=skip_space(p); + + if( *p == '-' ){ + tmpp = skip_space(p+1); + if( *tmpp == ';' || *tmpp == ',' ){ + add_scriptl(LABEL_NEXTLINE); + p++; + return p; + } + } + + if((op=C_NEG,*p=='-') || (op=C_LNOT,*p=='!') || (op=C_NOT,*p=='~')){ + p=parse_subexpr(p+1,10); + add_scriptc(op); + } else + p=parse_simpleexpr(p); + p=skip_space(p); + while(( + (op=C_OP3,opl=0,len=1,*p=='?') || + (op=C_ADD,opl=8,len=1,*p=='+') || + (op=C_SUB,opl=8,len=1,*p=='-') || + (op=C_MUL,opl=9,len=1,*p=='*') || + (op=C_DIV,opl=9,len=1,*p=='/') || + (op=C_MOD,opl=9,len=1,*p=='%') || + (op=C_LAND,opl=2,len=2,*p=='&' && p[1]=='&') || + (op=C_AND,opl=6,len=1,*p=='&') || + (op=C_LOR,opl=1,len=2,*p=='|' && p[1]=='|') || + (op=C_OR,opl=5,len=1,*p=='|') || + (op=C_XOR,opl=4,len=1,*p=='^') || + (op=C_EQ,opl=3,len=2,*p=='=' && p[1]=='=') || + (op=C_NE,opl=3,len=2,*p=='!' && p[1]=='=') || + (op=C_R_SHIFT,opl=7,len=2,*p=='>' && p[1]=='>') || + (op=C_GE,opl=3,len=2,*p=='>' && p[1]=='=') || + (op=C_GT,opl=3,len=1,*p=='>') || + (op=C_L_SHIFT,opl=7,len=2,*p=='<' && p[1]=='<') || + (op=C_LE,opl=3,len=2,*p=='<' && p[1]=='=') || + (op=C_LT,opl=3,len=1,*p=='<')) && opl>limit){ + p+=len; + if(op == C_OP3) { + p=parse_subexpr(p,-1); + p=skip_space(p); + if( *(p++) != ':') + disp_error_message("parse_subexpr: need ':'", p-1); + p=parse_subexpr(p,-1); + } else { + p=parse_subexpr(p,opl); + } + add_scriptc(op); + p=skip_space(p); + } + + return p; /* return first untreated operator */ +} + +/*========================================== + * Evaluation of the expression + *------------------------------------------*/ +const char* parse_expr(const char *p) +{ + switch(*p){ + case ')': case ';': case ':': case '[': case ']': + case '}': + disp_error_message("parse_expr: unexpected char",p); + } + p=parse_subexpr(p,-1); + return p; +} + +/*========================================== + * Analysis of the line + *------------------------------------------*/ +const char* parse_line(const char* p) +{ + const char* p2; + + p=skip_space(p); + if(*p==';') { + //Close decision for if(); for(); while(); + p = parse_syntax_close(p + 1); + return p; + } + if(*p==')' && parse_syntax_for_flag) + return p+1; + + p = skip_space(p); + if(p[0] == '{') { + syntax.curly[syntax.curly_count].type = TYPE_NULL; + syntax.curly[syntax.curly_count].count = -1; + syntax.curly[syntax.curly_count].index = -1; + syntax.curly_count++; + return p + 1; + } else if(p[0] == '}') { + return parse_curly_close(p); + } + + // Syntax-related processing + p2 = parse_syntax(p); + if(p2 != NULL) + return p2; + + // attempt to process a variable assignment + p2 = parse_variable(p); + + if( p2 != NULL ) + {// variable assignment processed so leave the method + return parse_syntax_close(p2 + 1); + } + + p = parse_callfunc(p,0,0); + p = skip_space(p); + + if(parse_syntax_for_flag) { + if( *p != ')' ) + disp_error_message("parse_line: need ')'",p); + } else { + if( *p != ';' ) + disp_error_message("parse_line: need ';'",p); + } + + //Binding decision for if(), for(), while() + p = parse_syntax_close(p+1); + + return p; +} + +// { ... } Closing process +const char* parse_curly_close(const char* p) +{ + if(syntax.curly_count <= 0) { + disp_error_message("parse_curly_close: unexpected string",p); + return p + 1; + } else if(syntax.curly[syntax.curly_count-1].type == TYPE_NULL) { + syntax.curly_count--; + //Close decision if, for , while + p = parse_syntax_close(p + 1); + return p; + } else if(syntax.curly[syntax.curly_count-1].type == TYPE_SWITCH) { + //Closing switch() + int pos = syntax.curly_count-1; + char label[256]; + int l; + // Remove temporary variables + sprintf(label,"set $@__SW%x_VAL,0;",syntax.curly[pos].index); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + + // Go to the end pointer unconditionally + sprintf(label,"goto __SW%x_FIN;",syntax.curly[pos].index); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + + // You are here labeled + sprintf(label,"__SW%x_%x",syntax.curly[pos].index,syntax.curly[pos].count); + l=add_str(label); + set_label(l,script_pos, p); + + if(syntax.curly[pos].flag) { + //Exists default + sprintf(label,"goto __SW%x_DEF;",syntax.curly[pos].index); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + } + + // Label end + sprintf(label,"__SW%x_FIN",syntax.curly[pos].index); + l=add_str(label); + set_label(l,script_pos, p); + linkdb_final(&syntax.curly[pos].case_label); // free the list of case label + syntax.curly_count--; + //Closing decision if, for , while + p = parse_syntax_close(p + 1); + return p; + } else { + disp_error_message("parse_curly_close: unexpected string",p); + return p + 1; + } +} + +// Syntax-related processing +// break, case, continue, default, do, for, function, +// if, switch, while ? will handle this internally. +const char* parse_syntax(const char* p) +{ + const char *p2 = skip_word(p); + + switch(*p) { + case 'B': + case 'b': + if(p2 - p == 5 && !strncasecmp(p,"break",5)) { + // break Processing + char label[256]; + int pos = syntax.curly_count - 1; + while(pos >= 0) { + if(syntax.curly[pos].type == TYPE_DO) { + sprintf(label,"goto __DO%x_FIN;",syntax.curly[pos].index); + break; + } else if(syntax.curly[pos].type == TYPE_FOR) { + sprintf(label,"goto __FR%x_FIN;",syntax.curly[pos].index); + break; + } else if(syntax.curly[pos].type == TYPE_WHILE) { + sprintf(label,"goto __WL%x_FIN;",syntax.curly[pos].index); + break; + } else if(syntax.curly[pos].type == TYPE_SWITCH) { + sprintf(label,"goto __SW%x_FIN;",syntax.curly[pos].index); + break; + } + pos--; + } + if(pos < 0) { + disp_error_message("parse_syntax: unexpected 'break'",p); + } else { + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + } + p = skip_space(p2); + if(*p != ';') + disp_error_message("parse_syntax: need ';'",p); + // Closing decision if, for , while + p = parse_syntax_close(p + 1); + return p; + } + break; + case 'c': + case 'C': + if(p2 - p == 4 && !strncasecmp(p,"case",4)) { + //Processing case + int pos = syntax.curly_count-1; + if(pos < 0 || syntax.curly[pos].type != TYPE_SWITCH) { + disp_error_message("parse_syntax: unexpected 'case' ",p); + return p+1; + } else { + char label[256]; + int l,v; + char *np; + if(syntax.curly[pos].count != 1) { + //Jump for FALLTHRU + sprintf(label,"goto __SW%x_%xJ;",syntax.curly[pos].index,syntax.curly[pos].count); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + + // You are here labeled + sprintf(label,"__SW%x_%x",syntax.curly[pos].index,syntax.curly[pos].count); + l=add_str(label); + set_label(l,script_pos, p); + } + //Decision statement switch + p = skip_space(p2); + if(p == p2) { + disp_error_message("parse_syntax: expect space ' '",p); + } + // check whether case label is integer or not + v = strtol(p,&np,0); + if(np == p) { //Check for constants + p2 = skip_word(p); + v = p2-p; // length of word at p2 + memcpy(label,p,v); + label[v]='\0'; + if( !script_get_constant(label, &v) ) + disp_error_message("parse_syntax: 'case' label not integer",p); + p = skip_word(p); + } else { //Numeric value + if((*p == '-' || *p == '+') && ISDIGIT(p[1])) // pre-skip because '-' can not skip_word + p++; + p = skip_word(p); + if(np != p) + disp_error_message("parse_syntax: 'case' label not integer",np); + } + p = skip_space(p); + if(*p != ':') + disp_error_message("parse_syntax: expect ':'",p); + sprintf(label,"if(%d != $@__SW%x_VAL) goto __SW%x_%x;", + v,syntax.curly[pos].index,syntax.curly[pos].index,syntax.curly[pos].count+1); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + // Bad I do not parse twice + p2 = parse_line(label); + parse_line(p2); + syntax.curly_count--; + if(syntax.curly[pos].count != 1) { + // Label after the completion of FALLTHRU + sprintf(label,"__SW%x_%xJ",syntax.curly[pos].index,syntax.curly[pos].count); + l=add_str(label); + set_label(l,script_pos,p); + } + // check duplication of case label [Rayce] + if(linkdb_search(&syntax.curly[pos].case_label, (void*)__64BPRTSIZE(v)) != NULL) + disp_error_message("parse_syntax: dup 'case'",p); + linkdb_insert(&syntax.curly[pos].case_label, (void*)__64BPRTSIZE(v), (void*)1); + + sprintf(label,"set $@__SW%x_VAL,0;",syntax.curly[pos].index); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + + parse_line(label); + syntax.curly_count--; + syntax.curly[pos].count++; + } + return p + 1; + } else if(p2 - p == 8 && !strncasecmp(p,"continue",8)) { + // Processing continue + char label[256]; + int pos = syntax.curly_count - 1; + while(pos >= 0) { + if(syntax.curly[pos].type == TYPE_DO) { + sprintf(label,"goto __DO%x_NXT;",syntax.curly[pos].index); + syntax.curly[pos].flag = 1; //Flag put the link for continue + break; + } else if(syntax.curly[pos].type == TYPE_FOR) { + sprintf(label,"goto __FR%x_NXT;",syntax.curly[pos].index); + break; + } else if(syntax.curly[pos].type == TYPE_WHILE) { + sprintf(label,"goto __WL%x_NXT;",syntax.curly[pos].index); + break; + } + pos--; + } + if(pos < 0) { + disp_error_message("parse_syntax: unexpected 'continue'",p); + } else { + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + } + p = skip_space(p2); + if(*p != ';') + disp_error_message("parse_syntax: need ';'",p); + //Closing decision if, for , while + p = parse_syntax_close(p + 1); + return p; + } + break; + case 'd': + case 'D': + if(p2 - p == 7 && !strncasecmp(p,"default",7)) { + // Switch - default processing + int pos = syntax.curly_count-1; + if(pos < 0 || syntax.curly[pos].type != TYPE_SWITCH) { + disp_error_message("parse_syntax: unexpected 'default'",p); + } else if(syntax.curly[pos].flag) { + disp_error_message("parse_syntax: dup 'default'",p); + } else { + char label[256]; + int l; + // Put the label location + p = skip_space(p2); + if(*p != ':') { + disp_error_message("parse_syntax: need ':'",p); + } + sprintf(label,"__SW%x_%x",syntax.curly[pos].index,syntax.curly[pos].count); + l=add_str(label); + set_label(l,script_pos,p); + + // Skip to the next link w/o condition + sprintf(label,"goto __SW%x_%x;",syntax.curly[pos].index,syntax.curly[pos].count+1); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + + // The default label + sprintf(label,"__SW%x_DEF",syntax.curly[pos].index); + l=add_str(label); + set_label(l,script_pos,p); + + syntax.curly[syntax.curly_count - 1].flag = 1; + syntax.curly[pos].count++; + } + return p + 1; + } else if(p2 - p == 2 && !strncasecmp(p,"do",2)) { + int l; + char label[256]; + p=skip_space(p2); + + syntax.curly[syntax.curly_count].type = TYPE_DO; + syntax.curly[syntax.curly_count].count = 1; + syntax.curly[syntax.curly_count].index = syntax.index++; + syntax.curly[syntax.curly_count].flag = 0; + // Label of the (do) form here + sprintf(label,"__DO%x_BGN",syntax.curly[syntax.curly_count].index); + l=add_str(label); + set_label(l,script_pos,p); + syntax.curly_count++; + return p; + } + break; + case 'f': + case 'F': + if(p2 - p == 3 && !strncasecmp(p,"for",3)) { + int l; + char label[256]; + int pos = syntax.curly_count; + syntax.curly[syntax.curly_count].type = TYPE_FOR; + syntax.curly[syntax.curly_count].count = 1; + syntax.curly[syntax.curly_count].index = syntax.index++; + syntax.curly[syntax.curly_count].flag = 0; + syntax.curly_count++; + + p=skip_space(p2); + + if(*p != '(') + disp_error_message("parse_syntax: need '('",p); + p++; + + // Execute the initialization statement + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + p=parse_line(p); + syntax.curly_count--; + + // Form the start of label decision + sprintf(label,"__FR%x_J",syntax.curly[pos].index); + l=add_str(label); + set_label(l,script_pos,p); + + p=skip_space(p); + if(*p == ';') { + // For (; Because the pattern of always true ;) + ; + } else { + // Skip to the end point if the condition is false + sprintf(label,"__FR%x_FIN",syntax.curly[pos].index); + add_scriptl(add_str("jump_zero")); + add_scriptc(C_ARG); + p=parse_expr(p); + p=skip_space(p); + add_scriptl(add_str(label)); + add_scriptc(C_FUNC); + } + if(*p != ';') + disp_error_message("parse_syntax: need ';'",p); + p++; + + // Skip to the beginning of the loop + sprintf(label,"goto __FR%x_BGN;",syntax.curly[pos].index); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + + // Labels to form the next loop + sprintf(label,"__FR%x_NXT",syntax.curly[pos].index); + l=add_str(label); + set_label(l,script_pos,p); + + // Process the next time you enter the loop + // A ')' last for; flag to be treated as' + parse_syntax_for_flag = 1; + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + p=parse_line(p); + syntax.curly_count--; + parse_syntax_for_flag = 0; + + // Skip to the determination process conditions + sprintf(label,"goto __FR%x_J;",syntax.curly[pos].index); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + + // Loop start labeling + sprintf(label,"__FR%x_BGN",syntax.curly[pos].index); + l=add_str(label); + set_label(l,script_pos,p); + return p; + } + else if( p2 - p == 8 && strncasecmp(p,"function",8) == 0 ) + {// internal script function + const char *func_name; + + func_name = skip_space(p2); + p = skip_word(func_name); + if( p == func_name ) + disp_error_message("parse_syntax:function: function name is missing or invalid", p); + p2 = skip_space(p); + if( *p2 == ';' ) + {// function <name> ; + // function declaration - just register the name + int l; + l = add_word(func_name); + if( str_data[l].type == C_NOP )// register only, if the name was not used by something else + str_data[l].type = C_USERFUNC; + else if( str_data[l].type == C_USERFUNC ) + ; // already registered + else + disp_error_message("parse_syntax:function: function name is invalid", func_name); + + // Close condition of if, for, while + p = parse_syntax_close(p2 + 1); + return p; + } + else if(*p2 == '{') + {// function <name> <line/block of code> + char label[256]; + int l; + + syntax.curly[syntax.curly_count].type = TYPE_USERFUNC; + syntax.curly[syntax.curly_count].count = 1; + syntax.curly[syntax.curly_count].index = syntax.index++; + syntax.curly[syntax.curly_count].flag = 0; + ++syntax.curly_count; + + // Jump over the function code + sprintf(label, "goto __FN%x_FIN;", syntax.curly[syntax.curly_count-1].index); + syntax.curly[syntax.curly_count].type = TYPE_NULL; + ++syntax.curly_count; + parse_line(label); + --syntax.curly_count; + + // Set the position of the function (label) + l=add_word(func_name); + if( str_data[l].type == C_NOP || str_data[l].type == C_USERFUNC )// register only, if the name was not used by something else + { + str_data[l].type = C_USERFUNC; + set_label(l, script_pos, p); + if( parse_options&SCRIPT_USE_LABEL_DB ) + strdb_iput(scriptlabel_db, get_str(l), script_pos); + } + else + disp_error_message("parse_syntax:function: function name is invalid", func_name); + + return skip_space(p); + } + else + { + disp_error_message("expect ';' or '{' at function syntax",p); + } + } + break; + case 'i': + case 'I': + if(p2 - p == 2 && !strncasecmp(p,"if",2)) { + // If process + char label[256]; + p=skip_space(p2); + if(*p != '(') { //Prevent if this {} non-c syntax. from Rayce (jA) + disp_error_message("need '('",p); + } + syntax.curly[syntax.curly_count].type = TYPE_IF; + syntax.curly[syntax.curly_count].count = 1; + syntax.curly[syntax.curly_count].index = syntax.index++; + syntax.curly[syntax.curly_count].flag = 0; + sprintf(label,"__IF%x_%x",syntax.curly[syntax.curly_count].index,syntax.curly[syntax.curly_count].count); + syntax.curly_count++; + add_scriptl(add_str("jump_zero")); + add_scriptc(C_ARG); + p=parse_expr(p); + p=skip_space(p); + add_scriptl(add_str(label)); + add_scriptc(C_FUNC); + return p; + } + break; + case 's': + case 'S': + if(p2 - p == 6 && !strncasecmp(p,"switch",6)) { + // Processing of switch () + char label[256]; + p=skip_space(p2); + if(*p != '(') { + disp_error_message("need '('",p); + } + syntax.curly[syntax.curly_count].type = TYPE_SWITCH; + syntax.curly[syntax.curly_count].count = 1; + syntax.curly[syntax.curly_count].index = syntax.index++; + syntax.curly[syntax.curly_count].flag = 0; + sprintf(label,"$@__SW%x_VAL",syntax.curly[syntax.curly_count].index); + syntax.curly_count++; + add_scriptl(add_str("set")); + add_scriptc(C_ARG); + add_scriptl(add_str(label)); + p=parse_expr(p); + p=skip_space(p); + if(*p != '{') { + disp_error_message("parse_syntax: need '{'",p); + } + add_scriptc(C_FUNC); + return p + 1; + } + break; + case 'w': + case 'W': + if(p2 - p == 5 && !strncasecmp(p,"while",5)) { + int l; + char label[256]; + p=skip_space(p2); + if(*p != '(') { + disp_error_message("need '('",p); + } + syntax.curly[syntax.curly_count].type = TYPE_WHILE; + syntax.curly[syntax.curly_count].count = 1; + syntax.curly[syntax.curly_count].index = syntax.index++; + syntax.curly[syntax.curly_count].flag = 0; + // Form the start of label decision + sprintf(label,"__WL%x_NXT",syntax.curly[syntax.curly_count].index); + l=add_str(label); + set_label(l,script_pos,p); + + // Skip to the end point if the condition is false + sprintf(label,"__WL%x_FIN",syntax.curly[syntax.curly_count].index); + syntax.curly_count++; + add_scriptl(add_str("jump_zero")); + add_scriptc(C_ARG); + p=parse_expr(p); + p=skip_space(p); + add_scriptl(add_str(label)); + add_scriptc(C_FUNC); + return p; + } + break; + } + return NULL; +} + +const char* parse_syntax_close(const char *p) { + // If (...) for (...) hoge (); as to make sure closed closed once again + int flag; + + do { + p = parse_syntax_close_sub(p,&flag); + } while(flag); + return p; +} + +// Close judgment if, for, while, of do +// flag == 1 : closed +// flag == 0 : not closed +const char* parse_syntax_close_sub(const char* p,int* flag) +{ + char label[256]; + int pos = syntax.curly_count - 1; + int l; + *flag = 1; + + if(syntax.curly_count <= 0) { + *flag = 0; + return p; + } else if(syntax.curly[pos].type == TYPE_IF) { + const char *bp = p; + const char *p2; + + // if-block and else-block end is a new line + parse_nextline(false, p); + + // Skip to the last location if + sprintf(label,"goto __IF%x_FIN;",syntax.curly[pos].index); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + + // Put the label of the location + sprintf(label,"__IF%x_%x",syntax.curly[pos].index,syntax.curly[pos].count); + l=add_str(label); + set_label(l,script_pos,p); + + syntax.curly[pos].count++; + p = skip_space(p); + p2 = skip_word(p); + if(!syntax.curly[pos].flag && p2 - p == 4 && !strncasecmp(p,"else",4)) { + // else or else - if + p = skip_space(p2); + p2 = skip_word(p); + if(p2 - p == 2 && !strncasecmp(p,"if",2)) { + // else - if + p=skip_space(p2); + if(*p != '(') { + disp_error_message("need '('",p); + } + sprintf(label,"__IF%x_%x",syntax.curly[pos].index,syntax.curly[pos].count); + add_scriptl(add_str("jump_zero")); + add_scriptc(C_ARG); + p=parse_expr(p); + p=skip_space(p); + add_scriptl(add_str(label)); + add_scriptc(C_FUNC); + *flag = 0; + return p; + } else { + // else + if(!syntax.curly[pos].flag) { + syntax.curly[pos].flag = 1; + *flag = 0; + return p; + } + } + } + // Close if + syntax.curly_count--; + // Put the label of the final location + sprintf(label,"__IF%x_FIN",syntax.curly[pos].index); + l=add_str(label); + set_label(l,script_pos,p); + if(syntax.curly[pos].flag == 1) { + // Because the position of the pointer is the same if not else for this + return bp; + } + return p; + } else if(syntax.curly[pos].type == TYPE_DO) { + int l; + char label[256]; + const char *p2; + + if(syntax.curly[pos].flag) { + // (Come here continue) to form the label here + sprintf(label,"__DO%x_NXT",syntax.curly[pos].index); + l=add_str(label); + set_label(l,script_pos,p); + } + + // Skip to the end point if the condition is false + p = skip_space(p); + p2 = skip_word(p); + if(p2 - p != 5 || strncasecmp(p,"while",5)) + disp_error_message("parse_syntax: need 'while'",p); + + p = skip_space(p2); + if(*p != '(') { + disp_error_message("need '('",p); + } + + // do-block end is a new line + parse_nextline(false, p); + + sprintf(label,"__DO%x_FIN",syntax.curly[pos].index); + add_scriptl(add_str("jump_zero")); + add_scriptc(C_ARG); + p=parse_expr(p); + p=skip_space(p); + add_scriptl(add_str(label)); + add_scriptc(C_FUNC); + + // Skip to the starting point + sprintf(label,"goto __DO%x_BGN;",syntax.curly[pos].index); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + + // Form label of the end point conditions + sprintf(label,"__DO%x_FIN",syntax.curly[pos].index); + l=add_str(label); + set_label(l,script_pos,p); + p = skip_space(p); + if(*p != ';') { + disp_error_message("parse_syntax: need ';'",p); + return p+1; + } + p++; + syntax.curly_count--; + return p; + } else if(syntax.curly[pos].type == TYPE_FOR) { + // for-block end is a new line + parse_nextline(false, p); + + // Skip to the next loop + sprintf(label,"goto __FR%x_NXT;",syntax.curly[pos].index); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + + // End for labeling + sprintf(label,"__FR%x_FIN",syntax.curly[pos].index); + l=add_str(label); + set_label(l,script_pos,p); + syntax.curly_count--; + return p; + } else if(syntax.curly[pos].type == TYPE_WHILE) { + // while-block end is a new line + parse_nextline(false, p); + + // Skip to the decision while + sprintf(label,"goto __WL%x_NXT;",syntax.curly[pos].index); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + + // End while labeling + sprintf(label,"__WL%x_FIN",syntax.curly[pos].index); + l=add_str(label); + set_label(l,script_pos,p); + syntax.curly_count--; + return p; + } else if(syntax.curly[syntax.curly_count-1].type == TYPE_USERFUNC) { + int pos = syntax.curly_count-1; + char label[256]; + int l; + // Back + sprintf(label,"return;"); + syntax.curly[syntax.curly_count++].type = TYPE_NULL; + parse_line(label); + syntax.curly_count--; + + // Put the label of the location + sprintf(label,"__FN%x_FIN",syntax.curly[pos].index); + l=add_str(label); + set_label(l,script_pos,p); + syntax.curly_count--; + return p; + } else { + *flag = 0; + return p; + } +} + +/*========================================== + * Added built-in functions + *------------------------------------------*/ +static void add_buildin_func(void) +{ + int i,n; + const char* p; + for( i = 0; buildin_func[i].func; i++ ) + { + // arg must follow the pattern: (v|s|i|r|l)*\?*\*? + // 'v' - value (either string or int or reference) + // 's' - string + // 'i' - int + // 'r' - reference (of a variable) + // 'l' - label + // '?' - one optional parameter + // '*' - unknown number of optional parameters + p = buildin_func[i].arg; + while( *p == 'v' || *p == 's' || *p == 'i' || *p == 'r' || *p == 'l' ) ++p; + while( *p == '?' ) ++p; + if( *p == '*' ) ++p; + if( *p != 0){ + ShowWarning("add_buildin_func: ignoring function \"%s\" with invalid arg \"%s\".\n", buildin_func[i].name, buildin_func[i].arg); + } else if( *skip_word(buildin_func[i].name) != 0 ){ + ShowWarning("add_buildin_func: ignoring function with invalid name \"%s\" (must be a word).\n", buildin_func[i].name); + } else { + 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; + + if (!strcmp(buildin_func[i].name, "set")) buildin_set_ref = n; + else if (!strcmp(buildin_func[i].name, "callsub")) buildin_callsub_ref = n; + else if (!strcmp(buildin_func[i].name, "callfunc")) buildin_callfunc_ref = n; + else if( !strcmp(buildin_func[i].name, "getelementofarray") ) buildin_getelementofarray_ref = n; + } + } +} + +/// Retrieves the value of a constant. +bool script_get_constant(const char* name, int* value) +{ + int n = search_str(name); + + if( n == -1 || str_data[n].type != C_INT ) + {// not found or not a constant + return false; + } + value[0] = str_data[n].val; + + return true; +} + +/// Creates new constant or parameter with given value. +void script_set_constant(const char* name, int value, bool isparameter) +{ + int n = add_str(name); + + if( str_data[n].type == C_NOP ) + {// new + str_data[n].type = isparameter ? C_PARAM : C_INT; + str_data[n].val = value; + } + else if( str_data[n].type == C_PARAM || str_data[n].type == C_INT ) + {// existing parameter or constant + ShowError("script_set_constant: Attempted to overwrite existing %s '%s' (old value=%d, new value=%d).\n", ( str_data[n].type == C_PARAM ) ? "parameter" : "constant", name, str_data[n].val, value); + } + else + {// existing name + ShowError("script_set_constant: Invalid name for %s '%s' (already defined as %s).\n", isparameter ? "parameter" : "constant", name, script_op2name(str_data[n].type)); + } +} + +/*========================================== + * Reading constant databases + * const.txt + *------------------------------------------*/ +static void read_constdb(void) +{ + FILE *fp; + char line[1024],name[1024],val[1024]; + int type; + + sprintf(line, "%s/const.txt", db_path); + fp=fopen(line, "r"); + if(fp==NULL){ + ShowError("can't read %s\n", line); + return ; + } + while(fgets(line, sizeof(line), fp)) + { + if(line[0]=='/' && line[1]=='/') + continue; + type=0; + if(sscanf(line,"%[A-Za-z0-9_],%[-0-9xXA-Fa-f],%d",name,val,&type)>=2 || + sscanf(line,"%[A-Za-z0-9_] %[-0-9xXA-Fa-f] %d",name,val,&type)>=2){ + script_set_constant(name, (int)strtol(val, NULL, 0), (bool)type); + } + } + fclose(fp); +} + +/*========================================== + * Display emplacement line of script + *------------------------------------------*/ +static const char* script_print_line(StringBuf* buf, const char* p, const char* mark, int line) +{ + int i; + if( p == NULL || !p[0] ) return NULL; + if( line < 0 ) + StringBuf_Printf(buf, "*% 5d : ", -line); + else + StringBuf_Printf(buf, " % 5d : ", line); + for(i=0;p[i] && p[i] != '\n';i++){ + if(p + i != mark) + StringBuf_Printf(buf, "%c", p[i]); + else + StringBuf_Printf(buf, "\'%c\'", p[i]); + } + StringBuf_AppendStr(buf, "\n"); + return p+i+(p[i] == '\n' ? 1 : 0); +} + +void script_error(const char* src, const char* file, int start_line, const char* error_msg, const char* error_pos) +{ + // Find the line where the error occurred + int j; + int line = start_line; + const char *p; + const char *linestart[5] = { NULL, NULL, NULL, NULL, NULL }; + StringBuf buf; + + for(p=src;p && *p;line++){ + const char *lineend=strchr(p,'\n'); + if(lineend==NULL || error_pos<lineend){ + break; + } + for( j = 0; j < 4; j++ ) { + linestart[j] = linestart[j+1]; + } + linestart[4] = p; + p=lineend+1; + } + + StringBuf_Init(&buf); + StringBuf_AppendStr(&buf, "\a\n"); + StringBuf_Printf(&buf, "script error on %s line %d\n", file, line); + StringBuf_Printf(&buf, " %s\n", error_msg); + for(j = 0; j < 5; j++ ) { + script_print_line(&buf, linestart[j], NULL, line + j - 5); + } + p = script_print_line(&buf, p, error_pos, -line); + for(j = 0; j < 5; j++) { + p = script_print_line(&buf, p, NULL, line + j + 1 ); + } + ShowError("%s", StringBuf_Value(&buf)); + StringBuf_Destroy(&buf); +} + +/*========================================== + * Analysis of the script + *------------------------------------------*/ +struct script_code* parse_script(const char *src,const char *file,int line,int options) +{ + const char *p,*tmpp; + int i; + struct script_code* code = NULL; + static int first=1; + char end; + bool unresolved_names = false; + + if( src == NULL ) + return NULL;// empty script + + memset(&syntax,0,sizeof(syntax)); + if(first){ + add_buildin_func(); + read_constdb(); + first=0; + } + + script_buf=(unsigned char *)aMalloc(SCRIPT_BLOCK_SIZE*sizeof(unsigned char)); + script_pos=0; + script_size=SCRIPT_BLOCK_SIZE; + parse_nextline(true, NULL); + + // who called parse_script is responsible for clearing the database after using it, but just in case... lets clear it here + if( options&SCRIPT_USE_LABEL_DB ) + db_clear(scriptlabel_db); + parse_options = options; + + if( setjmp( error_jump ) != 0 ) { + //Restore program state when script has problems. [from jA] + int i; + const int size = ARRAYLENGTH(syntax.curly); + if( error_report ) + script_error(src,file,line,error_msg,error_pos); + aFree( error_msg ); + aFree( script_buf ); + script_pos = 0; + script_size = 0; + script_buf = NULL; + for(i=LABEL_START;i<str_num;i++) + if(str_data[i].type == C_NOP) str_data[i].type = C_NAME; + for(i=0; i<size; i++) + linkdb_final(&syntax.curly[i].case_label); + return NULL; + } + + parse_syntax_for_flag=0; + p=src; + p=skip_space(p); + if( options&SCRIPT_IGNORE_EXTERNAL_BRACKETS ) + {// does not require brackets around the script + if( *p == '\0' && !(options&SCRIPT_RETURN_EMPTY_SCRIPT) ) + {// empty script and can return NULL + aFree( script_buf ); + script_pos = 0; + script_size = 0; + script_buf = NULL; + return NULL; + } + end = '\0'; + } + else + {// requires brackets around the script + if( *p != '{' ) + disp_error_message("not found '{'",p); + p = skip_space(p+1); + if( *p == '}' && !(options&SCRIPT_RETURN_EMPTY_SCRIPT) ) + {// empty script and can return NULL + aFree( script_buf ); + script_pos = 0; + script_size = 0; + script_buf = NULL; + return NULL; + } + end = '}'; + } + + // clear references of labels, variables and internal functions + 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_USERFUNC || str_data[i].type == C_USERFUNC_POS + ){ + str_data[i].type=C_NOP; + str_data[i].backpatch=-1; + str_data[i].label=-1; + } + } + + while( syntax.curly_count != 0 || *p != end ) + { + if( *p == '\0' ) + disp_error_message("unexpected end of script",p); + // Special handling only label + tmpp=skip_space(skip_word(p)); + if(*tmpp==':' && !(!strncasecmp(p,"default:",8) && p + 7 == tmpp)){ + i=add_word(p); + set_label(i,script_pos,p); + if( parse_options&SCRIPT_USE_LABEL_DB ) + strdb_iput(scriptlabel_db, get_str(i), script_pos); + p=tmpp+1; + p=skip_space(p); + continue; + } + + // All other lumped + p=parse_line(p); + p=skip_space(p); + + parse_nextline(false, p); + } + + add_scriptc(C_NOP); + + // trim code to size + script_size = script_pos; + RECREATE(script_buf,unsigned char,script_pos); + + // default unknown references to variables + 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=GETVALUE(script_buf,j); + SETVALUE(script_buf,j,i); + j=next; + } + } + else if( str_data[i].type == C_USERFUNC ) + {// 'function name;' without follow-up code + ShowError("parse_script: function '%s' declared but not defined.\n", str_buf+str_data[i].str); + unresolved_names = true; + } + } + + if( unresolved_names ) + { + disp_error_message("parse_script: unresolved function references", p); + } + +#ifdef DEBUG_DISP + for(i=0;i<script_pos;i++){ + if((i&15)==0) ShowMessage("%04x : ",i); + ShowMessage("%02x ",script_buf[i]); + if((i&15)==15) ShowMessage("\n"); + } + ShowMessage("\n"); +#endif +#ifdef DEBUG_DISASM + { + int i = 0,j; + while(i < script_pos) { + c_op op = get_com(script_buf,&i); + + ShowMessage("%06x %s", i, script_op2name(op)); + j = i; + switch(op) { + case C_INT: + ShowMessage(" %d", get_num(script_buf,&i)); + break; + case C_POS: + ShowMessage(" 0x%06x", *(int*)(script_buf+i)&0xffffff); + i += 3; + break; + case C_NAME: + j = (*(int*)(script_buf+i)&0xffffff); + ShowMessage(" %s", ( j == 0xffffff ) ? "?? unknown ??" : get_str(j)); + i += 3; + break; + case C_STR: + j = strlen(script_buf + i); + ShowMessage(" %s", script_buf + i); + i += j+1; + break; + } + ShowMessage(CL_CLL"\n"); + } + } +#endif + + CREATE(code,struct script_code,1); + code->script_buf = script_buf; + code->script_size = script_size; + code->script_vars = idb_alloc(DB_OPT_RELEASE_DATA); + return code; +} + +/// Returns the player attached to this script, identified by the rid. +/// If there is no player attached, the script is terminated. +TBL_PC *script_rid2sd(struct script_state *st) +{ + TBL_PC *sd=map_id2sd(st->rid); + if(!sd){ + ShowError("script_rid2sd: fatal error ! player not attached!\n"); + script_reportfunc(st); + script_reportsrc(st); + st->state = END; + } + return sd; +} + +/// Dereferences a variable/constant, replacing it with a copy of the value. +/// +/// @param st Script state +/// @param data Variable/constant +void get_val(struct script_state* st, struct script_data* data) +{ + const char* name; + char prefix; + char postfix; + TBL_PC* sd = NULL; + + if( !data_isreference(data) ) + return;// not a variable/constant + + name = reference_getname(data); + prefix = name[0]; + postfix = name[strlen(name) - 1]; + + //##TODO use reference_tovariable(data) when it's confirmed that it works [FlavioJS] + if( !reference_toconstant(data) && not_server_variable(prefix) ) + { + sd = script_rid2sd(st); + if( sd == NULL ) + {// needs player attached + if( postfix == '$' ) + {// string variable + ShowWarning("script:get_val: cannot access player variable '%s', defaulting to \"\"\n", name); + data->type = C_CONSTSTR; + data->u.str = ""; + } + else + {// integer variable + ShowWarning("script:get_val: cannot access player variable '%s', defaulting to 0\n", name); + data->type = C_INT; + data->u.num = 0; + } + return; + } + } + + if( postfix == '$' ) + {// string variable + + switch( prefix ) + { + case '@': + data->u.str = pc_readregstr(sd, data->u.num); + break; + case '$': + data->u.str = mapreg_readregstr(data->u.num); + break; + case '#': + if( name[1] == '#' ) + data->u.str = pc_readaccountreg2str(sd, name);// global + else + data->u.str = pc_readaccountregstr(sd, name);// local + break; + case '.': + { + struct DBMap* n = + data->ref ? *data->ref: + name[1] == '@' ? st->stack->var_function:// instance/scope variable + st->script->script_vars;// npc variable + if( n ) + data->u.str = (char*)idb_get(n,reference_getuid(data)); + else + data->u.str = NULL; + } + break; + case '\'': + if (st->instance_id) { + data->u.str = (char*)idb_get(instance[st->instance_id].vars,reference_getuid(data)); + } else { + ShowWarning("script:get_val: cannot access instance variable '%s', defaulting to \"\"\n", name); + data->u.str = NULL; + } + break; + default: + data->u.str = pc_readglobalreg_str(sd, name); + break; + } + + if( data->u.str == NULL || data->u.str[0] == '\0' ) + {// empty string + data->type = C_CONSTSTR; + data->u.str = ""; + } + else + {// duplicate string + data->type = C_STR; + data->u.str = aStrdup(data->u.str); + } + + } + else + {// integer variable + + data->type = C_INT; + + if( reference_toconstant(data) ) + { + data->u.num = reference_getconstant(data); + } + else if( reference_toparam(data) ) + { + data->u.num = pc_readparam(sd, reference_getparamtype(data)); + } + else + switch( prefix ) + { + case '@': + data->u.num = pc_readreg(sd, data->u.num); + break; + case '$': + data->u.num = mapreg_readreg(data->u.num); + break; + case '#': + if( name[1] == '#' ) + data->u.num = pc_readaccountreg2(sd, name);// global + else + data->u.num = pc_readaccountreg(sd, name);// local + break; + case '.': + { + struct DBMap* n = + data->ref ? *data->ref: + name[1] == '@' ? st->stack->var_function:// instance/scope variable + st->script->script_vars;// npc variable + if( n ) + data->u.num = (int)idb_iget(n,reference_getuid(data)); + else + data->u.num = 0; + } + break; + case '\'': + if( st->instance_id ) + data->u.num = (int)idb_iget(instance[st->instance_id].vars,reference_getuid(data)); + else { + ShowWarning("script:get_val: cannot access instance variable '%s', defaulting to 0\n", name); + data->u.num = 0; + } + break; + default: + data->u.num = pc_readglobalreg(sd, name); + break; + } + + } + + return; +} + +struct script_data* push_val2(struct script_stack* stack, enum c_op type, int val, struct DBMap** ref); + +/// Retrieves the value of a reference identified by uid (variable, constant, param) +/// The value is left in the top of the stack and needs to be removed manually. +void* get_val2(struct script_state* st, int uid, struct DBMap** ref) +{ + struct script_data* data; + push_val2(st->stack, C_NAME, uid, ref); + data = script_getdatatop(st, -1); + get_val(st, data); + return (data->type == C_INT ? (void*)__64BPRTSIZE(data->u.num) : (void*)__64BPRTSIZE(data->u.str)); +} + +/*========================================== + * Stores the value of a script variable + * Return value is 0 on fail, 1 on success. + *------------------------------------------*/ +static int set_reg(struct script_state* st, TBL_PC* sd, int num, const char* name, const void* value, struct DBMap** ref) +{ + char prefix = name[0]; + + if( is_string_variable(name) ) + {// string variable + const char* str = (const char*)value; + switch (prefix) { + case '@': + return pc_setregstr(sd, num, str); + case '$': + return mapreg_setregstr(num, str); + case '#': + return (name[1] == '#') ? + pc_setaccountreg2str(sd, name, str) : + pc_setaccountregstr(sd, name, str); + case '.': + { + struct DBMap* n; + n = (ref) ? *ref : (name[1] == '@') ? st->stack->var_function : st->script->script_vars; + if( n ) { + idb_remove(n, num); + if (str[0]) idb_put(n, num, aStrdup(str)); + } + } + return 1; + case '\'': + if( st->instance_id ) { + idb_remove(instance[st->instance_id].vars, num); + if( str[0] ) idb_put(instance[st->instance_id].vars, num, aStrdup(str)); + } + return 1; + default: + return pc_setglobalreg_str(sd, name, str); + } + } + else + {// integer variable + int val = (int)__64BPRTSIZE(value); + if(str_data[num&0x00ffffff].type == C_PARAM) + { + if( pc_setparam(sd, str_data[num&0x00ffffff].val, val) == 0 ) + { + if( st != NULL ) + { + ShowError("script:set_reg: failed to set param '%s' to %d.\n", name, val); + script_reportsrc(st); + st->state = END; + } + return 0; + } + return 1; + } + + switch (prefix) { + case '@': + return pc_setreg(sd, num, val); + case '$': + return mapreg_setreg(num, val); + case '#': + return (name[1] == '#') ? + pc_setaccountreg2(sd, name, val) : + pc_setaccountreg(sd, name, val); + case '.': + { + struct DBMap* n; + n = (ref) ? *ref : (name[1] == '@') ? st->stack->var_function : st->script->script_vars; + if( n ) { + idb_remove(n, num); + if( val != 0 ) + idb_iput(n, num, val); + } + } + return 1; + case '\'': + if( st->instance_id ) { + idb_remove(instance[st->instance_id].vars, num); + if( val != 0 ) + idb_iput(instance[st->instance_id].vars, num, val); + } + return 1; + default: + return pc_setglobalreg(sd, name, val); + } + } +} + +int set_var(TBL_PC* sd, char* name, void* val) +{ + return set_reg(NULL, sd, reference_uid(add_str(name),0), name, val, NULL); +} + +void setd_sub(struct script_state *st, TBL_PC *sd, const char *varname, int elem, void *value, struct DBMap **ref) +{ + set_reg(st, sd, reference_uid(add_str(varname),elem), varname, value, ref); +} + +/// Converts the data to a string +const char* conv_str(struct script_state* st, struct script_data* data) +{ + char* p; + + get_val(st, data); + if( data_isstring(data) ) + {// nothing to convert + } + else if( data_isint(data) ) + {// int -> string + CREATE(p, char, ITEM_NAME_LENGTH); + snprintf(p, ITEM_NAME_LENGTH, "%d", data->u.num); + p[ITEM_NAME_LENGTH-1] = '\0'; + data->type = C_STR; + data->u.str = p; + } + else if( data_isreference(data) ) + {// reference -> string + //##TODO when does this happen (check get_val) [FlavioJS] + data->type = C_CONSTSTR; + data->u.str = reference_getname(data); + } + else + {// unsupported data type + ShowError("script:conv_str: cannot convert to string, defaulting to \"\"\n"); + script_reportdata(data); + script_reportsrc(st); + data->type = C_CONSTSTR; + data->u.str = ""; + } + return data->u.str; +} + +/// Converts the data to an int +int conv_num(struct script_state* st, struct script_data* data) +{ + char* p; + long num; + + get_val(st, data); + if( data_isint(data) ) + {// nothing to convert + } + else if( data_isstring(data) ) + {// string -> int + // the result does not overflow or underflow, it is capped instead + // ex: 999999999999 is capped to INT_MAX (2147483647) + p = data->u.str; + errno = 0; + num = strtol(data->u.str, NULL, 10);// change radix to 0 to support octal numbers "o377" and hex numbers "0xFF" + if( errno == ERANGE +#if LONG_MAX > INT_MAX + || num < INT_MIN || num > INT_MAX +#endif + ) + { + if( num <= INT_MIN ) + { + num = INT_MIN; + ShowError("script:conv_num: underflow detected, capping to %ld\n", num); + } + else//if( num >= INT_MAX ) + { + num = INT_MAX; + ShowError("script:conv_num: overflow detected, capping to %ld\n", num); + } + script_reportdata(data); + script_reportsrc(st); + } + if( data->type == C_STR ) + aFree(p); + data->type = C_INT; + data->u.num = (int)num; + } +#if 0 + // FIXME this function is being used to retrieve the position of labels and + // probably other stuff [FlavioJS] + else + {// unsupported data type + ShowError("script:conv_num: cannot convert to number, defaulting to 0\n"); + script_reportdata(data); + script_reportsrc(st); + data->type = C_INT; + data->u.num = 0; + } +#endif + return data->u.num; +} + +// +// Stack operations +// + +/// Increases the size of the stack +void stack_expand(struct script_stack* stack) +{ + stack->sp_max += 64; + stack->stack_data = (struct script_data*)aRealloc(stack->stack_data, + stack->sp_max * sizeof(stack->stack_data[0]) ); + memset(stack->stack_data + (stack->sp_max - 64), 0, + 64 * sizeof(stack->stack_data[0]) ); +} + +/// Pushes a value into the stack +#define push_val(stack,type,val) push_val2(stack, type, val, NULL) + +/// Pushes a value into the stack (with reference) +struct script_data* push_val2(struct script_stack* stack, enum c_op type, int val, struct DBMap** ref) +{ + if( stack->sp >= stack->sp_max ) + stack_expand(stack); + stack->stack_data[stack->sp].type = type; + stack->stack_data[stack->sp].u.num = val; + stack->stack_data[stack->sp].ref = ref; + stack->sp++; + return &stack->stack_data[stack->sp-1]; +} + +/// Pushes a string into the stack +struct script_data* push_str(struct script_stack* stack, enum c_op type, char* str) +{ + if( stack->sp >= stack->sp_max ) + stack_expand(stack); + stack->stack_data[stack->sp].type = type; + stack->stack_data[stack->sp].u.str = str; + stack->stack_data[stack->sp].ref = NULL; + stack->sp++; + return &stack->stack_data[stack->sp-1]; +} + +/// Pushes a retinfo into the stack +struct script_data* push_retinfo(struct script_stack* stack, struct script_retinfo* ri, DBMap **ref) +{ + if( stack->sp >= stack->sp_max ) + stack_expand(stack); + stack->stack_data[stack->sp].type = C_RETINFO; + stack->stack_data[stack->sp].u.ri = ri; + stack->stack_data[stack->sp].ref = ref; + stack->sp++; + return &stack->stack_data[stack->sp-1]; +} + +/// Pushes a copy of the target position into the stack +struct script_data* push_copy(struct script_stack* stack, int pos) +{ + switch( stack->stack_data[pos].type ) + { + case C_CONSTSTR: + return push_str(stack, C_CONSTSTR, stack->stack_data[pos].u.str); + break; + case C_STR: + return push_str(stack, C_STR, aStrdup(stack->stack_data[pos].u.str)); + break; + case C_RETINFO: + ShowFatalError("script:push_copy: can't create copies of C_RETINFO. Exiting...\n"); + exit(1); + break; + default: + return push_val2( + stack,stack->stack_data[pos].type, + stack->stack_data[pos].u.num, + stack->stack_data[pos].ref + ); + break; + } +} + +/// Removes the values in indexes [start,end[ from the stack. +/// Adjusts all stack pointers. +void pop_stack(struct script_state* st, int start, int end) +{ + struct script_stack* stack = st->stack; + struct script_data* data; + int i; + + if( start < 0 ) + start = 0; + if( end > stack->sp ) + end = stack->sp; + if( start >= end ) + return;// nothing to pop + + // free stack elements + for( i = start; i < end; i++ ) + { + data = &stack->stack_data[i]; + if( data->type == C_STR ) + aFree(data->u.str); + if( data->type == C_RETINFO ) + { + struct script_retinfo* ri = data->u.ri; + if( ri->var_function ) + script_free_vars(ri->var_function); + if( data->ref ) + aFree(data->ref); + aFree(ri); + } + data->type = C_NOP; + } + // move the rest of the elements + if( stack->sp > end ) + { + memmove(&stack->stack_data[start], &stack->stack_data[end], sizeof(stack->stack_data[0])*(stack->sp - end)); + for( i = start + stack->sp - end; i < stack->sp; ++i ) + stack->stack_data[i].type = C_NOP; + } + // adjust stack pointers + if( st->start > end ) st->start -= end - start; + else if( st->start > start ) st->start = start; + if( st->end > end ) st->end -= end - start; + else if( st->end > start ) st->end = start; + if( stack->defsp > end ) stack->defsp -= end - start; + else if( stack->defsp > start ) stack->defsp = start; + stack->sp -= end - start; +} + +/// +/// +/// + +/*========================================== + * Release script dependent variable, dependent variable of function + *------------------------------------------*/ +void script_free_vars(struct DBMap* storage) +{ + if( storage ) + {// destroy the storage construct containing the variables + db_destroy(storage); + } +} + +void script_free_code(struct script_code* code) +{ + script_free_vars( code->script_vars ); + aFree( code->script_buf ); + aFree( code ); +} + +/// Creates a new script state. +/// +/// @param script Script code +/// @param pos Position in the code +/// @param rid Who is running the script (attached player) +/// @param oid Where the code is being run (npc 'object') +/// @return Script state +struct script_state* script_alloc_state(struct script_code* script, int pos, int rid, int oid) +{ + struct script_state* st; + CREATE(st, struct script_state, 1); + st->stack = (struct script_stack*)aMalloc(sizeof(struct script_stack)); + st->stack->sp = 0; + st->stack->sp_max = 64; + CREATE(st->stack->stack_data, struct script_data, st->stack->sp_max); + st->stack->defsp = st->stack->sp; + st->stack->var_function = idb_alloc(DB_OPT_RELEASE_DATA); + st->state = RUN; + st->script = script; + //st->scriptroot = script; + st->pos = pos; + st->rid = rid; + st->oid = oid; + st->sleep.timer = INVALID_TIMER; + return st; +} + +/// Frees a script state. +/// +/// @param st Script state +void script_free_state(struct script_state* st) +{ + if(st->bk_st) + {// backup was not restored + ShowDebug("script_free_state: Previous script state lost (rid=%d, oid=%d, state=%d, bk_npcid=%d).\n", st->bk_st->rid, st->bk_st->oid, st->bk_st->state, st->bk_npcid); + } + if( st->sleep.timer != INVALID_TIMER ) + delete_timer(st->sleep.timer, run_script_timer); + script_free_vars(st->stack->var_function); + pop_stack(st, 0, st->stack->sp); + aFree(st->stack->stack_data); + aFree(st->stack); + st->stack = NULL; + st->pos = -1; + aFree(st); +} + +// +// Main execution unit +// +/*========================================== + * Read command + *------------------------------------------*/ +c_op get_com(unsigned char *script,int *pos) +{ + int i = 0, j = 0; + + if(script[*pos]>=0x80){ + return C_INT; + } + while(script[*pos]>=0x40){ + i=script[(*pos)++]<<j; + j+=6; + } + return (c_op)(i+(script[(*pos)++]<<j)); +} + +/*========================================== + * Income figures + *------------------------------------------*/ +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); +} + +/*========================================== + * Remove the value from the stack + *------------------------------------------*/ +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; +} + +/// Ternary operators +/// test ? if_true : if_false +void op_3(struct script_state* st, int op) +{ + struct script_data* data; + int flag = 0; + + data = script_getdatatop(st, -3); + get_val(st, data); + + if( data_isstring(data) ) + flag = data->u.str[0];// "" -> false + else if( data_isint(data) ) + flag = data->u.num;// 0 -> false + else + { + ShowError("script:op_3: invalid data for the ternary operator test\n"); + script_reportdata(data); + script_reportsrc(st); + script_removetop(st, -3, 0); + script_pushnil(st); + return; + } + if( flag ) + script_pushcopytop(st, -2); + else + script_pushcopytop(st, -1); + script_removetop(st, -4, -1); +} + +/// Binary string operators +/// s1 EQ s2 -> i +/// s1 NE s2 -> i +/// s1 GT s2 -> i +/// s1 GE s2 -> i +/// s1 LT s2 -> i +/// s1 LE s2 -> i +/// s1 ADD s2 -> s +void op_2str(struct script_state* st, int op, const char* s1, const char* s2) +{ + 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; + case C_ADD: + { + char* buf = (char *)aMalloc((strlen(s1)+strlen(s2)+1)*sizeof(char)); + strcpy(buf, s1); + strcat(buf, s2); + script_pushstr(st, buf); + return; + } + default: + ShowError("script:op2_str: unexpected string operator %s\n", script_op2name(op)); + script_reportsrc(st); + script_pushnil(st); + st->state = END; + return; + } + + script_pushint(st,a); +} + +/// Binary number operators +/// i OP i -> i +void op_2num(struct script_state* st, int op, int i1, int i2) +{ + int ret; + double ret_double; + + switch( op ) + { + case C_AND: ret = i1 & i2; break; + case C_OR: ret = i1 | i2; break; + case C_XOR: ret = i1 ^ i2; break; + case C_LAND: ret = (i1 && i2); break; + case C_LOR: ret = (i1 || i2); break; + case C_EQ: ret = (i1 == i2); break; + case C_NE: ret = (i1 != i2); break; + case C_GT: ret = (i1 > i2); break; + case C_GE: ret = (i1 >= i2); break; + case C_LT: ret = (i1 < i2); break; + case C_LE: ret = (i1 <= i2); break; + case C_R_SHIFT: ret = i1>>i2; break; + case C_L_SHIFT: ret = i1<<i2; break; + case C_DIV: + case C_MOD: + if( i2 == 0 ) + { + ShowError("script:op_2num: division by zero detected op=%s i1=%d i2=%d\n", script_op2name(op), i1, i2); + script_reportsrc(st); + script_pushnil(st); + st->state = END; + return; + } + else if( op == C_DIV ) + ret = i1 / i2; + else//if( op == C_MOD ) + ret = i1 % i2; + break; + default: + switch( op ) + {// operators that can overflow/underflow + case C_ADD: ret = i1 + i2; ret_double = (double)i1 + (double)i2; break; + case C_SUB: ret = i1 - i2; ret_double = (double)i1 - (double)i2; break; + case C_MUL: ret = i1 * i2; ret_double = (double)i1 * (double)i2; break; + default: + ShowError("script:op_2num: unexpected number operator %s i1=%d i2=%d\n", script_op2name(op), i1, i2); + script_reportsrc(st); + script_pushnil(st); + return; + } + if( ret_double < (double)INT_MIN ) + { + ShowWarning("script:op_2num: underflow detected op=%s i1=%d i2=%d\n", script_op2name(op), i1, i2); + script_reportsrc(st); + ret = INT_MIN; + } + else if( ret_double > (double)INT_MAX ) + { + ShowWarning("script:op_2num: overflow detected op=%s i1=%d i2=%d\n", script_op2name(op), i1, i2); + script_reportsrc(st); + ret = INT_MAX; + } + } + script_pushint(st, ret); +} + +/// Binary operators +void op_2(struct script_state *st, int op) +{ + struct script_data* left, leftref; + struct script_data* right; + + leftref.type = C_NOP; + + left = script_getdatatop(st, -2); + right = script_getdatatop(st, -1); + + if (st->op2ref) { + if (data_isreference(left)) { + leftref = *left; + } + + st->op2ref = 0; + } + + get_val(st, left); + get_val(st, right); + + // automatic conversions + switch( op ) + { + case C_ADD: + if( data_isint(left) && data_isstring(right) ) + {// convert int-string to string-string + conv_str(st, left); + } + else if( data_isstring(left) && data_isint(right) ) + {// convert string-int to string-string + conv_str(st, right); + } + break; + } + + if( data_isstring(left) && data_isstring(right) ) + {// ss => op_2str + op_2str(st, op, left->u.str, right->u.str); + script_removetop(st, leftref.type == C_NOP ? -3 : -2, -1);// pop the two values before the top one + + if (leftref.type != C_NOP) + { + aFree(left->u.str); + *left = leftref; + } + } + else if( data_isint(left) && data_isint(right) ) + {// ii => op_2num + int i1 = left->u.num; + int i2 = right->u.num; + + script_removetop(st, leftref.type == C_NOP ? -2 : -1, 0); + op_2num(st, op, i1, i2); + + if (leftref.type != C_NOP) + *left = leftref; + } + else + {// invalid argument + ShowError("script:op_2: invalid data for operator %s\n", script_op2name(op)); + script_reportdata(left); + script_reportdata(right); + script_reportsrc(st); + script_removetop(st, -2, 0); + script_pushnil(st); + st->state = END; + } +} + +/// Unary operators +/// NEG i -> i +/// NOT i -> i +/// LNOT i -> i +void op_1(struct script_state* st, int op) +{ + struct script_data* data; + int i1; + + data = script_getdatatop(st, -1); + get_val(st, data); + + if( !data_isint(data) ) + {// not a number + ShowError("script:op_1: argument is not a number (op=%s)\n", script_op2name(op)); + script_reportdata(data); + script_reportsrc(st); + script_pushnil(st); + st->state = END; + return; + } + + i1 = data->u.num; + script_removetop(st, -1, 0); + switch( op ) + { + case C_NEG: i1 = -i1; break; + case C_NOT: i1 = ~i1; break; + case C_LNOT: i1 = !i1; break; + default: + ShowError("script:op_1: unexpected operator %s i1=%d\n", script_op2name(op), i1); + script_reportsrc(st); + script_pushnil(st); + st->state = END; + return; + } + script_pushint(st, i1); +} + + +/// Checks the type of all arguments passed to a built-in function. +/// +/// @param st Script state whose stack arguments should be inspected. +/// @param func Built-in function for which the arguments are intended. +static void script_check_buildin_argtype(struct script_state* st, int func) +{ + char type; + int idx, invalid = 0; + script_function* sf = &buildin_func[str_data[func].val]; + + for( idx = 2; script_hasdata(st, idx); idx++ ) + { + struct script_data* data = script_getdata(st, idx); + + type = sf->arg[idx-2]; + + if( type == '?' || type == '*' ) + {// optional argument or unknown number of optional parameters ( no types are after this ) + break; + } + else if( type == 0 ) + {// more arguments than necessary ( should not happen, as it is checked before ) + ShowWarning("Found more arguments than necessary. unexpected arg type %s\n",script_op2name(data->type)); + invalid++; + break; + } + else + { + const char* name = NULL; + + if( data_isreference(data) ) + {// get name for variables to determine the type they refer to + name = reference_getname(data); + } + + switch( type ) + { + case 'v': + if( !data_isstring(data) && !data_isint(data) && !data_isreference(data) ) + {// variant + ShowWarning("Unexpected type for argument %d. Expected string, number or variable.\n", idx-1); + script_reportdata(data); + invalid++; + } + break; + case 's': + if( !data_isstring(data) && !( data_isreference(data) && is_string_variable(name) ) ) + {// string + ShowWarning("Unexpected type for argument %d. Expected string.\n", idx-1); + script_reportdata(data); + invalid++; + } + break; + case 'i': + if( !data_isint(data) && !( data_isreference(data) && ( reference_toparam(data) || reference_toconstant(data) || !is_string_variable(name) ) ) ) + {// int ( params and constants are always int ) + ShowWarning("Unexpected type for argument %d. Expected number.\n", idx-1); + script_reportdata(data); + invalid++; + } + break; + case 'r': + if( !data_isreference(data) ) + {// variables + ShowWarning("Unexpected type for argument %d. Expected variable, got %s.\n", idx-1,script_op2name(data->type)); + script_reportdata(data); + invalid++; + } + break; + case 'l': + if( !data_islabel(data) && !data_isfunclabel(data) ) + {// label + ShowWarning("Unexpected type for argument %d. Expected label, got %s\n", idx-1,script_op2name(data->type)); + script_reportdata(data); + invalid++; + } + break; + } + } + } + + if(invalid) + { + ShowDebug("Function: %s\n", get_str(func)); + script_reportsrc(st); + } +} + + +/// Executes a buildin command. +/// Stack: C_NAME(<command>) C_ARG <arg0> <arg1> ... <argN> +int run_func(struct script_state *st) +{ + struct script_data* data; + int i,start_sp,end_sp,func; + + end_sp = st->stack->sp;// position after the last argument + for( i = end_sp-1; i > 0 ; --i ) + if( st->stack->stack_data[i].type == C_ARG ) + break; + if( i == 0 ) + { + ShowError("script:run_func: C_ARG not found. please report this!!!\n"); + st->state = END; + script_reportsrc(st); + return 1; + } + start_sp = i-1;// C_NAME of the command + st->start = start_sp; + st->end = end_sp; + + data = &st->stack->stack_data[st->start]; + if( data->type == C_NAME && str_data[data->u.num].type == C_FUNC ) + func = data->u.num; + else + { + ShowError("script:run_func: not a buildin command.\n"); + script_reportdata(data); + script_reportsrc(st); + st->state = END; + return 1; + } + + if( script_config.warn_func_mismatch_argtypes ) + { + script_check_buildin_argtype(st, func); + } + + if(str_data[func].func){ + if (str_data[func].func(st)) //Report error + script_reportsrc(st); + } else { + ShowError("script:run_func: '%s' (id=%d type=%s) has no C function. please report this!!!\n", get_str(func), func, script_op2name(str_data[func].type)); + script_reportsrc(st); + st->state = END; + } + + // Stack's datum are used when re-running functions [Eoe] + if( st->state == RERUNLINE ) + return 0; + + pop_stack(st, st->start, st->end); + if( st->state == RETFUNC ) + {// return from a user-defined function + struct script_retinfo* ri; + int olddefsp = st->stack->defsp; + int nargs; + + pop_stack(st, st->stack->defsp, st->start);// pop distractions from the stack + if( st->stack->defsp < 1 || st->stack->stack_data[st->stack->defsp-1].type != C_RETINFO ) + { + ShowWarning("script:run_func: return without callfunc or callsub!\n"); + script_reportsrc(st); + st->state = END; + return 1; + } + script_free_vars( st->stack->var_function ); + + ri = st->stack->stack_data[st->stack->defsp-1].u.ri; + nargs = ri->nargs; + st->pos = ri->pos; + st->script = ri->script; + st->stack->var_function = ri->var_function; + st->stack->defsp = ri->defsp; + memset(ri, 0, sizeof(struct script_retinfo)); + + pop_stack(st, olddefsp-nargs-1, olddefsp);// pop arguments and retinfo + + st->state = GOTO; + } + + return 0; +} + +/*========================================== + * script execution + *------------------------------------------*/ +void run_script(struct script_code *rootscript,int pos,int rid,int oid) +{ + struct script_state *st; + + if( rootscript == NULL || pos < 0 ) + return; + + // TODO In jAthena, this function can take over the pending script in the player. [FlavioJS] + // It is unclear how that can be triggered, so it needs the be traced/checked in more detail. + // NOTE At the time of this change, this function wasn't capable of taking over the script state because st->scriptroot was never set. + st = script_alloc_state(rootscript, pos, rid, oid); + run_script_main(st); +} + +void script_stop_sleeptimers(int id) +{ + struct script_state* st; + for(;;) + { + st = (struct script_state*)linkdb_erase(&sleep_db,(void*)__64BPRTSIZE(id)); + if( st == NULL ) + break; // no more sleep timers + script_free_state(st); + } +} + +/*========================================== + * Delete the specified node from sleep_db + *------------------------------------------*/ +struct linkdb_node* script_erase_sleepdb(struct linkdb_node *n) +{ + struct linkdb_node *retnode; + + if( n == NULL) + return NULL; + if( n->prev == NULL ) + sleep_db = n->next; + else + n->prev->next = n->next; + if( n->next ) + n->next->prev = n->prev; + retnode = n->next; + aFree( n ); + return retnode; // The following; return retnode +} + +/*========================================== + * Timer function for sleep + *------------------------------------------*/ +int run_script_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + struct script_state *st = (struct script_state *)data; + struct linkdb_node *node = (struct linkdb_node *)sleep_db; + TBL_PC *sd = map_id2sd(st->rid); + + if((sd && sd->status.char_id != id) || (st->rid && !sd)) + { //Character mismatch. Cancel execution. + st->rid = 0; + st->state = END; + } + while( node && st->sleep.timer != INVALID_TIMER ) { + if( (int)__64BPRTSIZE(node->key) == st->oid && ((struct script_state *)node->data)->sleep.timer == st->sleep.timer ) { + script_erase_sleepdb(node); + st->sleep.timer = INVALID_TIMER; + break; + } + node = node->next; + } + if(st->state != RERUNLINE) + st->sleep.tick = 0; + run_script_main(st); + return 0; +} + +/// Detaches script state from possibly attached character and restores it's previous script if any. +/// +/// @param st Script state to detach. +/// @param dequeue_event Whether to schedule any queued events, when there was no previous script. +static void script_detach_state(struct script_state* st, bool dequeue_event) +{ + struct map_session_data* sd; + + if(st->rid && (sd = map_id2sd(st->rid))!=NULL) + { + sd->st = st->bk_st; + sd->npc_id = st->bk_npcid; + /** + * For the Secure NPC Timeout option (check config/Secure.h) [RR] + **/ + #if SECURE_NPCTIMEOUT + /** + * We're done with this NPC session, so we cancel the timer (if existent) and move on + **/ + if( sd->npc_idle_timer != INVALID_TIMER ) { + delete_timer(sd->npc_idle_timer,npc_rr_secure_timeout_timer); + sd->npc_idle_timer = INVALID_TIMER; + } + #endif + if(st->bk_st) + { + //Remove tag for removal. + st->bk_st = NULL; + st->bk_npcid = 0; + } + else if(dequeue_event) + { + npc_event_dequeue(sd); + } + } + else if(st->bk_st) + {// rid was set to 0, before detaching the script state + ShowError("script_detach_state: Found previous script state without attached player (rid=%d, oid=%d, state=%d, bk_npcid=%d)\n", st->bk_st->rid, st->bk_st->oid, st->bk_st->state, st->bk_npcid); + script_reportsrc(st->bk_st); + + script_free_state(st->bk_st); + st->bk_st = NULL; + } +} + +/// Attaches script state to possibly attached character and backups it's previous script, if any. +/// +/// @param st Script state to attach. +static void script_attach_state(struct script_state* st) +{ + struct map_session_data* sd; + + if(st->rid && (sd = map_id2sd(st->rid))!=NULL) + { + if(st!=sd->st) + { + if(st->bk_st) + {// there is already a backup + ShowDebug("script_free_state: Previous script state lost (rid=%d, oid=%d, state=%d, bk_npcid=%d).\n", st->bk_st->rid, st->bk_st->oid, st->bk_st->state, st->bk_npcid); + } + st->bk_st = sd->st; + st->bk_npcid = sd->npc_id; + } + sd->st = st; + sd->npc_id = st->oid; +/** + * For the Secure NPC Timeout option (check config/Secure.h) [RR] + **/ +#if SECURE_NPCTIMEOUT + if( sd->npc_idle_timer == INVALID_TIMER ) + sd->npc_idle_timer = add_timer(gettick() + (SECURE_NPCTIMEOUT_INTERVAL*1000),npc_rr_secure_timeout_timer,sd->bl.id,0); + sd->npc_idle_tick = gettick(); +#endif + } +} + +/*========================================== + * The main part of the script execution + *------------------------------------------*/ +void run_script_main(struct script_state *st) +{ + int cmdcount = script_config.check_cmdcount; + int gotocount = script_config.check_gotocount; + TBL_PC *sd; + struct script_stack *stack=st->stack; + struct npc_data *nd; + + script_attach_state(st); + + nd = map_id2nd(st->oid); + if( nd && map[nd->bl.m].instance_id > 0 ) + st->instance_id = map[nd->bl.m].instance_id; + + if(st->state == RERUNLINE) { + run_func(st); + if(st->state == GOTO) + st->state = RUN; + } else if(st->state != END) + st->state = RUN; + + while(st->state == RUN) + { + enum c_op c = get_com(st->script->script_buf,&st->pos); + switch(c){ + case C_EOL: + if( stack->defsp > stack->sp ) + ShowError("script:run_script_main: unexpected stack position (defsp=%d sp=%d). please report this!!!\n", stack->defsp, stack->sp); + else + pop_stack(st, stack->defsp, stack->sp);// pop unused stack data. (unused return value) + break; + case C_INT: + push_val(stack,C_INT,get_num(st->script->script_buf,&st->pos)); + break; + case C_POS: + case C_NAME: + push_val(stack,c,GETVALUE(st->script->script_buf,st->pos)); + st->pos+=3; + break; + case C_ARG: + push_val(stack,c,0); + break; + case C_STR: + push_str(stack,C_CONSTSTR,(char*)(st->script->script_buf+st->pos)); + while(st->script->script_buf[st->pos++]); + break; + case C_FUNC: + run_func(st); + if(st->state==GOTO){ + st->state = RUN; + if( !st->freeloop && gotocount>0 && (--gotocount)<=0 ){ + ShowError("run_script: infinity loop !\n"); + script_reportsrc(st); + st->state=END; + } + } + break; + + case C_REF: + st->op2ref = 1; + break; + + case C_NEG: + case C_NOT: + case C_LNOT: + op_1(st ,c); + break; + + case C_ADD: + 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_OP3: + op_3(st, c); + break; + + case C_NOP: + st->state=END; + break; + + default: + ShowError("unknown command : %d @ %d\n",c,st->pos); + st->state=END; + break; + } + if( !st->freeloop && cmdcount>0 && (--cmdcount)<=0 ){ + ShowError("run_script: infinity loop !\n"); + script_reportsrc(st); + st->state=END; + } + } + + if(st->sleep.tick > 0) { + //Restore previous script + script_detach_state(st, false); + //Delay execution + sd = map_id2sd(st->rid); // Get sd since script might have attached someone while running. [Inkfish] + st->sleep.charid = sd?sd->status.char_id:0; + st->sleep.timer = add_timer(gettick()+st->sleep.tick, + run_script_timer, st->sleep.charid, (intptr_t)st); + linkdb_insert(&sleep_db, (void*)__64BPRTSIZE(st->oid), st); + } + else if(st->state != END && st->rid){ + //Resume later (st is already attached to player). + if(st->bk_st) { + ShowWarning("Unable to restore stack! Double continuation!\n"); + //Report BOTH scripts to see if that can help somehow. + ShowDebug("Previous script (lost):\n"); + script_reportsrc(st->bk_st); + ShowDebug("Current script:\n"); + script_reportsrc(st); + + script_free_state(st->bk_st); + st->bk_st = NULL; + } + } else { + //Dispose of script. + if ((sd = map_id2sd(st->rid))!=NULL) + { //Restore previous stack and save char. + if(sd->state.using_fake_npc){ + clif_clearunit_single(sd->npc_id, CLR_OUTSIGHT, sd->fd); + sd->state.using_fake_npc = 0; + } + //Restore previous script if any. + script_detach_state(st, true); + if (sd->state.reg_dirty&2) + intif_saveregistry(sd,2); + if (sd->state.reg_dirty&1) + intif_saveregistry(sd,1); + } + script_free_state(st); + st = NULL; + } +} + +int script_config_read(char *cfgName) +{ + int i; + char line[1024],w1[1024],w2[1024]; + FILE *fp; + + + fp=fopen(cfgName,"r"); + if(fp==NULL){ + ShowError("File not found: %s\n", cfgName); + return 1; + } + while(fgets(line, sizeof(line), fp)) + { + if(line[0] == '/' && line[1] == '/') + continue; + i=sscanf(line,"%[^:]: %[^\r\n]",w1,w2); + if(i!=2) + continue; + + if(strcmpi(w1,"warn_func_mismatch_paramnum")==0) { + script_config.warn_func_mismatch_paramnum = config_switch(w2); + } + else if(strcmpi(w1,"check_cmdcount")==0) { + script_config.check_cmdcount = config_switch(w2); + } + else if(strcmpi(w1,"check_gotocount")==0) { + script_config.check_gotocount = config_switch(w2); + } + else if(strcmpi(w1,"input_min_value")==0) { + script_config.input_min_value = config_switch(w2); + } + else if(strcmpi(w1,"input_max_value")==0) { + script_config.input_max_value = config_switch(w2); + } + else if(strcmpi(w1,"warn_func_mismatch_argtypes")==0) { + script_config.warn_func_mismatch_argtypes = config_switch(w2); + } + else if(strcmpi(w1,"import")==0){ + script_config_read(w2); + } + else { + ShowWarning("Unknown setting '%s' in file %s\n", w1, cfgName); + } + } + fclose(fp); + + return 0; +} + +/** + * @see DBApply + */ +static int db_script_free_code_sub(DBKey key, DBData *data, va_list ap) +{ + struct script_code *code = db_data2ptr(data); + if (code) + script_free_code(code); + return 0; +} + +void script_run_autobonus(const char *autobonus, int id, int pos) +{ + struct script_code *script = (struct script_code *)strdb_get(autobonus_db, autobonus); + + if( script ) + { + current_equip_item_index = pos; + run_script(script,0,id,0); + } +} + +void script_add_autobonus(const char *autobonus) +{ + if( strdb_get(autobonus_db, autobonus) == NULL ) + { + struct script_code *script = parse_script(autobonus, "autobonus", 0, 0); + + if( script ) + strdb_put(autobonus_db, autobonus, script); + } +} + + +/// resets a temporary character array variable to given value +void script_cleararray_pc(struct map_session_data* sd, const char* varname, void* value) +{ + int key; + uint8 idx; + + if( not_array_variable(varname[0]) || !not_server_variable(varname[0]) ) + { + ShowError("script_cleararray_pc: Variable '%s' has invalid scope (char_id=%d).\n", varname, sd->status.char_id); + return; + } + + key = add_str(varname); + + if( is_string_variable(varname) ) + { + for( idx = 0; idx < SCRIPT_MAX_ARRAYSIZE; idx++ ) + { + pc_setregstr(sd, reference_uid(key, idx), (const char*)value); + } + } + else + { + for( idx = 0; idx < SCRIPT_MAX_ARRAYSIZE; idx++ ) + { + pc_setreg(sd, reference_uid(key, idx), (int)__64BPRTSIZE(value)); + } + } +} + + +/// sets a temporary character array variable element idx to given value +/// @param refcache Pointer to an int variable, which keeps a copy of the reference to varname and must be initialized to 0. Can be NULL if only one element is set. +void script_setarray_pc(struct map_session_data* sd, const char* varname, uint8 idx, void* value, int* refcache) +{ + int key; + + if( not_array_variable(varname[0]) || !not_server_variable(varname[0]) ) + { + ShowError("script_setarray_pc: Variable '%s' has invalid scope (char_id=%d).\n", varname, sd->status.char_id); + return; + } + + if( idx >= SCRIPT_MAX_ARRAYSIZE ) + { + ShowError("script_setarray_pc: Variable '%s' has invalid index '%d' (char_id=%d).\n", varname, (int)idx, sd->status.char_id); + return; + } + + key = ( refcache && refcache[0] ) ? refcache[0] : add_str(varname); + + if( is_string_variable(varname) ) + { + pc_setregstr(sd, reference_uid(key, idx), (const char*)value); + } + else + { + pc_setreg(sd, reference_uid(key, idx), (int)__64BPRTSIZE(value)); + } + + if( refcache ) + {// save to avoid repeated add_str calls + refcache[0] = key; + } +} +#ifdef BETA_THREAD_TEST +int buildin_query_sql_sub(struct script_state* st, Sql* handle); + +/* used to receive items the queryThread has already processed */ +int queryThread_timer(int tid, unsigned int tick, int id, intptr_t data) { + int i, cursor = 0; + bool allOk = true; + + EnterSpinLock(&queryThreadLock); + + for( i = 0; i < queryThreadData.count; i++ ) { + struct queryThreadEntry *entry = queryThreadData.entry[i]; + + if( !entry->ok ) { + allOk = false; + continue; + } + + run_script_main(entry->st); + + entry->st = NULL;/* empty entries */ + aFree(entry); + queryThreadData.entry[i] = NULL; + } + + + if( allOk ) { + /* cancel the repeating timer -- it'll re-create itself when necessary, dont need to remain looping */ + delete_timer(queryThreadData.timer, queryThread_timer); + queryThreadData.timer = INVALID_TIMER; + } + + /* now lets clear the mess. */ + for( i = 0; i < queryThreadData.count; i++ ) { + struct queryThreadEntry *entry = queryThreadData.entry[i]; + if( entry == NULL ) + continue;/* entry on hold */ + + /* move */ + memmove(&queryThreadData.entry[cursor], &queryThreadData.entry[i], sizeof(struct queryThreadEntry*)); + + cursor++; + } + + queryThreadData.count = cursor; + + LeaveSpinLock(&queryThreadLock); + + return 0; +} + +void queryThread_add(struct script_state *st, bool type) { + int idx = 0; + struct queryThreadEntry* entry = NULL; + + EnterSpinLock(&queryThreadLock); + + if( queryThreadData.count++ != 0 ) + RECREATE(queryThreadData.entry, struct queryThreadEntry* , queryThreadData.count); + + idx = queryThreadData.count-1; + + CREATE(queryThreadData.entry[idx],struct queryThreadEntry,1); + + entry = queryThreadData.entry[idx]; + + entry->st = st; + entry->ok = false; + entry->type = type; + if( queryThreadData.timer == INVALID_TIMER ) { /* start the receiver timer */ + queryThreadData.timer = add_timer_interval(gettick() + 100, queryThread_timer, 0, 0, 100); + } + + LeaveSpinLock(&queryThreadLock); + + /* unlock the queryThread */ + racond_signal(queryThreadCond); +} +/* adds a new log to the queue */ +void queryThread_log(char * entry, int length) { + int idx = logThreadData.count; + + EnterSpinLock(&queryThreadLock); + + if( logThreadData.count++ != 0 ) + RECREATE(logThreadData.entry, char* , logThreadData.count); + + CREATE(logThreadData.entry[idx], char, length + 1 ); + safestrncpy(logThreadData.entry[idx], entry, length + 1 ); + + LeaveSpinLock(&queryThreadLock); + + /* unlock the queryThread */ + racond_signal(queryThreadCond); +} + +/* queryThread_main */ +static void *queryThread_main(void *x) { + Sql *queryThread_handle = Sql_Malloc(); + int i; + + if ( SQL_ERROR == Sql_Connect(queryThread_handle, map_server_id, map_server_pw, map_server_ip, map_server_port, map_server_db) ) + exit(EXIT_FAILURE); + + if( strlen(default_codepage) > 0 ) + if ( SQL_ERROR == Sql_SetEncoding(queryThread_handle, default_codepage) ) + Sql_ShowDebug(queryThread_handle); + + if( log_config.sql_logs ) { + logmysql_handle = Sql_Malloc(); + + if ( SQL_ERROR == Sql_Connect(logmysql_handle, log_db_id, log_db_pw, log_db_ip, log_db_port, log_db_db) ) + exit(EXIT_FAILURE); + + if( strlen(default_codepage) > 0 ) + if ( SQL_ERROR == Sql_SetEncoding(logmysql_handle, default_codepage) ) + Sql_ShowDebug(logmysql_handle); + } + + while( 1 ) { + + if(queryThreadTerminate > 0) + break; + + EnterSpinLock(&queryThreadLock); + + /* mess with queryThreadData within the lock */ + for( i = 0; i < queryThreadData.count; i++ ) { + struct queryThreadEntry *entry = queryThreadData.entry[i]; + + if( entry->ok ) + continue; + else if ( !entry->st || !entry->st->stack ) { + entry->ok = true;/* dispose */ + continue; + } + + buildin_query_sql_sub(entry->st, entry->type ? logmysql_handle : queryThread_handle); + + entry->ok = true;/* we're done with this */ + } + + /* also check for any logs in need to be sent */ + if( log_config.sql_logs ) { + for( i = 0; i < logThreadData.count; i++ ) { + if( SQL_ERROR == Sql_Query(logmysql_handle, logThreadData.entry[i]) ) + Sql_ShowDebug(logmysql_handle); + aFree(logThreadData.entry[i]); + } + logThreadData.count = 0; + } + + LeaveSpinLock(&queryThreadLock); + + ramutex_lock( queryThreadMutex ); + racond_wait( queryThreadCond, queryThreadMutex, -1 ); + ramutex_unlock( queryThreadMutex ); + + } + + Sql_Free(queryThread_handle); + + if( log_config.sql_logs ) { + Sql_Free(logmysql_handle); + } + + return NULL; +} +#endif +/*========================================== + * Destructor + *------------------------------------------*/ +int do_final_script() { + int i; +#ifdef DEBUG_HASH + if (battle_config.etc_log) + { + FILE *fp = fopen("hash_dump.txt","wt"); + if(fp) { + int count[SCRIPT_HASH_SIZE]; + int count2[SCRIPT_HASH_SIZE]; // number of buckets with a certain number of items + int n=0; + int min=INT_MAX,max=0,zero=0; + double mean=0.0f; + double median=0.0f; + + ShowNotice("Dumping script str hash information to hash_dump.txt\n"); + memset(count, 0, sizeof(count)); + fprintf(fp,"num : hash : data_name\n"); + fprintf(fp,"---------------------------------------------------------------\n"); + for(i=LABEL_START; i<str_num; i++) { + unsigned int h = calc_hash(get_str(i)); + fprintf(fp,"%04d : %4u : %s\n",i,h, get_str(i)); + ++count[h]; + } + fprintf(fp,"--------------------\n\n"); + memset(count2, 0, sizeof(count2)); + for(i=0; i<SCRIPT_HASH_SIZE; i++) { + fprintf(fp," hash %3d = %d\n",i,count[i]); + if(min > count[i]) + min = count[i]; // minimun count of collision + if(max < count[i]) + max = count[i]; // maximun count of collision + if(count[i] == 0) + zero++; + ++count2[count[i]]; + } + fprintf(fp,"\n--------------------\n items : buckets\n--------------------\n"); + for( i=min; i <= max; ++i ){ + fprintf(fp," %5d : %7d\n",i,count2[i]); + mean += 1.0f*i*count2[i]/SCRIPT_HASH_SIZE; // Note: this will always result in <nr labels>/<nr buckets> + } + for( i=min; i <= max; ++i ){ + n += count2[i]; + if( n*2 >= SCRIPT_HASH_SIZE ) + { + if( SCRIPT_HASH_SIZE%2 == 0 && SCRIPT_HASH_SIZE/2 == n ) + median = (i+i+1)/2.0f; + else + median = i; + break; + } + } + fprintf(fp,"--------------------\n min = %d, max = %d, zero = %d\n mean = %lf, median = %lf\n",min,max,zero,mean,median); + fclose(fp); + } + } +#endif + + mapreg_final(); + + db_destroy(scriptlabel_db); + userfunc_db->destroy(userfunc_db, db_script_free_code_sub); + autobonus_db->destroy(autobonus_db, db_script_free_code_sub); + if(sleep_db) { + struct linkdb_node *n = (struct linkdb_node *)sleep_db; + while(n) { + struct script_state *st = (struct script_state *)n->data; + script_free_state(st); + n = n->next; + } + linkdb_final(&sleep_db); + } + + if (str_data) + aFree(str_data); + if (str_buf) + aFree(str_buf); + + for( i = 0; i < atcmd_binding_count; i++ ) { + aFree(atcmd_binding[i]); + } + + if( atcmd_binding_count != 0 ) + aFree(atcmd_binding); +#ifdef BETA_THREAD_TEST + /* QueryThread */ + InterlockedIncrement(&queryThreadTerminate); + racond_signal(queryThreadCond); + rathread_wait(queryThread, NULL); + + // Destroy cond var and mutex. + racond_destroy( queryThreadCond ); + ramutex_destroy( queryThreadMutex ); + + /* Clear missing vars */ + for( i = 0; i < queryThreadData.count; i++ ) { + aFree(queryThreadData.entry[i]); + } + + aFree(queryThreadData.entry); + + for( i = 0; i < logThreadData.count; i++ ) { + aFree(logThreadData.entry[i]); + } + + aFree(logThreadData.entry); +#endif + + return 0; +} +/*========================================== + * Initialization + *------------------------------------------*/ +int do_init_script() { + userfunc_db=strdb_alloc(DB_OPT_DUP_KEY,0); + scriptlabel_db=strdb_alloc(DB_OPT_DUP_KEY,50); + autobonus_db = strdb_alloc(DB_OPT_DUP_KEY,0); + + mapreg_init(); +#ifdef BETA_THREAD_TEST + CREATE(queryThreadData.entry, struct queryThreadEntry*, 1); + queryThreadData.count = 0; + CREATE(logThreadData.entry, char *, 1); + logThreadData.count = 0; + /* QueryThread Start */ + + InitializeSpinLock(&queryThreadLock); + + queryThreadData.timer = INVALID_TIMER; + queryThreadTerminate = 0; + queryThreadMutex = ramutex_create(); + queryThreadCond = racond_create(); + + queryThread = rathread_create(queryThread_main, NULL); + + if(queryThread == NULL){ + ShowFatalError("do_init_script: cannot spawn Query Thread.\n"); + exit(EXIT_FAILURE); + } + + add_timer_func_list(queryThread_timer, "queryThread_timer"); +#endif + return 0; +} + +int script_reload() { + int i; + +#ifdef BETA_THREAD_TEST + /* we're reloading so any queries undergoing should be...exterminated. */ + EnterSpinLock(&queryThreadLock); + + for( i = 0; i < queryThreadData.count; i++ ) { + aFree(queryThreadData.entry[i]); + } + queryThreadData.count = 0; + + if( queryThreadData.timer != INVALID_TIMER ) { + delete_timer(queryThreadData.timer, queryThread_timer); + queryThreadData.timer = INVALID_TIMER; + } + + LeaveSpinLock(&queryThreadLock); +#endif + + + userfunc_db->clear(userfunc_db, db_script_free_code_sub); + db_clear(scriptlabel_db); + + // @commands (script based) + // Clear bindings + for( i = 0; i < atcmd_binding_count; i++ ) { + aFree(atcmd_binding[i]); + } + + if( atcmd_binding_count != 0 ) + aFree(atcmd_binding); + + atcmd_binding_count = 0; + + if(sleep_db) { + struct linkdb_node *n = (struct linkdb_node *)sleep_db; + while(n) { + struct script_state *st = (struct script_state *)n->data; + script_free_state(st); + n = n->next; + } + linkdb_final(&sleep_db); + } + mapreg_reload(); + return 0; +} + +//----------------------------------------------------------------------------- +// buildin functions +// + +#define BUILDIN_DEF(x,args) { buildin_ ## x , #x , args } +#define BUILDIN_DEF2(x,x2,args) { buildin_ ## x , x2 , args } +#define BUILDIN_FUNC(x) int buildin_ ## x (struct script_state* st) + +///////////////////////////////////////////////////////////////////// +// NPC interaction +// + +/// Appends a message to the npc dialog. +/// If a dialog doesn't exist yet, one is created. +/// +/// mes "<message>"; +BUILDIN_FUNC(mes) +{ + TBL_PC* sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if( !script_hasdata(st, 3) ) + {// only a single line detected in the script + clif_scriptmes(sd, st->oid, script_getstr(st, 2)); + } + else + {// parse multiple lines as they exist + int i; + + for( i = 2; script_hasdata(st, i); i++ ) + { + // send the message to the client + clif_scriptmes(sd, st->oid, script_getstr(st, i)); + } + } + + return 0; +} + +/// Displays the button 'next' in the npc dialog. +/// The dialog text is cleared and the script continues when the button is pressed. +/// +/// next; +BUILDIN_FUNC(next) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + st->state = STOP; + clif_scriptnext(sd, st->oid); + return 0; +} + +/// Ends the script and displays the button 'close' on the npc dialog. +/// The dialog is closed when the button is pressed. +/// +/// close; +BUILDIN_FUNC(close) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + st->state = END; + clif_scriptclose(sd, st->oid); + return 0; +} + +/// Displays the button 'close' on the npc dialog. +/// The dialog is closed and the script continues when the button is pressed. +/// +/// close2; +BUILDIN_FUNC(close2) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + st->state = STOP; + clif_scriptclose(sd, st->oid); + return 0; +} + +/// Counts the number of valid and total number of options in 'str' +/// If max_count > 0 the counting stops when that valid option is reached +/// total is incremented for each option (NULL is supported) +static int menu_countoptions(const char* str, int max_count, int* total) +{ + int count = 0; + int bogus_total; + + if( total == NULL ) + total = &bogus_total; + ++(*total); + + // initial empty options + while( *str == ':' ) + { + ++str; + ++(*total); + } + // count menu options + while( *str != '\0' ) + { + ++count; + --max_count; + if( max_count == 0 ) + break; + while( *str != ':' && *str != '\0' ) + ++str; + while( *str == ':' ) + { + ++str; + ++(*total); + } + } + return count; +} + +/// Displays a menu with options and goes to the target label. +/// The script is stopped if cancel is pressed. +/// Options with no text are not displayed in the client. +/// +/// Options can be grouped together, separated by the character ':' in the text: +/// ex: menu "A:B:C",L_target; +/// All these options go to the specified target label. +/// +/// The index of the selected option is put in the variable @menu. +/// Indexes start with 1 and are consistent with grouped and empty options. +/// ex: menu "A::B",-,"",L_Impossible,"C",-; +/// // displays "A", "B" and "C", corresponding to indexes 1, 3 and 5 +/// +/// NOTE: the client closes the npc dialog when cancel is pressed +/// +/// menu "<option_text>",<target_label>{,"<option_text>",<target_label>,...}; +BUILDIN_FUNC(menu) +{ + int i; + const char* text; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + // TODO detect multiple scripts waiting for input at the same time, and what to do when that happens + if( sd->state.menu_or_input == 0 ) + { + struct StringBuf buf; + struct script_data* data; + + if( script_lastdata(st) % 2 == 0 ) + {// argument count is not even (1st argument is at index 2) + ShowError("script:menu: illegal number of arguments (%d).\n", (script_lastdata(st) - 1)); + st->state = END; + return 1; + } + + StringBuf_Init(&buf); + sd->npc_menu = 0; + for( i = 2; i < script_lastdata(st); i += 2 ) + { + // menu options + text = script_getstr(st, i); + + // target label + data = script_getdata(st, i+1); + if( !data_islabel(data) ) + {// not a label + StringBuf_Destroy(&buf); + ShowError("script:menu: argument #%d (from 1) is not a label or label not found.\n", i); + script_reportdata(data); + st->state = END; + return 1; + } + + // append option(s) + if( text[0] == '\0' ) + continue;// empty string, ignore + if( sd->npc_menu > 0 ) + StringBuf_AppendStr(&buf, ":"); + StringBuf_AppendStr(&buf, text); + sd->npc_menu += menu_countoptions(text, 0, NULL); + } + st->state = RERUNLINE; + sd->state.menu_or_input = 1; + + /** + * menus beyond this length crash the client (see bugreport:6402) + **/ + if( StringBuf_Length(&buf) >= 2047 ) { + struct npc_data * nd = map_id2nd(st->oid); + char* menu; + CREATE(menu, char, 2048); + safestrncpy(menu, StringBuf_Value(&buf), 2047); + ShowWarning("NPC Menu too long! (source:%s / length:%d)\n",nd?nd->name:"Unknown",StringBuf_Length(&buf)); + clif_scriptmenu(sd, st->oid, menu); + aFree(menu); + } else + clif_scriptmenu(sd, st->oid, StringBuf_Value(&buf)); + + StringBuf_Destroy(&buf); + + if( sd->npc_menu >= 0xff ) + {// client supports only up to 254 entries; 0 is not used and 255 is reserved for cancel; excess entries are displayed but cause 'uint8' overflow + ShowWarning("buildin_menu: Too many options specified (current=%d, max=254).\n", sd->npc_menu); + script_reportsrc(st); + } + } + else if( sd->npc_menu == 0xff ) + {// Cancel was pressed + sd->state.menu_or_input = 0; + st->state = END; + } + else + {// goto target label + int menu = 0; + + sd->state.menu_or_input = 0; + if( sd->npc_menu <= 0 ) + { + ShowDebug("script:menu: unexpected selection (%d)\n", sd->npc_menu); + st->state = END; + return 1; + } + + // get target label + for( i = 2; i < script_lastdata(st); i += 2 ) + { + text = script_getstr(st, i); + sd->npc_menu -= menu_countoptions(text, sd->npc_menu, &menu); + if( sd->npc_menu <= 0 ) + break;// entry found + } + if( sd->npc_menu > 0 ) + {// Invalid selection + ShowDebug("script:menu: selection is out of range (%d pairs are missing?) - please report this\n", sd->npc_menu); + st->state = END; + return 1; + } + if( !data_islabel(script_getdata(st, i + 1)) ) + {// TODO remove this temporary crash-prevention code (fallback for multiple scripts requesting user input) + ShowError("script:menu: unexpected data in label argument\n"); + script_reportdata(script_getdata(st, i + 1)); + st->state = END; + return 1; + } + pc_setreg(sd, add_str("@menu"), menu); + st->pos = script_getnum(st, i + 1); + st->state = GOTO; + } + return 0; +} + +/// Displays a menu with options and returns the selected option. +/// Behaves like 'menu' without the target labels. +/// +/// select(<option_text>{,<option_text>,...}) -> <selected_option> +/// +/// @see menu +BUILDIN_FUNC(select) +{ + int i; + const char* text; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if( sd->state.menu_or_input == 0 ) { + struct StringBuf buf; + + StringBuf_Init(&buf); + sd->npc_menu = 0; + for( i = 2; i <= script_lastdata(st); ++i ) { + text = script_getstr(st, i); + + if( sd->npc_menu > 0 ) + StringBuf_AppendStr(&buf, ":"); + + StringBuf_AppendStr(&buf, text); + sd->npc_menu += menu_countoptions(text, 0, NULL); + } + + st->state = RERUNLINE; + sd->state.menu_or_input = 1; + + /** + * menus beyond this length crash the client (see bugreport:6402) + **/ + if( StringBuf_Length(&buf) >= 2047 ) { + struct npc_data * nd = map_id2nd(st->oid); + char* menu; + CREATE(menu, char, 2048); + safestrncpy(menu, StringBuf_Value(&buf), 2047); + ShowWarning("NPC Menu too long! (source:%s / length:%d)\n",nd?nd->name:"Unknown",StringBuf_Length(&buf)); + clif_scriptmenu(sd, st->oid, menu); + aFree(menu); + } else + clif_scriptmenu(sd, st->oid, StringBuf_Value(&buf)); + StringBuf_Destroy(&buf); + + if( sd->npc_menu >= 0xff ) { + ShowWarning("buildin_select: Too many options specified (current=%d, max=254).\n", sd->npc_menu); + script_reportsrc(st); + } + } else if( sd->npc_menu == 0xff ) {// Cancel was pressed + sd->state.menu_or_input = 0; + st->state = END; + } else {// return selected option + int menu = 0; + + sd->state.menu_or_input = 0; + for( i = 2; i <= script_lastdata(st); ++i ) { + text = script_getstr(st, i); + sd->npc_menu -= menu_countoptions(text, sd->npc_menu, &menu); + if( sd->npc_menu <= 0 ) + break;// entry found + } + pc_setreg(sd, add_str("@menu"), menu); + script_pushint(st, menu); + st->state = RUN; + } + return 0; +} + +/// Displays a menu with options and returns the selected option. +/// Behaves like 'menu' without the target labels, except when cancel is +/// pressed. +/// When cancel is pressed, the script continues and 255 is returned. +/// +/// prompt(<option_text>{,<option_text>,...}) -> <selected_option> +/// +/// @see menu +BUILDIN_FUNC(prompt) +{ + int i; + const char *text; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if( sd->state.menu_or_input == 0 ) + { + struct StringBuf buf; + + StringBuf_Init(&buf); + sd->npc_menu = 0; + for( i = 2; i <= script_lastdata(st); ++i ) + { + text = script_getstr(st, i); + if( sd->npc_menu > 0 ) + StringBuf_AppendStr(&buf, ":"); + StringBuf_AppendStr(&buf, text); + sd->npc_menu += menu_countoptions(text, 0, NULL); + } + + st->state = RERUNLINE; + sd->state.menu_or_input = 1; + + /** + * menus beyond this length crash the client (see bugreport:6402) + **/ + if( StringBuf_Length(&buf) >= 2047 ) { + struct npc_data * nd = map_id2nd(st->oid); + char* menu; + CREATE(menu, char, 2048); + safestrncpy(menu, StringBuf_Value(&buf), 2047); + ShowWarning("NPC Menu too long! (source:%s / length:%d)\n",nd?nd->name:"Unknown",StringBuf_Length(&buf)); + clif_scriptmenu(sd, st->oid, menu); + aFree(menu); + } else + clif_scriptmenu(sd, st->oid, StringBuf_Value(&buf)); + StringBuf_Destroy(&buf); + + if( sd->npc_menu >= 0xff ) + { + ShowWarning("buildin_prompt: Too many options specified (current=%d, max=254).\n", sd->npc_menu); + script_reportsrc(st); + } + } + else if( sd->npc_menu == 0xff ) + {// Cancel was pressed + sd->state.menu_or_input = 0; + pc_setreg(sd, add_str("@menu"), 0xff); + script_pushint(st, 0xff); + st->state = RUN; + } + else + {// return selected option + int menu = 0; + + sd->state.menu_or_input = 0; + for( i = 2; i <= script_lastdata(st); ++i ) + { + text = script_getstr(st, i); + sd->npc_menu -= menu_countoptions(text, sd->npc_menu, &menu); + if( sd->npc_menu <= 0 ) + break;// entry found + } + pc_setreg(sd, add_str("@menu"), menu); + script_pushint(st, menu); + st->state = RUN; + } + return 0; +} + +///////////////////////////////////////////////////////////////////// +// ... +// + +/// Jumps to the target script label. +/// +/// goto <label>; +BUILDIN_FUNC(goto) +{ + if( !data_islabel(script_getdata(st,2)) ) + { + ShowError("script:goto: not a label\n"); + script_reportdata(script_getdata(st,2)); + st->state = END; + return 1; + } + + st->pos = script_getnum(st,2); + st->state = GOTO; + return 0; +} + +/*========================================== + * user-defined function call + *------------------------------------------*/ +BUILDIN_FUNC(callfunc) +{ + int i, j; + struct script_retinfo* ri; + struct script_code* scr; + const char* str = script_getstr(st,2); + DBMap **ref = NULL; + + scr = (struct script_code*)strdb_get(userfunc_db, str); + if( !scr ) + { + ShowError("script:callfunc: function not found! [%s]\n", str); + st->state = END; + return 1; + } + + for( i = st->start+3, j = 0; i < st->end; i++, j++ ) + { + struct script_data* data = push_copy(st->stack,i); + if( data_isreference(data) && !data->ref ) + { + const char* name = reference_getname(data); + if( name[0] == '.' ) { + if ( !ref ) { + ref = (struct DBMap**)aCalloc(sizeof(struct DBMap*), 1); + ref[0] = (name[1] == '@' ? st->stack->var_function : st->script->script_vars); + } + data->ref = ref; + } + } + } + + CREATE(ri, struct script_retinfo, 1); + ri->script = st->script;// script code + ri->var_function = st->stack->var_function;// scope variables + ri->pos = st->pos;// script location + ri->nargs = j;// argument count + ri->defsp = st->stack->defsp;// default stack pointer + push_retinfo(st->stack, ri, ref); + + st->pos = 0; + st->script = scr; + st->stack->defsp = st->stack->sp; + st->state = GOTO; + st->stack->var_function = idb_alloc(DB_OPT_RELEASE_DATA); + + return 0; +} +/*========================================== + * subroutine call + *------------------------------------------*/ +BUILDIN_FUNC(callsub) +{ + int i,j; + struct script_retinfo* ri; + int pos = script_getnum(st,2); + DBMap **ref = NULL; + + if( !data_islabel(script_getdata(st,2)) && !data_isfunclabel(script_getdata(st,2)) ) + { + ShowError("script:callsub: argument is not a label\n"); + script_reportdata(script_getdata(st,2)); + st->state = END; + return 1; + } + + for( i = st->start+3, j = 0; i < st->end; i++, j++ ) + { + struct script_data* data = push_copy(st->stack,i); + if( data_isreference(data) && !data->ref ) + { + const char* name = reference_getname(data); + if( name[0] == '.' && name[1] == '@' ) { + if ( !ref ) { + ref = (struct DBMap**)aCalloc(sizeof(struct DBMap*), 1); + ref[0] = st->stack->var_function; + } + data->ref = ref; + } + } + } + + CREATE(ri, struct script_retinfo, 1); + ri->script = st->script;// script code + ri->var_function = st->stack->var_function;// scope variables + ri->pos = st->pos;// script location + ri->nargs = j;// argument count + ri->defsp = st->stack->defsp;// default stack pointer + push_retinfo(st->stack, ri, ref); + + st->pos = pos; + st->stack->defsp = st->stack->sp; + st->state = GOTO; + st->stack->var_function = idb_alloc(DB_OPT_RELEASE_DATA); + + return 0; +} + +/// Retrieves an argument provided to callfunc/callsub. +/// If the argument doesn't exist +/// +/// getarg(<index>{,<default_value>}) -> <value> +BUILDIN_FUNC(getarg) +{ + struct script_retinfo* ri; + int idx; + + if( st->stack->defsp < 1 || st->stack->stack_data[st->stack->defsp - 1].type != C_RETINFO ) + { + ShowError("script:getarg: no callfunc or callsub!\n"); + st->state = END; + return 1; + } + ri = st->stack->stack_data[st->stack->defsp - 1].u.ri; + + idx = script_getnum(st,2); + + if( idx >= 0 && idx < ri->nargs ) + push_copy(st->stack, st->stack->defsp - 1 - ri->nargs + idx); + else if( script_hasdata(st,3) ) + script_pushcopy(st, 3); + else + { + ShowError("script:getarg: index (idx=%d) out of range (nargs=%d) and no default value found\n", idx, ri->nargs); + st->state = END; + return 1; + } + + return 0; +} + +/// Returns from the current function, optionaly returning a value from the functions. +/// Don't use outside script functions. +/// +/// return; +/// return <value>; +BUILDIN_FUNC(return) +{ + if( script_hasdata(st,2) ) + {// return value + struct script_data* data; + script_pushcopy(st, 2); + data = script_getdatatop(st, -1); + if( data_isreference(data) ) + { + const char* name = reference_getname(data); + if( name[0] == '.' && name[1] == '@' ) + {// scope variable + if( !data->ref || data->ref == (DBMap**)&st->stack->var_function ) + get_val(st, data);// current scope, convert to value + } + else if( name[0] == '.' && !data->ref ) + {// script variable, link to current script + data->ref = &st->script->script_vars; + } + } + } + else + {// no return value + script_pushnil(st); + } + st->state = RETFUNC; + return 0; +} + +/// Returns a random number from 0 to <range>-1. +/// Or returns a random number from <min> to <max>. +/// If <min> is greater than <max>, their numbers are switched. +/// rand(<range>) -> <int> +/// rand(<min>,<max>) -> <int> +BUILDIN_FUNC(rand) +{ + int range; + int min; + int max; + + if( script_hasdata(st,3) ) + {// min,max + min = script_getnum(st,2); + max = script_getnum(st,3); + if( max < min ) + swap(min, max); + range = max - min + 1; + } + else + {// range + min = 0; + range = script_getnum(st,2); + } + if( range <= 1 ) + script_pushint(st, min); + else + script_pushint(st, rnd()%range + min); + + return 0; +} + +/*========================================== + * Warp sd to str,x,y or Random or SavePoint/Save + *------------------------------------------*/ +BUILDIN_FUNC(warp) +{ + int ret; + int x,y; + const char* str; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + str = script_getstr(st,2); + x = script_getnum(st,3); + y = script_getnum(st,4); + + if(strcmp(str,"Random")==0) + ret = pc_randomwarp(sd,CLR_TELEPORT); + else if(strcmp(str,"SavePoint")==0 || strcmp(str,"Save")==0) + ret = pc_setpos(sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,CLR_TELEPORT); + else + ret = pc_setpos(sd,mapindex_name2id(str),x,y,CLR_OUTSIGHT); + + if( ret ) { + ShowError("buildin_warp: moving player '%s' to \"%s\",%d,%d failed.\n", sd->status.name, str, x, y); + script_reportsrc(st); + } + + return 0; +} +/*========================================== + * Warp a specified area + *------------------------------------------*/ +static int buildin_areawarp_sub(struct block_list *bl,va_list ap) +{ + int x2,y2,x3,y3; + unsigned int index; + + index = va_arg(ap,unsigned int); + x2 = va_arg(ap,int); + y2 = va_arg(ap,int); + x3 = va_arg(ap,int); + y3 = va_arg(ap,int); + + if(index == 0) + pc_randomwarp((TBL_PC *)bl,CLR_TELEPORT); + else if(x3 && y3) { + int max, tx, ty, j = 0; + + // choose a suitable max number of attempts + if( (max = (y3-y2+1)*(x3-x2+1)*3) > 1000 ) + max = 1000; + + // find a suitable map cell + do { + tx = rnd()%(x3-x2+1)+x2; + ty = rnd()%(y3-y2+1)+y2; + j++; + } while( map_getcell(index,tx,ty,CELL_CHKNOPASS) && j < max ); + + pc_setpos((TBL_PC *)bl,index,tx,ty,CLR_OUTSIGHT); + } + else + pc_setpos((TBL_PC *)bl,index,x2,y2,CLR_OUTSIGHT); + return 0; +} +BUILDIN_FUNC(areawarp) +{ + int16 m, x0,y0,x1,y1, x2,y2,x3=0,y3=0; + unsigned int index; + const char *str; + const char *mapname; + + mapname = script_getstr(st,2); + x0 = script_getnum(st,3); + y0 = script_getnum(st,4); + x1 = script_getnum(st,5); + y1 = script_getnum(st,6); + str = script_getstr(st,7); + x2 = script_getnum(st,8); + y2 = script_getnum(st,9); + + if( script_hasdata(st,10) && script_hasdata(st,11) ) { // Warp area to area + if( (x3 = script_getnum(st,10)) < 0 || (y3 = script_getnum(st,11)) < 0 ){ + x3 = 0; + y3 = 0; + } else if( x3 && y3 ) { + // normalize x3/y3 coordinates + if( x3 < x2 ) swap(x3,x2); + if( y3 < y2 ) swap(y3,y2); + } + } + + if( (m = map_mapname2mapid(mapname)) < 0 ) + return 0; + + if( strcmp(str,"Random") == 0 ) + index = 0; + else if( !(index=mapindex_name2id(str)) ) + return 0; + + map_foreachinarea(buildin_areawarp_sub, m,x0,y0,x1,y1, BL_PC, index,x2,y2,x3,y3); + return 0; +} + +/*========================================== + * areapercentheal <map>,<x1>,<y1>,<x2>,<y2>,<hp>,<sp> + *------------------------------------------*/ +static int buildin_areapercentheal_sub(struct block_list *bl,va_list ap) +{ + int hp, sp; + hp = va_arg(ap, int); + sp = va_arg(ap, int); + pc_percentheal((TBL_PC *)bl,hp,sp); + return 0; +} +BUILDIN_FUNC(areapercentheal) +{ + int hp,sp,m; + const char *mapname; + int x0,y0,x1,y1; + + mapname=script_getstr(st,2); + x0=script_getnum(st,3); + y0=script_getnum(st,4); + x1=script_getnum(st,5); + y1=script_getnum(st,6); + hp=script_getnum(st,7); + sp=script_getnum(st,8); + + if( (m=map_mapname2mapid(mapname))< 0) + return 0; + + map_foreachinarea(buildin_areapercentheal_sub,m,x0,y0,x1,y1,BL_PC,hp,sp); + return 0; +} + +/*========================================== + * warpchar [LuzZza] + * Useful for warp one player from + * another player npc-session. + * Using: warpchar "mapname",x,y,Char_ID; + *------------------------------------------*/ +BUILDIN_FUNC(warpchar) +{ + int x,y,a; + const char *str; + TBL_PC *sd; + + str=script_getstr(st,2); + x=script_getnum(st,3); + y=script_getnum(st,4); + a=script_getnum(st,5); + + sd = map_charid2sd(a); + if( sd == NULL ) + return 0; + + if(strcmp(str, "Random") == 0) + pc_randomwarp(sd, CLR_TELEPORT); + else + if(strcmp(str, "SavePoint") == 0) + pc_setpos(sd, sd->status.save_point.map,sd->status.save_point.x, sd->status.save_point.y, CLR_TELEPORT); + else + pc_setpos(sd, mapindex_name2id(str), x, y, CLR_TELEPORT); + + return 0; +} +/*========================================== + * Warpparty - [Fredzilla] [Paradox924X] + * Syntax: warpparty "to_mapname",x,y,Party_ID,{"from_mapname"}; + * If 'from_mapname' is specified, only the party members on that map will be warped + *------------------------------------------*/ +BUILDIN_FUNC(warpparty) +{ + TBL_PC *sd = NULL; + TBL_PC *pl_sd; + struct party_data* p; + int type; + int mapindex; + int i; + + const char* str = script_getstr(st,2); + int x = script_getnum(st,3); + int y = script_getnum(st,4); + int p_id = script_getnum(st,5); + const char* str2 = NULL; + if ( script_hasdata(st,6) ) + str2 = script_getstr(st,6); + + p = party_search(p_id); + if(!p) + return 0; + + type = ( strcmp(str,"Random")==0 ) ? 0 + : ( strcmp(str,"SavePointAll")==0 ) ? 1 + : ( strcmp(str,"SavePoint")==0 ) ? 2 + : ( strcmp(str,"Leader")==0 ) ? 3 + : 4; + + switch (type) + { + case 3: + for(i = 0; i < MAX_PARTY && !p->party.member[i].leader; i++); + if (i == MAX_PARTY || !p->data[i].sd) //Leader not found / not online + return 0; + pl_sd = p->data[i].sd; + mapindex = pl_sd->mapindex; + x = pl_sd->bl.x; + y = pl_sd->bl.y; + break; + case 4: + mapindex = mapindex_name2id(str); + break; + case 2: + //"SavePoint" uses save point of the currently attached player + if (( sd = script_rid2sd(st) ) == NULL ) + return 0; + default: + mapindex = 0; + break; + } + + for (i = 0; i < MAX_PARTY; i++) + { + if( !(pl_sd = p->data[i].sd) || pl_sd->status.party_id != p_id ) + continue; + + if( str2 && strcmp(str2, map[pl_sd->bl.m].name) != 0 ) + continue; + + if( pc_isdead(pl_sd) ) + continue; + + switch( type ) + { + case 0: // Random + if(!map[pl_sd->bl.m].flag.nowarp) + pc_randomwarp(pl_sd,CLR_TELEPORT); + break; + case 1: // SavePointAll + if(!map[pl_sd->bl.m].flag.noreturn) + pc_setpos(pl_sd,pl_sd->status.save_point.map,pl_sd->status.save_point.x,pl_sd->status.save_point.y,CLR_TELEPORT); + break; + case 2: // SavePoint + if(!map[pl_sd->bl.m].flag.noreturn) + pc_setpos(pl_sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,CLR_TELEPORT); + break; + case 3: // Leader + case 4: // m,x,y + if(!map[pl_sd->bl.m].flag.noreturn && !map[pl_sd->bl.m].flag.nowarp) + pc_setpos(pl_sd,mapindex,x,y,CLR_TELEPORT); + break; + } + } + + return 0; +} +/*========================================== + * Warpguild - [Fredzilla] + * Syntax: warpguild "mapname",x,y,Guild_ID; + *------------------------------------------*/ +BUILDIN_FUNC(warpguild) +{ + TBL_PC *sd = NULL; + TBL_PC *pl_sd; + struct guild* g; + struct s_mapiterator* iter; + int type; + + const char* str = script_getstr(st,2); + int x = script_getnum(st,3); + int y = script_getnum(st,4); + int gid = script_getnum(st,5); + + g = guild_search(gid); + if( g == NULL ) + return 0; + + type = ( strcmp(str,"Random")==0 ) ? 0 + : ( strcmp(str,"SavePointAll")==0 ) ? 1 + : ( strcmp(str,"SavePoint")==0 ) ? 2 + : 3; + + if( type == 2 && ( sd = script_rid2sd(st) ) == NULL ) + {// "SavePoint" uses save point of the currently attached player + return 0; + } + + iter = mapit_getallusers(); + for( pl_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); pl_sd = (TBL_PC*)mapit_next(iter) ) + { + if( pl_sd->status.guild_id != gid ) + continue; + + switch( type ) + { + case 0: // Random + if(!map[pl_sd->bl.m].flag.nowarp) + pc_randomwarp(pl_sd,CLR_TELEPORT); + break; + case 1: // SavePointAll + if(!map[pl_sd->bl.m].flag.noreturn) + pc_setpos(pl_sd,pl_sd->status.save_point.map,pl_sd->status.save_point.x,pl_sd->status.save_point.y,CLR_TELEPORT); + break; + case 2: // SavePoint + if(!map[pl_sd->bl.m].flag.noreturn) + pc_setpos(pl_sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,CLR_TELEPORT); + break; + case 3: // m,x,y + if(!map[pl_sd->bl.m].flag.noreturn && !map[pl_sd->bl.m].flag.nowarp) + pc_setpos(pl_sd,mapindex_name2id(str),x,y,CLR_TELEPORT); + break; + } + } + mapit_free(iter); + + return 0; +} +/*========================================== + * Force Heal a player (hp and sp) + *------------------------------------------*/ +BUILDIN_FUNC(heal) +{ + TBL_PC *sd; + int hp,sp; + + sd = script_rid2sd(st); + if (!sd) return 0; + + hp=script_getnum(st,2); + sp=script_getnum(st,3); + status_heal(&sd->bl, hp, sp, 1); + return 0; +} +/*========================================== + * Heal a player by item (get vit bonus etc) + *------------------------------------------*/ +BUILDIN_FUNC(itemheal) +{ + TBL_PC *sd; + int hp,sp; + + hp=script_getnum(st,2); + sp=script_getnum(st,3); + + if(potion_flag==1) { + potion_hp = hp; + potion_sp = sp; + return 0; + } + + sd = script_rid2sd(st); + if (!sd) return 0; + pc_itemheal(sd,sd->itemid,hp,sp); + return 0; +} +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(percentheal) +{ + int hp,sp; + TBL_PC* sd; + + hp=script_getnum(st,2); + sp=script_getnum(st,3); + + if(potion_flag==1) { + potion_per_hp = hp; + potion_per_sp = sp; + return 0; + } + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; +#ifdef RENEWAL + if( sd->sc.data[SC_EXTREMITYFIST2] ) + sp = 0; +#endif + pc_percentheal(sd,hp,sp); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(jobchange) +{ + int job, upper=-1; + + job=script_getnum(st,2); + if( script_hasdata(st,3) ) + upper=script_getnum(st,3); + + if (pcdb_checkid(job)) + { + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + pc_jobchange(sd, job, upper); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(jobname) +{ + int class_=script_getnum(st,2); + script_pushconststr(st, (char*)job_name(class_)); + return 0; +} + +/// Get input from the player. +/// For numeric inputs the value is capped to the range [min,max]. Returns 1 if +/// the value was higher than 'max', -1 if lower than 'min' and 0 otherwise. +/// For string inputs it returns 1 if the string was longer than 'max', -1 is +/// shorter than 'min' and 0 otherwise. +/// +/// input(<var>{,<min>{,<max>}}) -> <int> +BUILDIN_FUNC(input) +{ + TBL_PC* sd; + struct script_data* data; + int uid; + const char* name; + int min; + int max; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + data = script_getdata(st,2); + if( !data_isreference(data) ){ + ShowError("script:input: not a variable\n"); + script_reportdata(data); + st->state = END; + return 1; + } + uid = reference_getuid(data); + name = reference_getname(data); + min = (script_hasdata(st,3) ? script_getnum(st,3) : script_config.input_min_value); + max = (script_hasdata(st,4) ? script_getnum(st,4) : script_config.input_max_value); + + if( !sd->state.menu_or_input ) + { // first invocation, display npc input box + sd->state.menu_or_input = 1; + st->state = RERUNLINE; + if( is_string_variable(name) ) + clif_scriptinputstr(sd,st->oid); + else + clif_scriptinput(sd,st->oid); + } + else + { // take received text/value and store it in the designated variable + sd->state.menu_or_input = 0; + if( is_string_variable(name) ) + { + int len = (int)strlen(sd->npc_str); + set_reg(st, sd, uid, name, (void*)sd->npc_str, script_getref(st,2)); + script_pushint(st, (len > max ? 1 : len < min ? -1 : 0)); + } + else + { + int amount = sd->npc_amount; + set_reg(st, sd, uid, name, (void*)__64BPRTSIZE(cap_value(amount,min,max)), script_getref(st,2)); + script_pushint(st, (amount > max ? 1 : amount < min ? -1 : 0)); + } + st->state = RUN; + } + return 0; +} + +// declare the copyarray method here for future reference +BUILDIN_FUNC(copyarray); + +/// Sets the value of a variable. +/// The value is converted to the type of the variable. +/// +/// set(<variable>,<value>) -> <variable> +BUILDIN_FUNC(set) +{ + TBL_PC* sd = NULL; + struct script_data* data; + //struct script_data* datavalue; + int num; + const char* name; + char prefix; + + data = script_getdata(st,2); + //datavalue = script_getdata(st,3); + if( !data_isreference(data) ) + { + ShowError("script:set: not a variable\n"); + script_reportdata(script_getdata(st,2)); + st->state = END; + return 1; + } + + num = reference_getuid(data); + name = reference_getname(data); + prefix = *name; + + if( not_server_variable(prefix) ) + { + sd = script_rid2sd(st); + if( sd == NULL ) + { + ShowError("script:set: no player attached for player variable '%s'\n", name); + return 0; + } + } + +#if 0 + if( data_isreference(datavalue) ) + {// the value being referenced is a variable + const char* namevalue = reference_getname(datavalue); + + if( !not_array_variable(*namevalue) ) + {// array variable being copied into another array variable + if( sd == NULL && not_server_variable(*namevalue) && !(sd = script_rid2sd(st)) ) + {// player must be attached in order to copy a player variable + ShowError("script:set: no player attached for player variable '%s'\n", namevalue); + return 0; + } + + if( is_string_variable(namevalue) != is_string_variable(name) ) + {// non-matching array value types + ShowWarning("script:set: two array variables do not match in type.\n"); + return 0; + } + + // push the maximum number of array values to the stack + push_val(st->stack, C_INT, SCRIPT_MAX_ARRAYSIZE); + + // call the copy array method directly + return buildin_copyarray(st); + } + } +#endif + + if( is_string_variable(name) ) + set_reg(st,sd,num,name,(void*)script_getstr(st,3),script_getref(st,2)); + else + set_reg(st,sd,num,name,(void*)__64BPRTSIZE(script_getnum(st,3)),script_getref(st,2)); + + // return a copy of the variable reference + script_pushcopy(st,2); + + return 0; +} + +///////////////////////////////////////////////////////////////////// +/// Array variables +/// + +/// Returns the size of the specified array +static int32 getarraysize(struct script_state* st, int32 id, int32 idx, int isstring, struct DBMap** ref) +{ + int32 ret = idx; + + if( isstring ) + { + for( ; idx < SCRIPT_MAX_ARRAYSIZE; ++idx ) + { + char* str = (char*)get_val2(st, reference_uid(id, idx), ref); + if( str && *str ) + ret = idx + 1; + script_removetop(st, -1, 0); + } + } + else + { + for( ; idx < SCRIPT_MAX_ARRAYSIZE; ++idx ) + { + int32 num = (int32)__64BPRTSIZE(get_val2(st, reference_uid(id, idx), ref)); + if( num ) + ret = idx + 1; + script_removetop(st, -1, 0); + } + } + return ret; +} + +/// Sets values of an array, from the starting index. +/// ex: setarray arr[1],1,2,3; +/// +/// setarray <array variable>,<value1>{,<value2>...}; +BUILDIN_FUNC(setarray) +{ + struct script_data* data; + const char* name; + int32 start; + int32 end; + int32 id; + int32 i; + TBL_PC* sd = NULL; + + data = script_getdata(st, 2); + if( !data_isreference(data) ) + { + ShowError("script:setarray: not a variable\n"); + script_reportdata(data); + st->state = END; + return 1;// not a variable + } + + id = reference_getid(data); + start = reference_getindex(data); + name = reference_getname(data); + if( not_array_variable(*name) ) + { + ShowError("script:setarray: illegal scope\n"); + script_reportdata(data); + st->state = END; + return 1;// not supported + } + + if( not_server_variable(*name) ) + { + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached + } + + end = start + script_lastdata(st) - 2; + if( end > SCRIPT_MAX_ARRAYSIZE ) + end = SCRIPT_MAX_ARRAYSIZE; + + if( is_string_variable(name) ) + {// string array + for( i = 3; start < end; ++start, ++i ) + set_reg(st, sd, reference_uid(id, start), name, (void*)script_getstr(st,i), reference_getref(data)); + } + else + {// int array + for( i = 3; start < end; ++start, ++i ) + set_reg(st, sd, reference_uid(id, start), name, (void*)__64BPRTSIZE(script_getnum(st,i)), reference_getref(data)); + } + return 0; +} + +/// Sets count values of an array, from the starting index. +/// ex: cleararray arr[0],0,1; +/// +/// cleararray <array variable>,<value>,<count>; +BUILDIN_FUNC(cleararray) +{ + struct script_data* data; + const char* name; + int32 start; + int32 end; + int32 id; + void* v; + TBL_PC* sd = NULL; + + data = script_getdata(st, 2); + if( !data_isreference(data) ) + { + ShowError("script:cleararray: not a variable\n"); + script_reportdata(data); + st->state = END; + return 1;// not a variable + } + + id = reference_getid(data); + start = reference_getindex(data); + name = reference_getname(data); + if( not_array_variable(*name) ) + { + ShowError("script:cleararray: illegal scope\n"); + script_reportdata(data); + st->state = END; + return 1;// not supported + } + + if( not_server_variable(*name) ) + { + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached + } + + if( is_string_variable(name) ) + v = (void*)script_getstr(st, 3); + else + v = (void*)__64BPRTSIZE(script_getnum(st, 3)); + + end = start + script_getnum(st, 4); + if( end > SCRIPT_MAX_ARRAYSIZE ) + end = SCRIPT_MAX_ARRAYSIZE; + + for( ; start < end; ++start ) + set_reg(st, sd, reference_uid(id, start), name, v, script_getref(st,2)); + return 0; +} + +/// Copies data from one array to another. +/// ex: copyarray arr[0],arr[2],2; +/// +/// copyarray <destination array variable>,<source array variable>,<count>; +BUILDIN_FUNC(copyarray) +{ + struct script_data* data1; + struct script_data* data2; + const char* name1; + const char* name2; + int32 idx1; + int32 idx2; + int32 id1; + int32 id2; + void* v; + int32 i; + int32 count; + TBL_PC* sd = NULL; + + data1 = script_getdata(st, 2); + data2 = script_getdata(st, 3); + if( !data_isreference(data1) || !data_isreference(data2) ) + { + ShowError("script:copyarray: not a variable\n"); + script_reportdata(data1); + script_reportdata(data2); + st->state = END; + return 1;// not a variable + } + + id1 = reference_getid(data1); + id2 = reference_getid(data2); + idx1 = reference_getindex(data1); + idx2 = reference_getindex(data2); + name1 = reference_getname(data1); + name2 = reference_getname(data2); + if( not_array_variable(*name1) || not_array_variable(*name2) ) + { + ShowError("script:copyarray: illegal scope\n"); + script_reportdata(data1); + script_reportdata(data2); + st->state = END; + return 1;// not supported + } + + if( is_string_variable(name1) != is_string_variable(name2) ) + { + ShowError("script:copyarray: type mismatch\n"); + script_reportdata(data1); + script_reportdata(data2); + st->state = END; + return 1;// data type mismatch + } + + if( not_server_variable(*name1) || not_server_variable(*name2) ) + { + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached + } + + count = script_getnum(st, 4); + if( count > SCRIPT_MAX_ARRAYSIZE - idx1 ) + count = SCRIPT_MAX_ARRAYSIZE - idx1; + if( count <= 0 || (id1 == id2 && idx1 == idx2) ) + return 0;// nothing to copy + + if( id1 == id2 && idx1 > idx2 ) + {// destination might be overlapping the source - copy in reverse order + for( i = count - 1; i >= 0; --i ) + { + v = get_val2(st, reference_uid(id2, idx2 + i), reference_getref(data2)); + set_reg(st, sd, reference_uid(id1, idx1 + i), name1, v, reference_getref(data1)); + script_removetop(st, -1, 0); + } + } + else + {// normal copy + for( i = 0; i < count; ++i ) + { + if( idx2 + i < SCRIPT_MAX_ARRAYSIZE ) + { + v = get_val2(st, reference_uid(id2, idx2 + i), reference_getref(data2)); + set_reg(st, sd, reference_uid(id1, idx1 + i), name1, v, reference_getref(data1)); + script_removetop(st, -1, 0); + } + else// out of range - assume ""/0 + set_reg(st, sd, reference_uid(id1, idx1 + i), name1, (is_string_variable(name1)?(void*)"":(void*)0), reference_getref(data1)); + } + } + return 0; +} + +/// Returns the size of the array. +/// Assumes that everything before the starting index exists. +/// ex: getarraysize(arr[3]) +/// +/// getarraysize(<array variable>) -> <int> +BUILDIN_FUNC(getarraysize) +{ + struct script_data* data; + const char* name; + + data = script_getdata(st, 2); + if( !data_isreference(data) ) + { + ShowError("script:getarraysize: not a variable\n"); + script_reportdata(data); + script_pushnil(st); + st->state = END; + return 1;// not a variable + } + + name = reference_getname(data); + if( not_array_variable(*name) ) + { + ShowError("script:getarraysize: illegal scope\n"); + script_reportdata(data); + script_pushnil(st); + st->state = END; + return 1;// not supported + } + + script_pushint(st, getarraysize(st, reference_getid(data), reference_getindex(data), is_string_variable(name), reference_getref(data))); + return 0; +} + +/// Deletes count or all the elements in an array, from the starting index. +/// ex: deletearray arr[4],2; +/// +/// deletearray <array variable>; +/// deletearray <array variable>,<count>; +BUILDIN_FUNC(deletearray) +{ + struct script_data* data; + const char* name; + int start; + int end; + int id; + TBL_PC *sd = NULL; + + data = script_getdata(st, 2); + if( !data_isreference(data) ) + { + ShowError("script:deletearray: not a variable\n"); + script_reportdata(data); + st->state = END; + return 1;// not a variable + } + + id = reference_getid(data); + start = reference_getindex(data); + name = reference_getname(data); + if( not_array_variable(*name) ) + { + ShowError("script:deletearray: illegal scope\n"); + script_reportdata(data); + st->state = END; + return 1;// not supported + } + + if( not_server_variable(*name) ) + { + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached + } + + end = SCRIPT_MAX_ARRAYSIZE; + + if( start >= end ) + return 0;// nothing to free + + if( script_hasdata(st,3) ) + { + int count = script_getnum(st, 3); + if( count > end - start ) + count = end - start; + if( count <= 0 ) + return 0;// nothing to free + + // move rest of the elements backward + for( ; start + count < end; ++start ) + { + void* v = get_val2(st, reference_uid(id, start + count), reference_getref(data)); + set_reg(st, sd, reference_uid(id, start), name, v, reference_getref(data)); + script_removetop(st, -1, 0); + } + } + + // clear the rest of the array + if( is_string_variable(name) ) + { + for( ; start < end; ++start ) + set_reg(st, sd, reference_uid(id, start), name, (void *)"", reference_getref(data)); + } + else + { + for( ; start < end; ++start ) + set_reg(st, sd, reference_uid(id, start), name, (void*)0, reference_getref(data)); + } + return 0; +} + +/// Returns a reference to the target index of the array variable. +/// Equivalent to var[index]. +/// +/// getelementofarray(<array variable>,<index>) -> <variable reference> +BUILDIN_FUNC(getelementofarray) +{ + struct script_data* data; + const char* name; + int32 id; + int i; + + data = script_getdata(st, 2); + if( !data_isreference(data) ) + { + ShowError("script:getelementofarray: not a variable\n"); + script_reportdata(data); + script_pushnil(st); + st->state = END; + return 1;// not a variable + } + + id = reference_getid(data); + name = reference_getname(data); + if( not_array_variable(*name) ) + { + ShowError("script:getelementofarray: illegal scope\n"); + script_reportdata(data); + script_pushnil(st); + st->state = END; + return 1;// not supported + } + + i = script_getnum(st, 3); + if( i < 0 || i >= SCRIPT_MAX_ARRAYSIZE ) + { + ShowWarning("script:getelementofarray: index out of range (%d)\n", i); + script_reportdata(data); + script_pushnil(st); + st->state = END; + return 1;// out of range + } + + push_val2(st->stack, C_NAME, reference_uid(id, i), reference_getref(data)); + return 0; +} + +///////////////////////////////////////////////////////////////////// +/// ... +/// + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(setlook) +{ + int type,val; + TBL_PC* sd; + + type=script_getnum(st,2); + val=script_getnum(st,3); + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + pc_changelook(sd,type,val); + + return 0; +} + +BUILDIN_FUNC(changelook) +{ // As setlook but only client side + int type,val; + TBL_PC* sd; + + type=script_getnum(st,2); + val=script_getnum(st,3); + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + clif_changelook(&sd->bl,type,val); + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(cutin) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + clif_cutin(sd,script_getstr(st,2),script_getnum(st,3)); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(viewpoint) +{ + int type,x,y,id,color; + TBL_PC* sd; + + type=script_getnum(st,2); + x=script_getnum(st,3); + y=script_getnum(st,4); + id=script_getnum(st,5); + color=script_getnum(st,6); + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + clif_viewpoint(sd,st->oid,type,x,y,id,color); + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(countitem) +{ + int nameid, i; + int count = 0; + struct item_data* id = NULL; + struct script_data* data; + + TBL_PC* sd = script_rid2sd(st); + if (!sd) { + script_pushint(st,0); + return 0; + } + + data = script_getdata(st,2); + get_val(st, data); // convert into value in case of a variable + + if( data_isstring(data) ) + {// item name + id = itemdb_searchname(conv_str(st, data)); + } + else + {// item id + id = itemdb_exists(conv_num(st, data)); + } + + if( id == NULL ) + { + ShowError("buildin_countitem: Invalid item '%s'.\n", script_getstr(st,2)); // returns string, regardless of what it was + script_pushint(st,0); + return 1; + } + + nameid = id->nameid; + + for(i = 0; i < MAX_INVENTORY; i++) + if(sd->status.inventory[i].nameid == nameid) + count += sd->status.inventory[i].amount; + + script_pushint(st,count); + return 0; +} + +/*========================================== + * countitem2(nameID,Identified,Refine,Attribute,Card0,Card1,Card2,Card3) [Lupus] + * returns number of items that meet the conditions + *------------------------------------------*/ +BUILDIN_FUNC(countitem2) +{ + int nameid, iden, ref, attr, c1, c2, c3, c4; + int count = 0; + int i; + struct item_data* id = NULL; + struct script_data* data; + + TBL_PC* sd = script_rid2sd(st); + if (!sd) { + script_pushint(st,0); + return 0; + } + + data = script_getdata(st,2); + get_val(st, data); // convert into value in case of a variable + + if( data_isstring(data) ) + {// item name + id = itemdb_searchname(conv_str(st, data)); + } + else + {// item id + id = itemdb_exists(conv_num(st, data)); + } + + if( id == NULL ) + { + ShowError("buildin_countitem2: Invalid item '%s'.\n", script_getstr(st,2)); // returns string, regardless of what it was + script_pushint(st,0); + return 1; + } + + nameid = id->nameid; + iden = script_getnum(st,3); + ref = script_getnum(st,4); + attr = script_getnum(st,5); + c1 = (short)script_getnum(st,6); + c2 = (short)script_getnum(st,7); + c3 = (short)script_getnum(st,8); + c4 = (short)script_getnum(st,9); + + for(i = 0; i < MAX_INVENTORY; i++) + if (sd->status.inventory[i].nameid > 0 && sd->inventory_data[i] != NULL && + sd->status.inventory[i].amount > 0 && sd->status.inventory[i].nameid == nameid && + sd->status.inventory[i].identify == iden && sd->status.inventory[i].refine == ref && + sd->status.inventory[i].attribute == attr && sd->status.inventory[i].card[0] == c1 && + sd->status.inventory[i].card[1] == c2 && sd->status.inventory[i].card[2] == c3 && + sd->status.inventory[i].card[3] == c4 + ) + count += sd->status.inventory[i].amount; + + script_pushint(st,count); + return 0; +} + +/*========================================== + * Check if item with this amount can fit in inventory + * Checking : weight, stack amount >32k, slots amount >(MAX_INVENTORY) + * Return + * 0 : fail + * 1 : success (npc side only) + *------------------------------------------*/ +BUILDIN_FUNC(checkweight) +{ + int nameid, amount, slots, amount2=0; + unsigned int weight=0, i, nbargs; + struct item_data* id = NULL; + struct map_session_data* sd; + struct script_data* data; + + if( ( sd = script_rid2sd(st) ) == NULL ){ + return 0; + } + nbargs = script_lastdata(st)+1; + if(nbargs%2){ + ShowError("buildin_checkweight: Invalid nb of args should be a multiple of 2.\n"); + script_pushint(st,0); + return 1; + } + slots = pc_inventoryblank(sd); //nb of empty slot + + for(i=2; i<nbargs; i=i+2){ + data = script_getdata(st,i); + get_val(st, data); // convert into value in case of a variable + if( data_isstring(data) ){// item name + id = itemdb_searchname(conv_str(st, data)); + } else {// item id + id = itemdb_exists(conv_num(st, data)); + } + if( id == NULL ) { + ShowError("buildin_checkweight: Invalid item '%s'.\n", script_getstr(st,i)); // returns string, regardless of what it was + script_pushint(st,0); + return 1; + } + nameid = id->nameid; + + amount = script_getnum(st,i+1); + if( amount < 1 ) { + ShowError("buildin_checkweight: Invalid amount '%d'.\n", amount); + script_pushint(st,0); + return 1; + } + + weight += itemdb_weight(nameid)*amount; //total weight for all chk + if( weight + sd->weight > sd->max_weight ) + {// too heavy + script_pushint(st,0); + return 0; + } + + switch( pc_checkadditem(sd, nameid, amount) ) + { + case ADDITEM_EXIST: + // item is already in inventory, but there is still space for the requested amount + break; + case ADDITEM_NEW: + if( itemdb_isstackable(nameid) ) {// stackable + amount2++; + if( slots < amount2 ) { + script_pushint(st,0); + return 0; + } + } + else {// non-stackable + amount2 += amount; + if( slots < amount2){ + script_pushint(st,0); + return 0; + } + } + break; + case ADDITEM_OVERAMOUNT: + script_pushint(st,0); + return 0; + } + } + script_pushint(st,1); + return 0; +} + +BUILDIN_FUNC(checkweight2) +{ + //variable sub checkweight + int32 nameid=-1, amount=-1; + int i=0, amount2=0, slots=0, weight=0; + short fail=0; + + //variable for array parsing + struct script_data* data_it; + struct script_data* data_nb; + const char* name_it; + const char* name_nb; + int32 id_it, id_nb; + int32 idx_it, idx_nb; + int nb_it, nb_nb; //array size + + TBL_PC *sd = script_rid2sd(st); + nullpo_retr(1,sd); + + data_it = script_getdata(st, 2); + data_nb = script_getdata(st, 3); + + if( !data_isreference(data_it) || !data_isreference(data_nb)) + { + ShowError("script:checkweight2: parameter not a variable\n"); + script_pushint(st,0); + return 1;// not a variable + } + id_it = reference_getid(data_it); + id_nb = reference_getid(data_nb); + idx_it = reference_getindex(data_it); + idx_nb = reference_getindex(data_nb); + name_it = reference_getname(data_it); + name_nb = reference_getname(data_nb); + + if( not_array_variable(*name_it) || not_array_variable(*name_nb)) + { + ShowError("script:checkweight2: illegal scope\n"); + script_pushint(st,0); + return 1;// not supported + } + if(is_string_variable(name_it) || is_string_variable(name_nb)){ + ShowError("script:checkweight2: illegal type, need int\n"); + script_pushint(st,0); + return 1;// not supported + } + nb_it = getarraysize(st, id_it, idx_it, 0, reference_getref(data_it)); + nb_nb = getarraysize(st, id_nb, idx_nb, 0, reference_getref(data_nb)); + if(nb_it != nb_nb){ + ShowError("Size mistmatch: nb_it=%d, nb_nb=%d\n",nb_it,nb_nb); + fail = 1; + } + + slots = pc_inventoryblank(sd); + for(i=0; i<nb_it; i++){ + nameid = (int32)__64BPRTSIZE(get_val2(st,reference_uid(id_it,idx_it+i),reference_getref(data_it))); + script_removetop(st, -1, 0); + amount = (int32)__64BPRTSIZE(get_val2(st,reference_uid(id_nb,idx_nb+i),reference_getref(data_nb))); + script_removetop(st, -1, 0); + if(fail) continue; //cpntonie to depop rest + + if(itemdb_exists(nameid) == NULL ){ + ShowError("buildin_checkweight2: Invalid item '%d'.\n", nameid); + fail=1; + continue; + } + if(amount < 0 ){ + ShowError("buildin_checkweight2: Invalid amount '%d'.\n", amount); + fail = 1; + continue; + } + weight += itemdb_weight(nameid)*amount; + if( weight + sd->weight > sd->max_weight ){ + fail = 1; + continue; + } + switch( pc_checkadditem(sd, nameid, amount) ) { + case ADDITEM_EXIST: + // item is already in inventory, but there is still space for the requested amount + break; + case ADDITEM_NEW: + if( itemdb_isstackable(nameid) ){// stackable + amount2++; + if( slots < amount2 ) + fail = 1; + } + else {// non-stackable + amount2 += amount; + if( slots < amount2 ){ + fail = 1; + } + } + break; + case ADDITEM_OVERAMOUNT: + fail = 1; + } //end switch + } //end loop DO NOT break it prematurly we need to depop all stack + + fail?script_pushint(st,0):script_pushint(st,1); + return 0; +} + +/*========================================== + * getitem <item id>,<amount>{,<account ID>}; + * getitem "<item name>",<amount>{,<account ID>}; + *------------------------------------------*/ +BUILDIN_FUNC(getitem) +{ + int nameid,amount,get_count,i,flag = 0; + struct item it; + TBL_PC *sd; + struct script_data *data; + + data=script_getdata(st,2); + get_val(st,data); + if( data_isstring(data) ) + {// "<item name>" + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + if( item_data == NULL ){ + ShowError("buildin_getitem: Nonexistant item %s requested.\n", name); + return 1; //No item created. + } + nameid=item_data->nameid; + } else if( data_isint(data) ) + {// <item id> + nameid=conv_num(st,data); + //Violet Box, Blue Box, etc - random item pick + if( nameid < 0 ) { + nameid = -nameid; + flag = 1; + } + if( nameid <= 0 || !itemdb_exists(nameid) ){ + ShowError("buildin_getitem: Nonexistant item %d requested.\n", nameid); + return 1; //No item created. + } + } else { + ShowError("buildin_getitem: invalid data type for argument #1 (%d).", data->type); + return 1; + } + + // <amount> + if( (amount=script_getnum(st,3)) <= 0) + return 0; //return if amount <=0, skip the useles iteration + + memset(&it,0,sizeof(it)); + it.nameid=nameid; + if(!flag) + it.identify=1; + else + it.identify=itemdb_isidentified(nameid); + + if( script_hasdata(st,4) ) + sd=map_id2sd(script_getnum(st,4)); // <Account ID> + else + sd=script_rid2sd(st); // Attached player + + if( sd == NULL ) // no target + return 0; + + //Check if it's stackable. + if (!itemdb_isstackable(nameid)) + get_count = 1; + else + get_count = amount; + + for (i = 0; i < amount; i += get_count) + { + // if not pet egg + if (!pet_create_egg(sd, nameid)) + { + if ((flag = pc_additem(sd, &it, get_count, LOG_TYPE_SCRIPT))) + { + clif_additem(sd, 0, 0, flag); + if( pc_candrop(sd,&it) ) + map_addflooritem(&it,get_count,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + } + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(getitem2) +{ + int nameid,amount,get_count,i,flag = 0; + int iden,ref,attr,c1,c2,c3,c4; + struct item_data *item_data; + struct item item_tmp; + TBL_PC *sd; + struct script_data *data; + + if( script_hasdata(st,11) ) + sd=map_id2sd(script_getnum(st,11)); // <Account ID> + else + sd=script_rid2sd(st); // Attached player + + if( sd == NULL ) // no target + return 0; + + data=script_getdata(st,2); + get_val(st,data); + if( data_isstring(data) ){ + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + if( item_data ) + nameid=item_data->nameid; + else + nameid=UNKNOWN_ITEM_ID; + }else + nameid=conv_num(st,data); + + amount=script_getnum(st,3); + iden=script_getnum(st,4); + ref=script_getnum(st,5); + attr=script_getnum(st,6); + c1=(short)script_getnum(st,7); + c2=(short)script_getnum(st,8); + c3=(short)script_getnum(st,9); + c4=(short)script_getnum(st,10); + + if(nameid<0) { // Invalide nameid + nameid = -nameid; + flag = 1; + } + + if(nameid > 0) { + memset(&item_tmp,0,sizeof(item_tmp)); + item_data=itemdb_exists(nameid); + if (item_data == NULL) + return -1; + if(item_data->type==IT_WEAPON || item_data->type==IT_ARMOR){ + if(ref > MAX_REFINE) ref = MAX_REFINE; + } + else if(item_data->type==IT_PETEGG) { + 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==IT_WEAPON || item_data->type==IT_ARMOR) + item_tmp.identify=0; + item_tmp.refine=ref; + item_tmp.attribute=attr; + item_tmp.card[0]=(short)c1; + item_tmp.card[1]=(short)c2; + item_tmp.card[2]=(short)c3; + item_tmp.card[3]=(short)c4; + + //Check if it's stackable. + if (!itemdb_isstackable(nameid)) + get_count = 1; + else + get_count = amount; + + for (i = 0; i < amount; i += get_count) + { + // if not pet egg + if (!pet_create_egg(sd, nameid)) + { + if ((flag = pc_additem(sd, &item_tmp, get_count, LOG_TYPE_SCRIPT))) + { + clif_additem(sd, 0, 0, flag); + if( pc_candrop(sd,&item_tmp) ) + map_addflooritem(&item_tmp,get_count,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + } + } + } + + return 0; +} + +/*========================================== + * rentitem <item id>,<seconds> + * rentitem "<item name>",<seconds> + *------------------------------------------*/ +BUILDIN_FUNC(rentitem) +{ + struct map_session_data *sd; + struct script_data *data; + struct item it; + int seconds; + int nameid = 0, flag; + + data = script_getdata(st,2); + get_val(st,data); + + if( (sd = script_rid2sd(st)) == NULL ) + return 0; + + if( data_isstring(data) ) + { + const char *name = conv_str(st,data); + struct item_data *itd = itemdb_searchname(name); + if( itd == NULL ) + { + ShowError("buildin_rentitem: Nonexistant item %s requested.\n", name); + return 1; + } + nameid = itd->nameid; + } + else if( data_isint(data) ) + { + nameid = conv_num(st,data); + if( nameid <= 0 || !itemdb_exists(nameid) ) + { + ShowError("buildin_rentitem: Nonexistant item %d requested.\n", nameid); + return 1; + } + } + else + { + ShowError("buildin_rentitem: invalid data type for argument #1 (%d).\n", data->type); + return 1; + } + + seconds = script_getnum(st,3); + memset(&it, 0, sizeof(it)); + it.nameid = nameid; + it.identify = 1; + it.expire_time = (unsigned int)(time(NULL) + seconds); + + if( (flag = pc_additem(sd, &it, 1, LOG_TYPE_SCRIPT)) ) + { + clif_additem(sd, 0, 0, flag); + return 1; + } + + return 0; +} + +/*========================================== + * gets an item with someone's name inscribed [Skotlex] + * getinscribeditem item_num, character_name + * Returned Qty is always 1, only works on equip-able + * equipment + *------------------------------------------*/ +BUILDIN_FUNC(getnameditem) +{ + int nameid; + struct item item_tmp; + TBL_PC *sd, *tsd; + struct script_data *data; + + sd = script_rid2sd(st); + if (sd == NULL) + { //Player not attached! + script_pushint(st,0); + return 0; + } + + data=script_getdata(st,2); + get_val(st,data); + if( data_isstring(data) ){ + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + if( item_data == NULL) + { //Failed + script_pushint(st,0); + return 0; + } + nameid = item_data->nameid; + }else + nameid = conv_num(st,data); + + if(!itemdb_exists(nameid)/* || itemdb_isstackable(nameid)*/) + { //Even though named stackable items "could" be risky, they are required for certain quests. + script_pushint(st,0); + return 0; + } + + data=script_getdata(st,3); + get_val(st,data); + if( data_isstring(data) ) //Char Name + tsd=map_nick2sd(conv_str(st,data)); + else //Char Id was given + tsd=map_charid2sd(conv_num(st,data)); + + if( tsd == NULL ) + { //Failed + script_pushint(st,0); + return 0; + } + + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid=nameid; + item_tmp.amount=1; + item_tmp.identify=1; + item_tmp.card[0]=CARD0_CREATE; //we don't use 255! because for example SIGNED WEAPON shouldn't get TOP10 BS Fame bonus [Lupus] + item_tmp.card[2]=tsd->status.char_id; + item_tmp.card[3]=tsd->status.char_id >> 16; + if(pc_additem(sd,&item_tmp,1,LOG_TYPE_SCRIPT)) { + script_pushint(st,0); + return 0; //Failed to add item, we will not drop if they don't fit + } + + script_pushint(st,1); + return 0; +} + +/*========================================== + * gets a random item ID from an item group [Skotlex] + * groupranditem group_num + *------------------------------------------*/ +BUILDIN_FUNC(grouprandomitem) +{ + int group; + + group = script_getnum(st,2); + script_pushint(st,itemdb_searchrandomid(group)); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(makeitem) +{ + int nameid,amount,flag = 0; + int x,y,m; + const char *mapname; + struct item item_tmp; + struct script_data *data; + + data=script_getdata(st,2); + get_val(st,data); + if( data_isstring(data) ){ + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + if( item_data ) + nameid=item_data->nameid; + else + nameid=UNKNOWN_ITEM_ID; + }else + nameid=conv_num(st,data); + + amount=script_getnum(st,3); + mapname =script_getstr(st,4); + x =script_getnum(st,5); + y =script_getnum(st,6); + + if(strcmp(mapname,"this")==0) + { + TBL_PC *sd; + sd = script_rid2sd(st); + if (!sd) return 0; //Failed... + m=sd->bl.m; + } else + m=map_mapname2mapid(mapname); + + if(nameid<0) { + nameid = -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_isidentified(nameid); + + map_addflooritem(&item_tmp,amount,m,x,y,0,0,0,0); + } + + return 0; +} + + +/// Counts / deletes the current item given by idx. +/// Used by buildin_delitem_search +/// Relies on all input data being already fully valid. +static void buildin_delitem_delete(struct map_session_data* sd, int idx, int* amount, bool delete_items) +{ + int delamount; + struct item* inv = &sd->status.inventory[idx]; + + delamount = ( amount[0] < inv->amount ) ? amount[0] : inv->amount; + + if( delete_items ) + { + if( sd->inventory_data[idx]->type == IT_PETEGG && inv->card[0] == CARD0_PET ) + {// delete associated pet + intif_delete_petdata(MakeDWord(inv->card[1], inv->card[2])); + } + pc_delitem(sd, idx, delamount, 0, 0, LOG_TYPE_SCRIPT); + } + + amount[0]-= delamount; +} + + +/// Searches for item(s) and checks, if there is enough of them. +/// Used by delitem and delitem2 +/// Relies on all input data being already fully valid. +/// @param exact_match will also match item attributes and cards, not just name id +/// @return true when all items could be deleted, false when there were not enough items to delete +static bool buildin_delitem_search(struct map_session_data* sd, struct item* it, bool exact_match) +{ + bool delete_items = false; + int i, amount, important; + struct item* inv; + + // prefer always non-equipped items + it->equip = 0; + + // when searching for nameid only, prefer additionally + if( !exact_match ) + { + // non-refined items + it->refine = 0; + // card-less items + memset(it->card, 0, sizeof(it->card)); + } + + for(;;) + { + amount = it->amount; + important = 0; + + // 1st pass -- less important items / exact match + for( i = 0; amount && i < ARRAYLENGTH(sd->status.inventory); i++ ) + { + inv = &sd->status.inventory[i]; + + if( !inv->nameid || !sd->inventory_data[i] || inv->nameid != it->nameid ) + {// wrong/invalid item + continue; + } + + if( inv->equip != it->equip || inv->refine != it->refine ) + {// not matching attributes + important++; + continue; + } + + if( exact_match ) + { + if( inv->identify != it->identify || inv->attribute != it->attribute || memcmp(inv->card, it->card, sizeof(inv->card)) ) + {// not matching exact attributes + continue; + } + } + else + { + if( sd->inventory_data[i]->type == IT_PETEGG ) + { + if( inv->card[0] == CARD0_PET && CheckForCharServer() ) + {// pet which cannot be deleted + continue; + } + } + else if( memcmp(inv->card, it->card, sizeof(inv->card)) ) + {// named/carded item + important++; + continue; + } + } + + // count / delete item + buildin_delitem_delete(sd, i, &amount, delete_items); + } + + // 2nd pass -- any matching item + if( amount == 0 || important == 0 ) + {// either everything was already consumed or no items were skipped + ; + } + else for( i = 0; amount && i < ARRAYLENGTH(sd->status.inventory); i++ ) + { + inv = &sd->status.inventory[i]; + + if( !inv->nameid || !sd->inventory_data[i] || inv->nameid != it->nameid ) + {// wrong/invalid item + continue; + } + + if( sd->inventory_data[i]->type == IT_PETEGG && inv->card[0] == CARD0_PET && CheckForCharServer() ) + {// pet which cannot be deleted + continue; + } + + if( exact_match ) + { + if( inv->refine != it->refine || inv->identify != it->identify || inv->attribute != it->attribute || memcmp(inv->card, it->card, sizeof(inv->card)) ) + {// not matching attributes + continue; + } + } + + // count / delete item + buildin_delitem_delete(sd, i, &amount, delete_items); + } + + if( amount ) + {// not enough items + return false; + } + else if( delete_items ) + {// we are done with the work + return true; + } + else + {// get rid of the items now + delete_items = true; + } + } +} + + +/// Deletes items from the target/attached player. +/// Prioritizes ordinary items. +/// +/// delitem <item id>,<amount>{,<account id>} +/// delitem "<item name>",<amount>{,<account id>} +BUILDIN_FUNC(delitem) +{ + TBL_PC *sd; + struct item it; + struct script_data *data; + + if( script_hasdata(st,4) ) + { + int account_id = script_getnum(st,4); + sd = map_id2sd(account_id); // <account id> + if( sd == NULL ) + { + ShowError("script:delitem: player not found (AID=%d).\n", account_id); + st->state = END; + return 1; + } + } + else + { + sd = script_rid2sd(st);// attached player + if( sd == NULL ) + return 0; + } + + data = script_getdata(st,2); + get_val(st,data); + if( data_isstring(data) ) + { + const char* item_name = conv_str(st,data); + struct item_data* id = itemdb_searchname(item_name); + if( id == NULL ) + { + ShowError("script:delitem: unknown item \"%s\".\n", item_name); + st->state = END; + return 1; + } + it.nameid = id->nameid;// "<item name>" + } + else + { + it.nameid = conv_num(st,data);// <item id> + if( !itemdb_exists( it.nameid ) ) + { + ShowError("script:delitem: unknown item \"%d\".\n", it.nameid); + st->state = END; + return 1; + } + } + + it.amount=script_getnum(st,3); + + if( it.amount <= 0 ) + return 0;// nothing to do + + if( buildin_delitem_search(sd, &it, false) ) + {// success + return 0; + } + + ShowError("script:delitem: failed to delete %d items (AID=%d item_id=%d).\n", it.amount, sd->status.account_id, it.nameid); + st->state = END; + clif_scriptclose(sd, st->oid); + return 1; +} + +/// Deletes items from the target/attached player. +/// +/// delitem2 <item id>,<amount>,<identify>,<refine>,<attribute>,<card1>,<card2>,<card3>,<card4>{,<account ID>} +/// delitem2 "<Item name>",<amount>,<identify>,<refine>,<attribute>,<card1>,<card2>,<card3>,<card4>{,<account ID>} +BUILDIN_FUNC(delitem2) +{ + TBL_PC *sd; + struct item it; + struct script_data *data; + + if( script_hasdata(st,11) ) + { + int account_id = script_getnum(st,11); + sd = map_id2sd(account_id); // <account id> + if( sd == NULL ) + { + ShowError("script:delitem2: player not found (AID=%d).\n", account_id); + st->state = END; + return 1; + } + } + else + { + sd = script_rid2sd(st);// attached player + if( sd == NULL ) + return 0; + } + + data = script_getdata(st,2); + get_val(st,data); + if( data_isstring(data) ) + { + const char* item_name = conv_str(st,data); + struct item_data* id = itemdb_searchname(item_name); + if( id == NULL ) + { + ShowError("script:delitem2: unknown item \"%s\".\n", item_name); + st->state = END; + return 1; + } + it.nameid = id->nameid;// "<item name>" + } + else + { + it.nameid = conv_num(st,data);// <item id> + if( !itemdb_exists( it.nameid ) ) + { + ShowError("script:delitem: unknown item \"%d\".\n", it.nameid); + st->state = END; + return 1; + } + } + + it.amount=script_getnum(st,3); + it.identify=script_getnum(st,4); + it.refine=script_getnum(st,5); + it.attribute=script_getnum(st,6); + it.card[0]=(short)script_getnum(st,7); + it.card[1]=(short)script_getnum(st,8); + it.card[2]=(short)script_getnum(st,9); + it.card[3]=(short)script_getnum(st,10); + + if( it.amount <= 0 ) + return 0;// nothing to do + + if( buildin_delitem_search(sd, &it, true) ) + {// success + return 0; + } + + ShowError("script:delitem2: failed to delete %d items (AID=%d item_id=%d).\n", it.amount, sd->status.account_id, it.nameid); + st->state = END; + clif_scriptclose(sd, st->oid); + return 1; +} + +/*========================================== + * Enables/Disables use of items while in an NPC [Skotlex] + *------------------------------------------*/ +BUILDIN_FUNC(enableitemuse) +{ + TBL_PC *sd; + sd=script_rid2sd(st); + if (sd) + sd->npc_item_flag = st->oid; + return 0; +} + +BUILDIN_FUNC(disableitemuse) +{ + TBL_PC *sd; + sd=script_rid2sd(st); + if (sd) + sd->npc_item_flag = 0; + return 0; +} + +/*========================================== + * return the basic stats of sd + * chk pc_readparam for available type + *------------------------------------------*/ +BUILDIN_FUNC(readparam) +{ + int type; + TBL_PC *sd; + + type=script_getnum(st,2); + if( script_hasdata(st,3) ) + sd=map_nick2sd(script_getstr(st,3)); + else + sd=script_rid2sd(st); + + if(sd==NULL){ + script_pushint(st,-1); + return 0; + } + + script_pushint(st,pc_readparam(sd,type)); + + return 0; +} + +/*========================================== + * Return charid identification + * return by @num : + * 0 : char_id + * 1 : party_id + * 2 : guild_id + * 3 : account_id + * 4 : bg_id + *------------------------------------------*/ +BUILDIN_FUNC(getcharid) +{ + int num; + TBL_PC *sd; + + num = script_getnum(st,2); + if( script_hasdata(st,3) ) + sd=map_nick2sd(script_getstr(st,3)); + else + sd=script_rid2sd(st); + + if(sd==NULL){ + script_pushint(st,0); //return 0, according docs + return 0; + } + + switch( num ) { + case 0: script_pushint(st,sd->status.char_id); break; + case 1: script_pushint(st,sd->status.party_id); break; + case 2: script_pushint(st,sd->status.guild_id); break; + case 3: script_pushint(st,sd->status.account_id); break; + case 4: script_pushint(st,sd->bg_id); break; + default: + ShowError("buildin_getcharid: invalid parameter (%d).\n", num); + script_pushint(st,0); + break; + } + + return 0; +} +/*========================================== + * returns the GID of an NPC + *------------------------------------------*/ +BUILDIN_FUNC(getnpcid) +{ + int num = script_getnum(st,2); + struct npc_data* nd = NULL; + + if( script_hasdata(st,3) ) + {// unique npc name + if( ( nd = npc_name2id(script_getstr(st,3)) ) == NULL ) + { + ShowError("buildin_getnpcid: No such NPC '%s'.\n", script_getstr(st,3)); + script_pushint(st,0); + return 1; + } + } + + switch (num) { + case 0: + script_pushint(st,nd ? nd->bl.id : st->oid); + break; + default: + ShowError("buildin_getnpcid: invalid parameter (%d).\n", num); + script_pushint(st,0); + return 1; + } + + return 0; +} + +/*========================================== + * Return the name of the party_id + * null if not found + *------------------------------------------*/ +BUILDIN_FUNC(getpartyname) +{ + int party_id; + struct party_data* p; + + party_id = script_getnum(st,2); + + if( ( p = party_search(party_id) ) != NULL ) + { + script_pushstrcopy(st,p->party.name); + } + else + { + script_pushconststr(st,"null"); + } + return 0; +} + +/*========================================== + * Get the information of the members of a party by type + * @party_id, @type + * return by @type : + * - : nom des membres + * 1 : char_id des membres + * 2 : account_id des membres + *------------------------------------------*/ +BUILDIN_FUNC(getpartymember) +{ + struct party_data *p; + int i,j=0,type=0; + + p=party_search(script_getnum(st,2)); + + if( script_hasdata(st,3) ) + type=script_getnum(st,3); + + if(p!=NULL){ + for(i=0;i<MAX_PARTY;i++){ + if(p->party.member[i].account_id){ + switch (type) { + case 2: + mapreg_setreg(reference_uid(add_str("$@partymemberaid"), j),p->party.member[i].account_id); + break; + case 1: + mapreg_setreg(reference_uid(add_str("$@partymembercid"), j),p->party.member[i].char_id); + break; + default: + mapreg_setregstr(reference_uid(add_str("$@partymembername$"), j),p->party.member[i].name); + } + j++; + } + } + } + mapreg_setreg(add_str("$@partymembercount"),j); + + return 0; +} + +/*========================================== + * Retrieves party leader. if flag is specified, + * return some of the leader data. Otherwise, return name. + *------------------------------------------*/ +BUILDIN_FUNC(getpartyleader) +{ + int party_id, type = 0, i=0; + struct party_data *p; + + party_id=script_getnum(st,2); + if( script_hasdata(st,3) ) + type=script_getnum(st,3); + + p=party_search(party_id); + + if (p) //Search leader + for(i = 0; i < MAX_PARTY && !p->party.member[i].leader; i++); + + if (!p || i == MAX_PARTY) { //leader not found + if (type) + script_pushint(st,-1); + else + script_pushconststr(st,"null"); + return 0; + } + + switch (type) { + case 1: script_pushint(st,p->party.member[i].account_id); break; + case 2: script_pushint(st,p->party.member[i].char_id); break; + case 3: script_pushint(st,p->party.member[i].class_); break; + case 4: script_pushstrcopy(st,mapindex_id2name(p->party.member[i].map)); break; + case 5: script_pushint(st,p->party.member[i].lv); break; + default: script_pushstrcopy(st,p->party.member[i].name); break; + } + return 0; +} + +/*========================================== + * Return the name of the @guild_id + * null if not found + *------------------------------------------*/ +BUILDIN_FUNC(getguildname) +{ + int guild_id; + struct guild* g; + + guild_id = script_getnum(st,2); + + if( ( g = guild_search(guild_id) ) != NULL ) + { + script_pushstrcopy(st,g->name); + } + else + { + script_pushconststr(st,"null"); + } + return 0; +} + +/*========================================== + * Return the name of the guild master of @guild_id + * null if not found + *------------------------------------------*/ +BUILDIN_FUNC(getguildmaster) +{ + int guild_id; + struct guild* g; + + guild_id = script_getnum(st,2); + + if( ( g = guild_search(guild_id) ) != NULL ) + { + script_pushstrcopy(st,g->member[0].name); + } + else + { + script_pushconststr(st,"null"); + } + return 0; +} + +BUILDIN_FUNC(getguildmasterid) +{ + int guild_id; + struct guild* g; + + guild_id = script_getnum(st,2); + + if( ( g = guild_search(guild_id) ) != NULL ) + { + script_pushint(st,g->member[0].char_id); + } + else + { + script_pushint(st,0); + } + return 0; +} + +/*========================================== + * Get char string information by type : + * Return by @type : + * 0 : char_name + * 1 : party_name or "" + * 2 : guild_name or "" + * 3 : map_name + * - : "" + *------------------------------------------*/ +BUILDIN_FUNC(strcharinfo) +{ + TBL_PC *sd; + int num; + struct guild* g; + struct party_data* p; + + sd=script_rid2sd(st); + if (!sd) { //Avoid crashing.... + script_pushconststr(st,""); + return 0; + } + num=script_getnum(st,2); + switch(num){ + case 0: + script_pushstrcopy(st,sd->status.name); + break; + case 1: + if( ( p = party_search(sd->status.party_id) ) != NULL ) + { + script_pushstrcopy(st,p->party.name); + } + else + { + script_pushconststr(st,""); + } + break; + case 2: + if( ( g = guild_search(sd->status.guild_id) ) != NULL ) + { + script_pushstrcopy(st,g->name); + } + else + { + script_pushconststr(st,""); + } + break; + case 3: + script_pushconststr(st,map[sd->bl.m].name); + break; + default: + ShowWarning("buildin_strcharinfo: unknown parameter.\n"); + script_pushconststr(st,""); + break; + } + + return 0; +} + +/*========================================== + * Get npc string information by type + * return by @type: + * 0 : name + * 1 : str# + * 2 : #str + * 3 : ::str + * 4 : map name + *------------------------------------------*/ +BUILDIN_FUNC(strnpcinfo) +{ + TBL_NPC* nd; + int num; + char *buf,*name=NULL; + + nd = map_id2nd(st->oid); + if (!nd) { + script_pushconststr(st, ""); + return 0; + } + + num = script_getnum(st,2); + switch(num){ + case 0: // display name + name = aStrdup(nd->name); + break; + case 1: // visible part of display name + if((buf = strchr(nd->name,'#')) != NULL) + { + name = aStrdup(nd->name); + name[buf - nd->name] = 0; + } else // Return the name, there is no '#' present + name = aStrdup(nd->name); + break; + case 2: // # fragment + if((buf = strchr(nd->name,'#')) != NULL) + name = aStrdup(buf+1); + break; + case 3: // unique name + name = aStrdup(nd->exname); + break; + case 4: // map name + name = aStrdup(map[nd->bl.m].name); + break; + } + + if(name) + script_pushstr(st, name); + else + script_pushconststr(st, ""); + + return 0; +} + + +// aegis->athena slot position conversion table +static unsigned int equip[] = {EQP_HEAD_TOP,EQP_ARMOR,EQP_HAND_L,EQP_HAND_R,EQP_GARMENT,EQP_SHOES,EQP_ACC_L,EQP_ACC_R,EQP_HEAD_MID,EQP_HEAD_LOW}; + +/*========================================== + * GetEquipID(Pos); Pos: 1-10 + *------------------------------------------*/ +BUILDIN_FUNC(getequipid) +{ + int i, num; + TBL_PC* sd; + struct item_data* item; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + num = script_getnum(st,2) - 1; + if( num < 0 || num >= ARRAYLENGTH(equip) ) + { + script_pushint(st,-1); + return 0; + } + + // get inventory position of item + i = pc_checkequip(sd,equip[num]); + if( i < 0 ) + { + script_pushint(st,-1); + return 0; + } + + item = sd->inventory_data[i]; + if( item != 0 ) + script_pushint(st,item->nameid); + else + script_pushint(st,0); + + return 0; +} + +/*========================================== + * Get the equipement name at pos + * return item jname or "" + *------------------------------------------*/ +BUILDIN_FUNC(getequipname) +{ + int i, num; + TBL_PC* sd; + struct item_data* item; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + num = script_getnum(st,2) - 1; + if( num < 0 || num >= ARRAYLENGTH(equip) ) + { + script_pushconststr(st,""); + return 0; + } + + // get inventory position of item + i = pc_checkequip(sd,equip[num]); + if( i < 0 ) + { + script_pushint(st,-1); + return 0; + } + + item = sd->inventory_data[i]; + if( item != 0 ) + script_pushstrcopy(st,item->jname); + else + script_pushconststr(st,""); + + return 0; +} + +/*========================================== + * getbrokenid [Valaris] + *------------------------------------------*/ +BUILDIN_FUNC(getbrokenid) +{ + int i,num,id=0,brokencounter=0; + TBL_PC *sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + num=script_getnum(st,2); + for(i=0; i<MAX_INVENTORY; i++) { + if(sd->status.inventory[i].attribute){ + brokencounter++; + if(num==brokencounter){ + id=sd->status.inventory[i].nameid; + break; + } + } + } + + script_pushint(st,id); + + return 0; +} + +/*========================================== + * repair [Valaris] + *------------------------------------------*/ +BUILDIN_FUNC(repair) +{ + int i,num; + int repaircounter=0; + TBL_PC *sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + num=script_getnum(st,2); + for(i=0; i<MAX_INVENTORY; i++) { + if(sd->status.inventory[i].attribute){ + repaircounter++; + if(num==repaircounter){ + sd->status.inventory[i].attribute=0; + clif_equiplist(sd); + clif_produceeffect(sd, 0, sd->status.inventory[i].nameid); + clif_misceffect(&sd->bl, 3); + break; + } + } + } + + return 0; +} + +/*========================================== + * repairall + *------------------------------------------*/ +BUILDIN_FUNC(repairall) +{ + int i, repaircounter = 0; + TBL_PC *sd; + + sd = script_rid2sd(st); + if(sd == NULL) + return 0; + + for(i = 0; i < MAX_INVENTORY; i++) + { + if(sd->status.inventory[i].nameid && sd->status.inventory[i].attribute) + { + sd->status.inventory[i].attribute = 0; + clif_produceeffect(sd,0,sd->status.inventory[i].nameid); + repaircounter++; + } + } + + if(repaircounter) + { + clif_misceffect(&sd->bl, 3); + clif_equiplist(sd); + } + + return 0; +} + +/*========================================== + * Chk if player have something equiped at pos + *------------------------------------------*/ +BUILDIN_FUNC(getequipisequiped) +{ + int i = -1,num; + TBL_PC *sd; + + num = script_getnum(st,2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if (num > 0 && num <= ARRAYLENGTH(equip)) + i=pc_checkequip(sd,equip[num-1]); + + if(i >= 0) + script_pushint(st,1); + else + script_pushint(st,0); + return 0; +} + +/*========================================== + * Chk if the player have something equiped at pos + * if so chk if this item ain't marked not refinable or rental + * return (npc) + * 1 : true + * 0 : false + *------------------------------------------*/ +BUILDIN_FUNC(getequipisenableref) +{ + int i = -1,num; + TBL_PC *sd; + + num = script_getnum(st,2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if( num > 0 && num <= ARRAYLENGTH(equip) ) + i = pc_checkequip(sd,equip[num-1]); + if( i >= 0 && sd->inventory_data[i] && !sd->inventory_data[i]->flag.no_refine && !sd->status.inventory[i].expire_time ) + script_pushint(st,1); + else + script_pushint(st,0); + + return 0; +} + +/*========================================== + * Chk if the item equiped at pos is identify (huh ?) + * return (npc) + * 1 : true + * 0 : false + *------------------------------------------*/ +BUILDIN_FUNC(getequipisidentify) +{ + int i = -1,num; + TBL_PC *sd; + + num = script_getnum(st,2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if (num > 0 && num <= ARRAYLENGTH(equip)) + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0) + script_pushint(st,sd->status.inventory[i].identify); + else + script_pushint(st,0); + + return 0; +} + +/*========================================== + * Get the item refined value at pos + * return (npc) + * x : refine amount + * 0 : false (not refined) + *------------------------------------------*/ +BUILDIN_FUNC(getequiprefinerycnt) +{ + int i = -1,num; + TBL_PC *sd; + + num = script_getnum(st,2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if (num > 0 && num <= ARRAYLENGTH(equip)) + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0) + script_pushint(st,sd->status.inventory[i].refine); + else + script_pushint(st,0); + + return 0; +} + +/*========================================== + * Get the weapon level value at pos + * (pos should normally only be EQI_HAND_L or EQI_HAND_R) + * return (npc) + * x : weapon level + * 0 : false + *------------------------------------------*/ +BUILDIN_FUNC(getequipweaponlv) +{ + int i = -1,num; + TBL_PC *sd; + + num = script_getnum(st,2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if (num > 0 && num <= ARRAYLENGTH(equip)) + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0 && sd->inventory_data[i]) + script_pushint(st,sd->inventory_data[i]->wlv); + else + script_pushint(st,0); + + return 0; +} + +/*========================================== + * Get the item refine chance (from refine.txt) for item at pos + * return (npc) + * x : refine chance + * 0 : false (max refine level or unequip..) + *------------------------------------------*/ +BUILDIN_FUNC(getequippercentrefinery) +{ + int i = -1,num; + TBL_PC *sd; + + num = script_getnum(st,2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if (num > 0 && num <= ARRAYLENGTH(equip)) + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0 && sd->status.inventory[i].nameid && sd->status.inventory[i].refine < MAX_REFINE) + script_pushint(st,status_get_refine_chance(itemdb_wlv(sd->status.inventory[i].nameid), (int)sd->status.inventory[i].refine)); + else + script_pushint(st,0); + + return 0; +} + +/*========================================== + * Refine +1 item at pos and log and display refine + *------------------------------------------*/ +BUILDIN_FUNC(successrefitem) +{ + int i=-1,num,ep; + TBL_PC *sd; + + num = script_getnum(st,2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if (num > 0 && num <= ARRAYLENGTH(equip)) + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0) { + ep=sd->status.inventory[i].equip; + + //Logs items, got from (N)PC scripts [Lupus] + log_pick_pc(sd, LOG_TYPE_SCRIPT, -1, &sd->status.inventory[i]); + + sd->status.inventory[i].refine++; + pc_unequipitem(sd,i,2); // status calc will happen in pc_equipitem() below + + clif_refine(sd->fd,0,i,sd->status.inventory[i].refine); + clif_delitem(sd,i,1,3); + + //Logs items, got from (N)PC scripts [Lupus] + log_pick_pc(sd, LOG_TYPE_SCRIPT, 1, &sd->status.inventory[i]); + + clif_additem(sd,i,1,0); + pc_equipitem(sd,i,ep); + clif_misceffect(&sd->bl,3); + if(sd->status.inventory[i].refine == MAX_REFINE && + sd->status.inventory[i].card[0] == CARD0_FORGE && + sd->status.char_id == (int)MakeDWord(sd->status.inventory[i].card[2],sd->status.inventory[i].card[3]) + ){ // Fame point system [DracoRPG] + switch (sd->inventory_data[i]->wlv){ + case 1: + pc_addfame(sd,1); // Success to refine to +10 a lv1 weapon you forged = +1 fame point + break; + case 2: + pc_addfame(sd,25); // Success to refine to +10 a lv2 weapon you forged = +25 fame point + break; + case 3: + pc_addfame(sd,1000); // Success to refine to +10 a lv3 weapon you forged = +1000 fame point + break; + } + } + } + + return 0; +} + +/*========================================== + * Show a failed Refine +1 attempt + *------------------------------------------*/ +BUILDIN_FUNC(failedrefitem) +{ + int i=-1,num; + TBL_PC *sd; + + num = script_getnum(st,2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if (num > 0 && num <= ARRAYLENGTH(equip)) + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0) { + sd->status.inventory[i].refine = 0; + pc_unequipitem(sd,i,3); //recalculate bonus + clif_refine(sd->fd,1,i,sd->status.inventory[i].refine); //notify client of failure + + pc_delitem(sd,i,1,0,2,LOG_TYPE_SCRIPT); + + clif_misceffect(&sd->bl,2); // display failure effect + } + + return 0; +} + +/*========================================== + * Downgrades an Equipment Part by -1 . [Masao] + *------------------------------------------*/ +BUILDIN_FUNC(downrefitem) +{ + int i = -1,num,ep; + TBL_PC *sd; + + num = script_getnum(st,2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if (num > 0 && num <= ARRAYLENGTH(equip)) + i = pc_checkequip(sd,equip[num-1]); + if(i >= 0) { + ep = sd->status.inventory[i].equip; + + //Logs items, got from (N)PC scripts [Lupus] + log_pick_pc(sd, LOG_TYPE_SCRIPT, -1, &sd->status.inventory[i]); + + sd->status.inventory[i].refine++; + pc_unequipitem(sd,i,2); // status calc will happen in pc_equipitem() below + + clif_refine(sd->fd,2,i,sd->status.inventory[i].refine = sd->status.inventory[i].refine - 2); + clif_delitem(sd,i,1,3); + + //Logs items, got from (N)PC scripts [Lupus] + log_pick_pc(sd, LOG_TYPE_SCRIPT, 1, &sd->status.inventory[i]); + + clif_additem(sd,i,1,0); + pc_equipitem(sd,i,ep); + clif_misceffect(&sd->bl,2); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(statusup) +{ + int type; + TBL_PC *sd; + + type=script_getnum(st,2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + pc_statusup(sd,type); + + return 0; +} +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(statusup2) +{ + int type,val; + TBL_PC *sd; + + type=script_getnum(st,2); + val=script_getnum(st,3); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + pc_statusup2(sd,type,val); + + return 0; +} + +/// See 'doc/item_bonus.txt' +/// +/// bonus <bonus type>,<val1>; +/// bonus2 <bonus type>,<val1>,<val2>; +/// bonus3 <bonus type>,<val1>,<val2>,<val3>; +/// bonus4 <bonus type>,<val1>,<val2>,<val3>,<val4>; +/// bonus5 <bonus type>,<val1>,<val2>,<val3>,<val4>,<val5>; +BUILDIN_FUNC(bonus) +{ + int type; + int val1; + int val2 = 0; + int val3 = 0; + int val4 = 0; + int val5 = 0; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; // no player attached + + type = script_getnum(st,2); + switch( type ) { + case SP_AUTOSPELL: + case SP_AUTOSPELL_WHENHIT: + case SP_AUTOSPELL_ONSKILL: + case SP_SKILL_ATK: + case SP_SKILL_HEAL: + case SP_SKILL_HEAL2: + case SP_ADD_SKILL_BLOW: + case SP_CASTRATE: + case SP_ADDEFF_ONSKILL: + case SP_SKILL_USE_SP_RATE: + case SP_SKILL_COOLDOWN: + case SP_SKILL_FIXEDCAST: + case SP_SKILL_VARIABLECAST: + case SP_VARCASTRATE: + case SP_SKILL_USE_SP: + // these bonuses support skill names + val1 = ( script_isstring(st,3) ? skill_name2id(script_getstr(st,3)) : script_getnum(st,3) ); + break; + default: + val1 = script_getnum(st,3); + break; + } + + switch( script_lastdata(st)-2 ) { + case 1: + pc_bonus(sd, type, val1); + break; + case 2: + val2 = script_getnum(st,4); + pc_bonus2(sd, type, val1, val2); + break; + case 3: + val2 = script_getnum(st,4); + val3 = script_getnum(st,5); + pc_bonus3(sd, type, val1, val2, val3); + break; + case 4: + if( type == SP_AUTOSPELL_ONSKILL && script_isstring(st,4) ) + val2 = skill_name2id(script_getstr(st,4)); // 2nd value can be skill name + else + val2 = script_getnum(st,4); + + val3 = script_getnum(st,5); + val4 = script_getnum(st,6); + pc_bonus4(sd, type, val1, val2, val3, val4); + break; + case 5: + if( type == SP_AUTOSPELL_ONSKILL && script_isstring(st,4) ) + val2 = skill_name2id(script_getstr(st,4)); // 2nd value can be skill name + else + val2 = script_getnum(st,4); + + val3 = script_getnum(st,5); + val4 = script_getnum(st,6); + val5 = script_getnum(st,7); + pc_bonus5(sd, type, val1, val2, val3, val4, val5); + break; + default: + ShowDebug("buildin_bonus: unexpected number of arguments (%d)\n", (script_lastdata(st) - 1)); + break; + } + + return 0; +} + +BUILDIN_FUNC(autobonus) +{ + unsigned int dur; + short rate; + short atk_type = 0; + TBL_PC* sd; + const char *bonus_script, *other_script = NULL; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; // no player attached + + if( sd->state.autobonus&sd->status.inventory[current_equip_item_index].equip ) + return 0; + + rate = script_getnum(st,3); + dur = script_getnum(st,4); + bonus_script = script_getstr(st,2); + if( !rate || !dur || !bonus_script ) + return 0; + + if( script_hasdata(st,5) ) + atk_type = script_getnum(st,5); + if( script_hasdata(st,6) ) + other_script = script_getstr(st,6); + + if( pc_addautobonus(sd->autobonus,ARRAYLENGTH(sd->autobonus), + bonus_script,rate,dur,atk_type,other_script,sd->status.inventory[current_equip_item_index].equip,false) ) + { + script_add_autobonus(bonus_script); + if( other_script ) + script_add_autobonus(other_script); + } + + return 0; +} + +BUILDIN_FUNC(autobonus2) +{ + unsigned int dur; + short rate; + short atk_type = 0; + TBL_PC* sd; + const char *bonus_script, *other_script = NULL; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; // no player attached + + if( sd->state.autobonus&sd->status.inventory[current_equip_item_index].equip ) + return 0; + + rate = script_getnum(st,3); + dur = script_getnum(st,4); + bonus_script = script_getstr(st,2); + if( !rate || !dur || !bonus_script ) + return 0; + + if( script_hasdata(st,5) ) + atk_type = script_getnum(st,5); + if( script_hasdata(st,6) ) + other_script = script_getstr(st,6); + + if( pc_addautobonus(sd->autobonus2,ARRAYLENGTH(sd->autobonus2), + bonus_script,rate,dur,atk_type,other_script,sd->status.inventory[current_equip_item_index].equip,false) ) + { + script_add_autobonus(bonus_script); + if( other_script ) + script_add_autobonus(other_script); + } + + return 0; +} + +BUILDIN_FUNC(autobonus3) +{ + unsigned int dur; + short rate,atk_type; + TBL_PC* sd; + const char *bonus_script, *other_script = NULL; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; // no player attached + + if( sd->state.autobonus&sd->status.inventory[current_equip_item_index].equip ) + return 0; + + rate = script_getnum(st,3); + dur = script_getnum(st,4); + atk_type = ( script_isstring(st,5) ? skill_name2id(script_getstr(st,5)) : script_getnum(st,5) ); + bonus_script = script_getstr(st,2); + if( !rate || !dur || !atk_type || !bonus_script ) + return 0; + + if( script_hasdata(st,6) ) + other_script = script_getstr(st,6); + + if( pc_addautobonus(sd->autobonus3,ARRAYLENGTH(sd->autobonus3), + bonus_script,rate,dur,atk_type,other_script,sd->status.inventory[current_equip_item_index].equip,true) ) + { + script_add_autobonus(bonus_script); + if( other_script ) + script_add_autobonus(other_script); + } + + return 0; +} + +/// Changes the level of a player skill. +/// <flag> defaults to 1 +/// <flag>=0 : set the level of the skill +/// <flag>=1 : set the temporary level of the skill +/// <flag>=2 : add to the level of the skill +/// +/// skill <skill id>,<level>,<flag> +/// skill <skill id>,<level> +/// skill "<skill name>",<level>,<flag> +/// skill "<skill name>",<level> +BUILDIN_FUNC(skill) +{ + int id; + int level; + int flag = 1; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + id = ( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); + level = script_getnum(st,3); + if( script_hasdata(st,4) ) + flag = script_getnum(st,4); + pc_skill(sd, id, level, flag); + + return 0; +} + +/// Changes the level of a player skill. +/// like skill, but <flag> defaults to 2 +/// +/// addtoskill <skill id>,<amount>,<flag> +/// addtoskill <skill id>,<amount> +/// addtoskill "<skill name>",<amount>,<flag> +/// addtoskill "<skill name>",<amount> +/// +/// @see skill +BUILDIN_FUNC(addtoskill) +{ + int id; + int level; + int flag = 2; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + id = ( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); + level = script_getnum(st,3); + if( script_hasdata(st,4) ) + flag = script_getnum(st,4); + pc_skill(sd, id, level, flag); + + return 0; +} + +/// Increases the level of a guild skill. +/// +/// guildskill <skill id>,<amount>; +/// guildskill "<skill name>",<amount>; +BUILDIN_FUNC(guildskill) +{ + int id; + int level; + TBL_PC* sd; + int i; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + id = ( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); + level = script_getnum(st,3); + for( i=0; i < level; i++ ) + guild_skillup(sd, id); + + return 0; +} + +/// Returns the level of the player skill. +/// +/// getskilllv(<skill id>) -> <level> +/// getskilllv("<skill name>") -> <level> +BUILDIN_FUNC(getskilllv) +{ + int id; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + id = ( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); + script_pushint(st, pc_checkskill(sd,id)); + + return 0; +} + +/// Returns the level of the guild skill. +/// +/// getgdskilllv(<guild id>,<skill id>) -> <level> +/// getgdskilllv(<guild id>,"<skill name>") -> <level> +BUILDIN_FUNC(getgdskilllv) +{ + int guild_id; + uint16 skill_id; + struct guild* g; + + guild_id = script_getnum(st,2); + skill_id = ( script_isstring(st,3) ? skill_name2id(script_getstr(st,3)) : script_getnum(st,3) ); + g = guild_search(guild_id); + if( g == NULL ) + script_pushint(st, -1); + else + script_pushint(st, guild_checkskill(g,skill_id)); + + return 0; +} + +/// Returns the 'basic_skill_check' setting. +/// This config determines if the server checks the skill level of NV_BASIC +/// before allowing the basic actions. +/// +/// basicskillcheck() -> <bool> +BUILDIN_FUNC(basicskillcheck) +{ + script_pushint(st, battle_config.basic_skill_check); + return 0; +} + +/// Returns the GM level of the player. +/// +/// getgmlevel() -> <level> +BUILDIN_FUNC(getgmlevel) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + script_pushint(st, pc_get_group_level(sd)); + + return 0; +} + +/// Returns the group ID of the player. +/// +/// getgroupid() -> <int> +BUILDIN_FUNC(getgroupid) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if (sd == NULL) + return 1; // no player attached, report source + script_pushint(st, pc_get_group_id(sd)); + + return 0; +} + +/// Terminates the execution of this script instance. +/// +/// end +BUILDIN_FUNC(end) +{ + st->state = END; + return 0; +} + +/// Checks if the player has that effect state (option). +/// +/// checkoption(<option>) -> <bool> +BUILDIN_FUNC(checkoption) +{ + int option; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + option = script_getnum(st,2); + if( sd->sc.option&option ) + script_pushint(st, 1); + else + script_pushint(st, 0); + + return 0; +} + +/// Checks if the player is in that body state (opt1). +/// +/// checkoption1(<opt1>) -> <bool> +BUILDIN_FUNC(checkoption1) +{ + int opt1; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + opt1 = script_getnum(st,2); + if( sd->sc.opt1 == opt1 ) + script_pushint(st, 1); + else + script_pushint(st, 0); + + return 0; +} + +/// Checks if the player has that health state (opt2). +/// +/// checkoption2(<opt2>) -> <bool> +BUILDIN_FUNC(checkoption2) +{ + int opt2; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + opt2 = script_getnum(st,2); + if( sd->sc.opt2&opt2 ) + script_pushint(st, 1); + else + script_pushint(st, 0); + + return 0; +} + +/// Changes the effect state (option) of the player. +/// <flag> defaults to 1 +/// <flag>=0 : removes the option +/// <flag>=other : adds the option +/// +/// setoption <option>,<flag>; +/// setoption <option>; +BUILDIN_FUNC(setoption) +{ + int option; + int flag = 1; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + option = script_getnum(st,2); + if( script_hasdata(st,3) ) + flag = script_getnum(st,3); + else if( !option ){// Request to remove everything. + flag = 0; + option = OPTION_FALCON|OPTION_RIDING; +#ifndef NEW_CARTS + option |= OPTION_CART; +#endif + } + if( flag ){// Add option + if( option&OPTION_WEDDING && !battle_config.wedding_modifydisplay ) + option &= ~OPTION_WEDDING;// Do not show the wedding sprites + pc_setoption(sd, sd->sc.option|option); + } else// Remove option + pc_setoption(sd, sd->sc.option&~option); + + return 0; +} + +/// Returns if the player has a cart. +/// +/// checkcart() -> <bool> +/// +/// @author Valaris +BUILDIN_FUNC(checkcart) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + if( pc_iscarton(sd) ) + script_pushint(st, 1); + else + script_pushint(st, 0); + + return 0; +} + +/// Sets the cart of the player. +/// <type> defaults to 1 +/// <type>=0 : removes the cart +/// <type>=1 : Normal cart +/// <type>=2 : Wooden cart +/// <type>=3 : Covered cart with flowers and ferns +/// <type>=4 : Wooden cart with a Panda doll on the back +/// <type>=5 : Normal cart with bigger wheels, a roof and a banner on the back +/// +/// setcart <type>; +/// setcart; +BUILDIN_FUNC(setcart) +{ + int type = 1; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + if( script_hasdata(st,2) ) + type = script_getnum(st,2); + pc_setcart(sd, type); + + return 0; +} + +/// Returns if the player has a falcon. +/// +/// checkfalcon() -> <bool> +/// +/// @author Valaris +BUILDIN_FUNC(checkfalcon) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + if( pc_isfalcon(sd) ) + script_pushint(st, 1); + else + script_pushint(st, 0); + + return 0; +} + +/// Sets if the player has a falcon or not. +/// <flag> defaults to 1 +/// +/// setfalcon <flag>; +/// setfalcon; +BUILDIN_FUNC(setfalcon) +{ + int flag = 1; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + if( script_hasdata(st,2) ) + flag = script_getnum(st,2); + + pc_setfalcon(sd, flag); + + return 0; +} + +/// Returns if the player is riding. +/// +/// checkriding() -> <bool> +/// +/// @author Valaris +BUILDIN_FUNC(checkriding) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + if( pc_isriding(sd) || pc_isridingwug(sd) || pc_isridingdragon(sd) ) + script_pushint(st, 1); + else + script_pushint(st, 0); + + return 0; +} + +/// Sets if the player is riding. +/// <flag> defaults to 1 +/// +/// setriding <flag>; +/// setriding; +BUILDIN_FUNC(setriding) +{ + int flag = 1; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + if( script_hasdata(st,2) ) + flag = script_getnum(st,2); + pc_setriding(sd, flag); + + return 0; +} + +/// Returns if the player has a warg. +/// +/// checkwug() -> <bool> +/// +BUILDIN_FUNC(checkwug) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + if( pc_iswug(sd) || pc_isridingwug(sd) ) + script_pushint(st, 1); + else + script_pushint(st, 0); + + return 0; +} + +/// Returns if the player is wearing MADO Gear. +/// +/// checkmadogear() -> <bool> +/// +BUILDIN_FUNC(checkmadogear) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + if( pc_ismadogear(sd) ) + script_pushint(st, 1); + else + script_pushint(st, 0); + + return 0; +} + +/// Sets if the player is riding MADO Gear. +/// <flag> defaults to 1 +/// +/// setmadogear <flag>; +/// setmadogear; +BUILDIN_FUNC(setmadogear) +{ + int flag = 1; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + if( script_hasdata(st,2) ) + flag = script_getnum(st,2); + pc_setmadogear(sd, flag); + + return 0; +} + +/// Sets the save point of the player. +/// +/// save "<map name>",<x>,<y> +/// savepoint "<map name>",<x>,<y> +BUILDIN_FUNC(savepoint) +{ + int x; + int y; + short map; + const char* str; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached, report source + + str = script_getstr(st, 2); + x = script_getnum(st,3); + y = script_getnum(st,4); + map = mapindex_name2id(str); + if( map ) + pc_setsavepoint(sd, map, x, y); + + return 0; +} + +/*========================================== + * GetTimeTick(0: System Tick, 1: Time Second Tick) + *------------------------------------------*/ +BUILDIN_FUNC(gettimetick) /* Asgard Version */ +{ + int type; + time_t timer; + struct tm *t; + + type=script_getnum(st,2); + + switch(type){ + case 2: + //type 2:(Get the number of seconds elapsed since 00:00 hours, Jan 1, 1970 UTC + // from the system clock.) + script_pushint(st,(int)time(NULL)); + break; + case 1: + //type 1:(Second Ticks: 0-86399, 00:00:00-23:59:59) + time(&timer); + t=localtime(&timer); + script_pushint(st,((t->tm_hour)*3600+(t->tm_min)*60+t->tm_sec)); + break; + case 0: + default: + //type 0:(System Ticks) + script_pushint(st,gettick()); + break; + } + return 0; +} + +/*========================================== + * GetTime(Type); + * 1: Sec 2: Min 3: Hour + * 4: WeekDay 5: MonthDay 6: Month + * 7: Year + *------------------------------------------*/ +BUILDIN_FUNC(gettime) /* Asgard Version */ +{ + int type; + time_t timer; + struct tm *t; + + type=script_getnum(st,2); + + time(&timer); + t=localtime(&timer); + + switch(type){ + case 1://Sec(0~59) + script_pushint(st,t->tm_sec); + break; + case 2://Min(0~59) + script_pushint(st,t->tm_min); + break; + case 3://Hour(0~23) + script_pushint(st,t->tm_hour); + break; + case 4://WeekDay(0~6) + script_pushint(st,t->tm_wday); + break; + case 5://MonthDay(01~31) + script_pushint(st,t->tm_mday); + break; + case 6://Month(01~12) + script_pushint(st,t->tm_mon+1); + break; + case 7://Year(20xx) + script_pushint(st,t->tm_year+1900); + break; + case 8://Year Day(01~366) + script_pushint(st,t->tm_yday+1); + break; + default://(format error) + script_pushint(st,-1); + break; + } + return 0; +} + +/*========================================== + * GetTimeStr("TimeFMT", Length); + *------------------------------------------*/ +BUILDIN_FUNC(gettimestr) +{ + char *tmpstr; + const char *fmtstr; + int maxlen; + time_t now = time(NULL); + + fmtstr=script_getstr(st,2); + maxlen=script_getnum(st,3); + + tmpstr=(char *)aMalloc((maxlen+1)*sizeof(char)); + strftime(tmpstr,maxlen,fmtstr,localtime(&now)); + tmpstr[maxlen]='\0'; + + script_pushstr(st,tmpstr); + return 0; +} + +/*========================================== + * Open player storage + *------------------------------------------*/ +BUILDIN_FUNC(openstorage) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + storage_storageopen(sd); + return 0; +} + +BUILDIN_FUNC(guildopenstorage) +{ + TBL_PC* sd; + int ret; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + ret = storage_guild_storageopen(sd); + script_pushint(st,ret); + return 0; +} + +/*========================================== + * Make player use a skill trought item usage + *------------------------------------------*/ +/// itemskill <skill id>,<level> +/// itemskill "<skill name>",<level> +BUILDIN_FUNC(itemskill) +{ + int id; + int lv; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL || sd->ud.skilltimer != INVALID_TIMER ) + return 0; + + id = ( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); + lv = script_getnum(st,3); + + sd->skillitem=id; + sd->skillitemlv=lv; + clif_item_skill(sd,id,lv); + return 0; +} +/*========================================== + * Attempt to create an item + *------------------------------------------*/ +BUILDIN_FUNC(produce) +{ + int trigger; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + trigger=script_getnum(st,2); + clif_skill_produce_mix_list(sd, -1, trigger); + return 0; +} +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(cooking) +{ + int trigger; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + trigger=script_getnum(st,2); + clif_cooking_list(sd, trigger, AM_PHARMACY, 1, 1); + return 0; +} +/*========================================== + * Create a pet + *------------------------------------------*/ +BUILDIN_FUNC(makepet) +{ + TBL_PC* sd; + int id,pet_id; + + id=script_getnum(st,2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + 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, + (short)pet_db[pet_id].class_, (short)mob_db(pet_db[pet_id].class_)->lv, + (short)pet_db[pet_id].EggID, 0, (short)pet_db[pet_id].intimate, + 100, 0, 1, pet_db[pet_id].jname); + } + + return 0; +} +/*========================================== + * Give player exp base,job * quest_exp_rate/100 + *------------------------------------------*/ +BUILDIN_FUNC(getexp) +{ + TBL_PC* sd; + int base=0,job=0; + double bonus; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + base=script_getnum(st,2); + job =script_getnum(st,3); + if(base<0 || job<0) + return 0; + + // bonus for npc-given exp + bonus = battle_config.quest_exp_rate / 100.; + base = (int) cap_value(base * bonus, 0, INT_MAX); + job = (int) cap_value(job * bonus, 0, INT_MAX); + + pc_gainexp(sd, NULL, base, job, true); + + return 0; +} + +/*========================================== + * Gain guild exp [Celest] + *------------------------------------------*/ +BUILDIN_FUNC(guildgetexp) +{ + TBL_PC* sd; + int exp; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + exp = script_getnum(st,2); + if(exp < 0) + return 0; + if(sd && sd->status.guild_id > 0) + guild_getexp (sd, exp); + + return 0; +} + +/*========================================== + * Changes the guild master of a guild [Skotlex] + *------------------------------------------*/ +BUILDIN_FUNC(guildchangegm) +{ + TBL_PC *sd; + int guild_id; + const char *name; + + guild_id = script_getnum(st,2); + name = script_getstr(st,3); + sd=map_nick2sd(name); + + if (!sd) + script_pushint(st,0); + else + script_pushint(st,guild_gm_change(guild_id, sd)); + + return 0; +} + +/*========================================== + * Spawn a monster : + @mapn,x,y : location + @str : monster name + @class_ : mob_id + @amount : nb to spawn + @event : event to attach to mob + *------------------------------------------*/ +BUILDIN_FUNC(monster) +{ + const char* mapn = script_getstr(st,2); + int x = script_getnum(st,3); + int y = script_getnum(st,4); + const char* str = script_getstr(st,5); + int class_ = script_getnum(st,6); + int amount = script_getnum(st,7); + const char* event = ""; + unsigned int size = SZ_SMALL; + unsigned int ai = AI_NONE; + + struct map_session_data* sd; + int16 m; + + if (script_hasdata(st, 8)) + { + event = script_getstr(st, 8); + check_event(st, event); + } + + if (script_hasdata(st, 9)) + { + size = script_getnum(st, 9); + if (size > 3) + { + ShowWarning("buildin_monster: Attempted to spawn non-existing size %d for monster class %d\n", size, class_); + return 1; + } + } + + if (script_hasdata(st, 10)) + { + ai = script_getnum(st, 10); + if (ai > 4) + { + ShowWarning("buildin_monster: Attempted to spawn non-existing ai %d for monster class %d\n", ai, class_); + return 1; + } + } + + if (class_ >= 0 && !mobdb_checkid(class_)) + { + ShowWarning("buildin_monster: Attempted to spawn non-existing monster class %d\n", class_); + return 1; + } + + sd = map_id2sd(st->rid); + + if (sd && strcmp(mapn, "this") == 0) + m = sd->bl.m; + else + { + m = map_mapname2mapid(mapn); + if (map[m].flag.src4instance && st->instance_id) + { // Try to redirect to the instance map, not the src map + if ((m = instance_mapid2imapid(m, st->instance_id)) < 0) + { + ShowError("buildin_monster: Trying to spawn monster (%d) on instance map (%s) without instance attached.\n", class_, mapn); + return 1; + } + } + } + + mob_once_spawn(sd, m, x, y, str, class_, amount, event, size, ai); + return 0; +} +/*========================================== + * Request List of Monster Drops + *------------------------------------------*/ +BUILDIN_FUNC(getmobdrops) +{ + int class_ = script_getnum(st,2); + int i, j = 0; + struct mob_db *mob; + + if( !mobdb_checkid(class_) ) + { + script_pushint(st, 0); + return 0; + } + + mob = mob_db(class_); + + for( i = 0; i < MAX_MOB_DROP; i++ ) + { + if( mob->dropitem[i].nameid < 1 ) + continue; + if( itemdb_exists(mob->dropitem[i].nameid) == NULL ) + continue; + + mapreg_setreg(reference_uid(add_str("$@MobDrop_item"), j), mob->dropitem[i].nameid); + mapreg_setreg(reference_uid(add_str("$@MobDrop_rate"), j), mob->dropitem[i].p); + + j++; + } + + mapreg_setreg(add_str("$@MobDrop_count"), j); + script_pushint(st, 1); + + return 0; +} +/*========================================== + * Same as monster but randomize location in x0,x1,y0,y1 area + *------------------------------------------*/ +BUILDIN_FUNC(areamonster) +{ + const char* mapn = script_getstr(st,2); + int x0 = script_getnum(st,3); + int y0 = script_getnum(st,4); + int x1 = script_getnum(st,5); + int y1 = script_getnum(st,6); + const char* str = script_getstr(st,7); + int class_ = script_getnum(st,8); + int amount = script_getnum(st,9); + const char* event = ""; + unsigned int size = SZ_SMALL; + unsigned int ai = AI_NONE; + + struct map_session_data* sd; + int16 m; + + if (script_hasdata(st,10)) + { + event = script_getstr(st, 10); + check_event(st, event); + } + + if (script_hasdata(st, 11)) + { + size = script_getnum(st, 11); + if (size > 3) + { + ShowWarning("buildin_monster: Attempted to spawn non-existing size %d for monster class %d\n", size, class_); + return 1; + } + } + + if (script_hasdata(st, 12)) + { + ai = script_getnum(st, 12); + if (ai > 4) + { + ShowWarning("buildin_monster: Attempted to spawn non-existing ai %d for monster class %d\n", ai, class_); + return 1; + } + } + + sd = map_id2sd(st->rid); + + if (sd && strcmp(mapn, "this") == 0) + m = sd->bl.m; + else + { + m = map_mapname2mapid(mapn); + if (map[m].flag.src4instance && st->instance_id) + { // Try to redirect to the instance map, not the src map + if ((m = instance_mapid2imapid(m, st->instance_id)) < 0) + { + ShowError("buildin_areamonster: Trying to spawn monster (%d) on instance map (%s) without instance attached.\n", class_, mapn); + return 1; + } + } + } + + mob_once_spawn_area(sd, m, x0, y0, x1, y1, str, class_, amount, event, size, ai); + return 0; +} +/*========================================== + * KillMonster subcheck, verify if mob to kill ain't got an even to handle, could be force kill by allflag + *------------------------------------------*/ + static int buildin_killmonster_sub_strip(struct block_list *bl,va_list ap) +{ //same fix but with killmonster instead - stripping events from mobs. + TBL_MOB* md = (TBL_MOB*)bl; + char *event=va_arg(ap,char *); + int allflag=va_arg(ap,int); + + md->state.npc_killmonster = 1; + + if(!allflag){ + if(strcmp(event,md->npc_event)==0) + status_kill(bl); + }else{ + if(!md->spawn) + status_kill(bl); + } + md->state.npc_killmonster = 0; + return 0; +} +static int buildin_killmonster_sub(struct block_list *bl,va_list ap) +{ + TBL_MOB* md = (TBL_MOB*)bl; + char *event=va_arg(ap,char *); + int allflag=va_arg(ap,int); + + if(!allflag){ + if(strcmp(event,md->npc_event)==0) + status_kill(bl); + }else{ + if(!md->spawn) + status_kill(bl); + } + return 0; +} +BUILDIN_FUNC(killmonster) +{ + const char *mapname,*event; + int16 m,allflag=0; + mapname=script_getstr(st,2); + event=script_getstr(st,3); + if(strcmp(event,"All")==0) + allflag = 1; + else + check_event(st, event); + + if( (m=map_mapname2mapid(mapname))<0 ) + return 0; + + if( map[m].flag.src4instance && st->instance_id && (m = instance_mapid2imapid(m, st->instance_id)) < 0 ) + return 0; + + if( script_hasdata(st,4) ) { + if ( script_getnum(st,4) == 1 ) { + map_foreachinmap(buildin_killmonster_sub, m, BL_MOB, event ,allflag); + return 0; + } + } + + map_freeblock_lock(); + map_foreachinmap(buildin_killmonster_sub_strip, m, BL_MOB, event ,allflag); + map_freeblock_unlock(); + return 0; +} + +static int buildin_killmonsterall_sub_strip(struct block_list *bl,va_list ap) +{ //Strips the event from the mob if it's killed the old method. + struct mob_data *md; + + md = BL_CAST(BL_MOB, bl); + if (md->npc_event[0]) + md->npc_event[0] = 0; + + status_kill(bl); + return 0; +} +static int buildin_killmonsterall_sub(struct block_list *bl,va_list ap) +{ + status_kill(bl); + return 0; +} +BUILDIN_FUNC(killmonsterall) +{ + const char *mapname; + int16 m; + mapname=script_getstr(st,2); + + if( (m = map_mapname2mapid(mapname))<0 ) + return 0; + + if( map[m].flag.src4instance && st->instance_id && (m = instance_mapid2imapid(m, st->instance_id)) < 0 ) + return 0; + + if( script_hasdata(st,3) ) { + if ( script_getnum(st,3) == 1 ) { + map_foreachinmap(buildin_killmonsterall_sub,m,BL_MOB); + return 0; + } + } + + map_foreachinmap(buildin_killmonsterall_sub_strip,m,BL_MOB); + return 0; +} + +/*========================================== + * Creates a clone of a player. + * clone map, x, y, event, char_id, master_id, mode, flag, duration + *------------------------------------------*/ +BUILDIN_FUNC(clone) +{ + TBL_PC *sd, *msd=NULL; + int char_id,master_id=0,x,y, mode = 0, flag = 0, m; + unsigned int duration = 0; + const char *map,*event=""; + + map=script_getstr(st,2); + x=script_getnum(st,3); + y=script_getnum(st,4); + event=script_getstr(st,5); + char_id=script_getnum(st,6); + + if( script_hasdata(st,7) ) + master_id=script_getnum(st,7); + + if( script_hasdata(st,8) ) + mode=script_getnum(st,8); + + if( script_hasdata(st,9) ) + flag=script_getnum(st,9); + + if( script_hasdata(st,10) ) + duration=script_getnum(st,10); + + check_event(st, event); + + m = map_mapname2mapid(map); + if (m < 0) return 0; + + sd = map_charid2sd(char_id); + + if (master_id) { + msd = map_charid2sd(master_id); + if (msd) + master_id = msd->bl.id; + else + master_id = 0; + } + if (sd) //Return ID of newly crafted clone. + script_pushint(st,mob_clone_spawn(sd, m, x, y, event, master_id, mode, flag, 1000*duration)); + else //Failed to create clone. + script_pushint(st,0); + + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(doevent) +{ + const char* event = script_getstr(st,2); + struct map_session_data* sd; + + if( ( sd = script_rid2sd(st) ) == NULL ) + { + return 0; + } + + check_event(st, event); + npc_event(sd, event, 0); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(donpcevent) +{ + const char* event = script_getstr(st,2); + check_event(st, event); + if( !npc_event_do(event) ) { + struct npc_data * nd = map_id2nd(st->oid); + ShowDebug("NPCEvent '%s' not found! (source: %s)\n",event,nd?nd->name:"Unknown"); + script_pushint(st, 0); + } else + script_pushint(st, 1); + return 0; +} + +/// for Aegis compatibility +/// basically a specialized 'donpcevent', with the event specified as two arguments instead of one +BUILDIN_FUNC(cmdothernpc) // Added by RoVeRT +{ + const char* npc = script_getstr(st,2); + const char* command = script_getstr(st,3); + char event[EVENT_NAME_LENGTH]; + snprintf(event, sizeof(event), "%s::OnCommand%s", npc, command); + check_event(st, event); + npc_event_do(event); + return 0; +} + +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(addtimer) +{ + int tick = script_getnum(st,2); + const char* event = script_getstr(st, 3); + TBL_PC* sd; + + check_event(st, event); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + pc_addeventtimer(sd,tick,event); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(deltimer) +{ + const char *event; + TBL_PC* sd; + + event=script_getstr(st, 2); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + check_event(st, event); + pc_deleventtimer(sd,event); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(addtimercount) +{ + const char *event; + int tick; + TBL_PC* sd; + + event=script_getstr(st, 2); + tick=script_getnum(st,3); + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + check_event(st, event); + pc_addeventtimercount(sd,event,tick); + return 0; +} + +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(initnpctimer) +{ + struct npc_data *nd; + int flag = 0; + + if( script_hasdata(st,3) ) + { //Two arguments: NPC name and attach flag. + nd = npc_name2id(script_getstr(st, 2)); + flag = script_getnum(st,3); + } + else if( script_hasdata(st,2) ) + { //Check if argument is numeric (flag) or string (npc name) + struct script_data *data; + data = script_getdata(st,2); + get_val(st,data); + if( data_isstring(data) ) //NPC name + nd = npc_name2id(conv_str(st, data)); + else if( data_isint(data) ) //Flag + { + nd = (struct npc_data *)map_id2bl(st->oid); + flag = conv_num(st,data); + } + else + { + ShowError("initnpctimer: invalid argument type #1 (needs be int or string)).\n"); + return 1; + } + } + else + nd = (struct npc_data *)map_id2bl(st->oid); + + if( !nd ) + return 0; + if( flag ) //Attach + { + TBL_PC* sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + nd->u.scr.rid = sd->bl.id; + } + + nd->u.scr.timertick = 0; + npc_settimerevent_tick(nd,0); + npc_timerevent_start(nd, st->rid); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(startnpctimer) +{ + struct npc_data *nd; + int flag = 0; + + if( script_hasdata(st,3) ) + { //Two arguments: NPC name and attach flag. + nd = npc_name2id(script_getstr(st, 2)); + flag = script_getnum(st,3); + } + else if( script_hasdata(st,2) ) + { //Check if argument is numeric (flag) or string (npc name) + struct script_data *data; + data = script_getdata(st,2); + get_val(st,data); + if( data_isstring(data) ) //NPC name + nd = npc_name2id(conv_str(st, data)); + else if( data_isint(data) ) //Flag + { + nd = (struct npc_data *)map_id2bl(st->oid); + flag = conv_num(st,data); + } + else + { + ShowError("initnpctimer: invalid argument type #1 (needs be int or string)).\n"); + return 1; + } + } + else + nd=(struct npc_data *)map_id2bl(st->oid); + + if( !nd ) + return 0; + if( flag ) //Attach + { + TBL_PC* sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + nd->u.scr.rid = sd->bl.id; + } + + npc_timerevent_start(nd, st->rid); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(stopnpctimer) +{ + struct npc_data *nd; + int flag = 0; + + if( script_hasdata(st,3) ) + { //Two arguments: NPC name and attach flag. + nd = npc_name2id(script_getstr(st, 2)); + flag = script_getnum(st,3); + } + else if( script_hasdata(st,2) ) + { //Check if argument is numeric (flag) or string (npc name) + struct script_data *data; + data = script_getdata(st,2); + get_val(st,data); + if( data_isstring(data) ) //NPC name + nd = npc_name2id(conv_str(st, data)); + else if( data_isint(data) ) //Flag + { + nd = (struct npc_data *)map_id2bl(st->oid); + flag = conv_num(st,data); + } + else + { + ShowError("initnpctimer: invalid argument type #1 (needs be int or string)).\n"); + return 1; + } + } + else + nd=(struct npc_data *)map_id2bl(st->oid); + + if( !nd ) + return 0; + if( flag ) //Detach + nd->u.scr.rid = 0; + + npc_timerevent_stop(nd); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(getnpctimer) +{ + struct npc_data *nd; + TBL_PC *sd; + int type = script_getnum(st,2); + int val = 0; + + if( script_hasdata(st,3) ) + nd = npc_name2id(script_getstr(st,3)); + else + nd = (struct npc_data *)map_id2bl(st->oid); + + if( !nd || nd->bl.type != BL_NPC ) + { + script_pushint(st,0); + ShowError("getnpctimer: Invalid NPC.\n"); + return 1; + } + + switch( type ) + { + case 0: val = npc_gettimerevent_tick(nd); break; + case 1: + if( nd->u.scr.rid ) + { + sd = map_id2sd(nd->u.scr.rid); + if( !sd ) + { + ShowError("buildin_getnpctimer: Attached player not found!\n"); + break; + } + val = (sd->npc_timer_id != INVALID_TIMER); + } + else + val = (nd->u.scr.timerid != INVALID_TIMER); + break; + case 2: val = nd->u.scr.timeramount; break; + } + + script_pushint(st,val); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(setnpctimer) +{ + int tick; + struct npc_data *nd; + + tick = script_getnum(st,2); + if( script_hasdata(st,3) ) + nd = npc_name2id(script_getstr(st,3)); + else + nd = (struct npc_data *)map_id2bl(st->oid); + + if( !nd || nd->bl.type != BL_NPC ) + { + script_pushint(st,1); + ShowError("setnpctimer: Invalid NPC.\n"); + return 1; + } + + npc_settimerevent_tick(nd,tick); + script_pushint(st,0); + return 0; +} + +/*========================================== + * attaches the player rid to the timer [Celest] + *------------------------------------------*/ +BUILDIN_FUNC(attachnpctimer) +{ + TBL_PC *sd; + struct npc_data *nd = (struct npc_data *)map_id2bl(st->oid); + + if( !nd || nd->bl.type != BL_NPC ) + { + script_pushint(st,1); + ShowError("setnpctimer: Invalid NPC.\n"); + return 1; + } + + if( script_hasdata(st,2) ) + sd = map_nick2sd(script_getstr(st,2)); + else + sd = script_rid2sd(st); + + if( !sd ) + { + script_pushint(st,1); + ShowWarning("attachnpctimer: Invalid player.\n"); + return 1; + } + + nd->u.scr.rid = sd->bl.id; + script_pushint(st,0); + return 0; +} + +/*========================================== + * detaches a player rid from the timer [Celest] + *------------------------------------------*/ +BUILDIN_FUNC(detachnpctimer) +{ + struct npc_data *nd; + + if( script_hasdata(st,2) ) + nd = npc_name2id(script_getstr(st,2)); + else + nd = (struct npc_data *)map_id2bl(st->oid); + + if( !nd || nd->bl.type != BL_NPC ) + { + script_pushint(st,1); + ShowError("detachnpctimer: Invalid NPC.\n"); + return 1; + } + + nd->u.scr.rid = 0; + script_pushint(st,0); + return 0; +} + +/*========================================== + * To avoid "player not attached" script errors, this function is provided, + * it checks if there is a player attached to the current script. [Skotlex] + * If no, returns 0, if yes, returns the account_id of the attached player. + *------------------------------------------*/ +BUILDIN_FUNC(playerattached) +{ + if(st->rid == 0 || map_id2sd(st->rid) == NULL) + script_pushint(st,0); + else + script_pushint(st,st->rid); + return 0; +} + +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(announce) +{ + const char *mes = script_getstr(st,2); + int flag = script_getnum(st,3); + const char *fontColor = script_hasdata(st,4) ? script_getstr(st,4) : NULL; + int fontType = script_hasdata(st,5) ? script_getnum(st,5) : 0x190; // default fontType (FW_NORMAL) + int fontSize = script_hasdata(st,6) ? script_getnum(st,6) : 12; // default fontSize + int fontAlign = script_hasdata(st,7) ? script_getnum(st,7) : 0; // default fontAlign + int fontY = script_hasdata(st,8) ? script_getnum(st,8) : 0; // default fontY + + if (flag&0x0f) // Broadcast source or broadcast region defined + { + send_target target; + struct block_list *bl = (flag&0x08) ? map_id2bl(st->oid) : (struct block_list *)script_rid2sd(st); // If bc_npc flag is set, use NPC as broadcast source + if (bl == NULL) + return 0; + + flag &= 0x07; + target = (flag == 1) ? ALL_SAMEMAP : + (flag == 2) ? AREA : + (flag == 3) ? SELF : + ALL_CLIENT; + if (fontColor) + clif_broadcast2(bl, mes, (int)strlen(mes)+1, strtol(fontColor, (char **)NULL, 0), fontType, fontSize, fontAlign, fontY, target); + else + clif_broadcast(bl, mes, (int)strlen(mes)+1, flag&0xf0, target); + } + else + { + if (fontColor) + intif_broadcast2(mes, (int)strlen(mes)+1, strtol(fontColor, (char **)NULL, 0), fontType, fontSize, fontAlign, fontY); + else + intif_broadcast(mes, (int)strlen(mes)+1, flag&0xf0); + } + return 0; +} +/*========================================== + *------------------------------------------*/ +static int buildin_announce_sub(struct block_list *bl, va_list ap) +{ + char *mes = va_arg(ap, char *); + int len = va_arg(ap, int); + int type = va_arg(ap, int); + char *fontColor = va_arg(ap, char *); + short fontType = (short)va_arg(ap, int); + short fontSize = (short)va_arg(ap, int); + short fontAlign = (short)va_arg(ap, int); + short fontY = (short)va_arg(ap, int); + if (fontColor) + clif_broadcast2(bl, mes, len, strtol(fontColor, (char **)NULL, 0), fontType, fontSize, fontAlign, fontY, SELF); + else + clif_broadcast(bl, mes, len, type, SELF); + return 0; +} + +BUILDIN_FUNC(mapannounce) +{ + const char *mapname = script_getstr(st,2); + const char *mes = script_getstr(st,3); + int flag = script_getnum(st,4); + const char *fontColor = script_hasdata(st,5) ? script_getstr(st,5) : NULL; + int fontType = script_hasdata(st,6) ? script_getnum(st,6) : 0x190; // default fontType (FW_NORMAL) + int fontSize = script_hasdata(st,7) ? script_getnum(st,7) : 12; // default fontSize + int fontAlign = script_hasdata(st,8) ? script_getnum(st,8) : 0; // default fontAlign + int fontY = script_hasdata(st,9) ? script_getnum(st,9) : 0; // default fontY + int16 m; + + if ((m = map_mapname2mapid(mapname)) < 0) + return 0; + + map_foreachinmap(buildin_announce_sub, m, BL_PC, + mes, strlen(mes)+1, flag&0xf0, fontColor, fontType, fontSize, fontAlign, fontY); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(areaannounce) +{ + const char *mapname = script_getstr(st,2); + int x0 = script_getnum(st,3); + int y0 = script_getnum(st,4); + int x1 = script_getnum(st,5); + int y1 = script_getnum(st,6); + const char *mes = script_getstr(st,7); + int flag = script_getnum(st,8); + const char *fontColor = script_hasdata(st,9) ? script_getstr(st,9) : NULL; + int fontType = script_hasdata(st,10) ? script_getnum(st,10) : 0x190; // default fontType (FW_NORMAL) + int fontSize = script_hasdata(st,11) ? script_getnum(st,11) : 12; // default fontSize + int fontAlign = script_hasdata(st,12) ? script_getnum(st,12) : 0; // default fontAlign + int fontY = script_hasdata(st,13) ? script_getnum(st,13) : 0; // default fontY + int16 m; + + if ((m = map_mapname2mapid(mapname)) < 0) + return 0; + + map_foreachinarea(buildin_announce_sub, m, x0, y0, x1, y1, BL_PC, + mes, strlen(mes)+1, flag&0xf0, fontColor, fontType, fontSize, fontAlign, fontY); + return 0; +} + +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(getusers) +{ + int flag, val = 0; + struct map_session_data* sd; + struct block_list* bl = NULL; + + flag = script_getnum(st,2); + + switch(flag&0x07) + { + case 0: + if(flag&0x8) + {// npc + bl = map_id2bl(st->oid); + } + else if((sd = script_rid2sd(st))!=NULL) + {// pc + bl = &sd->bl; + } + + if(bl) + { + val = map[bl->m].users; + } + break; + case 1: + val = map_getusers(); + break; + default: + ShowWarning("buildin_getusers: Unknown type %d.\n", flag); + script_pushint(st,0); + return 1; + } + + script_pushint(st,val); + return 0; +} +/*========================================== + * Works like @WHO - displays all online users names in window + *------------------------------------------*/ +BUILDIN_FUNC(getusersname) +{ + TBL_PC *sd, *pl_sd; + int /*disp_num=1,*/ group_level = 0; + struct s_mapiterator* iter; + + sd = script_rid2sd(st); + if (!sd) return 0; + + group_level = pc_get_group_level(sd); + iter = mapit_getallusers(); + for( pl_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); pl_sd = (TBL_PC*)mapit_next(iter) ) + { + if (pc_has_permission(pl_sd, PC_PERM_HIDE_SESSION) && pc_get_group_level(pl_sd) > group_level) + continue; // skip hidden sessions + + /* Temporary fix for bugreport:1023. + * Do not uncomment unless you want thousands of 'next' buttons. + if((disp_num++)%10==0) + clif_scriptnext(sd,st->oid);*/ + clif_scriptmes(sd,st->oid,pl_sd->status.name); + } + mapit_free(iter); + + return 0; +} +/*========================================== + * getmapguildusers("mapname",guild ID) Returns the number guild members present on a map [Reddozen] + *------------------------------------------*/ +BUILDIN_FUNC(getmapguildusers) +{ + const char *str; + int16 m; + int gid; + int i=0,c=0; + struct guild *g = NULL; + str=script_getstr(st,2); + gid=script_getnum(st,3); + if ((m = map_mapname2mapid(str)) < 0) { // map id on this server (m == -1 if not in actual map-server) + script_pushint(st,-1); + return 0; + } + g = guild_search(gid); + + if (g){ + for(i = 0; i < g->max_member; i++) + { + if (g->member[i].sd && g->member[i].sd->bl.m == m) + c++; + } + } + + script_pushint(st,c); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(getmapusers) +{ + const char *str; + int16 m; + str=script_getstr(st,2); + if( (m=map_mapname2mapid(str))< 0){ + script_pushint(st,-1); + return 0; + } + script_pushint(st,map[m].users); + return 0; +} +/*========================================== + *------------------------------------------*/ +static int buildin_getareausers_sub(struct block_list *bl,va_list ap) +{ + int *users=va_arg(ap,int *); + (*users)++; + return 0; +} +BUILDIN_FUNC(getareausers) +{ + const char *str; + int16 m,x0,y0,x1,y1,users=0; //doubt we can have more then 32k users on + str=script_getstr(st,2); + x0=script_getnum(st,3); + y0=script_getnum(st,4); + x1=script_getnum(st,5); + y1=script_getnum(st,6); + if( (m=map_mapname2mapid(str))< 0){ + script_pushint(st,-1); + return 0; + } + map_foreachinarea(buildin_getareausers_sub, + m,x0,y0,x1,y1,BL_PC,&users); + script_pushint(st,users); + return 0; +} + +/*========================================== + *------------------------------------------*/ +static 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; +} +BUILDIN_FUNC(getareadropitem) +{ + const char *str; + int16 m,x0,y0,x1,y1; + int item,amount=0; + struct script_data *data; + + str=script_getstr(st,2); + x0=script_getnum(st,3); + y0=script_getnum(st,4); + x1=script_getnum(st,5); + y1=script_getnum(st,6); + + data=script_getdata(st,7); + get_val(st,data); + if( data_isstring(data) ){ + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + item=UNKNOWN_ITEM_ID; + if( item_data ) + item=item_data->nameid; + }else + item=conv_num(st,data); + + if( (m=map_mapname2mapid(str))< 0){ + script_pushint(st,-1); + return 0; + } + map_foreachinarea(buildin_getareadropitem_sub, + m,x0,y0,x1,y1,BL_ITEM,item,&amount); + script_pushint(st,amount); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(enablenpc) +{ + const char *str; + str=script_getstr(st,2); + npc_enable(str,1); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(disablenpc) +{ + const char *str; + str=script_getstr(st,2); + npc_enable(str,0); + return 0; +} + +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(hideoffnpc) +{ + const char *str; + str=script_getstr(st,2); + npc_enable(str,2); + return 0; +} +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(hideonnpc) +{ + const char *str; + str=script_getstr(st,2); + npc_enable(str,4); + return 0; +} + +/// Starts a status effect on the target unit or on the attached player. +/// +/// sc_start <effect_id>,<duration>,<val1>{,<unit_id>}; +BUILDIN_FUNC(sc_start) +{ + struct block_list* bl; + enum sc_type type; + int tick; + int val1; + int val4 = 0; + + type = (sc_type)script_getnum(st,2); + tick = script_getnum(st,3); + val1 = script_getnum(st,4); + if( script_hasdata(st,5) ) + bl = map_id2bl(script_getnum(st,5)); + else + bl = map_id2bl(st->rid); + + if( tick == 0 && val1 > 0 && type > SC_NONE && type < SC_MAX && status_sc2skill(type) != 0 ) + {// When there isn't a duration specified, try to get it from the skill_db + tick = skill_get_time(status_sc2skill(type), val1); + } + + if( potion_flag == 1 && potion_target ) + { //skill.c set the flags before running the script, this must be a potion-pitched effect. + bl = map_id2bl(potion_target); + tick /= 2;// Thrown potions only last half. + val4 = 1;// Mark that this was a thrown sc_effect + } + + if( bl ) + status_change_start(bl, type, 10000, val1, 0, 0, val4, tick, 2); + + return 0; +} + +/// Starts a status effect on the target unit or on the attached player. +/// +/// sc_start2 <effect_id>,<duration>,<val1>,<percent chance>{,<unit_id>}; +BUILDIN_FUNC(sc_start2) +{ + struct block_list* bl; + enum sc_type type; + int tick; + int val1; + int val4 = 0; + int rate; + + type = (sc_type)script_getnum(st,2); + tick = script_getnum(st,3); + val1 = script_getnum(st,4); + rate = script_getnum(st,5); + if( script_hasdata(st,6) ) + bl = map_id2bl(script_getnum(st,6)); + else + bl = map_id2bl(st->rid); + + if( tick == 0 && val1 > 0 && type > SC_NONE && type < SC_MAX && status_sc2skill(type) != 0 ) + {// When there isn't a duration specified, try to get it from the skill_db + tick = skill_get_time(status_sc2skill(type), val1); + } + + if( potion_flag == 1 && potion_target ) + { //skill.c set the flags before running the script, this must be a potion-pitched effect. + bl = map_id2bl(potion_target); + tick /= 2;// Thrown potions only last half. + val4 = 1;// Mark that this was a thrown sc_effect + } + + if( bl ) + status_change_start(bl, type, rate, val1, 0, 0, val4, tick, 2); + + return 0; +} + +/// Starts a status effect on the target unit or on the attached player. +/// +/// sc_start4 <effect_id>,<duration>,<val1>,<val2>,<val3>,<val4>{,<unit_id>}; +BUILDIN_FUNC(sc_start4) +{ + struct block_list* bl; + enum sc_type type; + int tick; + int val1; + int val2; + int val3; + int val4; + + type = (sc_type)script_getnum(st,2); + tick = script_getnum(st,3); + val1 = script_getnum(st,4); + val2 = script_getnum(st,5); + val3 = script_getnum(st,6); + val4 = script_getnum(st,7); + if( script_hasdata(st,8) ) + bl = map_id2bl(script_getnum(st,8)); + else + bl = map_id2bl(st->rid); + + if( tick == 0 && val1 > 0 && type > SC_NONE && type < SC_MAX && status_sc2skill(type) != 0 ) + {// When there isn't a duration specified, try to get it from the skill_db + tick = skill_get_time(status_sc2skill(type), val1); + } + + if( potion_flag == 1 && potion_target ) + { //skill.c set the flags before running the script, this must be a potion-pitched effect. + bl = map_id2bl(potion_target); + tick /= 2;// Thrown potions only last half. + } + + if( bl ) + status_change_start(bl, type, 10000, val1, val2, val3, val4, tick, 2); + + return 0; +} + +/// Ends one or all status effects on the target unit or on the attached player. +/// +/// sc_end <effect_id>{,<unit_id>}; +BUILDIN_FUNC(sc_end) +{ + struct block_list* bl; + int type; + + type = script_getnum(st, 2); + if (script_hasdata(st, 3)) + bl = map_id2bl(script_getnum(st, 3)); + else + bl = map_id2bl(st->rid); + + if (potion_flag == 1 && potion_target) //##TODO how does this work [FlavioJS] + bl = map_id2bl(potion_target); + + if (!bl) + return 0; + + if (type >= 0 && type < SC_MAX) + { + struct status_change *sc = status_get_sc(bl); + struct status_change_entry *sce = sc ? sc->data[type] : NULL; + + if (!sce) + return 0; + + + switch (type) + { + case SC_WEIGHT50: + case SC_WEIGHT90: + case SC_NOCHAT: + case SC_PUSH_CART: + return 0; + + default: + break; + } + + //This should help status_change_end force disabling the SC in case it has no limit. + sce->val1 = sce->val2 = sce->val3 = sce->val4 = 0; + status_change_end(bl, (sc_type)type, INVALID_TIMER); + } + else + status_change_clear(bl, 3); // remove all effects + + return 0; +} + +/*========================================== + * @FIXME atm will return reduced tick, 0 immune, 1 no tick + *------------------------------------------*/ +BUILDIN_FUNC(getscrate) +{ + struct block_list *bl; + int type,rate; + + type=script_getnum(st,2); + rate=script_getnum(st,3); + if( script_hasdata(st,4) ) //get for the bl assigned + bl = map_id2bl(script_getnum(st,4)); + else + bl = map_id2bl(st->rid); + + if (bl) + rate = status_get_sc_def(bl, (sc_type)type, 10000, 10000, 0); + + script_pushint(st,rate); + return 0; +} + +/*========================================== + * getstatus <type>{, <info>}; + *------------------------------------------*/ +BUILDIN_FUNC(getstatus) +{ + int id, type; + struct map_session_data* sd = script_rid2sd(st); + + if( sd == NULL ) + {// no player attached + return 0; + } + + id = script_getnum(st, 2); + type = script_hasdata(st, 3) ? script_getnum(st, 3) : 0; + + if( id <= SC_NONE || id >= SC_MAX ) + {// invalid status type given + ShowWarning("script.c:getstatus: Invalid status type given (%d).\n", id); + return 0; + } + + if( sd->sc.count == 0 || !sd->sc.data[id] ) + {// no status is active + script_pushint(st, 0); + return 0; + } + + switch( type ) + { + case 1: script_pushint(st, sd->sc.data[id]->val1); break; + case 2: script_pushint(st, sd->sc.data[id]->val2); break; + case 3: script_pushint(st, sd->sc.data[id]->val3); break; + case 4: script_pushint(st, sd->sc.data[id]->val4); break; + case 5: + { + struct TimerData* timer = (struct TimerData*)get_timer(sd->sc.data[id]->timer); + + if( timer ) + {// return the amount of time remaining + script_pushint(st, timer->tick - gettick()); + } + } + break; + default: script_pushint(st, 1); break; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(debugmes) +{ + const char *str; + str=script_getstr(st,2); + ShowDebug("script debug : %d %d : %s\n",st->rid,st->oid,str); + return 0; +} + +/*========================================== + *------------------------------------------*/ +BUILDIN_FUNC(catchpet) +{ + int pet_id; + TBL_PC *sd; + + pet_id= script_getnum(st,2); + sd=script_rid2sd(st); + if( sd == NULL ) + return 0; + + pet_catch_process1(sd,pet_id); + return 0; +} + +/*========================================== + * [orn] + *------------------------------------------*/ +BUILDIN_FUNC(homunculus_evolution) +{ + TBL_PC *sd; + + sd=script_rid2sd(st); + if( sd == NULL ) + return 0; + + if(merc_is_hom_active(sd->hd)) + { + if (sd->hd->homunculus.intimacy > 91000) + merc_hom_evolution(sd->hd); + else + clif_emotion(&sd->hd->bl, E_SWT); + } + return 0; +} + +/*========================================== + * [Xantara] + *------------------------------------------*/ +BUILDIN_FUNC(homunculus_mutate) +{ + int homun_id, m_class, m_id; + TBL_PC *sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + if(script_hasdata(st,2)) + homun_id = script_getnum(st,2); + else + homun_id = 6048 + (rnd() % 4); + + if(merc_is_hom_active(sd->hd)) { + m_class = hom_class2mapid(sd->hd->homunculus.class_); + m_id = hom_class2mapid(homun_id); + + if ( m_class != -1 && m_id != -1 && m_class&HOM_EVO && m_id&HOM_S && sd->hd->homunculus.level >= 99 ) + hom_mutate(sd->hd, homun_id); + else + clif_emotion(&sd->hd->bl, E_SWT); + } + return 0; +} + +// [Zephyrus] +BUILDIN_FUNC(homunculus_shuffle) +{ + TBL_PC *sd; + + sd=script_rid2sd(st); + if( sd == NULL ) + return 0; + + if(merc_is_hom_active(sd->hd)) + merc_hom_shuffle(sd->hd); + + return 0; +} + +//These two functions bring the eA MAPID_* class functionality to scripts. +BUILDIN_FUNC(eaclass) +{ + int class_; + if( script_hasdata(st,2) ) + class_ = script_getnum(st,2); + else { + TBL_PC *sd; + sd=script_rid2sd(st); + if (!sd) { + script_pushint(st,-1); + return 0; + } + class_ = sd->status.class_; + } + script_pushint(st,pc_jobid2mapid(class_)); + return 0; +} + +BUILDIN_FUNC(roclass) +{ + int class_ =script_getnum(st,2); + int sex; + if( script_hasdata(st,3) ) + sex = script_getnum(st,3); + else { + TBL_PC *sd; + if (st->rid && (sd=script_rid2sd(st))) + sex = sd->status.sex; + else + sex = 1; //Just use male when not found. + } + script_pushint(st,pc_mapid2jobid(class_, sex)); + return 0; +} + +/*========================================== + * Tells client to open a hatching window, used for pet incubator + *------------------------------------------*/ +BUILDIN_FUNC(birthpet) +{ + TBL_PC *sd; + sd=script_rid2sd(st); + if( sd == NULL ) + return 0; + + if( sd->status.pet_id ) + {// do not send egg list, when you already have a pet + return 0; + } + + clif_sendegg(sd); + return 0; +} + +/*========================================== + * Added - AppleGirl For Advanced Classes, (Updated for Cleaner Script Purposes) + * @type + * 1 : make like after rebirth + * 2 : blvl,jlvl=1, skillpoint=0 + * 3 : don't reset skill, blvl=1 + * 4 : jlvl=0 + *------------------------------------------*/ +BUILDIN_FUNC(resetlvl) +{ + TBL_PC *sd; + + int type=script_getnum(st,2); + + sd=script_rid2sd(st); + if( sd == NULL ) + return 0; + + pc_resetlvl(sd,type); + return 0; +} +/*========================================== + * Reset a player status point + *------------------------------------------*/ +BUILDIN_FUNC(resetstatus) +{ + TBL_PC *sd; + sd=script_rid2sd(st); + pc_resetstate(sd); + return 0; +} + +/*========================================== + * script command resetskill + *------------------------------------------*/ +BUILDIN_FUNC(resetskill) +{ + TBL_PC *sd; + sd=script_rid2sd(st); + pc_resetskill(sd,1); + return 0; +} + +/*========================================== + * Counts total amount of skill points. + *------------------------------------------*/ +BUILDIN_FUNC(skillpointcount) +{ + TBL_PC *sd; + sd=script_rid2sd(st); + script_pushint(st,sd->status.skill_point + pc_resetskill(sd,2)); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(changebase) +{ + TBL_PC *sd=NULL; + int vclass; + + if( script_hasdata(st,3) ) + sd=map_id2sd(script_getnum(st,3)); + else + sd=script_rid2sd(st); + + if(sd == NULL) + return 0; + + vclass = script_getnum(st,2); + if(vclass == JOB_WEDDING) + { + if (!battle_config.wedding_modifydisplay || //Do not show the wedding sprites + sd->class_&JOBL_BABY //Baby classes screw up when showing wedding sprites. [Skotlex] They don't seem to anymore. + ) + return 0; + } + + if(!sd->disguise && vclass != sd->vd.class_) { + status_set_viewdata(&sd->bl, vclass); + //Updated client view. Base, Weapon and Cloth Colors. + clif_changelook(&sd->bl,LOOK_BASE,sd->vd.class_); + clif_changelook(&sd->bl,LOOK_WEAPON,sd->status.weapon); + if (sd->vd.cloth_color) + clif_changelook(&sd->bl,LOOK_CLOTHES_COLOR,sd->vd.cloth_color); + clif_skillinfoblock(sd); + } + + return 0; +} + +/*========================================== + * Unequip all item and request for a changesex to char-serv + *------------------------------------------*/ +BUILDIN_FUNC(changesex) +{ + int i; + TBL_PC *sd = NULL; + sd = script_rid2sd(st); + + pc_resetskill(sd,4); + // to avoid any problem with equipment and invalid sex, equipment is unequiped. + for( i=0; i<EQI_MAX; i++ ) + if( sd->equip_index[i] >= 0 ) pc_unequipitem(sd, sd->equip_index[i], 3); + chrif_changesex(sd); + return 0; +} + +/*========================================== + * Works like 'announce' but outputs in the common chat window + *------------------------------------------*/ +BUILDIN_FUNC(globalmes) +{ + struct block_list *bl = map_id2bl(st->oid); + struct npc_data *nd = (struct npc_data *)bl; + const char *name=NULL,*mes; + + mes=script_getstr(st,2); + if(mes==NULL) return 0; + + if(script_hasdata(st,3)){ // npc name to display + name=script_getstr(st,3); + } else { + name=nd->name; //use current npc name + } + + npc_globalmessage(name,mes); // broadcast to all players connected + + return 0; +} + +///////////////////////////////////////////////////////////////////// +// NPC waiting room (chat room) +// + +/// Creates a waiting room (chat room) for this npc. +/// +/// waitingroom "<title>",<limit>{,"<event>"{,<trigger>{,<zeny>{,<minlvl>{,<maxlvl>}}}}}; +BUILDIN_FUNC(waitingroom) +{ + struct npc_data* nd; + int pub = 1; + const char* title = script_getstr(st, 2); + int limit = script_getnum(st, 3); + const char* ev = script_hasdata(st,4) ? script_getstr(st,4) : ""; + int trigger = script_hasdata(st,5) ? script_getnum(st,5) : limit; + int zeny = script_hasdata(st,6) ? script_getnum(st,6) : 0; + int minLvl = script_hasdata(st,7) ? script_getnum(st,7) : 1; + int maxLvl = script_hasdata(st,8) ? script_getnum(st,8) : MAX_LEVEL; + + nd = (struct npc_data *)map_id2bl(st->oid); + if( nd != NULL ) + chat_createnpcchat(nd, title, limit, pub, trigger, ev, zeny, minLvl, maxLvl); + + return 0; +} + +/// Removes the waiting room of the current or target npc. +/// +/// delwaitingroom "<npc_name>"; +/// delwaitingroom; +BUILDIN_FUNC(delwaitingroom) +{ + struct npc_data* nd; + if( script_hasdata(st,2) ) + nd = npc_name2id(script_getstr(st, 2)); + else + nd = (struct npc_data *)map_id2bl(st->oid); + if( nd != NULL ) + chat_deletenpcchat(nd); + return 0; +} + +/// Kicks all the players from the waiting room of the current or target npc. +/// +/// kickwaitingroomall "<npc_name>"; +/// kickwaitingroomall; +BUILDIN_FUNC(waitingroomkickall) +{ + struct npc_data* nd; + struct chat_data* cd; + + if( script_hasdata(st,2) ) + nd = npc_name2id(script_getstr(st,2)); + else + nd = (struct npc_data *)map_id2bl(st->oid); + + if( nd != NULL && (cd=(struct chat_data *)map_id2bl(nd->chat_id)) != NULL ) + chat_npckickall(cd); + return 0; +} + +/// Enables the waiting room event of the current or target npc. +/// +/// enablewaitingroomevent "<npc_name>"; +/// enablewaitingroomevent; +BUILDIN_FUNC(enablewaitingroomevent) +{ + struct npc_data* nd; + struct chat_data* cd; + + if( script_hasdata(st,2) ) + nd = npc_name2id(script_getstr(st, 2)); + else + nd = (struct npc_data *)map_id2bl(st->oid); + + if( nd != NULL && (cd=(struct chat_data *)map_id2bl(nd->chat_id)) != NULL ) + chat_enableevent(cd); + return 0; +} + +/// Disables the waiting room event of the current or target npc. +/// +/// disablewaitingroomevent "<npc_name>"; +/// disablewaitingroomevent; +BUILDIN_FUNC(disablewaitingroomevent) +{ + struct npc_data *nd; + struct chat_data *cd; + + if( script_hasdata(st,2) ) + nd = npc_name2id(script_getstr(st, 2)); + else + nd = (struct npc_data *)map_id2bl(st->oid); + + if( nd != NULL && (cd=(struct chat_data *)map_id2bl(nd->chat_id)) != NULL ) + chat_disableevent(cd); + return 0; +} + +/// Returns info on the waiting room of the current or target npc. +/// Returns -1 if the type unknown +/// <type>=0 : current number of users +/// <type>=1 : maximum number of users allowed +/// <type>=2 : the number of users that trigger the event +/// <type>=3 : if the trigger is disabled +/// <type>=4 : the title of the waiting room +/// <type>=5 : the password of the waiting room +/// <type>=16 : the name of the waiting room event +/// <type>=32 : if the waiting room is full +/// <type>=33 : if there are enough users to trigger the event +/// +/// getwaitingroomstate(<type>,"<npc_name>") -> <info> +/// getwaitingroomstate(<type>) -> <info> +BUILDIN_FUNC(getwaitingroomstate) +{ + struct npc_data *nd; + struct chat_data *cd; + int type; + + type = script_getnum(st,2); + if( script_hasdata(st,3) ) + nd = npc_name2id(script_getstr(st, 3)); + else + nd = (struct npc_data *)map_id2bl(st->oid); + + if( nd == NULL || (cd=(struct chat_data *)map_id2bl(nd->chat_id)) == NULL ) + { + script_pushint(st, -1); + return 0; + } + + switch(type) + { + case 0: script_pushint(st, cd->users); break; + case 1: script_pushint(st, cd->limit); break; + case 2: script_pushint(st, cd->trigger&0x7f); break; + case 3: script_pushint(st, ((cd->trigger&0x80)!=0)); break; + case 4: script_pushstrcopy(st, cd->title); break; + case 5: script_pushstrcopy(st, cd->pass); break; + case 16: script_pushstrcopy(st, cd->npc_event);break; + case 32: script_pushint(st, (cd->users >= cd->limit)); break; + case 33: script_pushint(st, (cd->users >= cd->trigger)); break; + default: script_pushint(st, -1); break; + } + return 0; +} + +/// Warps the trigger or target amount of players to the target map and position. +/// Players are automatically removed from the waiting room. +/// Those waiting the longest will get warped first. +/// The target map can be "Random" for a random position in the current map, +/// and "SavePoint" for the savepoint map+position. +/// The map flag noteleport of the current map is only considered when teleporting to the savepoint. +/// +/// The id's of the teleported players are put into the array $@warpwaitingpc[] +/// The total number of teleported players is put into $@warpwaitingpcnum +/// +/// warpwaitingpc "<map name>",<x>,<y>,<number of players>; +/// warpwaitingpc "<map name>",<x>,<y>; +BUILDIN_FUNC(warpwaitingpc) +{ + int x; + int y; + int i; + int n; + const char* map_name; + struct npc_data* nd; + struct chat_data* cd; + TBL_PC* sd; + + nd = (struct npc_data *)map_id2bl(st->oid); + if( nd == NULL || (cd=(struct chat_data *)map_id2bl(nd->chat_id)) == NULL ) + return 0; + + map_name = script_getstr(st,2); + x = script_getnum(st,3); + y = script_getnum(st,4); + n = cd->trigger&0x7f; + + if( script_hasdata(st,5) ) + n = script_getnum(st,5); + + for( i = 0; i < n && cd->users > 0; i++ ) + { + sd = cd->usersd[0]; + + if( strcmp(map_name,"SavePoint") == 0 && map[sd->bl.m].flag.noteleport ) + {// can't teleport on this map + break; + } + + if( cd->zeny ) + {// fee set + if( (uint32)sd->status.zeny < cd->zeny ) + {// no zeny to cover set fee + break; + } + pc_payzeny(sd, cd->zeny, LOG_TYPE_NPC, NULL); + } + + mapreg_setreg(reference_uid(add_str("$@warpwaitingpc"), i), sd->bl.id); + + if( strcmp(map_name,"Random") == 0 ) + pc_randomwarp(sd,CLR_TELEPORT); + else if( strcmp(map_name,"SavePoint") == 0 ) + pc_setpos(sd, sd->status.save_point.map, sd->status.save_point.x, sd->status.save_point.y, CLR_TELEPORT); + else + pc_setpos(sd, mapindex_name2id(map_name), x, y, CLR_OUTSIGHT); + } + mapreg_setreg(add_str("$@warpwaitingpcnum"), i); + return 0; +} + +///////////////////////////////////////////////////////////////////// +// ... +// + +/// Detaches a character from a script. +/// +/// @param st Script state to detach the character from. +static void script_detach_rid(struct script_state* st) +{ + if(st->rid) + { + script_detach_state(st, false); + st->rid = 0; + } +} + +/*========================================== + * Attach sd char id to script and detach current one if any + *------------------------------------------*/ +BUILDIN_FUNC(attachrid) +{ + int rid = script_getnum(st,2); + struct map_session_data* sd; + + if ((sd = map_id2sd(rid))!=NULL) { + script_detach_rid(st); + + st->rid = rid; + script_attach_state(st); + script_pushint(st,1); + } else + script_pushint(st,0); + return 0; +} +/*========================================== + * Detach script to rid + *------------------------------------------*/ +BUILDIN_FUNC(detachrid) +{ + script_detach_rid(st); + return 0; +} +/*========================================== + * Chk if account connected, (and charid from account if specified) + *------------------------------------------*/ +BUILDIN_FUNC(isloggedin) +{ + TBL_PC* sd = map_id2sd(script_getnum(st,2)); + if (script_hasdata(st,3) && sd && + sd->status.char_id != script_getnum(st,3)) + sd = NULL; + push_val(st->stack,C_INT,sd!=NULL); + return 0; +} + + +/*========================================== + * + *------------------------------------------*/ +BUILDIN_FUNC(setmapflagnosave) +{ + int16 m,x,y; + unsigned short mapindex; + const char *str,*str2; + + str=script_getstr(st,2); + str2=script_getstr(st,3); + x=script_getnum(st,4); + y=script_getnum(st,5); + m = map_mapname2mapid(str); + mapindex = mapindex_name2id(str2); + + if(m >= 0 && mapindex) { + map[m].flag.nosave=1; + map[m].save.map=mapindex; + map[m].save.x=x; + map[m].save.y=y; + } + + return 0; +} + +BUILDIN_FUNC(getmapflag) +{ + int16 m,i; + const char *str; + + str=script_getstr(st,2); + i=script_getnum(st,3); + + m = map_mapname2mapid(str); + if(m >= 0) { + switch(i) { + case MF_NOMEMO: script_pushint(st,map[m].flag.nomemo); break; + case MF_NOTELEPORT: script_pushint(st,map[m].flag.noteleport); break; + case MF_NOSAVE: script_pushint(st,map[m].flag.nosave); break; + case MF_NOBRANCH: script_pushint(st,map[m].flag.nobranch); break; + case MF_NOPENALTY: script_pushint(st,map[m].flag.noexppenalty); break; + case MF_NOZENYPENALTY: script_pushint(st,map[m].flag.nozenypenalty); break; + case MF_PVP: script_pushint(st,map[m].flag.pvp); break; + case MF_PVP_NOPARTY: script_pushint(st,map[m].flag.pvp_noparty); break; + case MF_PVP_NOGUILD: script_pushint(st,map[m].flag.pvp_noguild); break; + case MF_GVG: script_pushint(st,map[m].flag.gvg); break; + case MF_GVG_NOPARTY: script_pushint(st,map[m].flag.gvg_noparty); break; + case MF_NOTRADE: script_pushint(st,map[m].flag.notrade); break; + case MF_NOSKILL: script_pushint(st,map[m].flag.noskill); break; + case MF_NOWARP: script_pushint(st,map[m].flag.nowarp); break; + case MF_PARTYLOCK: script_pushint(st,map[m].flag.partylock); break; + case MF_NOICEWALL: script_pushint(st,map[m].flag.noicewall); break; + case MF_SNOW: script_pushint(st,map[m].flag.snow); break; + case MF_FOG: script_pushint(st,map[m].flag.fog); break; + case MF_SAKURA: script_pushint(st,map[m].flag.sakura); break; + case MF_LEAVES: script_pushint(st,map[m].flag.leaves); break; + /** + * No longer available, keeping here just in case it's back someday. [Ind] + **/ + //case MF_RAIN: script_pushint(st,map[m].flag.rain); break; + case MF_NOGO: script_pushint(st,map[m].flag.nogo); break; + case MF_CLOUDS: script_pushint(st,map[m].flag.clouds); break; + case MF_CLOUDS2: script_pushint(st,map[m].flag.clouds2); break; + case MF_FIREWORKS: script_pushint(st,map[m].flag.fireworks); break; + case MF_GVG_CASTLE: script_pushint(st,map[m].flag.gvg_castle); break; + case MF_GVG_DUNGEON: script_pushint(st,map[m].flag.gvg_dungeon); break; + case MF_NIGHTENABLED: script_pushint(st,map[m].flag.nightenabled); break; + case MF_NOBASEEXP: script_pushint(st,map[m].flag.nobaseexp); break; + case MF_NOJOBEXP: script_pushint(st,map[m].flag.nojobexp); break; + case MF_NOMOBLOOT: script_pushint(st,map[m].flag.nomobloot); break; + case MF_NOMVPLOOT: script_pushint(st,map[m].flag.nomvploot); break; + case MF_NORETURN: script_pushint(st,map[m].flag.noreturn); break; + case MF_NOWARPTO: script_pushint(st,map[m].flag.nowarpto); break; + case MF_NIGHTMAREDROP: script_pushint(st,map[m].flag.pvp_nightmaredrop); break; + case MF_RESTRICTED: script_pushint(st,map[m].flag.restricted); break; + case MF_NOCOMMAND: script_pushint(st,map[m].nocommand); break; + case MF_NODROP: script_pushint(st,map[m].flag.nodrop); break; + case MF_JEXP: script_pushint(st,map[m].jexp); break; + case MF_BEXP: script_pushint(st,map[m].bexp); break; + case MF_NOVENDING: script_pushint(st,map[m].flag.novending); break; + case MF_LOADEVENT: script_pushint(st,map[m].flag.loadevent); break; + case MF_NOCHAT: script_pushint(st,map[m].flag.nochat); break; + case MF_NOEXPPENALTY: script_pushint(st,map[m].flag.noexppenalty ); break; + case MF_GUILDLOCK: script_pushint(st,map[m].flag.guildlock); break; + case MF_TOWN: script_pushint(st,map[m].flag.town); break; + case MF_AUTOTRADE: script_pushint(st,map[m].flag.autotrade); break; + case MF_ALLOWKS: script_pushint(st,map[m].flag.allowks); break; + case MF_MONSTER_NOTELEPORT: script_pushint(st,map[m].flag.monster_noteleport); break; + case MF_PVP_NOCALCRANK: script_pushint(st,map[m].flag.pvp_nocalcrank); break; + case MF_BATTLEGROUND: script_pushint(st,map[m].flag.battleground); break; + case MF_RESET: script_pushint(st,map[m].flag.reset); break; + } + } + + return 0; +} +/* pvp timer handling */ +static int script_mapflag_pvp_sub(struct block_list *bl,va_list ap) { + TBL_PC* sd = (TBL_PC*)bl; + if (sd->pvp_timer == INVALID_TIMER) { + 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; + sd->pvp_won = 0; + sd->pvp_lost = 0; + } + clif_map_property(sd, MAPPROPERTY_FREEPVPZONE); + return 0; +} +BUILDIN_FUNC(setmapflag) +{ + int16 m,i; + const char *str; + int val=0; + + str=script_getstr(st,2); + i=script_getnum(st,3); + if(script_hasdata(st,4)){ + val=script_getnum(st,4); + } + 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_NOSAVE: map[m].flag.nosave = 1; break; + case MF_NOBRANCH: map[m].flag.nobranch = 1; break; + case MF_NOPENALTY: map[m].flag.noexppenalty = 1; map[m].flag.nozenypenalty = 1; break; + case MF_NOZENYPENALTY: map[m].flag.nozenypenalty = 1; break; + case MF_PVP: + map[m].flag.pvp = 1; + if( !battle_config.pk_mode ) { + map_foreachinmap(script_mapflag_pvp_sub,m,BL_PC); + } + 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: + map[m].flag.gvg = 1; + clif_map_property_mapall(m, MAPPROPERTY_AGITZONE); + break; + case MF_GVG_NOPARTY: map[m].flag.gvg_noparty = 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_PARTYLOCK: map[m].flag.partylock = 1; break; + case MF_NOICEWALL: map[m].flag.noicewall = 1; break; + case MF_SNOW: map[m].flag.snow = 1; break; + case MF_FOG: map[m].flag.fog = 1; break; + case MF_SAKURA: map[m].flag.sakura = 1; break; + case MF_LEAVES: map[m].flag.leaves = 1; break; + /** + * No longer available, keeping here just in case it's back someday. [Ind] + **/ + //case MF_RAIN: map[m].flag.rain = 1; break; + case MF_NOGO: map[m].flag.nogo = 1; break; + case MF_CLOUDS: map[m].flag.clouds = 1; break; + case MF_CLOUDS2: map[m].flag.clouds2 = 1; break; + case MF_FIREWORKS: map[m].flag.fireworks = 1; break; + case MF_GVG_CASTLE: map[m].flag.gvg_castle = 1; break; + case MF_GVG_DUNGEON: map[m].flag.gvg_dungeon = 1; break; + case MF_NIGHTENABLED: map[m].flag.nightenabled = 1; break; + case MF_NOBASEEXP: map[m].flag.nobaseexp = 1; break; + case MF_NOJOBEXP: map[m].flag.nojobexp = 1; break; + case MF_NOMOBLOOT: map[m].flag.nomobloot = 1; break; + case MF_NOMVPLOOT: map[m].flag.nomvploot = 1; break; + case MF_NORETURN: map[m].flag.noreturn = 1; break; + case MF_NOWARPTO: map[m].flag.nowarpto = 1; break; + case MF_NIGHTMAREDROP: map[m].flag.pvp_nightmaredrop = 1; break; + case MF_RESTRICTED: + map[m].zone |= 1<<(val+1); + map[m].flag.restricted=1; + break; + case MF_NOCOMMAND: map[m].nocommand = (val <= 0) ? 100 : val; break; + case MF_NODROP: map[m].flag.nodrop = 1; break; + case MF_JEXP: map[m].jexp = (val <= 0) ? 100 : val; break; + case MF_BEXP: map[m].bexp = (val <= 0) ? 100 : val; break; + case MF_NOVENDING: map[m].flag.novending = 1; break; + case MF_LOADEVENT: map[m].flag.loadevent = 1; break; + case MF_NOCHAT: map[m].flag.nochat = 1; break; + case MF_NOEXPPENALTY: map[m].flag.noexppenalty = 1; break; + case MF_GUILDLOCK: map[m].flag.guildlock = 1; break; + case MF_TOWN: map[m].flag.town = 1; break; + case MF_AUTOTRADE: map[m].flag.autotrade = 1; break; + case MF_ALLOWKS: map[m].flag.allowks = 1; break; + case MF_MONSTER_NOTELEPORT: map[m].flag.monster_noteleport = 1; break; + case MF_PVP_NOCALCRANK: map[m].flag.pvp_nocalcrank = 1; break; + case MF_BATTLEGROUND: map[m].flag.battleground = (val <= 0 || val > 2) ? 1 : val; break; + case MF_RESET: map[m].flag.reset = 1; break; + } + } + + return 0; +} + +BUILDIN_FUNC(removemapflag) +{ + int16 m,i; + const char *str; + int val=0; + + str=script_getstr(st,2); + i=script_getnum(st,3); + if(script_hasdata(st,4)){ + val=script_getnum(st,4); + } + 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.noexppenalty = 0; map[m].flag.nozenypenalty = 0; break; + case MF_NOZENYPENALTY: map[m].flag.nozenypenalty = 0; break; + case MF_PVP: + map[m].flag.pvp = 0; + clif_map_property_mapall(m, MAPPROPERTY_NOTHING); + 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: + map[m].flag.gvg = 0; + clif_map_property_mapall(m, MAPPROPERTY_NOTHING); + break; + case MF_GVG_NOPARTY: map[m].flag.gvg_noparty = 0; break; + case MF_NOTRADE: map[m].flag.notrade = 0; break; + case MF_NOSKILL: map[m].flag.noskill = 0; break; + case MF_NOWARP: map[m].flag.nowarp = 0; break; + case MF_PARTYLOCK: map[m].flag.partylock = 0; break; + case MF_NOICEWALL: map[m].flag.noicewall = 0; break; + case MF_SNOW: map[m].flag.snow = 0; break; + case MF_FOG: map[m].flag.fog = 0; break; + case MF_SAKURA: map[m].flag.sakura = 0; break; + case MF_LEAVES: map[m].flag.leaves = 0; break; + /** + * No longer available, keeping here just in case it's back someday. [Ind] + **/ + //case MF_RAIN: map[m].flag.rain = 0; break; + case MF_NOGO: map[m].flag.nogo = 0; break; + case MF_CLOUDS: map[m].flag.clouds = 0; break; + case MF_CLOUDS2: map[m].flag.clouds2 = 0; break; + case MF_FIREWORKS: map[m].flag.fireworks = 0; break; + case MF_GVG_CASTLE: map[m].flag.gvg_castle = 0; break; + case MF_GVG_DUNGEON: map[m].flag.gvg_dungeon = 0; break; + case MF_NIGHTENABLED: map[m].flag.nightenabled = 0; break; + case MF_NOBASEEXP: map[m].flag.nobaseexp = 0; break; + case MF_NOJOBEXP: map[m].flag.nojobexp = 0; break; + case MF_NOMOBLOOT: map[m].flag.nomobloot = 0; break; + case MF_NOMVPLOOT: map[m].flag.nomvploot = 0; break; + case MF_NORETURN: map[m].flag.noreturn = 0; break; + case MF_NOWARPTO: map[m].flag.nowarpto = 0; break; + case MF_NIGHTMAREDROP: map[m].flag.pvp_nightmaredrop = 0; break; + case MF_RESTRICTED: + map[m].zone ^= 1<<(val+1); + if (map[m].zone == 0){ + map[m].flag.restricted=0; + } + break; + case MF_NOCOMMAND: map[m].nocommand = 0; break; + case MF_NODROP: map[m].flag.nodrop = 0; break; + case MF_JEXP: map[m].jexp = 0; break; + case MF_BEXP: map[m].bexp = 0; break; + case MF_NOVENDING: map[m].flag.novending = 0; break; + case MF_LOADEVENT: map[m].flag.loadevent = 0; break; + case MF_NOCHAT: map[m].flag.nochat = 0; break; + case MF_NOEXPPENALTY: map[m].flag.noexppenalty = 0; break; + case MF_GUILDLOCK: map[m].flag.guildlock = 0; break; + case MF_TOWN: map[m].flag.town = 0; break; + case MF_AUTOTRADE: map[m].flag.autotrade = 0; break; + case MF_ALLOWKS: map[m].flag.allowks = 0; break; + case MF_MONSTER_NOTELEPORT: map[m].flag.monster_noteleport = 0; break; + case MF_PVP_NOCALCRANK: map[m].flag.pvp_nocalcrank = 0; break; + case MF_BATTLEGROUND: map[m].flag.battleground = 0; break; + case MF_RESET: map[m].flag.reset = 0; break; + } + } + + return 0; +} + +BUILDIN_FUNC(pvpon) +{ + int16 m; + const char *str; + TBL_PC* sd = NULL; + struct s_mapiterator* iter; + + str = script_getstr(st,2); + m = map_mapname2mapid(str); + if( m < 0 || map[m].flag.pvp ) + return 0; // nothing to do + + map[m].flag.pvp = 1; + clif_map_property_mapall(m, MAPPROPERTY_FREEPVPZONE); + + if(battle_config.pk_mode) // disable ranking functions if pk_mode is on [Valaris] + return 0; + + iter = mapit_getallusers(); + for( sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC*)mapit_next(iter) ) + { + if( sd->bl.m != m || sd->pvp_timer != INVALID_TIMER ) + continue; // not applicable + + 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; + sd->pvp_won = 0; + sd->pvp_lost = 0; + } + mapit_free(iter); + + return 0; +} + +static int buildin_pvpoff_sub(struct block_list *bl,va_list ap) +{ + TBL_PC* sd = (TBL_PC*)bl; + clif_pvpset(sd, 0, 0, 2); + if (sd->pvp_timer != INVALID_TIMER) { + delete_timer(sd->pvp_timer, pc_calc_pvprank_timer); + sd->pvp_timer = INVALID_TIMER; + } + return 0; +} + +BUILDIN_FUNC(pvpoff) +{ + int16 m; + const char *str; + + str=script_getstr(st,2); + m = map_mapname2mapid(str); + if(m < 0 || !map[m].flag.pvp) + return 0; //fixed Lupus + + map[m].flag.pvp = 0; + clif_map_property_mapall(m, MAPPROPERTY_NOTHING); + + if(battle_config.pk_mode) // disable ranking options if pk_mode is on [Valaris] + return 0; + + map_foreachinmap(buildin_pvpoff_sub, m, BL_PC); + return 0; +} + +BUILDIN_FUNC(gvgon) +{ + int16 m; + const char *str; + + str=script_getstr(st,2); + m = map_mapname2mapid(str); + if(m >= 0 && !map[m].flag.gvg) { + map[m].flag.gvg = 1; + clif_map_property_mapall(m, MAPPROPERTY_AGITZONE); + } + + return 0; +} +BUILDIN_FUNC(gvgoff) +{ + int16 m; + const char *str; + + str=script_getstr(st,2); + m = map_mapname2mapid(str); + if(m >= 0 && map[m].flag.gvg) { + map[m].flag.gvg = 0; + clif_map_property_mapall(m, MAPPROPERTY_NOTHING); + } + + return 0; +} +/*========================================== + * Shows an emoticon on top of the player/npc + * emotion emotion#, <target: 0 - NPC, 1 - PC>, <NPC/PC name> + *------------------------------------------*/ +//Optional second parameter added by [Skotlex] +BUILDIN_FUNC(emotion) +{ + int type; + int player=0; + + type=script_getnum(st,2); + if(type < 0 || type > 100) + return 0; + + if( script_hasdata(st,3) ) + player=script_getnum(st,3); + + if (player) { + TBL_PC *sd = NULL; + if( script_hasdata(st,4) ) + sd = map_nick2sd(script_getstr(st,4)); + else + sd = script_rid2sd(st); + if (sd) + clif_emotion(&sd->bl,type); + } else + if( script_hasdata(st,4) ) + { + TBL_NPC *nd = npc_name2id(script_getstr(st,4)); + if(nd) + clif_emotion(&nd->bl,type); + } + else + clif_emotion(map_id2bl(st->oid),type); + return 0; +} + +static int buildin_maprespawnguildid_sub_pc(struct map_session_data* sd, va_list ap) +{ + int16 m=va_arg(ap,int); + int g_id=va_arg(ap,int); + int flag=va_arg(ap,int); + + if(!sd || sd->bl.m != m) + return 0; + if( + (sd->status.guild_id == g_id && flag&1) || //Warp out owners + (sd->status.guild_id != g_id && flag&2) || //Warp out outsiders + (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,CLR_TELEPORT); + return 1; +} + +static int buildin_maprespawnguildid_sub_mob(struct block_list *bl,va_list ap) +{ + struct mob_data *md=(struct mob_data *)bl; + + if(!md->guardian_data && md->class_ != MOBID_EMPERIUM) + status_kill(bl); + + return 0; +} + +BUILDIN_FUNC(maprespawnguildid) +{ + const char *mapname=script_getstr(st,2); + int g_id=script_getnum(st,3); + int flag=script_getnum(st,4); + + int16 m=map_mapname2mapid(mapname); + + if(m == -1) + return 0; + + //Catch ALL players (in case some are 'between maps' on execution time) + map_foreachpc(buildin_maprespawnguildid_sub_pc,m,g_id,flag); + if (flag&4) //Remove script mobs. + map_foreachinmap(buildin_maprespawnguildid_sub_mob,m,BL_MOB); + return 0; +} + +BUILDIN_FUNC(agitstart) +{ + if(agit_flag==1) return 0; // Agit already Start. + agit_flag=1; + guild_agit_start(); + return 0; +} + +BUILDIN_FUNC(agitend) +{ + if(agit_flag==0) return 0; // Agit already End. + agit_flag=0; + guild_agit_end(); + return 0; +} + +BUILDIN_FUNC(agitstart2) +{ + if(agit2_flag==1) return 0; // Agit2 already Start. + agit2_flag=1; + guild_agit2_start(); + return 0; +} + +BUILDIN_FUNC(agitend2) +{ + if(agit2_flag==0) return 0; // Agit2 already End. + agit2_flag=0; + guild_agit2_end(); + return 0; +} + +/*========================================== + * Returns whether woe is on or off. // choice script + *------------------------------------------*/ +BUILDIN_FUNC(agitcheck) +{ + script_pushint(st,agit_flag); + return 0; +} + +/*========================================== + * Returns whether woese is on or off. // choice script + *------------------------------------------*/ +BUILDIN_FUNC(agitcheck2) +{ + script_pushint(st,agit2_flag); + return 0; +} + +/// Sets the guild_id of this npc. +/// +/// flagemblem <guild_id>; +BUILDIN_FUNC(flagemblem) +{ + TBL_NPC* nd; + int g_id = script_getnum(st,2); + + if(g_id < 0) return 0; + + nd = (TBL_NPC*)map_id2nd(st->oid); + if( nd == NULL ) { + ShowError("script:flagemblem: npc %d not found\n", st->oid); + } else if( nd->subtype != SCRIPT ) { + ShowError("script:flagemblem: unexpected subtype %d for npc %d '%s'\n", nd->subtype, st->oid, nd->exname); + } else { + bool changed = ( nd->u.scr.guild_id != g_id )?true:false; + nd->u.scr.guild_id = g_id; + clif_guild_emblem_area(&nd->bl); + /* guild flag caching */ + if( g_id ) /* adding a id */ + guild_flag_add(nd); + else if( changed ) /* removing a flag */ + guild_flag_remove(nd); + } + return 0; +} + +BUILDIN_FUNC(getcastlename) +{ + const char* mapname = mapindex_getmapname(script_getstr(st,2),NULL); + struct guild_castle* gc = guild_mapname2gc(mapname); + const char* name = (gc) ? gc->castle_name : ""; + script_pushstrcopy(st,name); + return 0; +} + +BUILDIN_FUNC(getcastledata) +{ + const char *mapname = mapindex_getmapname(script_getstr(st,2),NULL); + int index = script_getnum(st,3); + struct guild_castle *gc = guild_mapname2gc(mapname); + + if (gc == NULL) { + script_pushint(st,0); + ShowWarning("buildin_setcastledata: guild castle for map '%s' not found\n", mapname); + return 1; + } + + switch (index) { + case 1: + script_pushint(st,gc->guild_id); break; + case 2: + script_pushint(st,gc->economy); break; + case 3: + script_pushint(st,gc->defense); break; + case 4: + script_pushint(st,gc->triggerE); break; + case 5: + script_pushint(st,gc->triggerD); break; + case 6: + script_pushint(st,gc->nextTime); break; + case 7: + script_pushint(st,gc->payTime); break; + case 8: + script_pushint(st,gc->createTime); break; + case 9: + script_pushint(st,gc->visibleC); break; + default: + if (index > 9 && index <= 9+MAX_GUARDIANS) { + script_pushint(st,gc->guardian[index-10].visible); + break; + } + script_pushint(st,0); + ShowWarning("buildin_setcastledata: index = '%d' is out of allowed range\n", index); + return 1; + } + return 0; +} + +BUILDIN_FUNC(setcastledata) +{ + const char *mapname = mapindex_getmapname(script_getstr(st,2),NULL); + int index = script_getnum(st,3); + int value = script_getnum(st,4); + struct guild_castle *gc = guild_mapname2gc(mapname); + + if (gc == NULL) { + ShowWarning("buildin_setcastledata: guild castle for map '%s' not found\n", mapname); + return 1; + } + + if (index <= 0 || index > 9+MAX_GUARDIANS) { + ShowWarning("buildin_setcastledata: index = '%d' is out of allowed range\n", index); + return 1; + } + + guild_castledatasave(gc->castle_id, index, value); + return 0; +} + +/* ===================================================================== + * ---------------------------------------------------------------------*/ +BUILDIN_FUNC(requestguildinfo) +{ + int guild_id=script_getnum(st,2); + const char *event=NULL; + + if( script_hasdata(st,3) ){ + event=script_getstr(st,3); + check_event(st, event); + } + + if(guild_id>0) + guild_npc_request_info(guild_id,event); + return 0; +} + +/// Returns the number of cards that have been compounded onto the specified equipped item. +/// getequipcardcnt(<equipment slot>); +BUILDIN_FUNC(getequipcardcnt) +{ + int i=-1,j,num; + TBL_PC *sd; + int count; + + num=script_getnum(st,2); + sd=script_rid2sd(st); + if (num > 0 && num <= ARRAYLENGTH(equip)) + i=pc_checkequip(sd,equip[num-1]); + + if (i < 0 || !sd->inventory_data[i]) { + script_pushint(st,0); + return 0; + } + + if(itemdb_isspecial(sd->status.inventory[i].card[0])) + { + script_pushint(st,0); + return 0; + } + + count = 0; + for( j = 0; j < sd->inventory_data[i]->slot; j++ ) + if( sd->status.inventory[i].card[j] && itemdb_type(sd->status.inventory[i].card[j]) == IT_CARD ) + count++; + + script_pushint(st,count); + return 0; +} + +/// Removes all cards from the item found in the specified equipment slot of the invoking character, +/// and give them to the character. If any cards were removed in this manner, it will also show a success effect. +/// successremovecards <slot>; +BUILDIN_FUNC(successremovecards) { + int i=-1,j,c,cardflag=0; + + TBL_PC* sd = script_rid2sd(st); + int num = script_getnum(st,2); + + if (num > 0 && num <= ARRAYLENGTH(equip)) + i=pc_checkequip(sd,equip[num-1]); + + if (i < 0 || !sd->inventory_data[i]) { + return 0; + } + + if(itemdb_isspecial(sd->status.inventory[i].card[0])) + return 0; + + for( c = sd->inventory_data[i]->slot - 1; c >= 0; --c ) { + if( sd->status.inventory[i].card[c] && itemdb_type(sd->status.inventory[i].card[c]) == IT_CARD ) {// extract this card from the item + int flag; + struct item item_tmp; + memset(&item_tmp,0,sizeof(item_tmp)); + cardflag = 1; + item_tmp.nameid = sd->status.inventory[i].card[c]; + item_tmp.identify = 1; + + if((flag=pc_additem(sd,&item_tmp,1,LOG_TYPE_SCRIPT))){ // get back the cart in inventory + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + } + } + + if(cardflag == 1) {//if card was remove remplace item with no card + int flag; + struct item item_tmp; + memset(&item_tmp,0,sizeof(item_tmp)); + + item_tmp.nameid = sd->status.inventory[i].nameid; + item_tmp.identify = 1; + item_tmp.refine = sd->status.inventory[i].refine; + item_tmp.attribute = sd->status.inventory[i].attribute; + item_tmp.expire_time = sd->status.inventory[i].expire_time; + + for (j = sd->inventory_data[i]->slot; j < MAX_SLOTS; j++) + item_tmp.card[j]=sd->status.inventory[i].card[j]; + + pc_delitem(sd,i,1,0,3,LOG_TYPE_SCRIPT); + if((flag=pc_additem(sd,&item_tmp,1,LOG_TYPE_SCRIPT))){ //chk if can be spawn in inventory otherwise put on floor + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + + clif_misceffect(&sd->bl,3); + } + return 0; +} + +/// Removes all cards from the item found in the specified equipment slot of the invoking character. +/// failedremovecards <slot>, <type>; +/// <type>=0 : will destroy both the item and the cards. +/// <type>=1 : will keep the item, but destroy the cards. +/// <type>=2 : will keep the cards, but destroy the item. +/// <type>=? : will just display the failure effect. +BUILDIN_FUNC(failedremovecards) { + int i=-1,j,c,cardflag=0; + + TBL_PC* sd = script_rid2sd(st); + int num = script_getnum(st,2); + int typefail = script_getnum(st,3); + + if (num > 0 && num <= ARRAYLENGTH(equip)) + i=pc_checkequip(sd,equip[num-1]); + + if (i < 0 || !sd->inventory_data[i]) + return 0; + + if(itemdb_isspecial(sd->status.inventory[i].card[0])) + return 0; + + for( c = sd->inventory_data[i]->slot - 1; c >= 0; --c ) { + if( sd->status.inventory[i].card[c] && itemdb_type(sd->status.inventory[i].card[c]) == IT_CARD ) { + cardflag = 1; + + if(typefail == 2) {// add cards to inventory, clear + int flag; + struct item item_tmp; + + memset(&item_tmp,0,sizeof(item_tmp)); + + item_tmp.nameid = sd->status.inventory[i].card[c]; + item_tmp.identify = 1; + + if((flag=pc_additem(sd,&item_tmp,1,LOG_TYPE_SCRIPT))){ + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + } + } + } + + if(cardflag == 1) { + if(typefail == 0 || typefail == 2){ // destroy the item + pc_delitem(sd,i,1,0,2,LOG_TYPE_SCRIPT); + } + if(typefail == 1){ // destroy the card + int flag; + struct item item_tmp; + + memset(&item_tmp,0,sizeof(item_tmp)); + + item_tmp.nameid = sd->status.inventory[i].nameid; + item_tmp.identify = 1; + item_tmp.refine = sd->status.inventory[i].refine; + item_tmp.attribute = sd->status.inventory[i].attribute; + item_tmp.expire_time = sd->status.inventory[i].expire_time; + + for (j = sd->inventory_data[i]->slot; j < MAX_SLOTS; j++) + item_tmp.card[j]=sd->status.inventory[i].card[j]; + + pc_delitem(sd,i,1,0,2,LOG_TYPE_SCRIPT); + + if((flag=pc_additem(sd,&item_tmp,1,LOG_TYPE_SCRIPT))){ + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + } + clif_misceffect(&sd->bl,2); + } + + return 0; +} + +/* ================================================================ + * mapwarp "<from map>","<to map>",<x>,<y>,<type>,<ID for Type>; + * type: 0=everyone, 1=guild, 2=party; [Reddozen] + * improved by [Lance] + * ================================================================*/ +BUILDIN_FUNC(mapwarp) // Added by RoVeRT +{ + int x,y,m,check_val=0,check_ID=0,i=0; + struct guild *g = NULL; + struct party_data *p = NULL; + const char *str; + const char *mapname; + unsigned int index; + mapname=script_getstr(st,2); + str=script_getstr(st,3); + x=script_getnum(st,4); + y=script_getnum(st,5); + if(script_hasdata(st,7)){ + check_val=script_getnum(st,6); + check_ID=script_getnum(st,7); + } + + if((m=map_mapname2mapid(mapname))< 0) + return 0; + + if(!(index=mapindex_name2id(str))) + return 0; + + switch(check_val){ + case 1: + g = guild_search(check_ID); + if (g){ + for( i=0; i < g->max_member; i++) + { + if(g->member[i].sd && g->member[i].sd->bl.m==m){ + pc_setpos(g->member[i].sd,index,x,y,CLR_TELEPORT); + } + } + } + break; + case 2: + p = party_search(check_ID); + if(p){ + for(i=0;i<MAX_PARTY; i++){ + if(p->data[i].sd && p->data[i].sd->bl.m == m){ + pc_setpos(p->data[i].sd,index,x,y,CLR_TELEPORT); + } + } + } + break; + default: + map_foreachinmap(buildin_areawarp_sub,m,BL_PC,index,x,y,0,0); + break; + } + + return 0; +} + +static int buildin_mobcount_sub(struct block_list *bl,va_list ap) // Added by RoVeRT +{ + char *event=va_arg(ap,char *); + struct mob_data *md = ((struct mob_data *)bl); + if( md->status.hp > 0 && (!event || strcmp(event,md->npc_event) == 0) ) + return 1; + return 0; +} + +BUILDIN_FUNC(mobcount) // Added by RoVeRT +{ + const char *mapname,*event; + int16 m; + mapname=script_getstr(st,2); + event=script_getstr(st,3); + + if( strcmp(event, "all") == 0 ) + event = NULL; + else + check_event(st, event); + + if( strcmp(mapname, "this") == 0 ) { + struct map_session_data *sd = script_rid2sd(st); + if( sd ) + m = sd->bl.m; + else { + script_pushint(st,-1); + return 0; + } + } + else if( (m = map_mapname2mapid(mapname)) < 0 ) { + script_pushint(st,-1); + return 0; + } + + if( map[m].flag.src4instance && map[m].instance_id == 0 && st->instance_id && (m = instance_mapid2imapid(m, st->instance_id)) < 0 ) + { + script_pushint(st,-1); + return 0; + } + + script_pushint(st,map_foreachinmap(buildin_mobcount_sub, m, BL_MOB, event)); + + return 0; +} + +BUILDIN_FUNC(marriage) +{ + const char *partner=script_getstr(st,2); + TBL_PC *sd=script_rid2sd(st); + TBL_PC *p_sd=map_nick2sd(partner); + + if(sd==NULL || p_sd==NULL || pc_marriage(sd,p_sd) < 0){ + script_pushint(st,0); + return 0; + } + script_pushint(st,1); + return 0; +} +BUILDIN_FUNC(wedding_effect) +{ + TBL_PC *sd=script_rid2sd(st); + struct block_list *bl; + + if(sd==NULL) { + bl=map_id2bl(st->oid); + } else + bl=&sd->bl; + clif_wedding_effect(bl); + return 0; +} +BUILDIN_FUNC(divorce) +{ + TBL_PC *sd=script_rid2sd(st); + if(sd==NULL || pc_divorce(sd) < 0){ + script_pushint(st,0); + return 0; + } + script_pushint(st,1); + return 0; +} + +BUILDIN_FUNC(ispartneron) +{ + TBL_PC *sd=script_rid2sd(st); + + if(sd==NULL || !pc_ismarried(sd) || + map_charid2sd(sd->status.partner_id) == NULL) { + script_pushint(st,0); + return 0; + } + + script_pushint(st,1); + return 0; +} + +BUILDIN_FUNC(getpartnerid) +{ + TBL_PC *sd=script_rid2sd(st); + if (sd == NULL) { + script_pushint(st,0); + return 0; + } + + script_pushint(st,sd->status.partner_id); + return 0; +} + +BUILDIN_FUNC(getchildid) +{ + TBL_PC *sd=script_rid2sd(st); + if (sd == NULL) { + script_pushint(st,0); + return 0; + } + + script_pushint(st,sd->status.child); + return 0; +} + +BUILDIN_FUNC(getmotherid) +{ + TBL_PC *sd=script_rid2sd(st); + if (sd == NULL) { + script_pushint(st,0); + return 0; + } + + script_pushint(st,sd->status.mother); + return 0; +} + +BUILDIN_FUNC(getfatherid) +{ + TBL_PC *sd=script_rid2sd(st); + if (sd == NULL) { + script_pushint(st,0); + return 0; + } + + script_pushint(st,sd->status.father); + return 0; +} + +BUILDIN_FUNC(warppartner) +{ + int x,y; + unsigned short mapindex; + const char *str; + TBL_PC *sd=script_rid2sd(st); + TBL_PC *p_sd=NULL; + + if(sd==NULL || !pc_ismarried(sd) || + (p_sd=map_charid2sd(sd->status.partner_id)) == NULL) { + script_pushint(st,0); + return 0; + } + + str=script_getstr(st,2); + x=script_getnum(st,3); + y=script_getnum(st,4); + + mapindex = mapindex_name2id(str); + if (mapindex) { + pc_setpos(p_sd,mapindex,x,y,CLR_OUTSIGHT); + script_pushint(st,1); + } else + script_pushint(st,0); + return 0; +} + +/*================================================ + * Script for Displaying MOB Information [Valaris] + *------------------------------------------------*/ +BUILDIN_FUNC(strmobinfo) +{ + + int num=script_getnum(st,2); + int class_=script_getnum(st,3); + + if(!mobdb_checkid(class_)) + { + if (num < 3) //requested a string + script_pushconststr(st,""); + else + script_pushint(st,0); + return 0; + } + + switch (num) { + case 1: script_pushstrcopy(st,mob_db(class_)->name); break; + case 2: script_pushstrcopy(st,mob_db(class_)->jname); break; + case 3: script_pushint(st,mob_db(class_)->lv); break; + case 4: script_pushint(st,mob_db(class_)->status.max_hp); break; + case 5: script_pushint(st,mob_db(class_)->status.max_sp); break; + case 6: script_pushint(st,mob_db(class_)->base_exp); break; + case 7: script_pushint(st,mob_db(class_)->job_exp); break; + default: + script_pushint(st,0); + break; + } + return 0; +} + +/*========================================== + * Summon guardians [Valaris] + * guardian("<map name>",<x>,<y>,"<name to show>",<mob id>{,"<event label>"}{,<guardian index>}) -> <id> + *------------------------------------------*/ +BUILDIN_FUNC(guardian) +{ + int class_=0,x=0,y=0,guardian=0; + const char *str,*map,*evt=""; + struct script_data *data; + bool has_index = false; + + map =script_getstr(st,2); + x =script_getnum(st,3); + y =script_getnum(st,4); + str =script_getstr(st,5); + class_=script_getnum(st,6); + + if( script_hasdata(st,8) ) + {// "<event label>",<guardian index> + evt=script_getstr(st,7); + guardian=script_getnum(st,8); + has_index = true; + } else if( script_hasdata(st,7) ){ + data=script_getdata(st,7); + get_val(st,data); + if( data_isstring(data) ) + {// "<event label>" + evt=script_getstr(st,7); + } else if( data_isint(data) ) + {// <guardian index> + guardian=script_getnum(st,7); + has_index = true; + } else { + ShowError("script:guardian: invalid data type for argument #6 (from 1)\n"); + script_reportdata(data); + return 1; + } + } + + check_event(st, evt); + script_pushint(st, mob_spawn_guardian(map,x,y,str,class_,evt,guardian,has_index)); + + return 0; +} +/*========================================== + * Invisible Walls [Zephyrus] + *------------------------------------------*/ +BUILDIN_FUNC(setwall) +{ + const char *map, *name; + int x, y, m, size, dir; + bool shootable; + + map = script_getstr(st,2); + x = script_getnum(st,3); + y = script_getnum(st,4); + size = script_getnum(st,5); + dir = script_getnum(st,6); + shootable = script_getnum(st,7); + name = script_getstr(st,8); + + if( (m = map_mapname2mapid(map)) < 0 ) + return 0; // Invalid Map + + map_iwall_set(m, x, y, size, dir, shootable, name); + return 0; +} +BUILDIN_FUNC(delwall) +{ + const char *name = script_getstr(st,2); + map_iwall_remove(name); + + return 0; +} + +/// Retrieves various information about the specified guardian. +/// +/// guardianinfo("<map_name>", <index>, <type>) -> <value> +/// type: 0 - whether it is deployed or not +/// 1 - maximum hp +/// 2 - current hp +/// +BUILDIN_FUNC(guardianinfo) +{ + const char* mapname = mapindex_getmapname(script_getstr(st,2),NULL); + int id = script_getnum(st,3); + int type = script_getnum(st,4); + + struct guild_castle* gc = guild_mapname2gc(mapname); + struct mob_data* gd; + + if( gc == NULL || id < 0 || id >= MAX_GUARDIANS ) + { + script_pushint(st,-1); + return 0; + } + + if( type == 0 ) + script_pushint(st, gc->guardian[id].visible); + else + if( !gc->guardian[id].visible ) + script_pushint(st,-1); + else + if( (gd = map_id2md(gc->guardian[id].id)) == NULL ) + script_pushint(st,-1); + else + { + if ( type == 1 ) script_pushint(st,gd->status.max_hp); + else if( type == 2 ) script_pushint(st,gd->status.hp); + else + script_pushint(st,-1); + } + + return 0; +} + +/*========================================== + * Get the item name by item_id or null + *------------------------------------------*/ +BUILDIN_FUNC(getitemname) +{ + int item_id=0; + struct item_data *i_data; + char *item_name; + struct script_data *data; + + data=script_getdata(st,2); + get_val(st,data); + + if( data_isstring(data) ){ + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + if( item_data ) + item_id=item_data->nameid; + }else + item_id=conv_num(st,data); + + i_data = itemdb_exists(item_id); + if (i_data == NULL) + { + script_pushconststr(st,"null"); + return 0; + } + item_name=(char *)aMalloc(ITEM_NAME_LENGTH*sizeof(char)); + + memcpy(item_name, i_data->jname, ITEM_NAME_LENGTH); + script_pushstr(st,item_name); + return 0; +} +/*========================================== + * Returns number of slots an item has. [Skotlex] + *------------------------------------------*/ +BUILDIN_FUNC(getitemslots) +{ + int item_id; + struct item_data *i_data; + + item_id=script_getnum(st,2); + + i_data = itemdb_exists(item_id); + + if (i_data) + script_pushint(st,i_data->slot); + else + script_pushint(st,-1); + return 0; +} + +// TODO: add matk here if needed/once we get rid of RENEWAL + +/*========================================== + * Returns some values of an item [Lupus] + * Price, Weight, etc... + getiteminfo(itemID,n), where n + 0 value_buy; + 1 value_sell; + 2 type; + 3 maxchance = Max drop chance of this item e.g. 1 = 0.01% , etc.. + if = 0, then monsters don't drop it at all (rare or a quest item) + if = -1, then this item is sold in NPC shops only + 4 sex; + 5 equip; + 6 weight; + 7 atk; + 8 def; + 9 range; + 10 slot; + 11 look; + 12 elv; + 13 wlv; + 14 view id + *------------------------------------------*/ +BUILDIN_FUNC(getiteminfo) +{ + int item_id,n; + int *item_arr; + struct item_data *i_data; + + item_id = script_getnum(st,2); + n = script_getnum(st,3); + i_data = itemdb_exists(item_id); + + if (i_data && n>=0 && n<=14) { + item_arr = (int*)&i_data->value_buy; + script_pushint(st,item_arr[n]); + } else + script_pushint(st,-1); + return 0; +} + +/*========================================== + * Set some values of an item [Lupus] + * Price, Weight, etc... + setiteminfo(itemID,n,Value), where n + 0 value_buy; + 1 value_sell; + 2 type; + 3 maxchance = Max drop chance of this item e.g. 1 = 0.01% , etc.. + if = 0, then monsters don't drop it at all (rare or a quest item) + if = -1, then this item is sold in NPC shops only + 4 sex; + 5 equip; + 6 weight; + 7 atk; + 8 def; + 9 range; + 10 slot; + 11 look; + 12 elv; + 13 wlv; + 14 view id + * Returns Value or -1 if the wrong field's been set + *------------------------------------------*/ +BUILDIN_FUNC(setiteminfo) +{ + int item_id,n,value; + int *item_arr; + struct item_data *i_data; + + item_id = script_getnum(st,2); + n = script_getnum(st,3); + value = script_getnum(st,4); + i_data = itemdb_exists(item_id); + + if (i_data && n>=0 && n<=14) { + item_arr = (int*)&i_data->value_buy; + item_arr[n] = value; + script_pushint(st,value); + } else + script_pushint(st,-1); + return 0; +} + +/*========================================== + * Returns value from equipped item slot n [Lupus] + getequipcardid(num,slot) + where + num = eqip position slot + slot = 0,1,2,3 (Card Slot N) + + This func returns CARD ID, 255,254,-255 (for card 0, if the item is produced) + it's useful when you want to check item cards or if it's signed + Useful for such quests as "Sign this refined item with players name" etc + Hat[0] +4 -> Player's Hat[0] +4 + *------------------------------------------*/ +BUILDIN_FUNC(getequipcardid) +{ + int i=-1,num,slot; + TBL_PC *sd; + + num=script_getnum(st,2); + slot=script_getnum(st,3); + sd=script_rid2sd(st); + if (num > 0 && num <= ARRAYLENGTH(equip)) + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0 && slot>=0 && slot<4) + script_pushint(st,sd->status.inventory[i].card[slot]); + else + script_pushint(st,0); + + return 0; +} + +/*========================================== + * petskillbonus [Valaris] //Rewritten by [Skotlex] + *------------------------------------------*/ +BUILDIN_FUNC(petskillbonus) +{ + struct pet_data *pd; + + TBL_PC *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + if (pd->bonus) + { //Clear previous bonus + if (pd->bonus->timer != INVALID_TIMER) + delete_timer(pd->bonus->timer, pet_skill_bonus_timer); + } else //init + pd->bonus = (struct pet_bonus *) aMalloc(sizeof(struct pet_bonus)); + + pd->bonus->type=script_getnum(st,2); + pd->bonus->val=script_getnum(st,3); + pd->bonus->duration=script_getnum(st,4); + pd->bonus->delay=script_getnum(st,5); + + if (pd->state.skillbonus == 1) + pd->state.skillbonus=0; // waiting state + + // wait for timer to start + if (battle_config.pet_equip_required && pd->pet.equip == 0) + pd->bonus->timer = INVALID_TIMER; + else + pd->bonus->timer = add_timer(gettick()+pd->bonus->delay*1000, pet_skill_bonus_timer, sd->bl.id, 0); + + return 0; +} + +/*========================================== + * pet looting [Valaris] //Rewritten by [Skotlex] + *------------------------------------------*/ +BUILDIN_FUNC(petloot) +{ + int max; + struct pet_data *pd; + TBL_PC *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + max=script_getnum(st,2); + + if(max < 1) + max = 1; //Let'em loot at least 1 item. + else if (max > MAX_PETLOOT_SIZE) + max = MAX_PETLOOT_SIZE; + + pd = sd->pd; + if (pd->loot != NULL) + { //Release whatever was there already and reallocate memory + pet_lootitem_drop(pd, pd->msd); + aFree(pd->loot->item); + } + else + pd->loot = (struct pet_loot *)aMalloc(sizeof(struct pet_loot)); + + pd->loot->item = (struct item *)aCalloc(max,sizeof(struct item)); + + pd->loot->max=max; + pd->loot->count = 0; + pd->loot->weight = 0; + + return 0; +} +/*========================================== + * Set arrays with info of all sd inventory : + * @inventorylist_id, @inventorylist_amount, @inventorylist_equip, + * @inventorylist_refine, @inventorylist_identify, @inventorylist_attribute, + * @inventorylist_card(0..3), @inventorylist_expire + * @inventorylist_count = scalar + *------------------------------------------*/ +BUILDIN_FUNC(getinventorylist) +{ + TBL_PC *sd=script_rid2sd(st); + char card_var[NAME_LENGTH]; + + int i,j=0,k; + 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,reference_uid(add_str("@inventorylist_id"), j),sd->status.inventory[i].nameid); + pc_setreg(sd,reference_uid(add_str("@inventorylist_amount"), j),sd->status.inventory[i].amount); + pc_setreg(sd,reference_uid(add_str("@inventorylist_equip"), j),sd->status.inventory[i].equip); + pc_setreg(sd,reference_uid(add_str("@inventorylist_refine"), j),sd->status.inventory[i].refine); + pc_setreg(sd,reference_uid(add_str("@inventorylist_identify"), j),sd->status.inventory[i].identify); + pc_setreg(sd,reference_uid(add_str("@inventorylist_attribute"), j),sd->status.inventory[i].attribute); + for (k = 0; k < MAX_SLOTS; k++) + { + sprintf(card_var, "@inventorylist_card%d",k+1); + pc_setreg(sd,reference_uid(add_str(card_var), j),sd->status.inventory[i].card[k]); + } + pc_setreg(sd,reference_uid(add_str("@inventorylist_expire"), j),sd->status.inventory[i].expire_time); + j++; + } + } + pc_setreg(sd,add_str("@inventorylist_count"),j); + return 0; +} + +BUILDIN_FUNC(getskilllist) +{ + TBL_PC *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,reference_uid(add_str("@skilllist_id"), j),sd->status.skill[i].id); + pc_setreg(sd,reference_uid(add_str("@skilllist_lv"), j),sd->status.skill[i].lv); + pc_setreg(sd,reference_uid(add_str("@skilllist_flag"), j),sd->status.skill[i].flag); + j++; + } + } + pc_setreg(sd,add_str("@skilllist_count"),j); + return 0; +} + +BUILDIN_FUNC(clearitem) +{ + TBL_PC *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, 0, LOG_TYPE_SCRIPT); + } + } + return 0; +} + +/*========================================== + * Disguise Player (returns Mob/NPC ID if success, 0 on fail) + *------------------------------------------*/ +BUILDIN_FUNC(disguise) +{ + int id; + TBL_PC* sd = script_rid2sd(st); + if (sd == NULL) return 0; + + id = script_getnum(st,2); + + if (mobdb_checkid(id) || npcdb_checkid(id)) { + pc_disguise(sd, id); + script_pushint(st,id); + } else + script_pushint(st,0); + + return 0; +} + +/*========================================== + * Undisguise Player (returns 1 if success, 0 on fail) + *------------------------------------------*/ +BUILDIN_FUNC(undisguise) +{ + TBL_PC* sd = script_rid2sd(st); + if (sd == NULL) return 0; + + if (sd->disguise) { + pc_disguise(sd, 0); + script_pushint(st,0); + } else { + script_pushint(st,1); + } + return 0; +} + +/*========================================== + * Transform a bl to another _class, + * @type unused + *------------------------------------------*/ +BUILDIN_FUNC(classchange) +{ + int _class,type; + struct block_list *bl=map_id2bl(st->oid); + + if(bl==NULL) return 0; + + _class=script_getnum(st,2); + type=script_getnum(st,3); + clif_class_change(bl,_class,type); + return 0; +} + +/*========================================== + * Display an effect + *------------------------------------------*/ +BUILDIN_FUNC(misceffect) +{ + int type; + + type=script_getnum(st,2); + if(st->oid && st->oid != fake_nd->bl.id) { + struct block_list *bl = map_id2bl(st->oid); + if (bl) + clif_specialeffect(bl,type,AREA); + } else{ + TBL_PC *sd=script_rid2sd(st); + if(sd) + clif_specialeffect(&sd->bl,type,AREA); + } + return 0; +} +/*========================================== + * Play a BGM on a single client [Rikter/Yommy] + *------------------------------------------*/ +BUILDIN_FUNC(playBGM) +{ + const char* name; + struct map_session_data* sd; + + if( ( sd = script_rid2sd(st) ) != NULL ) + { + name = script_getstr(st,2); + + clif_playBGM(sd, name); + } + + return 0; +} + +static int playBGM_sub(struct block_list* bl,va_list ap) +{ + const char* name = va_arg(ap,const char*); + + clif_playBGM(BL_CAST(BL_PC, bl), name); + + return 0; +} + +static int playBGM_foreachpc_sub(struct map_session_data* sd, va_list args) +{ + const char* name = va_arg(args, const char*); + + clif_playBGM(sd, name); + return 0; +} + +/*========================================== + * Play a BGM on multiple client [Rikter/Yommy] + *------------------------------------------*/ +BUILDIN_FUNC(playBGMall) +{ + const char* name; + + name = script_getstr(st,2); + + if( script_hasdata(st,7) ) + {// specified part of map + const char* map = script_getstr(st,3); + int x0 = script_getnum(st,4); + int y0 = script_getnum(st,5); + int x1 = script_getnum(st,6); + int y1 = script_getnum(st,7); + + map_foreachinarea(playBGM_sub, map_mapname2mapid(map), x0, y0, x1, y1, BL_PC, name); + } + else if( script_hasdata(st,3) ) + {// entire map + const char* map = script_getstr(st,3); + + map_foreachinmap(playBGM_sub, map_mapname2mapid(map), BL_PC, name); + } + else + {// entire server + map_foreachpc(&playBGM_foreachpc_sub, name); + } + + return 0; +} + +/*========================================== + * Play a .wav sound for sd + *------------------------------------------*/ +BUILDIN_FUNC(soundeffect) +{ + TBL_PC* sd = script_rid2sd(st); + const char* name = script_getstr(st,2); + int type = script_getnum(st,3); + + if(sd) + { + clif_soundeffect(sd,&sd->bl,name,type); + } + return 0; +} + +int soundeffect_sub(struct block_list* bl,va_list ap) +{ + char* name = va_arg(ap,char*); + int type = va_arg(ap,int); + + clif_soundeffect((TBL_PC *)bl, bl, name, type); + + return 0; +} + +/*========================================== + * Play a sound effect (.wav) on multiple clients + * soundeffectall "<filepath>",<type>{,"<map name>"}{,<x0>,<y0>,<x1>,<y1>}; + *------------------------------------------*/ +BUILDIN_FUNC(soundeffectall) +{ + struct block_list* bl; + const char* name; + int type; + + bl = (st->rid) ? &(script_rid2sd(st)->bl) : map_id2bl(st->oid); + if (!bl) + return 0; + + name = script_getstr(st,2); + type = script_getnum(st,3); + + //FIXME: enumerating map squares (map_foreach) is slower than enumerating the list of online players (map_foreachpc?) [ultramage] + + if(!script_hasdata(st,4)) + { // area around + clif_soundeffectall(bl, name, type, AREA); + } + else + if(!script_hasdata(st,5)) + { // entire map + const char* map = script_getstr(st,4); + map_foreachinmap(soundeffect_sub, map_mapname2mapid(map), BL_PC, name, type); + } + else + if(script_hasdata(st,8)) + { // specified part of map + const char* map = script_getstr(st,4); + int x0 = script_getnum(st,5); + int y0 = script_getnum(st,6); + int x1 = script_getnum(st,7); + int y1 = script_getnum(st,8); + map_foreachinarea(soundeffect_sub, map_mapname2mapid(map), x0, y0, x1, y1, BL_PC, name, type); + } + else + { + ShowError("buildin_soundeffectall: insufficient arguments for specific area broadcast.\n"); + } + + return 0; +} +/*========================================== + * pet status recovery [Valaris] / Rewritten by [Skotlex] + *------------------------------------------*/ +BUILDIN_FUNC(petrecovery) +{ + struct pet_data *pd; + TBL_PC *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + + if (pd->recovery) + { //Halt previous bonus + if (pd->recovery->timer != INVALID_TIMER) + delete_timer(pd->recovery->timer, pet_recovery_timer); + } else //Init + pd->recovery = (struct pet_recovery *)aMalloc(sizeof(struct pet_recovery)); + + pd->recovery->type = (sc_type)script_getnum(st,2); + pd->recovery->delay = script_getnum(st,3); + pd->recovery->timer = INVALID_TIMER; + + return 0; +} + +/*========================================== + * pet healing [Valaris] //Rewritten by [Skotlex] + *------------------------------------------*/ +BUILDIN_FUNC(petheal) +{ + struct pet_data *pd; + TBL_PC *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + if (pd->s_skill) + { //Clear previous skill + if (pd->s_skill->timer != INVALID_TIMER) + { + if (pd->s_skill->id) + delete_timer(pd->s_skill->timer, pet_skill_support_timer); + else + delete_timer(pd->s_skill->timer, pet_heal_timer); + } + } else //init memory + pd->s_skill = (struct pet_skill_support *) aMalloc(sizeof(struct pet_skill_support)); + + pd->s_skill->id=0; //This id identifies that it IS petheal rather than pet_skillsupport + //Use the lv as the amount to heal + pd->s_skill->lv=script_getnum(st,2); + pd->s_skill->delay=script_getnum(st,3); + pd->s_skill->hp=script_getnum(st,4); + pd->s_skill->sp=script_getnum(st,5); + + //Use delay as initial offset to avoid skill/heal exploits + if (battle_config.pet_equip_required && pd->pet.equip == 0) + pd->s_skill->timer = INVALID_TIMER; + else + pd->s_skill->timer = add_timer(gettick()+pd->s_skill->delay*1000,pet_heal_timer,sd->bl.id,0); + + return 0; +} + +/*========================================== + * pet attack skills [Valaris] //Rewritten by [Skotlex] + *------------------------------------------*/ +/// petskillattack <skill id>,<level>,<rate>,<bonusrate> +/// petskillattack "<skill name>",<level>,<rate>,<bonusrate> +BUILDIN_FUNC(petskillattack) +{ + struct pet_data *pd; + TBL_PC *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + if (pd->a_skill == NULL) + pd->a_skill = (struct pet_skill_attack *)aMalloc(sizeof(struct pet_skill_attack)); + + pd->a_skill->id=( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); + pd->a_skill->lv=script_getnum(st,3); + pd->a_skill->div_ = 0; + pd->a_skill->rate=script_getnum(st,4); + pd->a_skill->bonusrate=script_getnum(st,5); + + return 0; +} + +/*========================================== + * pet attack skills [Valaris] + *------------------------------------------*/ +/// petskillattack2 <skill id>,<level>,<div>,<rate>,<bonusrate> +/// petskillattack2 "<skill name>",<level>,<div>,<rate>,<bonusrate> +BUILDIN_FUNC(petskillattack2) +{ + struct pet_data *pd; + TBL_PC *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + if (pd->a_skill == NULL) + pd->a_skill = (struct pet_skill_attack *)aMalloc(sizeof(struct pet_skill_attack)); + + pd->a_skill->id=( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); + pd->a_skill->lv=script_getnum(st,3); + pd->a_skill->div_ = script_getnum(st,4); + pd->a_skill->rate=script_getnum(st,5); + pd->a_skill->bonusrate=script_getnum(st,6); + + return 0; +} + +/*========================================== + * pet support skills [Skotlex] + *------------------------------------------*/ +/// petskillsupport <skill id>,<level>,<delay>,<hp>,<sp> +/// petskillsupport "<skill name>",<level>,<delay>,<hp>,<sp> +BUILDIN_FUNC(petskillsupport) +{ + struct pet_data *pd; + TBL_PC *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + if (pd->s_skill) + { //Clear previous skill + if (pd->s_skill->timer != INVALID_TIMER) + { + if (pd->s_skill->id) + delete_timer(pd->s_skill->timer, pet_skill_support_timer); + else + delete_timer(pd->s_skill->timer, pet_heal_timer); + } + } else //init memory + pd->s_skill = (struct pet_skill_support *) aMalloc(sizeof(struct pet_skill_support)); + + pd->s_skill->id=( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); + pd->s_skill->lv=script_getnum(st,3); + pd->s_skill->delay=script_getnum(st,4); + pd->s_skill->hp=script_getnum(st,5); + pd->s_skill->sp=script_getnum(st,6); + + //Use delay as initial offset to avoid skill/heal exploits + if (battle_config.pet_equip_required && pd->pet.equip == 0) + pd->s_skill->timer = INVALID_TIMER; + else + pd->s_skill->timer = add_timer(gettick()+pd->s_skill->delay*1000,pet_skill_support_timer,sd->bl.id,0); + + return 0; +} + +/*========================================== + * Scripted skill effects [Celest] + *------------------------------------------*/ +/// skilleffect <skill id>,<level> +/// skilleffect "<skill name>",<level> +BUILDIN_FUNC(skilleffect) +{ + TBL_PC *sd; + + uint16 skill_id=( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); + uint16 skill_lv=script_getnum(st,3); + sd=script_rid2sd(st); + + clif_skill_nodamage(&sd->bl,&sd->bl,skill_id,skill_lv,1); + + return 0; +} + +/*========================================== + * NPC skill effects [Valaris] + *------------------------------------------*/ +/// npcskilleffect <skill id>,<level>,<x>,<y> +/// npcskilleffect "<skill name>",<level>,<x>,<y> +BUILDIN_FUNC(npcskilleffect) +{ + struct block_list *bl= map_id2bl(st->oid); + + uint16 skill_id=( script_isstring(st,2) ? skill_name2id(script_getstr(st,2)) : script_getnum(st,2) ); + uint16 skill_lv=script_getnum(st,3); + int x=script_getnum(st,4); + int y=script_getnum(st,5); + + if (bl) + clif_skill_poseffect(bl,skill_id,skill_lv,x,y,gettick()); + + return 0; +} + +/*========================================== + * Special effects [Valaris] + *------------------------------------------*/ +BUILDIN_FUNC(specialeffect) +{ + struct block_list *bl=map_id2bl(st->oid); + int type = script_getnum(st,2); + enum send_target target = script_hasdata(st,3) ? (send_target)script_getnum(st,3) : AREA; + + if(bl==NULL) + return 0; + + if( script_hasdata(st,4) ) + { + TBL_NPC *nd = npc_name2id(script_getstr(st,4)); + if(nd) + clif_specialeffect(&nd->bl, type, target); + } + else + { + if (target == SELF) { + TBL_PC *sd=script_rid2sd(st); + if (sd) + clif_specialeffect_single(bl,type,sd->fd); + } else { + clif_specialeffect(bl, type, target); + } + } + + return 0; +} + +BUILDIN_FUNC(specialeffect2) +{ + TBL_PC *sd=script_rid2sd(st); + int type = script_getnum(st,2); + enum send_target target = script_hasdata(st,3) ? (send_target)script_getnum(st,3) : AREA; + + if( script_hasdata(st,4) ) + sd = map_nick2sd(script_getstr(st,4)); + + if (sd) + clif_specialeffect(&sd->bl, type, target); + + return 0; +} + +/*========================================== + * Nude [Valaris] + *------------------------------------------*/ +BUILDIN_FUNC(nude) +{ + TBL_PC *sd = script_rid2sd(st); + int i, calcflag = 0; + + if( sd == NULL ) + return 0; + + for( i = 0 ; i < EQI_MAX; i++ ) { + if( sd->equip_index[ i ] >= 0 ) { + if( !calcflag ) + calcflag = 1; + pc_unequipitem( sd , sd->equip_index[ i ] , 2); + } + } + + if( calcflag ) + status_calc_pc(sd,0); + + return 0; +} + +/*========================================== + * gmcommand [MouseJstr] + *------------------------------------------*/ +BUILDIN_FUNC(atcommand) +{ + TBL_PC dummy_sd; + TBL_PC* sd; + int fd; + const char* cmd; + + cmd = script_getstr(st,2); + + if (st->rid) { + sd = script_rid2sd(st); + fd = sd->fd; + } else { //Use a dummy character. + sd = &dummy_sd; + fd = 0; + + memset(&dummy_sd, 0, sizeof(TBL_PC)); + if (st->oid) + { + struct block_list* bl = map_id2bl(st->oid); + memcpy(&dummy_sd.bl, bl, sizeof(struct block_list)); + if (bl->type == BL_NPC) + safestrncpy(dummy_sd.status.name, ((TBL_NPC*)bl)->name, NAME_LENGTH); + } + } + + if (!is_atcommand(fd, sd, cmd, 0)) { + ShowWarning("script: buildin_atcommand: failed to execute command '%s'\n", cmd); + script_reportsrc(st); + return 1; + } + + return 0; +} + +/*========================================== + * Displays a message for the player only (like system messages like "you got an apple" ) + *------------------------------------------*/ +BUILDIN_FUNC(dispbottom) +{ + TBL_PC *sd=script_rid2sd(st); + const char *message; + message=script_getstr(st,2); + if(sd) + clif_disp_onlyself(sd,message,(int)strlen(message)); + return 0; +} + +/*========================================== + * All The Players Full Recovery + * (HP/SP full restore and resurrect if need) + *------------------------------------------*/ +BUILDIN_FUNC(recovery) +{ + TBL_PC* sd; + struct s_mapiterator* iter; + + iter = mapit_getallusers(); + for( sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC*)mapit_next(iter) ) + { + if(pc_isdead(sd)) + status_revive(&sd->bl, 100, 100); + else + status_percent_heal(&sd->bl, 100, 100); + clif_displaymessage(sd->fd,msg_txt(680)); + } + mapit_free(iter); + return 0; +} +/*========================================== + * Get your pet info: getpetinfo(n) + * n -> 0:pet_id 1:pet_class 2:pet_name + * 3:friendly 4:hungry, 5: rename flag. + *------------------------------------------*/ +BUILDIN_FUNC(getpetinfo) +{ + TBL_PC *sd=script_rid2sd(st); + TBL_PET *pd; + int type=script_getnum(st,2); + + if(!sd || !sd->pd) { + if (type == 2) + script_pushconststr(st,"null"); + else + script_pushint(st,0); + return 0; + } + pd = sd->pd; + switch(type){ + case 0: script_pushint(st,pd->pet.pet_id); break; + case 1: script_pushint(st,pd->pet.class_); break; + case 2: script_pushstrcopy(st,pd->pet.name); break; + case 3: script_pushint(st,pd->pet.intimate); break; + case 4: script_pushint(st,pd->pet.hungry); break; + case 5: script_pushint(st,pd->pet.rename_flag); break; + default: + script_pushint(st,0); + break; + } + return 0; +} + +/*========================================== + * Get your homunculus info: gethominfo(n) + * n -> 0:hom_id 1:class 2:name + * 3:friendly 4:hungry, 5: rename flag. + * 6: level + *------------------------------------------*/ +BUILDIN_FUNC(gethominfo) +{ + TBL_PC *sd=script_rid2sd(st); + TBL_HOM *hd; + int type=script_getnum(st,2); + + hd = sd?sd->hd:NULL; + if(!merc_is_hom_active(hd)) + { + if (type == 2) + script_pushconststr(st,"null"); + else + script_pushint(st,0); + return 0; + } + + switch(type){ + case 0: script_pushint(st,hd->homunculus.hom_id); break; + case 1: script_pushint(st,hd->homunculus.class_); break; + case 2: script_pushstrcopy(st,hd->homunculus.name); break; + case 3: script_pushint(st,hd->homunculus.intimacy); break; + case 4: script_pushint(st,hd->homunculus.hunger); break; + case 5: script_pushint(st,hd->homunculus.rename_flag); break; + case 6: script_pushint(st,hd->homunculus.level); break; + default: + script_pushint(st,0); + break; + } + return 0; +} + +/// Retrieves information about character's mercenary +/// getmercinfo <type>[,<char id>]; +BUILDIN_FUNC(getmercinfo) +{ + int type, char_id; + struct map_session_data* sd; + struct mercenary_data* md; + + type = script_getnum(st,2); + + if( script_hasdata(st,3) ) + { + char_id = script_getnum(st,3); + + if( ( sd = map_charid2sd(char_id) ) == NULL ) + { + ShowError("buildin_getmercinfo: No such character (char_id=%d).\n", char_id); + script_pushnil(st); + return 1; + } + } + else + { + if( ( sd = script_rid2sd(st) ) == NULL ) + { + script_pushnil(st); + return 0; + } + } + + md = ( sd->status.mer_id && sd->md ) ? sd->md : NULL; + + switch( type ) + { + case 0: script_pushint(st,md ? md->mercenary.mercenary_id : 0); break; + case 1: script_pushint(st,md ? md->mercenary.class_ : 0); break; + case 2: + if( md ) + script_pushstrcopy(st,md->db->name); + else + script_pushconststr(st,""); + break; + case 3: script_pushint(st,md ? mercenary_get_faith(md) : 0); break; + case 4: script_pushint(st,md ? mercenary_get_calls(md) : 0); break; + case 5: script_pushint(st,md ? md->mercenary.kill_count : 0); break; + case 6: script_pushint(st,md ? mercenary_get_lifetime(md) : 0); break; + case 7: script_pushint(st,md ? md->db->lv : 0); break; + default: + ShowError("buildin_getmercinfo: Invalid type %d (char_id=%d).\n", type, sd->status.char_id); + script_pushnil(st); + return 1; + } + + return 0; +} + +/*========================================== + * Shows wether your inventory(and equips) contain + selected card or not. + checkequipedcard(4001); + *------------------------------------------*/ +BUILDIN_FUNC(checkequipedcard) +{ + TBL_PC *sd=script_rid2sd(st); + + if(sd){ + int n,i,c=0; + c=script_getnum(st,2); + + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid > 0 && sd->status.inventory[i].amount && sd->inventory_data[i]){ + if (itemdb_isspecial(sd->status.inventory[i].card[0])) + continue; + for(n=0;n<sd->inventory_data[i]->slot;n++){ + if(sd->status.inventory[i].card[n]==c){ + script_pushint(st,1); + return 0; + } + } + } + } + } + script_pushint(st,0); + return 0; +} + +BUILDIN_FUNC(jump_zero) +{ + int sel; + sel=script_getnum(st,2); + if(!sel) { + int pos; + if( !data_islabel(script_getdata(st,3)) ){ + ShowError("script: jump_zero: not label !\n"); + st->state=END; + return 1; + } + + pos=script_getnum(st,3); + st->pos=pos; + st->state=GOTO; + } + return 0; +} + +/*========================================== + * movenpc [MouseJstr] + *------------------------------------------*/ +BUILDIN_FUNC(movenpc) +{ + TBL_NPC *nd = NULL; + const char *npc; + int x,y; + + npc = script_getstr(st,2); + x = script_getnum(st,3); + y = script_getnum(st,4); + + if ((nd = npc_name2id(npc)) == NULL) + return -1; + + if (script_hasdata(st,5)) + nd->ud.dir = script_getnum(st,5) % 8; + npc_movenpc(nd, x, y); + return 0; +} + +/*========================================== + * message [MouseJstr] + *------------------------------------------*/ +BUILDIN_FUNC(message) +{ + const char *msg,*player; + TBL_PC *pl_sd = NULL; + + player = script_getstr(st,2); + msg = script_getstr(st,3); + + if((pl_sd=map_nick2sd((char *) player)) == NULL) + return 0; + clif_displaymessage(pl_sd->fd, msg); + + return 0; +} + +/*========================================== + * npctalk (sends message to surrounding area) + *------------------------------------------*/ +BUILDIN_FUNC(npctalk) +{ + const char* str; + char name[NAME_LENGTH], message[256]; + + struct npc_data* nd = (struct npc_data *)map_id2bl(st->oid); + str = script_getstr(st,2); + + if(nd) + { + safestrncpy(name, nd->name, sizeof(name)); + strtok(name, "#"); // discard extra name identifier if present + safesnprintf(message, sizeof(message), "%s : %s", name, str); + clif_message(&nd->bl, message); + } + + return 0; +} + +// change npc walkspeed [Valaris] +BUILDIN_FUNC(npcspeed) +{ + struct npc_data* nd; + int speed; + + speed = script_getnum(st,2); + nd =(struct npc_data *)map_id2bl(st->oid); + + if( nd ) + { + nd->speed = speed; + nd->ud.state.speed_changed = 1; + } + + return 0; +} +// make an npc walk to a position [Valaris] +BUILDIN_FUNC(npcwalkto) +{ + struct npc_data *nd=(struct npc_data *)map_id2bl(st->oid); + int x=0,y=0; + + x=script_getnum(st,2); + y=script_getnum(st,3); + + if(nd) { + if (!nd->status.hp) { + status_calc_npc(nd, true); + } else { + status_calc_npc(nd, false); + } + unit_walktoxy(&nd->bl,x,y,0); + } + + return 0; +} +// stop an npc's movement [Valaris] +BUILDIN_FUNC(npcstop) +{ + struct npc_data *nd=(struct npc_data *)map_id2bl(st->oid); + + if(nd) { + unit_stop_walking(&nd->bl,1|4); + } + + return 0; +} + + +/*========================================== + * getlook char info. getlook(arg) + *------------------------------------------*/ +BUILDIN_FUNC(getlook) +{ + int type,val; + TBL_PC *sd; + sd=script_rid2sd(st); + + type=script_getnum(st,2); + val=-1; + switch(type) { + case LOOK_HAIR: val=sd->status.hair; break; //1 + case LOOK_WEAPON: val=sd->status.weapon; break; //2 + case LOOK_HEAD_BOTTOM: val=sd->status.head_bottom; break; //3 + case LOOK_HEAD_TOP: val=sd->status.head_top; break; //4 + case LOOK_HEAD_MID: val=sd->status.head_mid; break; //5 + case LOOK_HAIR_COLOR: val=sd->status.hair_color; break; //6 + case LOOK_CLOTHES_COLOR: val=sd->status.clothes_color; break; //7 + case LOOK_SHIELD: val=sd->status.shield; break; //8 + case LOOK_SHOES: break; //9 + } + + script_pushint(st,val); + return 0; +} + +/*========================================== + * get char save point. argument: 0- map name, 1- x, 2- y + *------------------------------------------*/ +BUILDIN_FUNC(getsavepoint) +{ + TBL_PC* sd; + int type; + + sd = script_rid2sd(st); + if (sd == NULL) { + script_pushint(st,0); + return 0; + } + + type = script_getnum(st,2); + + switch(type) { + case 0: script_pushstrcopy(st,mapindex_id2name(sd->status.save_point.map)); break; + case 1: script_pushint(st,sd->status.save_point.x); break; + case 2: script_pushint(st,sd->status.save_point.y); break; + default: + script_pushint(st,0); + break; + } + return 0; +} + +/*========================================== + * Get position for char/npc/pet/mob objects. Added by Lorky + * + * int getMapXY(MapName$,MapX,MapY,type,[CharName$]); + * where type: + * MapName$ - String variable for output map name + * MapX - Integer variable for output coord X + * MapY - Integer variable for output coord Y + * type - type of object + * 0 - Character coord + * 1 - NPC coord + * 2 - Pet coord + * 3 - Mob coord (not released) + * 4 - Homun coord + * 5 - Mercenary coord + * 6 - Elemental coord + * CharName$ - Name object. If miss or "this" the current object + * + * Return: + * 0 - success + * -1 - some error, MapName$,MapX,MapY contains unknown value. + *------------------------------------------*/ +BUILDIN_FUNC(getmapxy) +{ + struct block_list *bl = NULL; + TBL_PC *sd=NULL; + + int num; + const char *name; + char prefix; + + int x,y,type; + char mapname[MAP_NAME_LENGTH]; + + if( !data_isreference(script_getdata(st,2)) ){ + ShowWarning("script: buildin_getmapxy: not mapname variable\n"); + script_pushint(st,-1); + return 1; + } + if( !data_isreference(script_getdata(st,3)) ){ + ShowWarning("script: buildin_getmapxy: not mapx variable\n"); + script_pushint(st,-1); + return 1; + } + if( !data_isreference(script_getdata(st,4)) ){ + ShowWarning("script: buildin_getmapxy: not mapy variable\n"); + script_pushint(st,-1); + return 1; + } + + // Possible needly check function parameters on C_STR,C_INT,C_INT + type=script_getnum(st,5); + + switch (type){ + case 0: //Get Character Position + if( script_hasdata(st,6) ) + sd=map_nick2sd(script_getstr(st,6)); + else + sd=script_rid2sd(st); + + if (sd) + bl = &sd->bl; + break; + case 1: //Get NPC Position + if( script_hasdata(st,6) ) + { + struct npc_data *nd; + nd=npc_name2id(script_getstr(st,6)); + if (nd) + bl = &nd->bl; + } else //In case the origin is not an npc? + bl=map_id2bl(st->oid); + break; + case 2: //Get Pet Position + if(script_hasdata(st,6)) + sd=map_nick2sd(script_getstr(st,6)); + else + sd=script_rid2sd(st); + + if (sd && sd->pd) + bl = &sd->pd->bl; + break; + case 3: //Get Mob Position + break; //Not supported? + case 4: //Get Homun Position + if(script_hasdata(st,6)) + sd=map_nick2sd(script_getstr(st,6)); + else + sd=script_rid2sd(st); + + if (sd && sd->hd) + bl = &sd->hd->bl; + break; + case 5: //Get Mercenary Position + if(script_hasdata(st,6)) + sd=map_nick2sd(script_getstr(st,6)); + else + sd=script_rid2sd(st); + + if (sd && sd->md) + bl = &sd->md->bl; + break; + case 6: //Get Elemental Position + if(script_hasdata(st,6)) + sd=map_nick2sd(script_getstr(st,6)); + else + sd=script_rid2sd(st); + + if (sd && sd->ed) + bl = &sd->ed->bl; + break; + default: + ShowWarning("script: buildin_getmapxy: Invalid type %d\n", type); + script_pushint(st,-1); + return 1; + } + if (!bl) { //No object found. + script_pushint(st,-1); + return 0; + } + + x= bl->x; + y= bl->y; + safestrncpy(mapname, map[bl->m].name, MAP_NAME_LENGTH); + + //Set MapName$ + num=st->stack->stack_data[st->start+2].u.num; + name=get_str(num&0x00ffffff); + prefix=*name; + + if(not_server_variable(prefix)) + sd=script_rid2sd(st); + else + sd=NULL; + set_reg(st,sd,num,name,(void*)mapname,script_getref(st,2)); + + //Set MapX + num=st->stack->stack_data[st->start+3].u.num; + name=get_str(num&0x00ffffff); + prefix=*name; + + if(not_server_variable(prefix)) + sd=script_rid2sd(st); + else + sd=NULL; + set_reg(st,sd,num,name,(void*)__64BPRTSIZE(x),script_getref(st,3)); + + //Set MapY + num=st->stack->stack_data[st->start+4].u.num; + name=get_str(num&0x00ffffff); + prefix=*name; + + if(not_server_variable(prefix)) + sd=script_rid2sd(st); + else + sd=NULL; + set_reg(st,sd,num,name,(void*)__64BPRTSIZE(y),script_getref(st,4)); + + //Return Success value + script_pushint(st,0); + return 0; +} + +/*========================================== + * Allows player to write NPC logs (i.e. Bank NPC, etc) [Lupus] + *------------------------------------------*/ +BUILDIN_FUNC(logmes) +{ + const char *str; + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 1; + + str = script_getstr(st,2); + log_npc(sd,str); + return 0; +} + +BUILDIN_FUNC(summon) +{ + int _class, timeout=0; + const char *str,*event=""; + TBL_PC *sd; + struct mob_data *md; + int tick = gettick(); + + sd=script_rid2sd(st); + if (!sd) return 0; + + str =script_getstr(st,2); + _class=script_getnum(st,3); + if( script_hasdata(st,4) ) + timeout=script_getnum(st,4); + if( script_hasdata(st,5) ){ + event=script_getstr(st,5); + check_event(st, event); + } + + clif_skill_poseffect(&sd->bl,AM_CALLHOMUN,1,sd->bl.x,sd->bl.y,tick); + + md = mob_once_spawn_sub(&sd->bl, sd->bl.m, sd->bl.x, sd->bl.y, str, _class, event, SZ_SMALL, AI_NONE); + if (md) { + md->master_id=sd->bl.id; + md->special_state.ai = AI_ATTACK; + if( md->deletetimer != INVALID_TIMER ) + delete_timer(md->deletetimer, mob_timer_delete); + md->deletetimer = add_timer(tick+(timeout>0?timeout*1000:60000),mob_timer_delete,md->bl.id,0); + mob_spawn (md); //Now it is ready for spawning. + clif_specialeffect(&md->bl,344,AREA); + sc_start4(&md->bl, SC_MODECHANGE, 100, 1, 0, MD_AGGRESSIVE, 0, 60000); + } + return 0; +} + +/*========================================== + * Checks whether it is daytime/nighttime + *------------------------------------------*/ +BUILDIN_FUNC(isnight) +{ + script_pushint(st,(night_flag == 1)); + return 0; +} + +BUILDIN_FUNC(isday) +{ + script_pushint(st,(night_flag == 0)); + return 0; +} + +/*================================================ + * Check how many items/cards in the list are + * equipped - used for 2/15's cards patch [celest] + *------------------------------------------------*/ +BUILDIN_FUNC(isequippedcnt) +{ + TBL_PC *sd; + int i, j, k, id = 1; + int ret = 0; + + sd = script_rid2sd(st); + if (!sd) { //If the player is not attached it is a script error anyway... but better prevent the map server from crashing... + script_pushint(st,0); + return 0; + } + + for (i=0; id!=0; i++) { + FETCH (i+2, id) else id = 0; + if (id <= 0) + continue; + + for (j=0; j<EQI_MAX; j++) { + int index; + index = sd->equip_index[j]; + if(index < 0) continue; + if(j == EQI_HAND_R && sd->equip_index[EQI_HAND_L] == index) continue; + if(j == EQI_HEAD_MID && sd->equip_index[EQI_HEAD_LOW] == index) continue; + if(j == EQI_HEAD_TOP && (sd->equip_index[EQI_HEAD_MID] == index || sd->equip_index[EQI_HEAD_LOW] == index)) continue; + + if(!sd->inventory_data[index]) + continue; + + if (itemdb_type(id) != IT_CARD) { //No card. Count amount in inventory. + if (sd->inventory_data[index]->nameid == id) + ret+= sd->status.inventory[index].amount; + } else { //Count cards. + if (itemdb_isspecial(sd->status.inventory[index].card[0])) + continue; //No cards + for(k=0; k<sd->inventory_data[index]->slot; k++) { + if (sd->status.inventory[index].card[k] == id) + ret++; //[Lupus] + } + } + } + } + + script_pushint(st,ret); + return 0; +} + +/*================================================ + * Check whether another card has been + * equipped - used for 2/15's cards patch [celest] + * -- Items checked cannot be reused in another + * card set to prevent exploits + *------------------------------------------------*/ +BUILDIN_FUNC(isequipped) +{ + TBL_PC *sd; + int i, j, k, id = 1; + int index, flag; + int ret = -1; + //Original hash to reverse it when full check fails. + unsigned int setitem_hash = 0, setitem_hash2 = 0; + + sd = script_rid2sd(st); + + if (!sd) { //If the player is not attached it is a script error anyway... but better prevent the map server from crashing... + script_pushint(st,0); + return 0; + } + + setitem_hash = sd->bonus.setitem_hash; + setitem_hash2 = sd->bonus.setitem_hash2; + for (i=0; id!=0; i++) { + FETCH (i+2, id) else id = 0; + if (id <= 0) + continue; + flag = 0; + for (j=0; j<EQI_MAX; j++) { + index = sd->equip_index[j]; + if(index < 0) continue; + if(j == EQI_HAND_R && sd->equip_index[EQI_HAND_L] == index) continue; + if(j == EQI_HEAD_MID && sd->equip_index[EQI_HEAD_LOW] == index) continue; + if(j == EQI_HEAD_TOP && (sd->equip_index[EQI_HEAD_MID] == index || sd->equip_index[EQI_HEAD_LOW] == index)) continue; + + if(!sd->inventory_data[index]) + continue; + + if (itemdb_type(id) != IT_CARD) { + if (sd->inventory_data[index]->nameid != id) + continue; + flag = 1; + break; + } else { //Cards + if (sd->inventory_data[index]->slot == 0 || + itemdb_isspecial(sd->status.inventory[index].card[0])) + continue; + + for (k = 0; k < sd->inventory_data[index]->slot; k++) + { //New hash system which should support up to 4 slots on any equipment. [Skotlex] + unsigned int hash = 0; + if (sd->status.inventory[index].card[k] != id) + continue; + + hash = 1<<((j<5?j:j-5)*4 + k); + // check if card is already used by another set + if ( ( j < 5 ? sd->bonus.setitem_hash : sd->bonus.setitem_hash2 ) & hash) + continue; + + // We have found a match + flag = 1; + // Set hash so this card cannot be used by another + if (j<5) + sd->bonus.setitem_hash |= hash; + else + sd->bonus.setitem_hash2 |= hash; + break; + } + } + if (flag) break; //Card found + } + if (ret == -1) + ret = flag; + else + ret &= flag; + if (!ret) break; + } + if (!ret) {//When check fails, restore original hash values. [Skotlex] + sd->bonus.setitem_hash = setitem_hash; + sd->bonus.setitem_hash2 = setitem_hash2; + } + script_pushint(st,ret); + return 0; +} + +/*================================================ + * Check how many given inserted cards in the CURRENT + * weapon - used for 2/15's cards patch [Lupus] + *------------------------------------------------*/ +BUILDIN_FUNC(cardscnt) +{ + TBL_PC *sd; + int i, k, id = 1; + int ret = 0; + int index; + + sd = script_rid2sd(st); + + for (i=0; id!=0; i++) { + FETCH (i+2, id) else id = 0; + if (id <= 0) + continue; + + index = current_equip_item_index; //we get CURRENT WEAPON inventory index from status.c [Lupus] + if(index < 0) continue; + + if(!sd->inventory_data[index]) + continue; + + if(itemdb_type(id) != IT_CARD) { + if (sd->inventory_data[index]->nameid == id) + ret+= sd->status.inventory[index].amount; + } else { + if (itemdb_isspecial(sd->status.inventory[index].card[0])) + continue; + for(k=0; k<sd->inventory_data[index]->slot; k++) { + if (sd->status.inventory[index].card[k] == id) + ret++; + } + } + } + script_pushint(st,ret); +// script_pushint(st,current_equip_item_index); + return 0; +} + +/*======================================================= + * Returns the refined number of the current item, or an + * item with inventory index specified + *-------------------------------------------------------*/ +BUILDIN_FUNC(getrefine) +{ + TBL_PC *sd; + if ((sd = script_rid2sd(st))!= NULL) + script_pushint(st,sd->status.inventory[current_equip_item_index].refine); + else + script_pushint(st,0); + return 0; +} + +/*======================================================= + * Day/Night controls + *-------------------------------------------------------*/ +BUILDIN_FUNC(night) +{ + if (night_flag != 1) map_night_timer(night_timer_tid, 0, 0, 1); + return 0; +} +BUILDIN_FUNC(day) +{ + if (night_flag != 0) map_day_timer(day_timer_tid, 0, 0, 1); + return 0; +} + +//======================================================= +// Unequip [Spectre] +//------------------------------------------------------- +BUILDIN_FUNC(unequip) +{ + int i; + size_t num; + TBL_PC *sd; + + num = script_getnum(st,2); + sd = script_rid2sd(st); + if( sd != NULL && num >= 1 && num <= ARRAYLENGTH(equip) ) + { + i = pc_checkequip(sd,equip[num-1]); + if (i >= 0) + pc_unequipitem(sd,i,1|2); + } + return 0; +} + +BUILDIN_FUNC(equip) +{ + int nameid=0,i; + TBL_PC *sd; + struct item_data *item_data; + + sd = script_rid2sd(st); + + nameid=script_getnum(st,2); + if((item_data = itemdb_exists(nameid)) == NULL) + { + ShowError("wrong item ID : equipitem(%i)\n",nameid); + return 1; + } + ARR_FIND( 0, MAX_INVENTORY, i, sd->status.inventory[i].nameid == nameid ); + if( i < MAX_INVENTORY ) + pc_equipitem(sd,i,item_data->equip); + + return 0; +} + +BUILDIN_FUNC(autoequip) +{ + int nameid, flag; + struct item_data *item_data; + nameid=script_getnum(st,2); + flag=script_getnum(st,3); + + if( ( item_data = itemdb_exists(nameid) ) == NULL ) + { + ShowError("buildin_autoequip: Invalid item '%d'.\n", nameid); + return 1; + } + + if( !itemdb_isequip2(item_data) ) + { + ShowError("buildin_autoequip: Item '%d' cannot be equipped.\n", nameid); + return 1; + } + + item_data->flag.autoequip = flag>0?1:0; + return 0; +} + +BUILDIN_FUNC(setbattleflag) +{ + const char *flag, *value; + + flag = script_getstr(st,2); + value = script_getstr(st,3); // HACK: Retrieve number as string (auto-converted) for battle_set_value + + if (battle_set_value(flag, value) == 0) + ShowWarning("buildin_setbattleflag: unknown battle_config flag '%s'\n",flag); + else + ShowInfo("buildin_setbattleflag: battle_config flag '%s' is now set to '%s'.\n",flag,value); + + return 0; +} + +BUILDIN_FUNC(getbattleflag) +{ + const char *flag; + flag = script_getstr(st,2); + script_pushint(st,battle_get_value(flag)); + return 0; +} + +//======================================================= +// strlen [Valaris] +//------------------------------------------------------- +BUILDIN_FUNC(getstrlen) +{ + + const char *str = script_getstr(st,2); + int len = (str) ? (int)strlen(str) : 0; + + script_pushint(st,len); + return 0; +} + +//======================================================= +// isalpha [Valaris] +//------------------------------------------------------- +BUILDIN_FUNC(charisalpha) +{ + const char *str=script_getstr(st,2); + int pos=script_getnum(st,3); + + int val = ( str && pos >= 0 && (unsigned int)pos < strlen(str) ) ? ISALPHA( str[pos] ) != 0 : 0; + + script_pushint(st,val); + return 0; +} + +//======================================================= +// charisupper <str>, <index> +//------------------------------------------------------- +BUILDIN_FUNC(charisupper) +{ + const char *str = script_getstr(st,2); + int pos = script_getnum(st,3); + + int val = ( str && pos >= 0 && (unsigned int)pos < strlen(str) ) ? ISUPPER( str[pos] ) : 0; + + script_pushint(st,val); + return 0; +} + +//======================================================= +// charislower <str>, <index> +//------------------------------------------------------- +BUILDIN_FUNC(charislower) +{ + const char *str = script_getstr(st,2); + int pos = script_getnum(st,3); + + int val = ( str && pos >= 0 && (unsigned int)pos < strlen(str) ) ? ISLOWER( str[pos] ) : 0; + + script_pushint(st,val); + return 0; +} + +//======================================================= +// charat <str>, <index> +//------------------------------------------------------- +BUILDIN_FUNC(charat) { + const char *str = script_getstr(st,2); + int pos = script_getnum(st,3); + + if( pos >= 0 && (unsigned int)pos < strlen(str) ) { + char output[2]; + output[0] = str[pos]; + output[1] = '\0'; + script_pushstrcopy(st, output); + } else + script_pushconststr(st, ""); + return 0; +} + +//======================================================= +// setchar <string>, <char>, <index> +//------------------------------------------------------- +BUILDIN_FUNC(setchar) +{ + const char *str = script_getstr(st,2); + const char *c = script_getstr(st,3); + int index = script_getnum(st,4); + char *output = aStrdup(str); + + if(index >= 0 && index < strlen(output)) + output[index] = *c; + + script_pushstr(st, output); + return 0; +} + +//======================================================= +// insertchar <string>, <char>, <index> +//------------------------------------------------------- +BUILDIN_FUNC(insertchar) +{ + const char *str = script_getstr(st,2); + const char *c = script_getstr(st,3); + int index = script_getnum(st,4); + char *output; + size_t len = strlen(str); + + if(index < 0) + index = 0; + else if(index > len) + index = len; + + output = (char*)aMalloc(len + 2); + + memcpy(output, str, index); + output[index] = c[0]; + memcpy(&output[index+1], &str[index], len - index); + output[len+1] = '\0'; + + script_pushstr(st, output); + return 0; +} + +//======================================================= +// delchar <string>, <index> +//------------------------------------------------------- +BUILDIN_FUNC(delchar) +{ + const char *str = script_getstr(st,2); + int index = script_getnum(st,3); + char *output; + size_t len = strlen(str); + + if(index < 0 || index > len) { + //return original + output = aStrdup(str); + script_pushstr(st, output); + return 0; + } + + output = (char*)aMalloc(len); + + memcpy(output, str, index); + memcpy(&output[index], &str[index+1], len - index); + + script_pushstr(st, output); + return 0; +} + +//======================================================= +// strtoupper <str> +//------------------------------------------------------- +BUILDIN_FUNC(strtoupper) +{ + const char *str = script_getstr(st,2); + char *output = aStrdup(str); + char *cursor = output; + + while (*cursor != '\0') { + *cursor = TOUPPER(*cursor); + cursor++; + } + + script_pushstr(st, output); + return 0; +} + +//======================================================= +// strtolower <str> +//------------------------------------------------------- +BUILDIN_FUNC(strtolower) +{ + const char *str = script_getstr(st,2); + char *output = aStrdup(str); + char *cursor = output; + + while (*cursor != '\0') { + *cursor = TOLOWER(*cursor); + cursor++; + } + + script_pushstr(st, output); + return 0; +} + +//======================================================= +// substr <str>, <start>, <end> +//------------------------------------------------------- +BUILDIN_FUNC(substr) +{ + const char *str = script_getstr(st,2); + char *output; + int start = script_getnum(st,3); + int end = script_getnum(st,4); + + int len = 0; + + if(start >= 0 && end < strlen(str) && start <= end) { + len = end - start + 1; + output = (char*)aMalloc(len + 1); + memcpy(output, &str[start], len); + } else + output = (char*)aMalloc(1); + + output[len] = '\0'; + + script_pushstr(st, output); + return 0; +} + +//======================================================= +// explode <dest_string_array>, <str>, <delimiter> +// Note: delimiter is limited to 1 char +//------------------------------------------------------- +BUILDIN_FUNC(explode) +{ + struct script_data* data = script_getdata(st, 2); + const char *str = script_getstr(st,3); + const char delimiter = script_getstr(st, 4)[0]; + int32 id; + size_t len = strlen(str); + int i = 0, j = 0; + int start; + + + char *temp; + const char* name; + + TBL_PC* sd = NULL; + + temp = (char*)aMalloc(len + 1); + + if( !data_isreference(data) ) + { + ShowError("script:explode: not a variable\n"); + script_reportdata(data); + st->state = END; + return 1;// not a variable + } + + id = reference_getid(data); + start = reference_getindex(data); + name = reference_getname(data); + + if( not_array_variable(*name) ) + { + ShowError("script:explode: illegal scope\n"); + script_reportdata(data); + st->state = END; + return 1;// not supported + } + + if( !is_string_variable(name) ) + { + ShowError("script:explode: not string array\n"); + script_reportdata(data); + st->state = END; + return 1;// data type mismatch + } + + if( not_server_variable(*name) ) + { + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached + } + + while(str[i] != '\0') { + if(str[i] == delimiter && start < SCRIPT_MAX_ARRAYSIZE-1) { //break at delimiter but ignore after reaching last array index + temp[j] = '\0'; + set_reg(st, sd, reference_uid(id, start++), name, (void*)temp, reference_getref(data)); + j = 0; + ++i; + } else { + temp[j++] = str[i++]; + } + } + //set last string + temp[j] = '\0'; + set_reg(st, sd, reference_uid(id, start), name, (void*)temp, reference_getref(data)); + + aFree(temp); + return 0; +} + +//======================================================= +// implode <string_array> +// implode <string_array>, <glue> +//------------------------------------------------------- +BUILDIN_FUNC(implode) +{ + struct script_data* data = script_getdata(st, 2); + const char *glue = NULL, *name, *temp; + int32 glue_len = 0, array_size, id; + size_t len = 0; + int i, k = 0; + + TBL_PC* sd = NULL; + + char *output; + + if( !data_isreference(data) ) + { + ShowError("script:implode: not a variable\n"); + script_reportdata(data); + st->state = END; + return 1;// not a variable + } + + id = reference_getid(data); + name = reference_getname(data); + + if( not_array_variable(*name) ) + { + ShowError("script:implode: illegal scope\n"); + script_reportdata(data); + st->state = END; + return 1;// not supported + } + + if( !is_string_variable(name) ) + { + ShowError("script:implode: not string array\n"); + script_reportdata(data); + st->state = END; + return 1;// data type mismatch + } + + if( not_server_variable(*name) ) + { + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached + } + + //count chars + array_size = getarraysize(st, id, reference_getindex(data), is_string_variable(name), reference_getref(data)) - 1; + + if(array_size == -1) //empty array check (AmsTaff) + { + ShowWarning("script:implode: array length = 0\n"); + output = (char*)aMalloc(sizeof(char)*5); + sprintf(output,"%s","NULL"); + } else { + for(i = 0; i <= array_size; ++i) { + temp = (char*) get_val2(st, reference_uid(id, i), reference_getref(data)); + len += strlen(temp); + script_removetop(st, -1, 0); + } + + //allocate mem + if( script_hasdata(st,3) ) { + glue = script_getstr(st,3); + glue_len = strlen(glue); + len += glue_len * (array_size); + } + output = (char*)aMalloc(len + 1); + + //build output + for(i = 0; i < array_size; ++i) { + temp = (char*) get_val2(st, reference_uid(id, i), reference_getref(data)); + len = strlen(temp); + memcpy(&output[k], temp, len); + k += len; + if(glue_len != 0) { + memcpy(&output[k], glue, glue_len); + k += glue_len; + } + script_removetop(st, -1, 0); + } + temp = (char*) get_val2(st, reference_uid(id, array_size), reference_getref(data)); + len = strlen(temp); + memcpy(&output[k], temp, len); + k += len; + script_removetop(st, -1, 0); + + output[k] = '\0'; + } + + script_pushstr(st, output); + return 0; +} + +//======================================================= +// sprintf(<format>, ...); +// Implements C sprintf, except format %n. The resulting string is +// returned, instead of being saved in variable by reference. +//------------------------------------------------------- +BUILDIN_FUNC(sprintf) +{ + unsigned int len, argc = 0, arg = 0, buf2_len = 0; + const char* format; + char* p; + char* q; + char* buf = NULL; + char* buf2 = NULL; + struct script_data* data; + StringBuf final_buf; + + // Fetch init data + format = script_getstr(st, 2); + argc = script_lastdata(st)-2; + len = strlen(format); + + // Skip parsing, where no parsing is required. + if(len==0){ + script_pushconststr(st,""); + return 0; + } + + // Pessimistic alloc + CREATE(buf, char, len+1); + + // Need not be parsed, just solve stuff like %%. + if(argc==0){ + memcpy(buf,format,len+1); + script_pushstrcopy(st, buf); + aFree(buf); + return 0; + } + + safestrncpy(buf, format, len+1); + + // Issue sprintf for each parameter + StringBuf_Init(&final_buf); + q = buf; + while((p = strchr(q, '%'))!=NULL){ + if(p!=q){ + len = p-q+1; + if(buf2_len<len){ + RECREATE(buf2, char, len); + buf2_len = len; + } + safestrncpy(buf2, q, len); + StringBuf_AppendStr(&final_buf, buf2); + q = p; + } + p = q+1; + if(*p=='%'){ // %% + StringBuf_AppendStr(&final_buf, "%"); + q+=2; + continue; + } + if(*p=='n'){ // %n + ShowWarning("buildin_sprintf: Format %%n not supported! Skipping...\n"); + script_reportsrc(st); + q+=2; + continue; + } + if(arg>=argc){ + ShowError("buildin_sprintf: Not enough arguments passed!\n"); + if(buf) aFree(buf); + if(buf2) aFree(buf2); + StringBuf_Destroy(&final_buf); + script_pushconststr(st,""); + return 1; + } + if((p = strchr(q+1, '%'))==NULL){ + p = strchr(q, 0); // EOS + } + len = p-q+1; + if(buf2_len<len){ + RECREATE(buf2, char, len); + buf2_len = len; + } + safestrncpy(buf2, q, len); + q = p; + + // Note: This assumes the passed value being the correct + // type to the current format specifier. If not, the server + // probably crashes or returns anything else, than expected, + // but it would behave in normal code the same way so it's + // the scripter's responsibility. + data = script_getdata(st, arg+3); + if(data_isstring(data)){ // String + StringBuf_Printf(&final_buf, buf2, script_getstr(st, arg+3)); + }else if(data_isint(data)){ // Number + StringBuf_Printf(&final_buf, buf2, script_getnum(st, arg+3)); + }else if(data_isreference(data)){ // Variable + char* name = reference_getname(data); + if(name[strlen(name)-1]=='$'){ // var Str + StringBuf_Printf(&final_buf, buf2, script_getstr(st, arg+3)); + }else{ // var Int + StringBuf_Printf(&final_buf, buf2, script_getnum(st, arg+3)); + } + }else{ // Unsupported type + ShowError("buildin_sprintf: Unknown argument type!\n"); + if(buf) aFree(buf); + if(buf2) aFree(buf2); + StringBuf_Destroy(&final_buf); + script_pushconststr(st,""); + return 1; + } + arg++; + } + + // Append anything left + if(*q){ + StringBuf_AppendStr(&final_buf, q); + } + + // Passed more, than needed + if(arg<argc){ + ShowWarning("buildin_sprintf: Unused arguments passed.\n"); + script_reportsrc(st); + } + + script_pushstrcopy(st, StringBuf_Value(&final_buf)); + + if(buf) aFree(buf); + if(buf2) aFree(buf2); + StringBuf_Destroy(&final_buf); + + return 0; +} + +//======================================================= +// sscanf(<str>, <format>, ...); +// Implements C sscanf. +//------------------------------------------------------- +BUILDIN_FUNC(sscanf){ + unsigned int argc, arg = 0, len; + struct script_data* data; + struct map_session_data* sd = NULL; + const char* str; + const char* format; + const char* p; + const char* q; + char* buf = NULL; + char* buf_p; + char* ref_str = NULL; + int ref_int; + + // Get data + str = script_getstr(st, 2); + format = script_getstr(st, 3); + argc = script_lastdata(st)-3; + + len = strlen(format); + CREATE(buf, char, len*2+1); + + // Issue sscanf for each parameter + *buf = 0; + q = format; + while((p = strchr(q, '%'))){ + if(p!=q){ + strncat(buf, q, (size_t)(p-q)); + q = p; + } + p = q+1; + if(*p=='*' || *p=='%'){ // Skip + strncat(buf, q, 2); + q+=2; + continue; + } + if(arg>=argc){ + ShowError("buildin_sscanf: Not enough arguments passed!\n"); + script_pushint(st, -1); + if(buf) aFree(buf); + if(ref_str) aFree(ref_str); + return 1; + } + if((p = strchr(q+1, '%'))==NULL){ + p = strchr(q, 0); // EOS + } + len = p-q; + strncat(buf, q, len); + q = p; + + // Validate output + data = script_getdata(st, arg+4); + if(!data_isreference(data) || !reference_tovariable(data)){ + ShowError("buildin_sscanf: Target argument is not a variable!\n"); + script_pushint(st, -1); + if(buf) aFree(buf); + if(ref_str) aFree(ref_str); + return 1; + } + buf_p = reference_getname(data); + if(not_server_variable(*buf_p) && (sd = script_rid2sd(st))==NULL){ + script_pushint(st, -1); + if(buf) aFree(buf); + if(ref_str) aFree(ref_str); + return 0; + } + + // Save value if any + if(buf_p[strlen(buf_p)-1]=='$'){ // String + if(ref_str==NULL){ + CREATE(ref_str, char, strlen(str)+1); + } + if(sscanf(str, buf, ref_str)==0){ + break; + } + set_reg(st, sd, add_str(buf_p), buf_p, (void *)(ref_str), reference_getref(data)); + }else{ // Number + if(sscanf(str, buf, &ref_int)==0){ + break; + } + set_reg(st, sd, add_str(buf_p), buf_p, (void *)__64BPRTSIZE(ref_int), reference_getref(data)); + } + arg++; + + // Disable used format (%... -> %*...) + buf_p = strchr(buf, 0); + memmove(buf_p-len+2, buf_p-len+1, len); + *(buf_p-len+1) = '*'; + } + + script_pushint(st, arg); + if(buf) aFree(buf); + if(ref_str) aFree(ref_str); + + return 0; +} + +//======================================================= +// strpos(<haystack>, <needle>) +// strpos(<haystack>, <needle>, <offset>) +// +// Implements PHP style strpos. Adapted from code from +// http://www.daniweb.com/code/snippet313.html, Dave Sinkula +//------------------------------------------------------- +BUILDIN_FUNC(strpos) { + const char *haystack = script_getstr(st,2); + const char *needle = script_getstr(st,3); + int i; + size_t len; + + if( script_hasdata(st,4) ) + i = script_getnum(st,4); + else + i = 0; + + if (needle[0] == '\0') { + script_pushint(st, -1); + return 0; + } + + len = strlen(haystack); + for ( ; i < len; ++i ) { + if ( haystack[i] == *needle ) { + // matched starting char -- loop through remaining chars + const char *h, *n; + for ( h = &haystack[i], n = needle; *h && *n; ++h, ++n ) { + if ( *h != *n ) { + break; + } + } + if ( !*n ) { // matched all of 'needle' to null termination + script_pushint(st, i); + return 0; + } + } + } + script_pushint(st, -1); + return 0; +} + +//=============================================================== +// replacestr <input>, <search>, <replace>{, <usecase>{, <count>}} +// +// Note: Finds all instances of <search> in <input> and replaces +// with <replace>. If specified will only replace as many +// instances as specified in <count>. By default will be case +// sensitive. +//--------------------------------------------------------------- +BUILDIN_FUNC(replacestr) +{ + const char *input = script_getstr(st, 2); + const char *find = script_getstr(st, 3); + const char *replace = script_getstr(st, 4); + size_t inputlen = strlen(input); + size_t findlen = strlen(find); + struct StringBuf output; + bool usecase = true; + + int count = 0; + int numFinds = 0; + int i = 0, f = 0; + + if(findlen == 0) { + ShowError("script:replacestr: Invalid search length.\n"); + st->state = END; + return 1; + } + + if(script_hasdata(st, 5)) { + if( !script_isstring(st,5) ) + usecase = script_getnum(st, 5) != 0; + else { + ShowError("script:replacestr: Invalid usecase value. Expected int got string\n"); + st->state = END; + return 1; + } + } + + if(script_hasdata(st, 6)) { + count = script_getnum(st, 6); + if(count == 0) { + ShowError("script:replacestr: Invalid count value. Expected int got string\n"); + st->state = END; + return 1; + } + } + + StringBuf_Init(&output); + + for(; i < inputlen; i++) { + if(count && count == numFinds) { //found enough, stop looking + break; + } + + for(f = 0; f <= findlen; f++) { + if(f == findlen) { //complete match + numFinds++; + StringBuf_AppendStr(&output, replace); + + i += findlen - 1; + break; + } else { + if(usecase) { + if((i + f) > inputlen || input[i + f] != find[f]) { + StringBuf_Printf(&output, "%c", input[i]); + break; + } + } else { + if(((i + f) > inputlen || input[i + f] != find[f]) && TOUPPER(input[i+f]) != TOUPPER(find[f])) { + StringBuf_Printf(&output, "%c", input[i]); + break; + } + } + } + } + } + + //append excess after enough found + if(i < inputlen) + StringBuf_AppendStr(&output, &(input[i])); + + script_pushstrcopy(st, StringBuf_Value(&output)); + StringBuf_Destroy(&output); + return 0; +} + +//======================================================== +// countstr <input>, <search>{, <usecase>} +// +// Note: Counts the number of times <search> occurs in +// <input>. By default will be case sensitive. +//-------------------------------------------------------- +BUILDIN_FUNC(countstr) +{ + const char *input = script_getstr(st, 2); + const char *find = script_getstr(st, 3); + size_t inputlen = strlen(input); + size_t findlen = strlen(find); + bool usecase = true; + + int numFinds = 0; + int i = 0, f = 0; + + if(findlen == 0) { + ShowError("script:countstr: Invalid search length.\n"); + st->state = END; + return 1; + } + + if(script_hasdata(st, 4)) { + if( !script_isstring(st,4) ) + usecase = script_getnum(st, 4) != 0; + else { + ShowError("script:countstr: Invalid usecase value. Expected int got string\n"); + st->state = END; + return 1; + } + } + + for(; i < inputlen; i++) { + for(f = 0; f <= findlen; f++) { + if(f == findlen) { //complete match + numFinds++; + i += findlen - 1; + break; + } else { + if(usecase) { + if((i + f) > inputlen || input[i + f] != find[f]) { + break; + } + } else { + if(((i + f) > inputlen || input[i + f] != find[f]) && TOUPPER(input[i+f]) != TOUPPER(find[f])) { + break; + } + } + } + } + } + script_pushint(st, numFinds); + return 0; +} + + +/// Changes the display name and/or display class of the npc. +/// Returns 0 is successful, 1 if the npc does not exist. +/// +/// setnpcdisplay("<npc name>", "<new display name>", <new class id>, <new size>) -> <int> +/// setnpcdisplay("<npc name>", "<new display name>", <new class id>) -> <int> +/// setnpcdisplay("<npc name>", "<new display name>") -> <int> +/// setnpcdisplay("<npc name>", <new class id>) -> <int> +BUILDIN_FUNC(setnpcdisplay) +{ + const char* name; + const char* newname = NULL; + int class_ = -1, size = -1; + struct script_data* data; + struct npc_data* nd; + + name = script_getstr(st,2); + data = script_getdata(st,3); + + if( script_hasdata(st,4) ) + class_ = script_getnum(st,4); + if( script_hasdata(st,5) ) + size = script_getnum(st,5); + + get_val(st, data); + if( data_isstring(data) ) + newname = conv_str(st,data); + else if( data_isint(data) ) + class_ = conv_num(st,data); + else + { + ShowError("script:setnpcdisplay: expected a string or number\n"); + script_reportdata(data); + return 1; + } + + nd = npc_name2id(name); + if( nd == NULL ) + {// not found + script_pushint(st,1); + return 0; + } + + // update npc + if( newname ) + npc_setdisplayname(nd, newname); + + if( size != -1 && size != (int)nd->size ) + nd->size = size; + else + size = -1; + + if( class_ != -1 && nd->class_ != class_ ) + npc_setclass(nd, class_); + else if( size != -1 ) + { // Required to update the visual size + clif_clearunit_area(&nd->bl, CLR_OUTSIGHT); + clif_spawn(&nd->bl); + } + + script_pushint(st,0); + return 0; +} + +BUILDIN_FUNC(atoi) +{ + const char *value; + value = script_getstr(st,2); + script_pushint(st,atoi(value)); + return 0; +} + +// case-insensitive substring search [lordalfa] +BUILDIN_FUNC(compare) +{ + const char *message; + const char *cmpstring; + message = script_getstr(st,2); + cmpstring = script_getstr(st,3); + script_pushint(st,(stristr(message,cmpstring) != NULL)); + return 0; +} + +// [zBuffer] List of mathematics commands ---> +BUILDIN_FUNC(sqrt) +{ + double i, a; + i = script_getnum(st,2); + a = sqrt(i); + script_pushint(st,(int)a); + return 0; +} + +BUILDIN_FUNC(pow) +{ + double i, a, b; + a = script_getnum(st,2); + b = script_getnum(st,3); + i = pow(a,b); + script_pushint(st,(int)i); + return 0; +} + +BUILDIN_FUNC(distance) +{ + int x0, y0, x1, y1; + + x0 = script_getnum(st,2); + y0 = script_getnum(st,3); + x1 = script_getnum(st,4); + y1 = script_getnum(st,5); + + script_pushint(st,distance_xy(x0,y0,x1,y1)); + return 0; +} + +// <--- [zBuffer] List of mathematics commands + +BUILDIN_FUNC(md5) +{ + const char *tmpstr; + char *md5str; + + tmpstr = script_getstr(st,2); + md5str = (char *)aMalloc((32+1)*sizeof(char)); + MD5_String(tmpstr, md5str); + script_pushstr(st, md5str); + return 0; +} + +// [zBuffer] List of dynamic var commands ---> + +BUILDIN_FUNC(setd) +{ + TBL_PC *sd=NULL; + char varname[100]; + const char *buffer; + int elem; + buffer = script_getstr(st, 2); + + if(sscanf(buffer, "%99[^[][%d]", varname, &elem) < 2) + elem = 0; + + if( not_server_variable(*varname) ) + { + sd = script_rid2sd(st); + if( sd == NULL ) + { + ShowError("script:setd: no player attached for player variable '%s'\n", buffer); + return 0; + } + } + + if( is_string_variable(varname) ) { + setd_sub(st, sd, varname, elem, (void *)script_getstr(st, 3), NULL); + } else { + setd_sub(st, sd, varname, elem, (void *)__64BPRTSIZE(script_getnum(st, 3)), NULL); + } + + return 0; +} + +int buildin_query_sql_sub(struct script_state* st, Sql* handle) +{ + int i, j; + TBL_PC* sd = NULL; + const char* query; + struct script_data* data; + const char* name; + int max_rows = SCRIPT_MAX_ARRAYSIZE; // maximum number of rows + int num_vars; + int num_cols; + + // check target variables + for( i = 3; script_hasdata(st,i); ++i ) { + data = script_getdata(st, i); + if( data_isreference(data) ) { // it's a variable + name = reference_getname(data); + if( not_server_variable(*name) && sd == NULL ) { // requires a player + sd = script_rid2sd(st); + if( sd == NULL ) { // no player attached + script_reportdata(data); + st->state = END; + return 1; + } + } + if( not_array_variable(*name) ) + max_rows = 1;// not an array, limit to one row + } else { + ShowError("script:query_sql: not a variable\n"); + script_reportdata(data); + st->state = END; + return 1; + } + } + num_vars = i - 3; + + // Execute the query + query = script_getstr(st,2); + + if( SQL_ERROR == Sql_QueryStr(handle, query) ) { + Sql_ShowDebug(handle); + script_pushint(st, 0); + return 1; + } + + if( Sql_NumRows(handle) == 0 ) { // No data received + Sql_FreeResult(handle); + script_pushint(st, 0); + return 0; + } + + // Count the number of columns to store + num_cols = Sql_NumColumns(handle); + if( num_vars < num_cols ) { + ShowWarning("script:query_sql: Too many columns, discarding last %u columns.\n", (unsigned int)(num_cols-num_vars)); + script_reportsrc(st); + } else if( num_vars > num_cols ) { + ShowWarning("script:query_sql: Too many variables (%u extra).\n", (unsigned int)(num_vars-num_cols)); + script_reportsrc(st); + } + + // Store data + for( i = 0; i < max_rows && SQL_SUCCESS == Sql_NextRow(handle); ++i ) { + for( j = 0; j < num_vars; ++j ) { + char* str = NULL; + + if( j < num_cols ) + Sql_GetData(handle, j, &str, NULL); + + data = script_getdata(st, j+3); + name = reference_getname(data); + if( is_string_variable(name) ) + setd_sub(st, sd, name, i, (void *)(str?str:""), reference_getref(data)); + else + setd_sub(st, sd, name, i, (void *)__64BPRTSIZE((str?atoi(str):0)), reference_getref(data)); + } + } + if( i == max_rows && max_rows < Sql_NumRows(handle) ) { + ShowWarning("script:query_sql: Only %d/%u rows have been stored.\n", max_rows, (unsigned int)Sql_NumRows(handle)); + script_reportsrc(st); + } + + // Free data + Sql_FreeResult(handle); + script_pushint(st, i); + + return 0; +} + +BUILDIN_FUNC(query_sql) { +#ifdef BETA_THREAD_TEST + if( st->state != RERUNLINE ) { + queryThread_add(st,false); + + st->state = RERUNLINE;/* will continue when the query is finished running. */ + } else + st->state = RUN; + + return 0; +#else + return buildin_query_sql_sub(st, mmysql_handle); +#endif +} + +BUILDIN_FUNC(query_logsql) { + if( !log_config.sql_logs ) {// logmysql_handle == NULL + ShowWarning("buildin_query_logsql: SQL logs are disabled, query '%s' will not be executed.\n", script_getstr(st,2)); + script_pushint(st,-1); + return 1; + } +#ifdef BETA_THREAD_TEST + if( st->state != RERUNLINE ) { + queryThread_add(st,true); + + st->state = RERUNLINE;/* will continue when the query is finished running. */ + } else + st->state = RUN; + + return 0; +#else + return buildin_query_sql_sub(st, logmysql_handle); +#endif +} + +//Allows escaping of a given string. +BUILDIN_FUNC(escape_sql) +{ + const char *str; + char *esc_str; + size_t len; + + str = script_getstr(st,2); + len = strlen(str); + esc_str = (char*)aMalloc(len*2+1); + Sql_EscapeStringLen(mmysql_handle, esc_str, str, len); + script_pushstr(st, esc_str); + return 0; +} + +BUILDIN_FUNC(getd) +{ + char varname[100]; + const char *buffer; + int elem; + + buffer = script_getstr(st, 2); + + if(sscanf(buffer, "%[^[][%d]", varname, &elem) < 2) + elem = 0; + + // Push the 'pointer' so it's more flexible [Lance] + push_val(st->stack, C_NAME, reference_uid(add_str(varname), elem)); + + return 0; +} + +// <--- [zBuffer] List of dynamic var commands +// Pet stat [Lance] +BUILDIN_FUNC(petstat) +{ + TBL_PC *sd = NULL; + struct pet_data *pd; + int flag = script_getnum(st,2); + sd = script_rid2sd(st); + if(!sd || !sd->status.pet_id || !sd->pd){ + if(flag == 2) + script_pushconststr(st, ""); + else + script_pushint(st,0); + return 0; + } + pd = sd->pd; + switch(flag){ + case 1: script_pushint(st,(int)pd->pet.class_); break; + case 2: script_pushstrcopy(st, pd->pet.name); break; + case 3: script_pushint(st,(int)pd->pet.level); break; + case 4: script_pushint(st,(int)pd->pet.hungry); break; + case 5: script_pushint(st,(int)pd->pet.intimate); break; + default: + script_pushint(st,0); + break; + } + return 0; +} + +BUILDIN_FUNC(callshop) +{ + TBL_PC *sd = NULL; + struct npc_data *nd; + const char *shopname; + int flag = 0; + sd = script_rid2sd(st); + if (!sd) { + script_pushint(st,0); + return 0; + } + shopname = script_getstr(st, 2); + if( script_hasdata(st,3) ) + flag = script_getnum(st,3); + nd = npc_name2id(shopname); + if( !nd || nd->bl.type != BL_NPC || (nd->subtype != SHOP && nd->subtype != CASHSHOP) ) + { + ShowError("buildin_callshop: Shop [%s] not found (or NPC is not shop type)\n", shopname); + script_pushint(st,0); + return 1; + } + + if( nd->subtype == SHOP ) + { + // flag the user as using a valid script call for opening the shop (for floating NPCs) + sd->state.callshop = 1; + + switch( flag ) + { + case 1: npc_buysellsel(sd,nd->bl.id,0); break; //Buy window + case 2: npc_buysellsel(sd,nd->bl.id,1); break; //Sell window + default: clif_npcbuysell(sd,nd->bl.id); break; //Show menu + } + } + else + clif_cashshop_show(sd, nd); + + sd->npc_shopid = nd->bl.id; + script_pushint(st,1); + return 0; +} + +BUILDIN_FUNC(npcshopitem) +{ + const char* npcname = script_getstr(st, 2); + struct npc_data* nd = npc_name2id(npcname); + int n, i; + int amount; + + if( !nd || ( nd->subtype != SHOP && nd->subtype != CASHSHOP ) ) + { //Not found. + script_pushint(st,0); + return 0; + } + + // get the count of new entries + amount = (script_lastdata(st)-2)/2; + + // generate new shop item list + RECREATE(nd->u.shop.shop_item, struct npc_item_list, amount); + for( n = 0, i = 3; n < amount; n++, i+=2 ) + { + nd->u.shop.shop_item[n].nameid = script_getnum(st,i); + nd->u.shop.shop_item[n].value = script_getnum(st,i+1); + } + nd->u.shop.count = n; + + script_pushint(st,1); + return 0; +} + +BUILDIN_FUNC(npcshopadditem) +{ + const char* npcname = script_getstr(st,2); + struct npc_data* nd = npc_name2id(npcname); + int n, i; + int amount; + + if( !nd || ( nd->subtype != SHOP && nd->subtype != CASHSHOP ) ) + { //Not found. + script_pushint(st,0); + return 0; + } + + // get the count of new entries + amount = (script_lastdata(st)-2)/2; + + // append new items to existing shop item list + RECREATE(nd->u.shop.shop_item, struct npc_item_list, nd->u.shop.count+amount); + for( n = nd->u.shop.count, i = 3; n < nd->u.shop.count+amount; n++, i+=2 ) + { + nd->u.shop.shop_item[n].nameid = script_getnum(st,i); + nd->u.shop.shop_item[n].value = script_getnum(st,i+1); + } + nd->u.shop.count = n; + + script_pushint(st,1); + return 0; +} + +BUILDIN_FUNC(npcshopdelitem) +{ + const char* npcname = script_getstr(st,2); + struct npc_data* nd = npc_name2id(npcname); + unsigned int nameid; + int n, i; + int amount; + int size; + + if( !nd || ( nd->subtype != SHOP && nd->subtype != CASHSHOP ) ) + { //Not found. + script_pushint(st,0); + return 0; + } + + amount = script_lastdata(st)-2; + size = nd->u.shop.count; + + // remove specified items from the shop item list + for( i = 3; i < 3 + amount; i++ ) + { + nameid = script_getnum(st,i); + + ARR_FIND( 0, size, n, nd->u.shop.shop_item[n].nameid == nameid ); + if( n < size ) + { + memmove(&nd->u.shop.shop_item[n], &nd->u.shop.shop_item[n+1], sizeof(nd->u.shop.shop_item[0])*(size-n)); + size--; + } + } + + RECREATE(nd->u.shop.shop_item, struct npc_item_list, size); + nd->u.shop.count = size; + + script_pushint(st,1); + return 0; +} + +//Sets a script to attach to a shop npc. +BUILDIN_FUNC(npcshopattach) +{ + const char* npcname = script_getstr(st,2); + struct npc_data* nd = npc_name2id(npcname); + int flag = 1; + + if( script_hasdata(st,3) ) + flag = script_getnum(st,3); + + if( !nd || nd->subtype != SHOP ) + { //Not found. + script_pushint(st,0); + return 0; + } + + if (flag) + nd->master_nd = ((struct npc_data *)map_id2bl(st->oid)); + else + nd->master_nd = NULL; + + script_pushint(st,1); + return 0; +} + +/*========================================== + * Returns some values of an item [Lupus] + * Price, Weight, etc... + setitemscript(itemID,"{new item bonus script}",[n]); + Where n: + 0 - script + 1 - Equip script + 2 - Unequip script + *------------------------------------------*/ +BUILDIN_FUNC(setitemscript) +{ + int item_id,n=0; + const char *script; + struct item_data *i_data; + struct script_code **dstscript; + + item_id = script_getnum(st,2); + script = script_getstr(st,3); + if( script_hasdata(st,4) ) + n=script_getnum(st,4); + i_data = itemdb_exists(item_id); + + if (!i_data || script==NULL || ( script[0] && script[0]!='{' )) { + script_pushint(st,0); + return 0; + } + switch (n) { + case 2: + dstscript = &i_data->unequip_script; + break; + case 1: + dstscript = &i_data->equip_script; + break; + default: + dstscript = &i_data->script; + break; + } + if(*dstscript) + script_free_code(*dstscript); + + *dstscript = script[0] ? parse_script(script, "script_setitemscript", 0, 0) : NULL; + script_pushint(st,1); + return 0; +} + +/* Work In Progress [Lupus] +BUILDIN_FUNC(addmonsterdrop) +{ + int class_,item_id,chance; + class_=script_getnum(st,2); + item_id=script_getnum(st,3); + chance=script_getnum(st,4); + if(class_>1000 && item_id>500 && chance>0) { + script_pushint(st,1); + } else { + script_pushint(st,0); + } +} + +BUILDIN_FUNC(delmonsterdrop) +{ + int class_,item_id; + class_=script_getnum(st,2); + item_id=script_getnum(st,3); + if(class_>1000 && item_id>500) { + script_pushint(st,1); + } else { + script_pushint(st,0); + } +} +*/ + +/*========================================== + * Returns some values of a monster [Lupus] + * Name, Level, race, size, etc... + getmonsterinfo(monsterID,queryIndex); + *------------------------------------------*/ +BUILDIN_FUNC(getmonsterinfo) +{ + struct mob_db *mob; + int mob_id; + + mob_id = script_getnum(st,2); + if (!mobdb_checkid(mob_id)) { + ShowError("buildin_getmonsterinfo: Wrong Monster ID: %i\n", mob_id); + if ( !script_getnum(st,3) ) //requested a string + script_pushconststr(st,"null"); + else + script_pushint(st,-1); + return -1; + } + mob = mob_db(mob_id); + switch ( script_getnum(st,3) ) { + case 0: script_pushstrcopy(st,mob->jname); break; + case 1: script_pushint(st,mob->lv); break; + case 2: script_pushint(st,mob->status.max_hp); break; + case 3: script_pushint(st,mob->base_exp); break; + case 4: script_pushint(st,mob->job_exp); break; + case 5: script_pushint(st,mob->status.rhw.atk); break; + case 6: script_pushint(st,mob->status.rhw.atk2); break; + case 7: script_pushint(st,mob->status.def); break; + case 8: script_pushint(st,mob->status.mdef); break; + case 9: script_pushint(st,mob->status.str); break; + case 10: script_pushint(st,mob->status.agi); break; + case 11: script_pushint(st,mob->status.vit); break; + case 12: script_pushint(st,mob->status.int_); break; + case 13: script_pushint(st,mob->status.dex); break; + case 14: script_pushint(st,mob->status.luk); break; + case 15: script_pushint(st,mob->status.rhw.range); break; + case 16: script_pushint(st,mob->range2); break; + case 17: script_pushint(st,mob->range3); break; + case 18: script_pushint(st,mob->status.size); break; + case 19: script_pushint(st,mob->status.race); break; + case 20: script_pushint(st,mob->status.def_ele); break; + case 21: script_pushint(st,mob->status.mode); break; + case 22: script_pushint(st,mob->mexp); break; + default: script_pushint(st,-1); //wrong Index + } + return 0; +} + +BUILDIN_FUNC(checkvending) // check vending [Nab4] +{ + TBL_PC *sd = NULL; + + if(script_hasdata(st,2)) + sd = map_nick2sd(script_getstr(st,2)); + else + sd = script_rid2sd(st); + + if(sd) + script_pushint(st, sd->state.autotrade ? 2 : sd->state.vending); + else + script_pushint(st,0); + + return 0; +} + + +BUILDIN_FUNC(checkchatting) // check chatting [Marka] +{ + TBL_PC *sd = NULL; + + if(script_hasdata(st,2)) + sd = map_nick2sd(script_getstr(st,2)); + else + sd = script_rid2sd(st); + + if(sd) + script_pushint(st,(sd->chatID != 0)); + else + script_pushint(st,0); + + return 0; +} + +BUILDIN_FUNC(searchitem) +{ + struct script_data* data = script_getdata(st, 2); + const char *itemname = script_getstr(st,3); + struct item_data *items[MAX_SEARCH]; + int count; + + char* name; + int32 start; + int32 id; + int32 i; + TBL_PC* sd = NULL; + + if ((items[0] = itemdb_exists(atoi(itemname)))) + count = 1; + else { + count = itemdb_searchname_array(items, ARRAYLENGTH(items), itemname); + if (count > MAX_SEARCH) count = MAX_SEARCH; + } + + if (!count) { + script_pushint(st, 0); + return 0; + } + + if( !data_isreference(data) ) + { + ShowError("script:searchitem: not a variable\n"); + script_reportdata(data); + st->state = END; + return 1;// not a variable + } + + id = reference_getid(data); + start = reference_getindex(data); + name = reference_getname(data); + if( not_array_variable(*name) ) + { + ShowError("script:searchitem: illegal scope\n"); + script_reportdata(data); + st->state = END; + return 1;// not supported + } + + if( not_server_variable(*name) ) + { + sd = script_rid2sd(st); + if( sd == NULL ) + return 0;// no player attached + } + + if( is_string_variable(name) ) + {// string array + ShowError("script:searchitem: not an integer array reference\n"); + script_reportdata(data); + st->state = END; + return 1;// not supported + } + + for( i = 0; i < count; ++start, ++i ) + {// Set array + void* v = (void*)__64BPRTSIZE((int)items[i]->nameid); + set_reg(st, sd, reference_uid(id, start), name, v, reference_getref(data)); + } + + script_pushint(st, count); + return 0; +} + +int axtoi(const char *hexStg) +{ + int n = 0; // position in string + int16 m = 0; // position in digit[] to shift + int count; // loop index + int intValue = 0; // integer value of hex string + int digit[11]; // hold values to convert + while (n < 10) { + if (hexStg[n]=='\0') + break; + if (hexStg[n] > 0x29 && hexStg[n] < 0x40 ) //if 0 to 9 + digit[n] = hexStg[n] & 0x0f; //convert to int + else if (hexStg[n] >='a' && hexStg[n] <= 'f') //if a to f + digit[n] = (hexStg[n] & 0x0f) + 9; //convert to int + else if (hexStg[n] >='A' && hexStg[n] <= 'F') //if A to F + digit[n] = (hexStg[n] & 0x0f) + 9; //convert to int + else break; + n++; + } + count = n; + m = n - 1; + n = 0; + while(n < count) { + // digit[n] is value of hex digit at position n + // (m << 2) is the number of positions to shift + // OR the bits into return value + intValue = intValue | (digit[n] << (m << 2)); + m--; // adjust the position to set + n++; // next digit to process + } + return (intValue); +} + +// [Lance] Hex string to integer converter +BUILDIN_FUNC(axtoi) +{ + const char *hex = script_getstr(st,2); + script_pushint(st,axtoi(hex)); + return 0; +} + +// [zBuffer] List of player cont commands ---> +BUILDIN_FUNC(rid2name) +{ + struct block_list *bl = NULL; + int rid = script_getnum(st,2); + if((bl = map_id2bl(rid))) + { + switch(bl->type) { + case BL_MOB: script_pushstrcopy(st,((TBL_MOB*)bl)->name); break; + case BL_PC: script_pushstrcopy(st,((TBL_PC*)bl)->status.name); break; + case BL_NPC: script_pushstrcopy(st,((TBL_NPC*)bl)->exname); break; + case BL_PET: script_pushstrcopy(st,((TBL_PET*)bl)->pet.name); break; + case BL_HOM: script_pushstrcopy(st,((TBL_HOM*)bl)->homunculus.name); break; + case BL_MER: script_pushstrcopy(st,((TBL_MER*)bl)->db->name); break; + default: + ShowError("buildin_rid2name: BL type unknown.\n"); + script_pushconststr(st,""); + break; + } + } else { + ShowError("buildin_rid2name: invalid RID\n"); + script_pushconststr(st,"(null)"); + } + return 0; +} + +BUILDIN_FUNC(pcblockmove) +{ + int id, flag; + TBL_PC *sd = NULL; + + id = script_getnum(st,2); + flag = script_getnum(st,3); + + if(id) + sd = map_id2sd(id); + else + sd = script_rid2sd(st); + + if(sd) + sd->state.blockedmove = flag > 0; + + return 0; +} + +BUILDIN_FUNC(pcfollow) +{ + int id, targetid; + TBL_PC *sd = NULL; + + + id = script_getnum(st,2); + targetid = script_getnum(st,3); + + if(id) + sd = map_id2sd(id); + else + sd = script_rid2sd(st); + + if(sd) + pc_follow(sd, targetid); + + return 0; +} + +BUILDIN_FUNC(pcstopfollow) +{ + int id; + TBL_PC *sd = NULL; + + + id = script_getnum(st,2); + + if(id) + sd = map_id2sd(id); + else + sd = script_rid2sd(st); + + if(sd) + pc_stop_following(sd); + + return 0; +} +// <--- [zBuffer] List of player cont commands +// [zBuffer] List of mob control commands ---> +//## TODO always return if the request/whatever was successfull [FlavioJS] + +/// Makes the unit walk to target position or map +/// Returns if it was successfull +/// +/// unitwalk(<unit_id>,<x>,<y>) -> <bool> +/// unitwalk(<unit_id>,<map_id>) -> <bool> +BUILDIN_FUNC(unitwalk) +{ + struct block_list* bl; + + bl = map_id2bl(script_getnum(st,2)); + if( bl == NULL ) + { + script_pushint(st, 0); + } + else if( script_hasdata(st,4) ) + { + int x = script_getnum(st,3); + int y = script_getnum(st,4); + script_pushint(st, unit_walktoxy(bl,x,y,0));// We'll use harder calculations. + } + else + { + int map_id = script_getnum(st,3); + script_pushint(st, unit_walktobl(bl,map_id2bl(map_id),65025,1)); + } + + return 0; +} + +/// Kills the unit +/// +/// unitkill <unit_id>; +BUILDIN_FUNC(unitkill) +{ + struct block_list* bl = map_id2bl(script_getnum(st,2)); + if( bl != NULL ) + status_kill(bl); + + return 0; +} + +/// Warps the unit to the target position in the target map +/// Returns if it was successfull +/// +/// unitwarp(<unit_id>,"<map name>",<x>,<y>) -> <bool> +BUILDIN_FUNC(unitwarp) +{ + int unit_id; + int map; + short x; + short y; + struct block_list* bl; + const char *mapname; + + unit_id = script_getnum(st,2); + mapname = script_getstr(st, 3); + x = (short)script_getnum(st,4); + y = (short)script_getnum(st,5); + + if (!unit_id) //Warp the script's runner + bl = map_id2bl(st->rid); + else + bl = map_id2bl(unit_id); + + if( strcmp(mapname,"this") == 0 ) + map = bl?bl->m:-1; + else + map = map_mapname2mapid(mapname); + + if( map >= 0 && bl != NULL ) + script_pushint(st, unit_warp(bl,map,x,y,CLR_OUTSIGHT)); + else + script_pushint(st, 0); + + return 0; +} + +/// Makes the unit attack the target. +/// If the unit is a player and <action type> is not 0, it does a continuous +/// attack instead of a single attack. +/// Returns if the request was successfull. +/// +/// unitattack(<unit_id>,"<target name>"{,<action type>}) -> <bool> +/// unitattack(<unit_id>,<target_id>{,<action type>}) -> <bool> +BUILDIN_FUNC(unitattack) +{ + struct block_list* unit_bl; + struct block_list* target_bl = NULL; + struct script_data* data; + int actiontype = 0; + + // get unit + unit_bl = map_id2bl(script_getnum(st,2)); + if( unit_bl == NULL ) { + script_pushint(st, 0); + return 0; + } + + data = script_getdata(st, 3); + get_val(st, data); + if( data_isstring(data) ) + { + TBL_PC* sd = map_nick2sd(conv_str(st, data)); + if( sd != NULL ) + target_bl = &sd->bl; + } else + target_bl = map_id2bl(conv_num(st, data)); + // request the attack + if( target_bl == NULL ) + { + script_pushint(st, 0); + return 0; + } + + // get actiontype + if( script_hasdata(st,4) ) + actiontype = script_getnum(st,4); + + switch( unit_bl->type ) + { + case BL_PC: + clif_parse_ActionRequest_sub(((TBL_PC *)unit_bl), actiontype > 0 ? 0x07 : 0x00, target_bl->id, gettick()); + script_pushint(st, 1); + return 0; + case BL_MOB: + ((TBL_MOB *)unit_bl)->target_id = target_bl->id; + break; + case BL_PET: + ((TBL_PET *)unit_bl)->target_id = target_bl->id; + break; + default: + ShowError("script:unitattack: unsupported source unit type %d\n", unit_bl->type); + script_pushint(st, 0); + return 1; + } + script_pushint(st, unit_walktobl(unit_bl, target_bl, 65025, 2)); + return 0; +} + +/// Makes the unit stop attacking and moving +/// +/// unitstop <unit_id>; +BUILDIN_FUNC(unitstop) +{ + int unit_id; + struct block_list* bl; + + unit_id = script_getnum(st,2); + + bl = map_id2bl(unit_id); + if( bl != NULL ) + { + unit_stop_attack(bl); + unit_stop_walking(bl,4); + if( bl->type == BL_MOB ) + ((TBL_MOB*)bl)->target_id = 0; + } + + return 0; +} + +/// Makes the unit say the message +/// +/// unittalk <unit_id>,"<message>"; +BUILDIN_FUNC(unittalk) +{ + int unit_id; + const char* message; + struct block_list* bl; + + unit_id = script_getnum(st,2); + message = script_getstr(st, 3); + + bl = map_id2bl(unit_id); + if( bl != NULL ) + { + struct StringBuf sbuf; + StringBuf_Init(&sbuf); + StringBuf_Printf(&sbuf, "%s : %s", status_get_name(bl), message); + clif_message(bl, StringBuf_Value(&sbuf)); + if( bl->type == BL_PC ) + clif_displaymessage(((TBL_PC*)bl)->fd, StringBuf_Value(&sbuf)); + StringBuf_Destroy(&sbuf); + } + + return 0; +} + +/// Makes the unit do an emotion +/// +/// unitemote <unit_id>,<emotion>; +/// +/// @see e_* in const.txt +BUILDIN_FUNC(unitemote) +{ + int unit_id; + int emotion; + struct block_list* bl; + + unit_id = script_getnum(st,2); + emotion = script_getnum(st,3); + bl = map_id2bl(unit_id); + if( bl != NULL ) + clif_emotion(bl, emotion); + + return 0; +} + +/// Makes the unit cast the skill on the target or self if no target is specified +/// +/// unitskilluseid <unit_id>,<skill_id>,<skill_lv>{,<target_id>}; +/// unitskilluseid <unit_id>,"<skill name>",<skill_lv>{,<target_id>}; +BUILDIN_FUNC(unitskilluseid) +{ + int unit_id; + uint16 skill_id; + uint16 skill_lv; + int target_id; + struct block_list* bl; + + unit_id = script_getnum(st,2); + skill_id = ( script_isstring(st,3) ? skill_name2id(script_getstr(st,3)) : script_getnum(st,3) ); + skill_lv = script_getnum(st,4); + target_id = ( script_hasdata(st,5) ? script_getnum(st,5) : unit_id ); + + bl = map_id2bl(unit_id); + if( bl != NULL ) + unit_skilluse_id(bl, target_id, skill_id, skill_lv); + + return 0; +} + +/// Makes the unit cast the skill on the target position. +/// +/// unitskillusepos <unit_id>,<skill_id>,<skill_lv>,<target_x>,<target_y>; +/// unitskillusepos <unit_id>,"<skill name>",<skill_lv>,<target_x>,<target_y>; +BUILDIN_FUNC(unitskillusepos) +{ + int unit_id; + uint16 skill_id; + uint16 skill_lv; + int skill_x; + int skill_y; + struct block_list* bl; + + unit_id = script_getnum(st,2); + skill_id = ( script_isstring(st,3) ? skill_name2id(script_getstr(st,3)) : script_getnum(st,3) ); + skill_lv = script_getnum(st,4); + skill_x = script_getnum(st,5); + skill_y = script_getnum(st,6); + + bl = map_id2bl(unit_id); + if( bl != NULL ) + unit_skilluse_pos(bl, skill_x, skill_y, skill_id, skill_lv); + + return 0; +} + +// <--- [zBuffer] List of mob control commands + +/// Pauses the execution of the script, detaching the player +/// +/// sleep <mili seconds>; +BUILDIN_FUNC(sleep) +{ + int ticks; + + ticks = script_getnum(st,2); + + // detach the player + script_detach_rid(st); + + if( ticks <= 0 ) + {// do nothing + } + else if( st->sleep.tick == 0 ) + {// sleep for the target amount of time + st->state = RERUNLINE; + st->sleep.tick = ticks; + } + else + {// sleep time is over + st->state = RUN; + st->sleep.tick = 0; + } + return 0; +} + +/// Pauses the execution of the script, keeping the player attached +/// Returns if a player is still attached +/// +/// sleep2(<mili secconds>) -> <bool> +BUILDIN_FUNC(sleep2) +{ + int ticks; + + ticks = script_getnum(st,2); + + if( ticks <= 0 ) + {// do nothing + script_pushint(st, (map_id2sd(st->rid)!=NULL)); + } + else if( !st->sleep.tick ) + {// sleep for the target amount of time + st->state = RERUNLINE; + st->sleep.tick = ticks; + } + else + {// sleep time is over + st->state = RUN; + st->sleep.tick = 0; + script_pushint(st, (map_id2sd(st->rid)!=NULL)); + } + return 0; +} + +/// Awakes all the sleep timers of the target npc +/// +/// awake "<npc name>"; +BUILDIN_FUNC(awake) +{ + struct npc_data* nd; + struct linkdb_node *node = (struct linkdb_node *)sleep_db; + + nd = npc_name2id(script_getstr(st, 2)); + if( nd == NULL ) { + ShowError("awake: NPC \"%s\" not found\n", script_getstr(st, 2)); + return 1; + } + + while( node ) + { + if( (int)__64BPRTSIZE(node->key) == nd->bl.id ) + {// sleep timer for the npc + struct script_state* tst = (struct script_state*)node->data; + TBL_PC* sd = map_id2sd(tst->rid); + + if( tst->sleep.timer == INVALID_TIMER ) + {// already awake ??? + node = node->next; + continue; + } + if( (sd && sd->status.char_id != tst->sleep.charid) || (tst->rid && !sd)) + {// char not online anymore / another char of the same account is online - Cancel execution + tst->state = END; + tst->rid = 0; + } + + delete_timer(tst->sleep.timer, run_script_timer); + node = script_erase_sleepdb(node); + tst->sleep.timer = INVALID_TIMER; + if(tst->state != RERUNLINE) + tst->sleep.tick = 0; + run_script_main(tst); + } + else + { + node = node->next; + } + } + return 0; +} + +/// Returns a reference to a variable of the target NPC. +/// Returns 0 if an error occurs. +/// +/// getvariableofnpc(<variable>, "<npc name>") -> <reference> +BUILDIN_FUNC(getvariableofnpc) +{ + struct script_data* data; + const char* name; + struct npc_data* nd; + + data = script_getdata(st,2); + if( !data_isreference(data) ) + {// Not a reference (aka varaible name) + ShowError("script:getvariableofnpc: not a variable\n"); + script_reportdata(data); + script_pushnil(st); + st->state = END; + return 1; + } + + name = reference_getname(data); + if( *name != '.' || name[1] == '@' ) + {// not a npc variable + ShowError("script:getvariableofnpc: invalid scope (not npc variable)\n"); + script_reportdata(data); + script_pushnil(st); + st->state = END; + return 1; + } + + nd = npc_name2id(script_getstr(st,3)); + if( nd == NULL || nd->subtype != SCRIPT || nd->u.scr.script == NULL ) + {// NPC not found or has no script + ShowError("script:getvariableofnpc: can't find npc %s\n", script_getstr(st,3)); + script_pushnil(st); + st->state = END; + return 1; + } + + push_val2(st->stack, C_NAME, reference_getuid(data), &nd->u.scr.script->script_vars ); + return 0; +} + +/// Opens a warp portal. +/// Has no "portal opening" effect/sound, it opens the portal immediately. +/// +/// warpportal <source x>,<source y>,"<target map>",<target x>,<target y>; +/// +/// @author blackhole89 +BUILDIN_FUNC(warpportal) +{ + int spx; + int spy; + unsigned short mapindex; + int tpx; + int tpy; + struct skill_unit_group* group; + struct block_list* bl; + + bl = map_id2bl(st->oid); + if( bl == NULL ) + { + ShowError("script:warpportal: npc is needed\n"); + return 1; + } + + spx = script_getnum(st,2); + spy = script_getnum(st,3); + mapindex = mapindex_name2id(script_getstr(st, 4)); + tpx = script_getnum(st,5); + tpy = script_getnum(st,6); + + if( mapindex == 0 ) + return 0;// map not found + + group = skill_unitsetting(bl, AL_WARP, 4, spx, spy, 0); + if( group == NULL ) + return 0;// failed + group->val2 = (tpx<<16) | tpy; + group->val3 = mapindex; + + return 0; +} + +BUILDIN_FUNC(openmail) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + mail_openmail(sd); + + return 0; +} + +BUILDIN_FUNC(openauction) +{ + TBL_PC* sd; + + sd = script_rid2sd(st); + if( sd == NULL ) + return 0; + + clif_Auction_openwindow(sd); + + return 0; +} + +/// Retrieves the value of the specified flag of the specified cell. +/// +/// checkcell("<map name>",<x>,<y>,<type>) -> <bool> +/// +/// @see cell_chk* constants in const.txt for the types +BUILDIN_FUNC(checkcell) +{ + int16 m = map_mapname2mapid(script_getstr(st,2)); + int16 x = script_getnum(st,3); + int16 y = script_getnum(st,4); + cell_chk type = (cell_chk)script_getnum(st,5); + + script_pushint(st, map_getcell(m, x, y, type)); + + return 0; +} + +/// Modifies flags of cells in the specified area. +/// +/// setcell "<map name>",<x1>,<y1>,<x2>,<y2>,<type>,<flag>; +/// +/// @see cell_* constants in const.txt for the types +BUILDIN_FUNC(setcell) +{ + int16 m = map_mapname2mapid(script_getstr(st,2)); + int16 x1 = script_getnum(st,3); + int16 y1 = script_getnum(st,4); + int16 x2 = script_getnum(st,5); + int16 y2 = script_getnum(st,6); + cell_t type = (cell_t)script_getnum(st,7); + bool flag = (bool)script_getnum(st,8); + + int x,y; + + if( x1 > x2 ) swap(x1,x2); + if( y1 > y2 ) swap(y1,y2); + + for( y = y1; y <= y2; ++y ) + for( x = x1; x <= x2; ++x ) + map_setcell(m, x, y, type, flag); + + return 0; +} + +/*========================================== + * Mercenary Commands + *------------------------------------------*/ +BUILDIN_FUNC(mercenary_create) +{ + struct map_session_data *sd; + int class_, contract_time; + + if( (sd = script_rid2sd(st)) == NULL || sd->md || sd->status.mer_id != 0 ) + return 0; + + class_ = script_getnum(st,2); + + if( !merc_class(class_) ) + return 0; + + contract_time = script_getnum(st,3); + merc_create(sd, class_, contract_time); + return 0; +} + +BUILDIN_FUNC(mercenary_heal) +{ + struct map_session_data *sd = script_rid2sd(st); + int hp, sp; + + if( sd == NULL || sd->md == NULL ) + return 0; + hp = script_getnum(st,2); + sp = script_getnum(st,3); + + status_heal(&sd->md->bl, hp, sp, 0); + return 0; +} + +BUILDIN_FUNC(mercenary_sc_start) +{ + struct map_session_data *sd = script_rid2sd(st); + enum sc_type type; + int tick, val1; + + if( sd == NULL || sd->md == NULL ) + return 0; + + type = (sc_type)script_getnum(st,2); + tick = script_getnum(st,3); + val1 = script_getnum(st,4); + + status_change_start(&sd->md->bl, type, 10000, val1, 0, 0, 0, tick, 2); + return 0; +} + +BUILDIN_FUNC(mercenary_get_calls) +{ + struct map_session_data *sd = script_rid2sd(st); + int guild; + + if( sd == NULL ) + return 0; + + guild = script_getnum(st,2); + switch( guild ) + { + case ARCH_MERC_GUILD: + script_pushint(st,sd->status.arch_calls); + break; + case SPEAR_MERC_GUILD: + script_pushint(st,sd->status.spear_calls); + break; + case SWORD_MERC_GUILD: + script_pushint(st,sd->status.sword_calls); + break; + default: + script_pushint(st,0); + break; + } + + return 0; +} + +BUILDIN_FUNC(mercenary_set_calls) +{ + struct map_session_data *sd = script_rid2sd(st); + int guild, value, *calls; + + if( sd == NULL ) + return 0; + + guild = script_getnum(st,2); + value = script_getnum(st,3); + + switch( guild ) + { + case ARCH_MERC_GUILD: + calls = &sd->status.arch_calls; + break; + case SPEAR_MERC_GUILD: + calls = &sd->status.spear_calls; + break; + case SWORD_MERC_GUILD: + calls = &sd->status.sword_calls; + break; + default: + return 0; // Invalid Guild + } + + *calls += value; + *calls = cap_value(*calls, 0, INT_MAX); + + return 0; +} + +BUILDIN_FUNC(mercenary_get_faith) +{ + struct map_session_data *sd = script_rid2sd(st); + int guild; + + if( sd == NULL ) + return 0; + + guild = script_getnum(st,2); + switch( guild ) + { + case ARCH_MERC_GUILD: + script_pushint(st,sd->status.arch_faith); + break; + case SPEAR_MERC_GUILD: + script_pushint(st,sd->status.spear_faith); + break; + case SWORD_MERC_GUILD: + script_pushint(st,sd->status.sword_faith); + break; + default: + script_pushint(st,0); + break; + } + + return 0; +} + +BUILDIN_FUNC(mercenary_set_faith) +{ + struct map_session_data *sd = script_rid2sd(st); + int guild, value, *calls; + + if( sd == NULL ) + return 0; + + guild = script_getnum(st,2); + value = script_getnum(st,3); + + switch( guild ) + { + case ARCH_MERC_GUILD: + calls = &sd->status.arch_faith; + break; + case SPEAR_MERC_GUILD: + calls = &sd->status.spear_faith; + break; + case SWORD_MERC_GUILD: + calls = &sd->status.sword_faith; + break; + default: + return 0; // Invalid Guild + } + + *calls += value; + *calls = cap_value(*calls, 0, INT_MAX); + if( mercenary_get_guild(sd->md) == guild ) + clif_mercenary_updatestatus(sd,SP_MERCFAITH); + + return 0; +} + +/*------------------------------------------ + * Book Reading + *------------------------------------------*/ +BUILDIN_FUNC(readbook) +{ + struct map_session_data *sd; + int book_id, page; + + if( (sd = script_rid2sd(st)) == NULL ) + return 0; + + book_id = script_getnum(st,2); + page = script_getnum(st,3); + + clif_readbook(sd->fd, book_id, page); + return 0; +} + +/****************** +Questlog script commands +*******************/ + +BUILDIN_FUNC(setquest) +{ + struct map_session_data *sd = script_rid2sd(st); + nullpo_ret(sd); + + quest_add(sd, script_getnum(st, 2)); + return 0; +} + +BUILDIN_FUNC(erasequest) +{ + struct map_session_data *sd = script_rid2sd(st); + nullpo_ret(sd); + + quest_delete(sd, script_getnum(st, 2)); + return 0; +} + +BUILDIN_FUNC(completequest) +{ + struct map_session_data *sd = script_rid2sd(st); + nullpo_ret(sd); + + quest_update_status(sd, script_getnum(st, 2), Q_COMPLETE); + return 0; +} + +BUILDIN_FUNC(changequest) +{ + struct map_session_data *sd = script_rid2sd(st); + nullpo_ret(sd); + + quest_change(sd, script_getnum(st, 2),script_getnum(st, 3)); + return 0; +} + +BUILDIN_FUNC(checkquest) +{ + struct map_session_data *sd = script_rid2sd(st); + quest_check_type type = HAVEQUEST; + + nullpo_ret(sd); + + if( script_hasdata(st, 3) ) + type = (quest_check_type)script_getnum(st, 3); + + script_pushint(st, quest_check(sd, script_getnum(st, 2), type)); + + return 0; +} + +BUILDIN_FUNC(showevent) +{ + TBL_PC *sd = script_rid2sd(st); + struct npc_data *nd = map_id2nd(st->oid); + int state, color; + + if( sd == NULL || nd == NULL ) + return 0; + state = script_getnum(st, 2); + color = script_getnum(st, 3); + + if( color < 0 || color > 3 ) + color = 0; // set default color + + clif_quest_show_event(sd, &nd->bl, state, color); + return 0; +} + +/*========================================== + * BattleGround System + *------------------------------------------*/ +BUILDIN_FUNC(waitingroom2bg) +{ + struct npc_data *nd; + struct chat_data *cd; + const char *map_name, *ev = "", *dev = ""; + int x, y, i, mapindex = 0, bg_id, n; + struct map_session_data *sd; + + if( script_hasdata(st,7) ) + nd = npc_name2id(script_getstr(st,7)); + else + nd = (struct npc_data *)map_id2bl(st->oid); + + if( nd == NULL || (cd = (struct chat_data *)map_id2bl(nd->chat_id)) == NULL ) + { + script_pushint(st,0); + return 0; + } + + map_name = script_getstr(st,2); + if( strcmp(map_name,"-") != 0 ) + { + mapindex = mapindex_name2id(map_name); + if( mapindex == 0 ) + { // Invalid Map + script_pushint(st,0); + return 0; + } + } + + x = script_getnum(st,3); + y = script_getnum(st,4); + ev = script_getstr(st,5); // Logout Event + dev = script_getstr(st,6); // Die Event + + if( (bg_id = bg_create(mapindex, x, y, ev, dev)) == 0 ) + { // Creation failed + script_pushint(st,0); + return 0; + } + + n = cd->users; + for( i = 0; i < n && i < MAX_BG_MEMBERS; i++ ) + { + if( (sd = cd->usersd[i]) != NULL && bg_team_join(bg_id, sd) ) + mapreg_setreg(reference_uid(add_str("$@arenamembers"), i), sd->bl.id); + else + mapreg_setreg(reference_uid(add_str("$@arenamembers"), i), 0); + } + + mapreg_setreg(add_str("$@arenamembersnum"), i); + script_pushint(st,bg_id); + return 0; +} + +BUILDIN_FUNC(waitingroom2bg_single) +{ + const char* map_name; + struct npc_data *nd; + struct chat_data *cd; + struct map_session_data *sd; + int x, y, mapindex, bg_id; + + bg_id = script_getnum(st,2); + map_name = script_getstr(st,3); + if( (mapindex = mapindex_name2id(map_name)) == 0 ) + return 0; // Invalid Map + + x = script_getnum(st,4); + y = script_getnum(st,5); + nd = npc_name2id(script_getstr(st,6)); + + if( nd == NULL || (cd = (struct chat_data *)map_id2bl(nd->chat_id)) == NULL || cd->users <= 0 ) + return 0; + + if( (sd = cd->usersd[0]) == NULL ) + return 0; + + if( bg_team_join(bg_id, sd) ) + { + pc_setpos(sd, mapindex, x, y, CLR_TELEPORT); + script_pushint(st,1); + } + else + script_pushint(st,0); + + return 0; +} + +BUILDIN_FUNC(bg_team_setxy) +{ + struct battleground_data *bg; + int bg_id; + + bg_id = script_getnum(st,2); + if( (bg = bg_team_search(bg_id)) == NULL ) + return 0; + + bg->x = script_getnum(st,3); + bg->y = script_getnum(st,4); + return 0; +} + +BUILDIN_FUNC(bg_warp) +{ + int x, y, mapindex, bg_id; + const char* map_name; + + bg_id = script_getnum(st,2); + map_name = script_getstr(st,3); + if( (mapindex = mapindex_name2id(map_name)) == 0 ) + return 0; // Invalid Map + x = script_getnum(st,4); + y = script_getnum(st,5); + bg_team_warp(bg_id, mapindex, x, y); + return 0; +} + +BUILDIN_FUNC(bg_monster) +{ + int class_ = 0, x = 0, y = 0, bg_id = 0; + const char *str,*map, *evt=""; + + bg_id = script_getnum(st,2); + map = script_getstr(st,3); + x = script_getnum(st,4); + y = script_getnum(st,5); + str = script_getstr(st,6); + class_ = script_getnum(st,7); + if( script_hasdata(st,8) ) evt = script_getstr(st,8); + check_event(st, evt); + script_pushint(st, mob_spawn_bg(map,x,y,str,class_,evt,bg_id)); + return 0; +} + +BUILDIN_FUNC(bg_monster_set_team) +{ + struct mob_data *md; + struct block_list *mbl; + int id = script_getnum(st,2), + bg_id = script_getnum(st,3); + + if( (mbl = map_id2bl(id)) == NULL || mbl->type != BL_MOB ) + return 0; + md = (TBL_MOB *)mbl; + md->bg_id = bg_id; + + mob_stop_attack(md); + mob_stop_walking(md, 0); + md->target_id = md->attacked_id = 0; + clif_charnameack(0, &md->bl); + + return 0; +} + +BUILDIN_FUNC(bg_leave) +{ + struct map_session_data *sd = script_rid2sd(st); + if( sd == NULL || !sd->bg_id ) + return 0; + + bg_team_leave(sd,0); + return 0; +} + +BUILDIN_FUNC(bg_destroy) +{ + int bg_id = script_getnum(st,2); + bg_team_delete(bg_id); + return 0; +} + +BUILDIN_FUNC(bg_getareausers) +{ + const char *str; + int16 m, x0, y0, x1, y1; + int bg_id; + int i = 0, c = 0; + struct battleground_data *bg = NULL; + struct map_session_data *sd; + + bg_id = script_getnum(st,2); + str = script_getstr(st,3); + + if( (bg = bg_team_search(bg_id)) == NULL || (m = map_mapname2mapid(str)) < 0 ) + { + script_pushint(st,0); + return 0; + } + + x0 = script_getnum(st,4); + y0 = script_getnum(st,5); + x1 = script_getnum(st,6); + y1 = script_getnum(st,7); + + for( i = 0; i < MAX_BG_MEMBERS; i++ ) + { + if( (sd = bg->members[i].sd) == NULL ) + continue; + if( sd->bl.m != m || sd->bl.x < x0 || sd->bl.y < y0 || sd->bl.x > x1 || sd->bl.y > y1 ) + continue; + c++; + } + + script_pushint(st,c); + return 0; +} + +BUILDIN_FUNC(bg_updatescore) +{ + const char *str; + int16 m; + + str = script_getstr(st,2); + if( (m = map_mapname2mapid(str)) < 0 ) + return 0; + + map[m].bgscore_lion = script_getnum(st,3); + map[m].bgscore_eagle = script_getnum(st,4); + + clif_bg_updatescore(m); + return 0; +} + +BUILDIN_FUNC(bg_get_data) +{ + struct battleground_data *bg; + int bg_id = script_getnum(st,2), + type = script_getnum(st,3); + + if( (bg = bg_team_search(bg_id)) == NULL ) + { + script_pushint(st,0); + return 0; + } + + switch( type ) + { + case 0: script_pushint(st, bg->count); break; + default: + ShowError("script:bg_get_data: unknown data identifier %d\n", type); + break; + } + + return 0; +} + +/*========================================== + * Instancing Script Commands + *------------------------------------------*/ + +BUILDIN_FUNC(instance_create) +{ + const char *name; + int party_id, res; + + name = script_getstr(st, 2); + party_id = script_getnum(st, 3); + + res = instance_create(party_id, name); + if( res == -4 ) // Already exists + { + script_pushint(st, -1); + return 0; + } + else if( res < 0 ) + { + const char *err; + switch(res) + { + case -3: err = "No free instances"; break; + case -2: err = "Invalid party ID"; break; + case -1: err = "Invalid type"; break; + default: err = "Unknown"; break; + } + ShowError("buildin_instance_create: %s [%d].\n", err, res); + script_pushint(st, -2); + return 0; + } + + script_pushint(st, res); + return 0; +} + +BUILDIN_FUNC(instance_destroy) +{ + int instance_id; + struct map_session_data *sd; + struct party_data *p; + + if( script_hasdata(st, 2) ) + instance_id = script_getnum(st, 2); + else if( st->instance_id ) + instance_id = st->instance_id; + else if( (sd = script_rid2sd(st)) != NULL && sd->status.party_id && (p = party_search(sd->status.party_id)) != NULL && p->instance_id ) + instance_id = p->instance_id; + else return 0; + + if( instance_id <= 0 || instance_id >= MAX_INSTANCE ) + { + ShowError("buildin_instance_destroy: Trying to destroy invalid instance %d.\n", instance_id); + return 0; + } + + instance_destroy(instance_id); + return 0; +} + +BUILDIN_FUNC(instance_attachmap) +{ + const char *name; + int16 m; + int instance_id; + bool usebasename = false; + + name = script_getstr(st,2); + instance_id = script_getnum(st,3); + if( script_hasdata(st,4) && script_getnum(st,4) > 0) + usebasename = true; + + if( (m = instance_add_map(name, instance_id, usebasename)) < 0 ) // [Saithis] + { + ShowError("buildin_instance_attachmap: instance creation failed (%s): %d\n", name, m); + script_pushconststr(st, ""); + return 0; + } + script_pushconststr(st, map[m].name); + + return 0; +} + +BUILDIN_FUNC(instance_detachmap) +{ + struct map_session_data *sd; + struct party_data *p; + const char *str; + int16 m; + int instance_id; + + str = script_getstr(st, 2); + if( script_hasdata(st, 3) ) + instance_id = script_getnum(st, 3); + else if( st->instance_id ) + instance_id = st->instance_id; + else if( (sd = script_rid2sd(st)) != NULL && sd->status.party_id && (p = party_search(sd->status.party_id)) != NULL && p->instance_id ) + instance_id = p->instance_id; + else return 0; + + if( (m = map_mapname2mapid(str)) < 0 || (m = instance_map2imap(m,instance_id)) < 0 ) + { + ShowError("buildin_instance_detachmap: Trying to detach invalid map %s\n", str); + return 0; + } + + instance_del_map(m); + return 0; +} + +BUILDIN_FUNC(instance_attach) +{ + int instance_id; + + instance_id = script_getnum(st, 2); + if( instance_id <= 0 || instance_id >= MAX_INSTANCE ) + return 0; + + st->instance_id = instance_id; + return 0; +} + +BUILDIN_FUNC(instance_id) +{ + int type, instance_id; + struct map_session_data *sd; + struct party_data *p; + + if( script_hasdata(st, 2) ) + { + type = script_getnum(st, 2); + if( type == 0 ) + instance_id = st->instance_id; + else if( type == 1 && (sd = script_rid2sd(st)) != NULL && sd->status.party_id && (p = party_search(sd->status.party_id)) != NULL ) + instance_id = p->instance_id; + else + instance_id = 0; + } + else + instance_id = st->instance_id; + + script_pushint(st, instance_id); + return 0; +} + +BUILDIN_FUNC(instance_set_timeout) +{ + int progress_timeout, idle_timeout; + int instance_id; + struct map_session_data *sd; + struct party_data *p; + + progress_timeout = script_getnum(st, 2); + idle_timeout = script_getnum(st, 3); + + if( script_hasdata(st, 4) ) + instance_id = script_getnum(st, 4); + else if( st->instance_id ) + instance_id = st->instance_id; + else if( (sd = script_rid2sd(st)) != NULL && sd->status.party_id && (p = party_search(sd->status.party_id)) != NULL && p->instance_id ) + instance_id = p->instance_id; + else return 0; + + if( instance_id > 0 ) + instance_set_timeout(instance_id, progress_timeout, idle_timeout); + + return 0; +} + +BUILDIN_FUNC(instance_init) +{ + int instance_id = script_getnum(st, 2); + + if( instance[instance_id].state != INSTANCE_IDLE ) + { + ShowError("instance_init: instance already initialized.\n"); + return 0; + } + + instance_init(instance_id); + return 0; +} + +BUILDIN_FUNC(instance_announce) +{ + int instance_id = script_getnum(st,2); + const char *mes = script_getstr(st,3); + int flag = script_getnum(st,4); + const char *fontColor = script_hasdata(st,5) ? script_getstr(st,5) : NULL; + int fontType = script_hasdata(st,6) ? script_getnum(st,6) : 0x190; // default fontType (FW_NORMAL) + int fontSize = script_hasdata(st,7) ? script_getnum(st,7) : 12; // default fontSize + int fontAlign = script_hasdata(st,8) ? script_getnum(st,8) : 0; // default fontAlign + int fontY = script_hasdata(st,9) ? script_getnum(st,9) : 0; // default fontY + + int i; + struct map_session_data *sd; + struct party_data *p; + + if( instance_id == 0 ) + { + if( st->instance_id ) + instance_id = st->instance_id; + else if( (sd = script_rid2sd(st)) != NULL && sd->status.party_id && (p = party_search(sd->status.party_id)) != NULL && p->instance_id ) + instance_id = p->instance_id; + else return 0; + } + + if( instance_id <= 0 || instance_id >= MAX_INSTANCE ) + return 0; + + for( i = 0; i < instance[instance_id].num_map; i++ ) + map_foreachinmap(buildin_announce_sub, instance[instance_id].map[i], BL_PC, + mes, strlen(mes)+1, flag&0xf0, fontColor, fontType, fontSize, fontAlign, fontY); + + return 0; +} + +BUILDIN_FUNC(instance_npcname) +{ + const char *str; + int instance_id = 0; + + struct map_session_data *sd; + struct party_data *p; + struct npc_data *nd; + + str = script_getstr(st, 2); + if( script_hasdata(st, 3) ) + instance_id = script_getnum(st, 3); + else if( st->instance_id ) + instance_id = st->instance_id; + else if( (sd = script_rid2sd(st)) != NULL && sd->status.party_id && (p = party_search(sd->status.party_id)) != NULL && p->instance_id ) + instance_id = p->instance_id; + + if( instance_id && (nd = npc_name2id(str)) != NULL ) + { + static char npcname[NAME_LENGTH]; + snprintf(npcname, sizeof(npcname), "dup_%d_%d", instance_id, nd->bl.id); + script_pushconststr(st,npcname); + } + else + { + ShowError("script:instance_npcname: invalid instance NPC (instance_id: %d, NPC name: \"%s\".)\n", instance_id, str); + st->state = END; + return 1; + } + + return 0; +} + +BUILDIN_FUNC(has_instance) +{ + struct map_session_data *sd; + struct party_data *p; + const char *str; + int16 m; + int instance_id = 0; + + str = script_getstr(st, 2); + if( script_hasdata(st, 3) ) + instance_id = script_getnum(st, 3); + else if( st->instance_id ) + instance_id = st->instance_id; + else if( (sd = script_rid2sd(st)) != NULL && sd->status.party_id && (p = party_search(sd->status.party_id)) != NULL && p->instance_id ) + instance_id = p->instance_id; + + if( !instance_id || (m = map_mapname2mapid(str)) < 0 || (m = instance_map2imap(m, instance_id)) < 0 ) + { + script_pushconststr(st, ""); + return 0; + } + + script_pushconststr(st, map[m].name); + return 0; +} + +BUILDIN_FUNC(instance_warpall) +{ + struct map_session_data *pl_sd; + int16 m, i; + int instance_id; + const char *mapn; + int x, y; + unsigned short mapindex; + struct party_data *p = NULL; + + mapn = script_getstr(st,2); + x = script_getnum(st,3); + y = script_getnum(st,4); + if( script_hasdata(st,5) ) + instance_id = script_getnum(st,5); + else if( st->instance_id ) + instance_id = st->instance_id; + else if( (pl_sd = script_rid2sd(st)) != NULL && pl_sd->status.party_id && (p = party_search(pl_sd->status.party_id)) != NULL && p->instance_id ) + instance_id = p->instance_id; + else return 0; + + if( (m = map_mapname2mapid(mapn)) < 0 || (map[m].flag.src4instance && (m = instance_mapid2imapid(m, instance_id)) < 0) ) + return 0; + + if( !(p = party_search(instance[instance_id].party_id)) ) + return 0; + + mapindex = map_id2index(m); + for( i = 0; i < MAX_PARTY; i++ ) + if( (pl_sd = p->data[i].sd) && map[pl_sd->bl.m].instance_id == st->instance_id ) pc_setpos(pl_sd,mapindex,x,y,CLR_TELEPORT); + + return 0; +} + +/*========================================== + * instance_check_party [malufett] + * Values: + * party_id : Party ID of the invoking character. [Required Parameter] + * amount : Amount of needed Partymembers for the Instance. [Optional Parameter] + * min : Minimum Level needed to join the Instance. [Optional Parameter] + * max : Maxium Level allowed to join the Instance. [Optional Parameter] + * Example: instance_check_party (getcharid(1){,amount}{,min}{,max}); + * Example 2: instance_check_party (getcharid(1),1,1,99); + *------------------------------------------*/ +BUILDIN_FUNC(instance_check_party) +{ + struct map_session_data *pl_sd; + int amount, min, max, i, party_id, c = 0; + struct party_data *p = NULL; + + amount = script_hasdata(st,3) ? script_getnum(st,3) : 1; // Amount of needed Partymembers for the Instance. + min = script_hasdata(st,4) ? script_getnum(st,4) : 1; // Minimum Level needed to join the Instance. + max = script_hasdata(st,5) ? script_getnum(st,5) : MAX_LEVEL; // Maxium Level allowed to join the Instance. + + if( min < 1 || min > MAX_LEVEL){ + ShowError("instance_check_party: Invalid min level, %d\n", min); + return 0; + }else if( max < 1 || max > MAX_LEVEL){ + ShowError("instance_check_party: Invalid max level, %d\n", max); + return 0; + } + + if( script_hasdata(st,2) ) + party_id = script_getnum(st,2); + else return 0; + + if( !(p = party_search(party_id)) ){ + script_pushint(st, 0); // Returns false if party does not exist. + return 0; + } + + for( i = 0; i < MAX_PARTY; i++ ) + if( (pl_sd = p->data[i].sd) ) + if(map_id2bl(pl_sd->bl.id)){ + if(pl_sd->status.base_level < min){ + script_pushint(st, 0); + return 0; + }else if(pl_sd->status.base_level > max){ + script_pushint(st, 0); + return 0; + } + c++; + } + + if(c < amount){ + script_pushint(st, 0); // Not enough Members in the Party to join Instance. + }else + script_pushint(st, 1); + + return 0; +} + +/*========================================== + * Custom Fonts + *------------------------------------------*/ +BUILDIN_FUNC(setfont) +{ + struct map_session_data *sd = script_rid2sd(st); + int font = script_getnum(st,2); + if( sd == NULL ) + return 0; + + if( sd->user_font != font ) + sd->user_font = font; + else + sd->user_font = 0; + + clif_font(sd); + return 0; +} + +static int buildin_mobuseskill_sub(struct block_list *bl,va_list ap) +{ + TBL_MOB* md = (TBL_MOB*)bl; + struct block_list *tbl; + int mobid = va_arg(ap,int); + uint16 skill_id = va_arg(ap,int); + uint16 skill_lv = va_arg(ap,int); + int casttime = va_arg(ap,int); + int cancel = va_arg(ap,int); + int emotion = va_arg(ap,int); + int target = va_arg(ap,int); + + if( md->class_ != mobid ) + return 0; + + // 0:self, 1:target, 2:master, default:random + switch( target ) + { + case 0: tbl = map_id2bl(md->bl.id); break; + case 1: tbl = map_id2bl(md->target_id); break; + case 2: tbl = map_id2bl(md->master_id); break; + default:tbl = battle_getenemy(&md->bl, DEFAULT_ENEMY_TYPE(md),skill_get_range2(&md->bl, skill_id, skill_lv)); break; + } + + if( !tbl ) + return 0; + + if( md->ud.skilltimer != INVALID_TIMER ) // Cancel the casting skill. + unit_skillcastcancel(bl,0); + + if( skill_get_casttype(skill_id) == CAST_GROUND ) + unit_skilluse_pos2(&md->bl, tbl->x, tbl->y, skill_id, skill_lv, casttime, cancel); + else + unit_skilluse_id2(&md->bl, tbl->id, skill_id, skill_lv, casttime, cancel); + + clif_emotion(&md->bl, emotion); + + return 0; +} +/*========================================== + * areamobuseskill "Map Name",<x>,<y>,<range>,<Mob ID>,"Skill Name"/<Skill ID>,<Skill Lv>,<Cast Time>,<Cancelable>,<Emotion>,<Target Type>; + *------------------------------------------*/ +BUILDIN_FUNC(areamobuseskill) +{ + struct block_list center; + int16 m; + int range,mobid,skill_id,skill_lv,casttime,emotion,target,cancel; + + if( (m = map_mapname2mapid(script_getstr(st,2))) < 0 ) + { + ShowError("areamobuseskill: invalid map name.\n"); + return 0; + } + + if( map[m].flag.src4instance && st->instance_id && (m = instance_mapid2imapid(m, st->instance_id)) < 0 ) + return 0; + + center.m = m; + center.x = script_getnum(st,3); + center.y = script_getnum(st,4); + range = script_getnum(st,5); + mobid = script_getnum(st,6); + skill_id = ( script_isstring(st,7) ? skill_name2id(script_getstr(st,7)) : script_getnum(st,7) ); + if( (skill_lv = script_getnum(st,8)) > battle_config.mob_max_skilllvl ) + skill_lv = battle_config.mob_max_skilllvl; + + casttime = script_getnum(st,9); + cancel = script_getnum(st,10); + emotion = script_getnum(st,11); + target = script_getnum(st,12); + + map_foreachinrange(buildin_mobuseskill_sub, ¢er, range, BL_MOB, mobid, skill_id, skill_lv, casttime, cancel, emotion, target); + return 0; +} + + +BUILDIN_FUNC(progressbar) +{ + struct map_session_data * sd = script_rid2sd(st); + const char * color; + unsigned int second; + + if( !st || !sd ) + return 0; + + st->state = STOP; + + color = script_getstr(st,2); + second = script_getnum(st,3); + + sd->progressbar.npc_id = st->oid; + sd->progressbar.timeout = gettick() + second*1000; + + clif_progressbar(sd, strtol(color, (char **)NULL, 0), second); + return 0; +} + +BUILDIN_FUNC(pushpc) +{ + uint8 dir; + int cells, dx, dy; + struct map_session_data* sd; + + if((sd = script_rid2sd(st))==NULL) + { + return 0; + } + + dir = script_getnum(st,2); + cells = script_getnum(st,3); + + if(dir>7) + { + ShowWarning("buildin_pushpc: Invalid direction %d specified.\n", dir); + script_reportsrc(st); + + dir%= 8; // trim spin-over + } + + if(!cells) + {// zero distance + return 0; + } + else if(cells<0) + {// pushing backwards + dir = (dir+4)%8; // turn around + cells = -cells; + } + + dx = dirx[dir]; + dy = diry[dir]; + + unit_blown(&sd->bl, dx, dy, cells, 0); + return 0; +} + + +/// Invokes buying store preparation window +/// buyingstore <slots>; +BUILDIN_FUNC(buyingstore) +{ + struct map_session_data* sd; + + if( ( sd = script_rid2sd(st) ) == NULL ) + { + return 0; + } + + buyingstore_setup(sd, script_getnum(st,2)); + return 0; +} + + +/// Invokes search store info window +/// searchstores <uses>,<effect>; +BUILDIN_FUNC(searchstores) +{ + unsigned short effect; + unsigned int uses; + struct map_session_data* sd; + + if( ( sd = script_rid2sd(st) ) == NULL ) + { + return 0; + } + + uses = script_getnum(st,2); + effect = script_getnum(st,3); + + if( !uses ) + { + ShowError("buildin_searchstores: Amount of uses cannot be zero.\n"); + return 1; + } + + if( effect > 1 ) + { + ShowError("buildin_searchstores: Invalid effect id %hu, specified.\n", effect); + return 1; + } + + searchstore_open(sd, uses, effect); + return 0; +} +/// Displays a number as large digital clock. +/// showdigit <value>[,<type>]; +BUILDIN_FUNC(showdigit) +{ + unsigned int type = 0; + int value; + struct map_session_data* sd; + + if( ( sd = script_rid2sd(st) ) == NULL ) + { + return 0; + } + + value = script_getnum(st,2); + + if( script_hasdata(st,3) ) + { + type = script_getnum(st,3); + + if( type > 3 ) + { + ShowError("buildin_showdigit: Invalid type %u.\n", type); + return 1; + } + } + + clif_showdigit(sd, (unsigned char)type, value); + return 0; +} +/** + * Rune Knight + **/ +BUILDIN_FUNC(makerune) { + TBL_PC* sd; + if( (sd = script_rid2sd(st)) == NULL ) + return 0; + clif_skill_produce_mix_list(sd,RK_RUNEMASTERY,24); + sd->itemid = script_getnum(st,2); + return 0; +} +/** + * checkdragon() returns 1 if mounting a dragon or 0 otherwise. + **/ +BUILDIN_FUNC(checkdragon) { + TBL_PC* sd; + if( (sd = script_rid2sd(st)) == NULL ) + return 0; + if( pc_isridingdragon(sd) ) + script_pushint(st,1); + else + script_pushint(st,0); + return 0; +} +/** + * setdragon({optional Color}) returns 1 on success or 0 otherwise + * - Toggles the dragon on a RK if he can mount; + * @param Color - when not provided uses the green dragon; + * - 1 : Green Dragon + * - 2 : Brown Dragon + * - 3 : Gray Dragon + * - 4 : Blue Dragon + * - 5 : Red Dragon + **/ +BUILDIN_FUNC(setdragon) { + TBL_PC* sd; + int color = script_hasdata(st,2) ? script_getnum(st,2) : 0; + unsigned int option = OPTION_DRAGON1; + if( (sd = script_rid2sd(st)) == NULL ) + return 0; + if( !pc_checkskill(sd,RK_DRAGONTRAINING) || (sd->class_&MAPID_THIRDMASK) != MAPID_RUNE_KNIGHT ) + script_pushint(st,0);//Doesn't have the skill or it's not a Rune Knight + else if ( pc_isridingdragon(sd) ) {//Is mounted; release + pc_setoption(sd, sd->sc.option&~OPTION_DRAGON); + script_pushint(st,1); + } else {//Not mounted; Mount now. + if( color ) { + option = ( color == 1 ? OPTION_DRAGON1 : + color == 2 ? OPTION_DRAGON2 : + color == 3 ? OPTION_DRAGON3 : + color == 4 ? OPTION_DRAGON4 : + color == 5 ? OPTION_DRAGON5 : 0); + if( !option ) { + ShowWarning("script_setdragon: Unknown Color %d used; changing to green (1)\n",color); + option = OPTION_DRAGON1; + } + } + pc_setoption(sd, sd->sc.option|option); + script_pushint(st,1); + } + return 0; +} + +/** + * ismounting() returns 1 if mounting a new mount or 0 otherwise + **/ +BUILDIN_FUNC(ismounting) { + TBL_PC* sd; + if( (sd = script_rid2sd(st)) == NULL ) + return 0; + if( sd->sc.option&OPTION_MOUNTING ) + script_pushint(st,1); + else + script_pushint(st,0); + return 0; +} + +/** + * setmounting() returns 1 on success or 0 otherwise + * - Toggles new mounts on a player when he can mount + * - Will fail if the player is mounting a non-new mount, e.g. dragon, peco, wug, etc. + * - Will unmount the player is he is already mounting + **/ +BUILDIN_FUNC(setmounting) { + TBL_PC* sd; + if( (sd = script_rid2sd(st)) == NULL ) + return 0; + if( sd->sc.option&(OPTION_WUGRIDER|OPTION_RIDING|OPTION_DRAGON|OPTION_MADOGEAR) ) + script_pushint(st,0);//can't mount with one of these + else { + if( sd->sc.option&OPTION_MOUNTING ) + pc_setoption(sd, sd->sc.option&~OPTION_MOUNTING);//release mount + else + pc_setoption(sd, sd->sc.option|OPTION_MOUNTING);//mount + script_pushint(st,1);//in both cases, return 1. + } + return 0; +} +/** + * Retrieves quantity of arguments provided to callfunc/callsub. + * getargcount() -> amount of arguments received in a function + **/ +BUILDIN_FUNC(getargcount) { + struct script_retinfo* ri; + + if( st->stack->defsp < 1 || st->stack->stack_data[st->stack->defsp - 1].type != C_RETINFO ) { + ShowError("script:getargcount: used out of function or callsub label!\n"); + st->state = END; + return 1; + } + ri = st->stack->stack_data[st->stack->defsp - 1].u.ri; + + script_pushint(st, ri->nargs); + + return 0; +} +/** + * getcharip(<account ID>/<character ID>/<character name>) + **/ +BUILDIN_FUNC(getcharip) +{ + struct map_session_data* sd = NULL; + int id = 0; + + /* check if a character name is specified */ + if( script_hasdata(st, 2) ) + { + if (script_isstring(st, 2)) + sd = map_nick2sd(script_getstr(st, 2)); + else if (script_isint(st, 2) || script_getnum(st, 2)) + { + id = script_getnum(st, 2); + sd = (map_id2sd(id) ? map_id2sd(id) : map_charid2sd(id)); + } + } + else + sd = script_rid2sd(st); + + /* check for sd and IP */ + if (!sd || !session[sd->fd]->client_addr) + { + script_pushconststr(st, ""); + return 0; + } + + /* return the client ip_addr converted for output */ + if (sd && sd->fd && session[sd->fd]) + { + /* initiliaze */ + const char *ip_addr = NULL; + uint32 ip; + + /* set ip, ip_addr and convert to ip and push str */ + ip = session[sd->fd]->client_addr; + ip_addr = ip2str(ip, NULL); + script_pushstrcopy(st, ip_addr); + } + + return 0; +} +/** + * is_function(<function name>) -> 1 if function exists, 0 otherwise + **/ +BUILDIN_FUNC(is_function) { + const char* str = script_getstr(st,2); + + if( strdb_exists(userfunc_db, str) ) + script_pushint(st,1); + else + script_pushint(st,0); + + return 0; +} +/** + * get_revision() -> retrieves the current svn revision (if available) + **/ +BUILDIN_FUNC(get_revision) { + const char * revision; + + if ( (revision = get_svn_revision()) != 0 ) + script_pushint(st,atoi(revision)); + else + script_pushint(st,-1);//unknown + + return 0; +} +/** + * freeloop(<toggle>) -> toggles this script instance's looping-check ability + **/ +BUILDIN_FUNC(freeloop) { + + if( script_getnum(st,2) ) + st->freeloop = 1; + else + st->freeloop = 0; + + script_pushint(st, st->freeloop); + + return 0; +} + +/** + * @commands (script based) + **/ +BUILDIN_FUNC(bindatcmd) { + const char* atcmd; + const char* eventName; + int i, level = 0, level2 = 0; + bool create = false; + + atcmd = script_getstr(st,2); + eventName = script_getstr(st,3); + + if( *atcmd == atcommand_symbol || *atcmd == charcommand_symbol ) + atcmd++; + + if( script_hasdata(st,4) ) level = script_getnum(st,4); + if( script_hasdata(st,5) ) level2 = script_getnum(st,5); + + if( atcmd_binding_count == 0 ) { + CREATE(atcmd_binding,struct atcmd_binding_data*,1); + + create = true; + } else { + ARR_FIND(0, atcmd_binding_count, i, strcmp(atcmd_binding[i]->command,atcmd) == 0); + if( i < atcmd_binding_count ) {/* update existent entry */ + safestrncpy(atcmd_binding[i]->npc_event, eventName, 50); + atcmd_binding[i]->level = level; + atcmd_binding[i]->level2 = level2; + } else + create = true; + } + + if( create ) { + i = atcmd_binding_count; + + if( atcmd_binding_count++ != 0 ) + RECREATE(atcmd_binding,struct atcmd_binding_data*,atcmd_binding_count); + + CREATE(atcmd_binding[i],struct atcmd_binding_data,1); + + safestrncpy(atcmd_binding[i]->command, atcmd, 50); + safestrncpy(atcmd_binding[i]->npc_event, eventName, 50); + atcmd_binding[i]->level = level; + atcmd_binding[i]->level2 = level2; + } + + return 0; +} + +BUILDIN_FUNC(unbindatcmd) { + const char* atcmd; + int i = 0; + + atcmd = script_getstr(st, 2); + + if( *atcmd == atcommand_symbol || *atcmd == charcommand_symbol ) + atcmd++; + + if( atcmd_binding_count == 0 ) { + script_pushint(st, 0); + return 0; + } + + ARR_FIND(0, atcmd_binding_count, i, strcmp(atcmd_binding[i]->command, atcmd) == 0); + if( i < atcmd_binding_count ) { + int cursor = 0; + aFree(atcmd_binding[i]); + atcmd_binding[i] = NULL; + /* compact the list now that we freed a slot somewhere */ + for( i = 0, cursor = 0; i < atcmd_binding_count; i++ ) { + if( atcmd_binding[i] == NULL ) + continue; + + if( cursor != i ) { + memmove(&atcmd_binding[cursor], &atcmd_binding[i], sizeof(struct atcmd_binding_data*)); + } + + cursor++; + } + + if( (atcmd_binding_count = cursor) == 0 ) + aFree(atcmd_binding); + + script_pushint(st, 1); + } else + script_pushint(st, 0);/* not found */ + + return 0; +} + +BUILDIN_FUNC(useatcmd) +{ + TBL_PC dummy_sd; + TBL_PC* sd; + int fd; + const char* cmd; + + cmd = script_getstr(st,2); + + if( st->rid ) + { + sd = script_rid2sd(st); + fd = sd->fd; + } + else + { // Use a dummy character. + sd = &dummy_sd; + fd = 0; + + memset(&dummy_sd, 0, sizeof(TBL_PC)); + if( st->oid ) + { + struct block_list* bl = map_id2bl(st->oid); + memcpy(&dummy_sd.bl, bl, sizeof(struct block_list)); + if( bl->type == BL_NPC ) + safestrncpy(dummy_sd.status.name, ((TBL_NPC*)bl)->name, NAME_LENGTH); + } + } + + // compatibility with previous implementation (deprecated!) + if( cmd[0] != atcommand_symbol ) + { + cmd += strlen(sd->status.name); + while( *cmd != atcommand_symbol && *cmd != 0 ) + cmd++; + } + + is_atcommand(fd, sd, cmd, 1); + return 0; +} + +BUILDIN_FUNC(checkre) +{ + int num; + + num=script_getnum(st,2); + switch(num){ + case 0: + #ifdef RENEWAL + script_pushint(st, 1); + #else + script_pushint(st, 0); + #endif + break; + case 1: + #ifdef RENEWAL_CAST + script_pushint(st, 1); + #else + script_pushint(st, 0); + #endif + break; + case 2: + #ifdef RENEWAL_DROP + script_pushint(st, 1); + #else + script_pushint(st, 0); + #endif + break; + case 3: + #ifdef RENEWAL_EXP + script_pushint(st, 1); + #else + script_pushint(st, 0); + #endif + break; + case 4: + #ifdef RENEWAL_LVDMG + script_pushint(st, 1); + #else + script_pushint(st, 0); + #endif + break; + case 5: + #ifdef RENEWAL_EDP + script_pushint(st, 1); + #else + script_pushint(st, 0); + #endif + break; + case 6: + #ifdef RENEWAL_ASPD + script_pushint(st, 1); + #else + script_pushint(st, 0); + #endif + break; + default: + ShowWarning("buildin_checkre: unknown parameter.\n"); + break; + } + return 0; +} + +/* getrandgroupitem <group_id>,<quantity> */ +BUILDIN_FUNC(getrandgroupitem) { + TBL_PC* sd; + int i, get_count = 0, flag, nameid, group = script_getnum(st, 2), qty = script_getnum(st,3); + struct item item_tmp; + + if( !( sd = script_rid2sd(st) ) ) + return 0; + + if( qty <= 0 ) { + ShowError("getrandgroupitem: qty is <= 0!\n"); + return 1; + } + if( (nameid = itemdb_searchrandomid(group)) == UNKNOWN_ITEM_ID ) { + return 1;/* itemdb_searchrandomid will already scream a error */ + } + + memset(&item_tmp,0,sizeof(item_tmp)); + + item_tmp.nameid = nameid; + item_tmp.identify = itemdb_isidentified(nameid); + + //Check if it's stackable. + if (!itemdb_isstackable(nameid)) + get_count = 1; + else + get_count = qty; + + for (i = 0; i < qty; i += get_count) { + // if not pet egg + if (!pet_create_egg(sd, nameid)) { + if ((flag = pc_additem(sd, &item_tmp, get_count, LOG_TYPE_SCRIPT))) { + clif_additem(sd, 0, 0, flag); + if( pc_candrop(sd,&item_tmp) ) + map_addflooritem(&item_tmp,get_count,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + } + } + + return 0; +} + +/* cleanmap <map_name>; + * cleanarea <map_name>, <x0>, <y0>, <x1>, <y1>; */ +static int atcommand_cleanfloor_sub(struct block_list *bl, va_list ap) +{ + nullpo_ret(bl); + map_clearflooritem(bl); + + return 0; +} + +BUILDIN_FUNC(cleanmap) +{ + const char *map; + int16 m = -1; + int16 x0 = 0, y0 = 0, x1 = 0, y1 = 0; + + map = script_getstr(st, 2); + m = map_mapname2mapid(map); + if (!m) + return 1; + + if ((script_lastdata(st) - 2) < 4) { + map_foreachinmap(atcommand_cleanfloor_sub, m, BL_ITEM); + } else { + x0 = script_getnum(st, 3); + y0 = script_getnum(st, 4); + x1 = script_getnum(st, 5); + y1 = script_getnum(st, 6); + if (x0 > 0 && y0 > 0 && x1 > 0 && y1 > 0) { + map_foreachinarea(atcommand_cleanfloor_sub, m, x0, y0, x1, y1, BL_ITEM); + } else { + ShowError("cleanarea: invalid coordinate defined!\n"); + return 1; + } + } + + return 0; +} +/* Cast a skill on the attached player. + * npcskill <skill id>, <skill lvl>, <stat point>, <NPC level>; + * npcskill "<skill name>", <skill lvl>, <stat point>, <NPC level>; */ +BUILDIN_FUNC(npcskill) +{ + uint16 skill_id; + unsigned short skill_level; + unsigned int stat_point; + unsigned int npc_level; + struct npc_data *nd; + struct map_session_data *sd; + + skill_id = script_isstring(st, 2) ? skill_name2id(script_getstr(st, 2)) : script_getnum(st, 2); + skill_level = script_getnum(st, 3); + stat_point = script_getnum(st, 4); + npc_level = script_getnum(st, 5); + sd = script_rid2sd(st); + nd = (struct npc_data *)map_id2bl(sd->npc_id); + + if (stat_point > battle_config.max_third_parameter) { + ShowError("npcskill: stat point exceeded maximum of %d.\n",battle_config.max_third_parameter ); + return 1; + } + if (npc_level > MAX_LEVEL) { + ShowError("npcskill: level exceeded maximum of %d.\n", MAX_LEVEL); + return 1; + } + if (sd == NULL || nd == NULL) { //ain't possible, but I don't trust people. + return 1; + } + + nd->level = npc_level; + nd->stat_point = stat_point; + + if (!nd->status.hp) { + status_calc_npc(nd, true); + } else { + status_calc_npc(nd, false); + } + + if (skill_get_inf(skill_id)&INF_GROUND_SKILL) { + unit_skilluse_pos(&nd->bl, sd->bl.x, sd->bl.y, skill_id, skill_level); + } else { + unit_skilluse_id(&nd->bl, sd->bl.id, skill_id, skill_level); + } + + return 0; +} + +// declarations that were supposed to be exported from npc_chat.c +#ifdef PCRE_SUPPORT +BUILDIN_FUNC(defpattern); +BUILDIN_FUNC(activatepset); +BUILDIN_FUNC(deactivatepset); +BUILDIN_FUNC(deletepset); +#endif + +/// script command definitions +/// for an explanation on args, see add_buildin_func +struct script_function buildin_func[] = { + // NPC interaction + BUILDIN_DEF(mes,"s*"), + BUILDIN_DEF(next,""), + BUILDIN_DEF(close,""), + BUILDIN_DEF(close2,""), + BUILDIN_DEF(menu,"sl*"), + BUILDIN_DEF(select,"s*"), //for future jA script compatibility + BUILDIN_DEF(prompt,"s*"), + // + BUILDIN_DEF(goto,"l"), + BUILDIN_DEF(callsub,"l*"), + BUILDIN_DEF(callfunc,"s*"), + BUILDIN_DEF(return,"?"), + BUILDIN_DEF(getarg,"i?"), + BUILDIN_DEF(jobchange,"i?"), + BUILDIN_DEF(jobname,"i"), + BUILDIN_DEF(input,"r??"), + BUILDIN_DEF(warp,"sii"), + BUILDIN_DEF(areawarp,"siiiisii??"), + BUILDIN_DEF(warpchar,"siii"), // [LuzZza] + BUILDIN_DEF(warpparty,"siii?"), // [Fredzilla] [Paradox924X] + BUILDIN_DEF(warpguild,"siii"), // [Fredzilla] + BUILDIN_DEF(setlook,"ii"), + BUILDIN_DEF(changelook,"ii"), // Simulates but don't Store it + BUILDIN_DEF(set,"rv"), + BUILDIN_DEF(setarray,"rv*"), + BUILDIN_DEF(cleararray,"rvi"), + BUILDIN_DEF(copyarray,"rri"), + BUILDIN_DEF(getarraysize,"r"), + BUILDIN_DEF(deletearray,"r?"), + BUILDIN_DEF(getelementofarray,"ri"), + BUILDIN_DEF(getitem,"vi?"), + BUILDIN_DEF(rentitem,"vi"), + BUILDIN_DEF(getitem2,"viiiiiiii?"), + BUILDIN_DEF(getnameditem,"vv"), + BUILDIN_DEF2(grouprandomitem,"groupranditem","i"), + BUILDIN_DEF(makeitem,"visii"), + BUILDIN_DEF(delitem,"vi?"), + BUILDIN_DEF(delitem2,"viiiiiiii?"), + BUILDIN_DEF2(enableitemuse,"enable_items",""), + BUILDIN_DEF2(disableitemuse,"disable_items",""), + BUILDIN_DEF(cutin,"si"), + BUILDIN_DEF(viewpoint,"iiiii"), + BUILDIN_DEF(heal,"ii"), + BUILDIN_DEF(itemheal,"ii"), + BUILDIN_DEF(percentheal,"ii"), + BUILDIN_DEF(rand,"i?"), + BUILDIN_DEF(countitem,"v"), + BUILDIN_DEF(countitem2,"viiiiiii"), + BUILDIN_DEF(checkweight,"vi*"), + BUILDIN_DEF(checkweight2,"rr"), + BUILDIN_DEF(readparam,"i?"), + BUILDIN_DEF(getcharid,"i?"), + BUILDIN_DEF(getnpcid,"i?"), + BUILDIN_DEF(getpartyname,"i"), + BUILDIN_DEF(getpartymember,"i?"), + BUILDIN_DEF(getpartyleader,"i?"), + BUILDIN_DEF(getguildname,"i"), + BUILDIN_DEF(getguildmaster,"i"), + BUILDIN_DEF(getguildmasterid,"i"), + BUILDIN_DEF(strcharinfo,"i"), + BUILDIN_DEF(strnpcinfo,"i"), + BUILDIN_DEF(getequipid,"i"), + BUILDIN_DEF(getequipname,"i"), + BUILDIN_DEF(getbrokenid,"i"), // [Valaris] + BUILDIN_DEF(repair,"i"), // [Valaris] + BUILDIN_DEF(repairall,""), + BUILDIN_DEF(getequipisequiped,"i"), + BUILDIN_DEF(getequipisenableref,"i"), + BUILDIN_DEF(getequipisidentify,"i"), + BUILDIN_DEF(getequiprefinerycnt,"i"), + BUILDIN_DEF(getequipweaponlv,"i"), + BUILDIN_DEF(getequippercentrefinery,"i"), + BUILDIN_DEF(successrefitem,"i"), + BUILDIN_DEF(failedrefitem,"i"), + BUILDIN_DEF(downrefitem,"i"), + BUILDIN_DEF(statusup,"i"), + BUILDIN_DEF(statusup2,"ii"), + BUILDIN_DEF(bonus,"iv"), + BUILDIN_DEF2(bonus,"bonus2","ivi"), + BUILDIN_DEF2(bonus,"bonus3","ivii"), + BUILDIN_DEF2(bonus,"bonus4","ivvii"), + BUILDIN_DEF2(bonus,"bonus5","ivviii"), + BUILDIN_DEF(autobonus,"sii??"), + BUILDIN_DEF(autobonus2,"sii??"), + BUILDIN_DEF(autobonus3,"siiv?"), + BUILDIN_DEF(skill,"vi?"), + BUILDIN_DEF(addtoskill,"vi?"), // [Valaris] + BUILDIN_DEF(guildskill,"vi"), + BUILDIN_DEF(getskilllv,"v"), + BUILDIN_DEF(getgdskilllv,"iv"), + BUILDIN_DEF(basicskillcheck,""), + BUILDIN_DEF(getgmlevel,""), + BUILDIN_DEF(getgroupid,""), + BUILDIN_DEF(end,""), + BUILDIN_DEF(checkoption,"i"), + BUILDIN_DEF(setoption,"i?"), + BUILDIN_DEF(setcart,"?"), + BUILDIN_DEF(checkcart,""), + BUILDIN_DEF(setfalcon,"?"), + BUILDIN_DEF(checkfalcon,""), + BUILDIN_DEF(setriding,"?"), + BUILDIN_DEF(checkriding,""), + BUILDIN_DEF(checkwug,""), + BUILDIN_DEF(checkmadogear,""), + BUILDIN_DEF(setmadogear,"?"), + BUILDIN_DEF2(savepoint,"save","sii"), + BUILDIN_DEF(savepoint,"sii"), + BUILDIN_DEF(gettimetick,"i"), + BUILDIN_DEF(gettime,"i"), + BUILDIN_DEF(gettimestr,"si"), + BUILDIN_DEF(openstorage,""), + BUILDIN_DEF(guildopenstorage,""), + BUILDIN_DEF(itemskill,"vi"), + BUILDIN_DEF(produce,"i"), + BUILDIN_DEF(cooking,"i"), + BUILDIN_DEF(monster,"siisii???"), + BUILDIN_DEF(getmobdrops,"i"), + BUILDIN_DEF(areamonster,"siiiisii???"), + BUILDIN_DEF(killmonster,"ss?"), + BUILDIN_DEF(killmonsterall,"s?"), + BUILDIN_DEF(clone,"siisi????"), + BUILDIN_DEF(doevent,"s"), + BUILDIN_DEF(donpcevent,"s"), + BUILDIN_DEF(cmdothernpc,"ss"), + BUILDIN_DEF(addtimer,"is"), + BUILDIN_DEF(deltimer,"s"), + BUILDIN_DEF(addtimercount,"si"), + BUILDIN_DEF(initnpctimer,"??"), + BUILDIN_DEF(stopnpctimer,"??"), + BUILDIN_DEF(startnpctimer,"??"), + BUILDIN_DEF(setnpctimer,"i?"), + BUILDIN_DEF(getnpctimer,"i?"), + BUILDIN_DEF(attachnpctimer,"?"), // attached the player id to the npc timer [Celest] + BUILDIN_DEF(detachnpctimer,"?"), // detached the player id from the npc timer [Celest] + BUILDIN_DEF(playerattached,""), // returns id of the current attached player. [Skotlex] + BUILDIN_DEF(announce,"si?????"), + BUILDIN_DEF(mapannounce,"ssi?????"), + BUILDIN_DEF(areaannounce,"siiiisi?????"), + BUILDIN_DEF(getusers,"i"), + BUILDIN_DEF(getmapguildusers,"si"), + BUILDIN_DEF(getmapusers,"s"), + BUILDIN_DEF(getareausers,"siiii"), + BUILDIN_DEF(getareadropitem,"siiiiv"), + BUILDIN_DEF(enablenpc,"s"), + BUILDIN_DEF(disablenpc,"s"), + BUILDIN_DEF(hideoffnpc,"s"), + BUILDIN_DEF(hideonnpc,"s"), + BUILDIN_DEF(sc_start,"iii?"), + BUILDIN_DEF(sc_start2,"iiii?"), + BUILDIN_DEF(sc_start4,"iiiiii?"), + BUILDIN_DEF(sc_end,"i?"), + BUILDIN_DEF(getstatus, "i?"), + BUILDIN_DEF(getscrate,"ii?"), + BUILDIN_DEF(debugmes,"s"), + BUILDIN_DEF2(catchpet,"pet","i"), + BUILDIN_DEF2(birthpet,"bpet",""), + BUILDIN_DEF(resetlvl,"i"), + BUILDIN_DEF(resetstatus,""), + BUILDIN_DEF(resetskill,""), + BUILDIN_DEF(skillpointcount,""), + BUILDIN_DEF(changebase,"i?"), + BUILDIN_DEF(changesex,""), + BUILDIN_DEF(waitingroom,"si?????"), + BUILDIN_DEF(delwaitingroom,"?"), + BUILDIN_DEF2(waitingroomkickall,"kickwaitingroomall","?"), + BUILDIN_DEF(enablewaitingroomevent,"?"), + BUILDIN_DEF(disablewaitingroomevent,"?"), + BUILDIN_DEF2(enablewaitingroomevent,"enablearena",""), // Added by RoVeRT + BUILDIN_DEF2(disablewaitingroomevent,"disablearena",""), // Added by RoVeRT + BUILDIN_DEF(getwaitingroomstate,"i?"), + BUILDIN_DEF(warpwaitingpc,"sii?"), + BUILDIN_DEF(attachrid,"i"), + BUILDIN_DEF(detachrid,""), + BUILDIN_DEF(isloggedin,"i?"), + BUILDIN_DEF(setmapflagnosave,"ssii"), + BUILDIN_DEF(getmapflag,"si"), + BUILDIN_DEF(setmapflag,"si?"), + BUILDIN_DEF(removemapflag,"si?"), + BUILDIN_DEF(pvpon,"s"), + BUILDIN_DEF(pvpoff,"s"), + BUILDIN_DEF(gvgon,"s"), + BUILDIN_DEF(gvgoff,"s"), + BUILDIN_DEF(emotion,"i??"), + BUILDIN_DEF(maprespawnguildid,"sii"), + BUILDIN_DEF(agitstart,""), // <Agit> + BUILDIN_DEF(agitend,""), + BUILDIN_DEF(agitcheck,""), // <Agitcheck> + BUILDIN_DEF(flagemblem,"i"), // Flag Emblem + BUILDIN_DEF(getcastlename,"s"), + BUILDIN_DEF(getcastledata,"si"), + BUILDIN_DEF(setcastledata,"sii"), + BUILDIN_DEF(requestguildinfo,"i?"), + BUILDIN_DEF(getequipcardcnt,"i"), + BUILDIN_DEF(successremovecards,"i"), + BUILDIN_DEF(failedremovecards,"ii"), + BUILDIN_DEF(marriage,"s"), + BUILDIN_DEF2(wedding_effect,"wedding",""), + BUILDIN_DEF(divorce,""), + BUILDIN_DEF(ispartneron,""), + BUILDIN_DEF(getpartnerid,""), + BUILDIN_DEF(getchildid,""), + BUILDIN_DEF(getmotherid,""), + BUILDIN_DEF(getfatherid,""), + BUILDIN_DEF(warppartner,"sii"), + BUILDIN_DEF(getitemname,"v"), + BUILDIN_DEF(getitemslots,"i"), + BUILDIN_DEF(makepet,"i"), + BUILDIN_DEF(getexp,"ii"), + BUILDIN_DEF(getinventorylist,""), + BUILDIN_DEF(getskilllist,""), + BUILDIN_DEF(clearitem,""), + BUILDIN_DEF(classchange,"ii"), + BUILDIN_DEF(misceffect,"i"), + BUILDIN_DEF(playBGM,"s"), + BUILDIN_DEF(playBGMall,"s?????"), + BUILDIN_DEF(soundeffect,"si"), + BUILDIN_DEF(soundeffectall,"si?????"), // SoundEffectAll [Codemaster] + BUILDIN_DEF(strmobinfo,"ii"), // display mob data [Valaris] + BUILDIN_DEF(guardian,"siisi??"), // summon guardians + BUILDIN_DEF(guardianinfo,"sii"), // display guardian data [Valaris] + BUILDIN_DEF(petskillbonus,"iiii"), // [Valaris] + BUILDIN_DEF(petrecovery,"ii"), // [Valaris] + BUILDIN_DEF(petloot,"i"), // [Valaris] + BUILDIN_DEF(petheal,"iiii"), // [Valaris] + BUILDIN_DEF(petskillattack,"viii"), // [Skotlex] + BUILDIN_DEF(petskillattack2,"viiii"), // [Valaris] + BUILDIN_DEF(petskillsupport,"viiii"), // [Skotlex] + BUILDIN_DEF(skilleffect,"vi"), // skill effect [Celest] + BUILDIN_DEF(npcskilleffect,"viii"), // npc skill effect [Valaris] + BUILDIN_DEF(specialeffect,"i??"), // npc skill effect [Valaris] + BUILDIN_DEF(specialeffect2,"i??"), // skill effect on players[Valaris] + BUILDIN_DEF(nude,""), // nude command [Valaris] + BUILDIN_DEF(mapwarp,"ssii??"), // Added by RoVeRT + BUILDIN_DEF(atcommand,"s"), // [MouseJstr] + BUILDIN_DEF2(atcommand,"charcommand","s"), // [MouseJstr] + BUILDIN_DEF(movenpc,"sii?"), // [MouseJstr] + BUILDIN_DEF(message,"ss"), // [MouseJstr] + BUILDIN_DEF(npctalk,"s"), // [Valaris] + BUILDIN_DEF(mobcount,"ss"), + BUILDIN_DEF(getlook,"i"), + BUILDIN_DEF(getsavepoint,"i"), + BUILDIN_DEF(npcspeed,"i"), // [Valaris] + BUILDIN_DEF(npcwalkto,"ii"), // [Valaris] + BUILDIN_DEF(npcstop,""), // [Valaris] + BUILDIN_DEF(getmapxy,"rrri?"), //by Lorky [Lupus] + BUILDIN_DEF(checkoption1,"i"), + BUILDIN_DEF(checkoption2,"i"), + BUILDIN_DEF(guildgetexp,"i"), + BUILDIN_DEF(guildchangegm,"is"), + BUILDIN_DEF(logmes,"s"), //this command actls as MES but rints info into LOG file either SQL/TXT [Lupus] + BUILDIN_DEF(summon,"si??"), // summons a slave monster [Celest] + BUILDIN_DEF(isnight,""), // check whether it is night time [Celest] + BUILDIN_DEF(isday,""), // check whether it is day time [Celest] + BUILDIN_DEF(isequipped,"i*"), // check whether another item/card has been equipped [Celest] + BUILDIN_DEF(isequippedcnt,"i*"), // check how many items/cards are being equipped [Celest] + BUILDIN_DEF(cardscnt,"i*"), // check how many items/cards are being equipped in the same arm [Lupus] + BUILDIN_DEF(getrefine,""), // returns the refined number of the current item, or an item with index specified [celest] + BUILDIN_DEF(night,""), // sets the server to night time + BUILDIN_DEF(day,""), // sets the server to day time +#ifdef PCRE_SUPPORT + BUILDIN_DEF(defpattern,"iss"), // Define pattern to listen for [MouseJstr] + BUILDIN_DEF(activatepset,"i"), // Activate a pattern set [MouseJstr] + BUILDIN_DEF(deactivatepset,"i"), // Deactive a pattern set [MouseJstr] + BUILDIN_DEF(deletepset,"i"), // Delete a pattern set [MouseJstr] +#endif + BUILDIN_DEF(dispbottom,"s"), //added from jA [Lupus] + BUILDIN_DEF(getusersname,""), + BUILDIN_DEF(recovery,""), + BUILDIN_DEF(getpetinfo,"i"), + BUILDIN_DEF(gethominfo,"i"), + BUILDIN_DEF(getmercinfo,"i?"), + BUILDIN_DEF(checkequipedcard,"i"), + BUILDIN_DEF(jump_zero,"il"), //for future jA script compatibility + BUILDIN_DEF(globalmes,"s?"), //end jA addition + BUILDIN_DEF(unequip,"i"), // unequip command [Spectre] + BUILDIN_DEF(getstrlen,"s"), //strlen [Valaris] + BUILDIN_DEF(charisalpha,"si"), //isalpha [Valaris] + BUILDIN_DEF(charat,"si"), + BUILDIN_DEF(setchar,"ssi"), + BUILDIN_DEF(insertchar,"ssi"), + BUILDIN_DEF(delchar,"si"), + BUILDIN_DEF(strtoupper,"s"), + BUILDIN_DEF(strtolower,"s"), + BUILDIN_DEF(charisupper, "si"), + BUILDIN_DEF(charislower, "si"), + BUILDIN_DEF(substr,"sii"), + BUILDIN_DEF(explode, "rss"), + BUILDIN_DEF(implode, "r?"), + BUILDIN_DEF(sprintf,"s*"), // [Mirei] + BUILDIN_DEF(sscanf,"ss*"), // [Mirei] + BUILDIN_DEF(strpos,"ss?"), + BUILDIN_DEF(replacestr,"sss??"), + BUILDIN_DEF(countstr,"ss?"), + BUILDIN_DEF(setnpcdisplay,"sv??"), + BUILDIN_DEF(compare,"ss"), // Lordalfa - To bring strstr to scripting Engine. + BUILDIN_DEF(getiteminfo,"ii"), //[Lupus] returns Items Buy / sell Price, etc info + BUILDIN_DEF(setiteminfo,"iii"), //[Lupus] set Items Buy / sell Price, etc info + BUILDIN_DEF(getequipcardid,"ii"), //[Lupus] returns CARD ID or other info from CARD slot N of equipped item + // [zBuffer] List of mathematics commands ---> + BUILDIN_DEF(sqrt,"i"), + BUILDIN_DEF(pow,"ii"), + BUILDIN_DEF(distance,"iiii"), + // <--- [zBuffer] List of mathematics commands + BUILDIN_DEF(md5,"s"), + // [zBuffer] List of dynamic var commands ---> + BUILDIN_DEF(getd,"s"), + BUILDIN_DEF(setd,"sv"), + // <--- [zBuffer] List of dynamic var commands + BUILDIN_DEF(petstat,"i"), + BUILDIN_DEF(callshop,"s?"), // [Skotlex] + BUILDIN_DEF(npcshopitem,"sii*"), // [Lance] + BUILDIN_DEF(npcshopadditem,"sii*"), + BUILDIN_DEF(npcshopdelitem,"si*"), + BUILDIN_DEF(npcshopattach,"s?"), + BUILDIN_DEF(equip,"i"), + BUILDIN_DEF(autoequip,"ii"), + BUILDIN_DEF(setbattleflag,"si"), + BUILDIN_DEF(getbattleflag,"s"), + BUILDIN_DEF(setitemscript,"is?"), //Set NEW item bonus script. Lupus + BUILDIN_DEF(disguise,"i"), //disguise player. Lupus + BUILDIN_DEF(undisguise,""), //undisguise player. Lupus + BUILDIN_DEF(getmonsterinfo,"ii"), //Lupus + BUILDIN_DEF(axtoi,"s"), + BUILDIN_DEF(query_sql,"s*"), + BUILDIN_DEF(query_logsql,"s*"), + BUILDIN_DEF(escape_sql,"v"), + BUILDIN_DEF(atoi,"s"), + // [zBuffer] List of player cont commands ---> + BUILDIN_DEF(rid2name,"i"), + BUILDIN_DEF(pcfollow,"ii"), + BUILDIN_DEF(pcstopfollow,"i"), + BUILDIN_DEF(pcblockmove,"ii"), + // <--- [zBuffer] List of player cont commands + // [zBuffer] List of mob control commands ---> + BUILDIN_DEF(unitwalk,"ii?"), + BUILDIN_DEF(unitkill,"i"), + BUILDIN_DEF(unitwarp,"isii"), + BUILDIN_DEF(unitattack,"iv?"), + BUILDIN_DEF(unitstop,"i"), + BUILDIN_DEF(unittalk,"is"), + BUILDIN_DEF(unitemote,"ii"), + BUILDIN_DEF(unitskilluseid,"ivi?"), // originally by Qamera [Celest] + BUILDIN_DEF(unitskillusepos,"iviii"), // [Celest] +// <--- [zBuffer] List of mob control commands + BUILDIN_DEF(sleep,"i"), + BUILDIN_DEF(sleep2,"i"), + BUILDIN_DEF(awake,"s"), + BUILDIN_DEF(getvariableofnpc,"rs"), + BUILDIN_DEF(warpportal,"iisii"), + BUILDIN_DEF2(homunculus_evolution,"homevolution",""), //[orn] + BUILDIN_DEF2(homunculus_mutate,"hommutate","?"), + BUILDIN_DEF2(homunculus_shuffle,"homshuffle",""), //[Zephyrus] + BUILDIN_DEF(eaclass,"?"), //[Skotlex] + BUILDIN_DEF(roclass,"i?"), //[Skotlex] + BUILDIN_DEF(checkvending,"?"), + BUILDIN_DEF(checkchatting,"?"), + BUILDIN_DEF(openmail,""), + BUILDIN_DEF(openauction,""), + BUILDIN_DEF(checkcell,"siii"), + BUILDIN_DEF(setcell,"siiiiii"), + BUILDIN_DEF(setwall,"siiiiis"), + BUILDIN_DEF(delwall,"s"), + BUILDIN_DEF(searchitem,"rs"), + BUILDIN_DEF(mercenary_create,"ii"), + BUILDIN_DEF(mercenary_heal,"ii"), + BUILDIN_DEF(mercenary_sc_start,"iii"), + BUILDIN_DEF(mercenary_get_calls,"i"), + BUILDIN_DEF(mercenary_get_faith,"i"), + BUILDIN_DEF(mercenary_set_calls,"ii"), + BUILDIN_DEF(mercenary_set_faith,"ii"), + BUILDIN_DEF(readbook,"ii"), + BUILDIN_DEF(setfont,"i"), + BUILDIN_DEF(areamobuseskill,"siiiiviiiii"), + BUILDIN_DEF(progressbar,"si"), + BUILDIN_DEF(pushpc,"ii"), + BUILDIN_DEF(buyingstore,"i"), + BUILDIN_DEF(searchstores,"ii"), + BUILDIN_DEF(showdigit,"i?"), + // WoE SE + BUILDIN_DEF(agitstart2,""), + BUILDIN_DEF(agitend2,""), + BUILDIN_DEF(agitcheck2,""), + // BattleGround + BUILDIN_DEF(waitingroom2bg,"siiss?"), + BUILDIN_DEF(waitingroom2bg_single,"isiis"), + BUILDIN_DEF(bg_team_setxy,"iii"), + BUILDIN_DEF(bg_warp,"isii"), + BUILDIN_DEF(bg_monster,"isiisi?"), + BUILDIN_DEF(bg_monster_set_team,"ii"), + BUILDIN_DEF(bg_leave,""), + BUILDIN_DEF(bg_destroy,"i"), + BUILDIN_DEF(areapercentheal,"siiiiii"), + BUILDIN_DEF(bg_get_data,"ii"), + BUILDIN_DEF(bg_getareausers,"isiiii"), + BUILDIN_DEF(bg_updatescore,"sii"), + + // Instancing + BUILDIN_DEF(instance_create,"si"), + BUILDIN_DEF(instance_destroy,"?"), + BUILDIN_DEF(instance_attachmap,"si?"), + BUILDIN_DEF(instance_detachmap,"s?"), + BUILDIN_DEF(instance_attach,"i"), + BUILDIN_DEF(instance_id,"?"), + BUILDIN_DEF(instance_set_timeout,"ii?"), + BUILDIN_DEF(instance_init,"i"), + BUILDIN_DEF(instance_announce,"isi?????"), + BUILDIN_DEF(instance_npcname,"s?"), + BUILDIN_DEF(has_instance,"s?"), + BUILDIN_DEF(instance_warpall,"sii?"), + BUILDIN_DEF(instance_check_party,"i???"), + /** + * 3rd-related + **/ + BUILDIN_DEF(makerune,"i"), + BUILDIN_DEF(checkdragon,""),//[Ind] + BUILDIN_DEF(setdragon,"?"),//[Ind] + BUILDIN_DEF(ismounting,""),//[Ind] + BUILDIN_DEF(setmounting,""),//[Ind] + BUILDIN_DEF(checkre,"i"), + /** + * rAthena and beyond! + **/ + BUILDIN_DEF(getargcount,""), + BUILDIN_DEF(getcharip,"?"), + BUILDIN_DEF(is_function,"s"), + BUILDIN_DEF(get_revision,""), + BUILDIN_DEF(freeloop,"i"), + BUILDIN_DEF(getrandgroupitem,"ii"), + BUILDIN_DEF(cleanmap,"s"), + BUILDIN_DEF2(cleanmap,"cleanarea","siiii"), + BUILDIN_DEF(npcskill,"viii"), + /** + * @commands (script based) + **/ + BUILDIN_DEF(bindatcmd, "ss??"), + BUILDIN_DEF(unbindatcmd, "s"), + BUILDIN_DEF(useatcmd, "s"), + + //Quest Log System [Inkfish] + BUILDIN_DEF(setquest, "i"), + BUILDIN_DEF(erasequest, "i"), + BUILDIN_DEF(completequest, "i"), + BUILDIN_DEF(checkquest, "i?"), + BUILDIN_DEF(changequest, "ii"), + BUILDIN_DEF(showevent, "ii"), + {NULL,NULL,NULL}, +}; diff --git a/src/map/skill.c b/src/map/skill.c index 757165107..3b3ac547d 100644 --- a/src/map/skill.c +++ b/src/map/skill.c @@ -1,17983 +1,17998 @@ -// Copyright (c) Athena Dev Teams - Licensed under GNU GPL -// For more information, see LICENCE in the main folder - -#include "../common/cbasetypes.h" -#include "../common/timer.h" -#include "../common/nullpo.h" -#include "../common/malloc.h" -#include "../common/random.h" -#include "../common/showmsg.h" -#include "../common/strlib.h" -#include "../common/utils.h" -#include "../common/ers.h" - -#include "map.h" -#include "path.h" -#include "clif.h" -#include "pc.h" -#include "status.h" -#include "skill.h" -#include "pet.h" -#include "homunculus.h" -#include "mercenary.h" -#include "elemental.h" -#include "mob.h" -#include "npc.h" -#include "battle.h" -#include "battleground.h" -#include "party.h" -#include "itemdb.h" -#include "script.h" -#include "intif.h" -#include "log.h" -#include "chrif.h" -#include "guild.h" -#include "date.h" -#include "unit.h" - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> -#include <math.h> - - -#define SKILLUNITTIMER_INTERVAL 100 - -// ranges reserved for mapping skill ids to skilldb offsets -#define HM_SKILLRANGEMIN 700 -#define HM_SKILLRANGEMAX HM_SKILLRANGEMIN + MAX_HOMUNSKILL -#define MC_SKILLRANGEMIN HM_SKILLRANGEMAX + 1 -#define MC_SKILLRANGEMAX MC_SKILLRANGEMIN + MAX_MERCSKILL -#define EL_SKILLRANGEMIN MC_SKILLRANGEMAX + 1 -#define EL_SKILLRANGEMAX EL_SKILLRANGEMIN + MAX_ELEMENTALSKILL -#define GD_SKILLRANGEMIN EL_SKILLRANGEMAX + 1 -#define GD_SKILLRANGEMAX GD_SKILLRANGEMIN + MAX_GUILDSKILL - -#if GD_SKILLRANGEMAX > 999 - #error GD_SKILLRANGEMAX is greater than 999 -#endif -static struct eri *skill_unit_ers = NULL; //For handling skill_unit's [Skotlex] -static struct eri *skill_timer_ers = NULL; //For handling skill_timerskills [Skotlex] - -DBMap* skillunit_db = NULL; // int id -> struct skill_unit* - -DBMap* skilldb_name2id = NULL; - -/** - * Skill Cool Down Delay Saving - * Struct skill_cd is not a member of struct map_session_data - * to keep cooldowns in memory between player log-ins. - * All cooldowns are reset when server is restarted. - **/ -DBMap* skillcd_db = NULL; // char_id -> struct skill_cd -struct skill_cd { - int duration[MAX_SKILL_TREE];//milliseconds - short skidx[MAX_SKILL_TREE];//the skill index entries belong to - short nameid[MAX_SKILL_TREE];//skill id - unsigned char cursor; -}; - -/** - * Skill Unit Persistency during endack routes (mostly for songs see bugreport:4574) - **/ -DBMap* skillusave_db = NULL; // char_id -> struct skill_usave -struct skill_usave { - uint16 skill_id, skill_lv; -}; - -struct s_skill_db skill_db[MAX_SKILL_DB]; -struct s_skill_produce_db skill_produce_db[MAX_SKILL_PRODUCE_DB]; -struct s_skill_arrow_db skill_arrow_db[MAX_SKILL_ARROW_DB]; -struct s_skill_abra_db skill_abra_db[MAX_SKILL_ABRA_DB]; -struct s_skill_improvise_db { - uint16 skill_id; - short per;//1-10000 -}; -struct s_skill_improvise_db skill_improvise_db[MAX_SKILL_IMPROVISE_DB]; -bool skill_reproduce_db[MAX_SKILL_DB]; -struct s_skill_changematerial_db { - int itemid; - short rate; - int qty[5]; - short qty_rate[5]; -}; -struct s_skill_changematerial_db skill_changematerial_db[MAX_SKILL_PRODUCE_DB]; - -//Warlock -struct s_skill_spellbook_db { - int nameid; - uint16 skill_id; - int point; -}; - -struct s_skill_spellbook_db skill_spellbook_db[MAX_SKILL_SPELLBOOK_DB]; -//Guillotine Cross -struct s_skill_magicmushroom_db skill_magicmushroom_db[MAX_SKILL_MAGICMUSHROOM_DB]; - -struct s_skill_unit_layout skill_unit_layout[MAX_SKILL_UNIT_LAYOUT]; -int firewall_unit_pos; -int icewall_unit_pos; -int earthstrain_unit_pos; -//early declaration -int skill_block_check(struct block_list *bl, enum sc_type type, uint16 skill_id); -static int skill_check_unit_range (struct block_list *bl, int x, int y, uint16 skill_id, uint16 skill_lv); -static int skill_check_unit_range2 (struct block_list *bl, int x, int y, uint16 skill_id, uint16 skill_lv); -static int skill_destroy_trap( struct block_list *bl, va_list ap ); -//Since only mob-casted splash skills can hit ice-walls -static inline int splash_target(struct block_list* bl) -{ -#ifndef RENEWAL - return ( bl->type == BL_MOB ) ? BL_SKILL|BL_CHAR : BL_CHAR; -#else // Some skills can now hit ground skills(traps, ice wall & etc.) - return BL_SKILL|BL_CHAR; -#endif -} - -/// Returns the id of the skill, or 0 if not found. -int skill_name2id(const char* name) -{ - if( name == NULL ) - return 0; - - return strdb_iget(skilldb_name2id, name); -} - -/// Maps skill ids to skill db offsets. -/// Returns the skill's array index, or 0 (Unknown Skill). -int skill_get_index( uint16 skill_id ) -{ - // avoid ranges reserved for mapping guild/homun/mercenary skills - if( (skill_id >= GD_SKILLRANGEMIN && skill_id <= GD_SKILLRANGEMAX) - || (skill_id >= HM_SKILLRANGEMIN && skill_id <= HM_SKILLRANGEMAX) - || (skill_id >= MC_SKILLRANGEMIN && skill_id <= MC_SKILLRANGEMAX) - || (skill_id >= EL_SKILLRANGEMIN && skill_id <= EL_SKILLRANGEMAX) ) - return 0; - - // map skill id to skill db index - if( skill_id >= GD_SKILLBASE ) - skill_id = GD_SKILLRANGEMIN + skill_id - GD_SKILLBASE; - else if( skill_id >= EL_SKILLBASE ) - skill_id = EL_SKILLRANGEMIN + skill_id - EL_SKILLBASE; - else if( skill_id >= MC_SKILLBASE ) - skill_id = MC_SKILLRANGEMIN + skill_id - MC_SKILLBASE; - else if( skill_id >= HM_SKILLBASE ) - skill_id = HM_SKILLRANGEMIN + skill_id - HM_SKILLBASE; - - // validate result - if( !skill_id || skill_id >= MAX_SKILL_DB ) - return 0; - - return skill_id; -} - -const char* skill_get_name( uint16 skill_id ) -{ - return skill_db[skill_get_index(skill_id)].name; -} - -const char* skill_get_desc( uint16 skill_id ) -{ - return skill_db[skill_get_index(skill_id)].desc; -} - -// out of bounds error checking [celest] -static void skill_chk(int16* skill_id, uint16 skill_lv) -{ - *skill_id = skill_get_index(*skill_id); // checks/adjusts id - if( skill_lv > MAX_SKILL_LEVEL ) *skill_id = 0; -} - -#define skill_get(var,id,lv) { skill_chk(&id,lv); if(!id) return 0; return var; } - -// Skill DB -int skill_get_hit( uint16 skill_id ) { skill_get (skill_db[skill_id].hit, skill_id, 1); } -int skill_get_inf( uint16 skill_id ) { skill_get (skill_db[skill_id].inf, skill_id, 1); } -int skill_get_ele( uint16 skill_id , uint16 skill_lv ) { skill_get (skill_db[skill_id].element[skill_lv-1], skill_id, skill_lv); } -int skill_get_nk( uint16 skill_id ) { skill_get (skill_db[skill_id].nk, skill_id, 1); } -int skill_get_max( uint16 skill_id ) { skill_get (skill_db[skill_id].max, skill_id, 1); } -int skill_get_range( uint16 skill_id , uint16 skill_lv ) { skill_get (skill_db[skill_id].range[skill_lv-1], skill_id, skill_lv); } -int skill_get_splash( uint16 skill_id , uint16 skill_lv ) { skill_get ( (skill_db[skill_id].splash[skill_lv-1]>=0?skill_db[skill_id].splash[skill_lv-1]:AREA_SIZE), skill_id, skill_lv); } -int skill_get_hp( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].hp[skill_lv-1], skill_id, skill_lv); } -int skill_get_sp( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].sp[skill_lv-1], skill_id, skill_lv); } -int skill_get_hp_rate(uint16 skill_id, uint16 skill_lv ) { skill_get (skill_db[skill_id].hp_rate[skill_lv-1], skill_id, skill_lv); } -int skill_get_sp_rate(uint16 skill_id, uint16 skill_lv ) { skill_get (skill_db[skill_id].sp_rate[skill_lv-1], skill_id, skill_lv); } -int skill_get_state(uint16 skill_id) { skill_get (skill_db[skill_id].state, skill_id, 1); } -int skill_get_spiritball(uint16 skill_id, uint16 skill_lv) { skill_get (skill_db[skill_id].spiritball[skill_lv-1], skill_id, skill_lv); } -int skill_get_itemid(uint16 skill_id, int idx) { skill_get (skill_db[skill_id].itemid[idx], skill_id, 1); } -int skill_get_itemqty(uint16 skill_id, int idx) { skill_get (skill_db[skill_id].amount[idx], skill_id, 1); } -int skill_get_zeny( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].zeny[skill_lv-1], skill_id, skill_lv); } -int skill_get_num( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].num[skill_lv-1], skill_id, skill_lv); } -int skill_get_cast( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].cast[skill_lv-1], skill_id, skill_lv); } -int skill_get_delay( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].delay[skill_lv-1], skill_id, skill_lv); } -int skill_get_walkdelay( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].walkdelay[skill_lv-1], skill_id, skill_lv); } -int skill_get_time( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].upkeep_time[skill_lv-1], skill_id, skill_lv); } -int skill_get_time2( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].upkeep_time2[skill_lv-1], skill_id, skill_lv); } -int skill_get_castdef( uint16 skill_id ) { skill_get (skill_db[skill_id].cast_def_rate, skill_id, 1); } -int skill_get_weapontype( uint16 skill_id ) { skill_get (skill_db[skill_id].weapon, skill_id, 1); } -int skill_get_ammotype( uint16 skill_id ) { skill_get (skill_db[skill_id].ammo, skill_id, 1); } -int skill_get_ammo_qty( uint16 skill_id, uint16 skill_lv ) { skill_get (skill_db[skill_id].ammo_qty[skill_lv-1], skill_id, skill_lv); } -int skill_get_inf2( uint16 skill_id ) { skill_get (skill_db[skill_id].inf2, skill_id, 1); } -int skill_get_castcancel( uint16 skill_id ) { skill_get (skill_db[skill_id].castcancel, skill_id, 1); } -int skill_get_maxcount( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].maxcount[skill_lv-1], skill_id, skill_lv); } -int skill_get_blewcount( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].blewcount[skill_lv-1], skill_id, skill_lv); } -int skill_get_mhp( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].mhp[skill_lv-1], skill_id, skill_lv); } -int skill_get_castnodex( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].castnodex[skill_lv-1], skill_id, skill_lv); } -int skill_get_delaynodex( uint16 skill_id ,uint16 skill_lv ){ skill_get (skill_db[skill_id].delaynodex[skill_lv-1], skill_id, skill_lv); } -int skill_get_nocast ( uint16 skill_id ) { skill_get (skill_db[skill_id].nocast, skill_id, 1); } -int skill_get_type( uint16 skill_id ) { skill_get (skill_db[skill_id].skill_type, skill_id, 1); } -int skill_get_unit_id ( uint16 skill_id, int flag ){ skill_get (skill_db[skill_id].unit_id[flag], skill_id, 1); } -int skill_get_unit_interval( uint16 skill_id ) { skill_get (skill_db[skill_id].unit_interval, skill_id, 1); } -int skill_get_unit_range( uint16 skill_id, uint16 skill_lv ){ skill_get (skill_db[skill_id].unit_range[skill_lv-1], skill_id, skill_lv); } -int skill_get_unit_target( uint16 skill_id ) { skill_get (skill_db[skill_id].unit_target&BCT_ALL, skill_id, 1); } -int skill_get_unit_bl_target( uint16 skill_id ) { skill_get (skill_db[skill_id].unit_target&BL_ALL, skill_id, 1); } -int skill_get_unit_flag( uint16 skill_id ) { skill_get (skill_db[skill_id].unit_flag, skill_id, 1); } -int skill_get_unit_layout_type( uint16 skill_id ,uint16 skill_lv ){ skill_get (skill_db[skill_id].unit_layout_type[skill_lv-1], skill_id, skill_lv); } -int skill_get_cooldown( uint16 skill_id, uint16 skill_lv ) { skill_get (skill_db[skill_id].cooldown[skill_lv-1], skill_id, skill_lv); } -#ifdef RENEWAL_CAST -int skill_get_fixed_cast( uint16 skill_id ,uint16 skill_lv ){ skill_get (skill_db[skill_id].fixed_cast[skill_lv-1], skill_id, skill_lv); } -#endif -int skill_tree_get_max(uint16 skill_id, int b_class) -{ - int i; - b_class = pc_class2idx(b_class); - - ARR_FIND( 0, MAX_SKILL_TREE, i, skill_tree[b_class][i].id == 0 || skill_tree[b_class][i].id == skill_id ); - if( i < MAX_SKILL_TREE && skill_tree[b_class][i].id == skill_id ) - return skill_tree[b_class][i].max; - else - return skill_get_max(skill_id); -} - -int skill_frostjoke_scream(struct block_list *bl,va_list ap); -int skill_attack_area(struct block_list *bl,va_list ap); -struct skill_unit_group *skill_locate_element_field(struct block_list *bl); // [Skotlex] -int skill_graffitiremover(struct block_list *bl, va_list ap); // [Valaris] -int skill_greed(struct block_list *bl, va_list ap); -static void skill_toggle_magicpower(struct block_list *bl, uint16 skill_id); -static int skill_cell_overlap(struct block_list *bl, va_list ap); -static int skill_trap_splash(struct block_list *bl, va_list ap); -struct skill_unit_group_tickset *skill_unitgrouptickset_search(struct block_list *bl,struct skill_unit_group *sg,int tick); -static int skill_unit_onplace(struct skill_unit *src,struct block_list *bl,unsigned int tick); -static int skill_unit_onleft(uint16 skill_id, struct block_list *bl,unsigned int tick); -static int skill_unit_effect(struct block_list *bl,va_list ap); - -int enchant_eff[5] = { 10, 14, 17, 19, 20 }; -int deluge_eff[5] = { 5, 9, 12, 14, 15 }; - -int skill_get_casttype (uint16 skill_id) -{ - int inf = skill_get_inf(skill_id); - if (inf&(INF_GROUND_SKILL)) - return CAST_GROUND; - if (inf&INF_SUPPORT_SKILL) - return CAST_NODAMAGE; - if (inf&INF_SELF_SKILL) { - if(skill_get_inf2(skill_id)&INF2_NO_TARGET_SELF) - return CAST_DAMAGE; //Combo skill. - return CAST_NODAMAGE; - } - if (skill_get_nk(skill_id)&NK_NO_DAMAGE) - return CAST_NODAMAGE; - return CAST_DAMAGE; -} - -//Returns actual skill range taking into account attack range and AC_OWL [Skotlex] -int skill_get_range2 (struct block_list *bl, uint16 skill_id, uint16 skill_lv) -{ - int range; - if( bl->type == BL_MOB && battle_config.mob_ai&0x400 ) - return 9; //Mobs have a range of 9 regardless of skill used. - - range = skill_get_range(skill_id, skill_lv); - - if( range < 0 ) - { - if( battle_config.use_weapon_skill_range&bl->type ) - return status_get_range(bl); - range *=-1; - } - - //TODO: Find a way better than hardcoding the list of skills affected by AC_VULTURE - switch( skill_id ) - { - case AC_SHOWER: case MA_SHOWER: - case AC_DOUBLE: case MA_DOUBLE: - case HT_BLITZBEAT: - case AC_CHARGEARROW: - case MA_CHARGEARROW: - case SN_FALCONASSAULT: - case HT_POWER: - /** - * Ranger - **/ - case RA_ARROWSTORM: - case RA_AIMEDBOLT: - case RA_WUGBITE: - if( bl->type == BL_PC ) - range += pc_checkskill((TBL_PC*)bl, AC_VULTURE); - else - range += 10; //Assume level 10? - break; - // added to allow GS skills to be effected by the range of Snake Eyes [Reddozen] - case GS_RAPIDSHOWER: - case GS_PIERCINGSHOT: - case GS_FULLBUSTER: - case GS_SPREADATTACK: - case GS_GROUNDDRIFT: - if (bl->type == BL_PC) - range += pc_checkskill((TBL_PC*)bl, GS_SNAKEEYE); - else - range += 10; //Assume level 10? - break; - case NJ_KIRIKAGE: - if (bl->type == BL_PC) - range = skill_get_range(NJ_SHADOWJUMP,pc_checkskill((TBL_PC*)bl,NJ_SHADOWJUMP)); - break; - /** - * Warlock - **/ - case WL_WHITEIMPRISON: - case WL_SOULEXPANSION: - case WL_FROSTMISTY: - case WL_MARSHOFABYSS: - case WL_SIENNAEXECRATE: - case WL_DRAINLIFE: - case WL_CRIMSONROCK: - case WL_HELLINFERNO: - case WL_COMET: - case WL_CHAINLIGHTNING: - case WL_TETRAVORTEX: - case WL_RELEASE: - if( bl->type == BL_PC ) - range += pc_checkskill((TBL_PC*)bl, WL_RADIUS); - break; - /** - * Ranger Bonus - **/ - case HT_LANDMINE: - case HT_FREEZINGTRAP: - case HT_BLASTMINE: - case HT_CLAYMORETRAP: - case RA_CLUSTERBOMB: - case RA_FIRINGTRAP: - case RA_ICEBOUNDTRAP: - if( bl->type == BL_PC ) - range += (1 + pc_checkskill((TBL_PC*)bl, RA_RESEARCHTRAP))/2; - } - - if( !range && bl->type != BL_PC ) - return 9; // Enable non players to use self skills on others. [Skotlex] - return range; -} - -int skill_calc_heal(struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv, bool heal) { - int skill, hp; - struct map_session_data *sd = BL_CAST(BL_PC, src); - struct map_session_data *tsd = BL_CAST(BL_PC, target); - struct status_change* sc; - - switch( skill_id ) { - case BA_APPLEIDUN: - #ifdef RENEWAL - hp = 100+5*skill_lv+5*(status_get_vit(src)/10); // HP recovery - #else - hp = 30+5*skill_lv+5*(status_get_vit(src)/10); // HP recovery - #endif - if( sd ) - hp += 5*pc_checkskill(sd,BA_MUSICALLESSON); - break; - case PR_SANCTUARY: - hp = (skill_lv>6)?777:skill_lv*100; - break; - case NPC_EVILLAND: - hp = (skill_lv>6)?666:skill_lv*100; - break; - default: - if (skill_lv >= battle_config.max_heal_lv) - return battle_config.max_heal; - #ifdef RENEWAL - /** - * Renewal Heal Formula - * Formula: ( [(Base Level + INT) / 5] × 30 ) × (Heal Level / 10) × (Modifiers) + MATK - **/ - hp = (status_get_lv(src) + status_get_int(src)) / 5 * 30 * skill_lv / 10; - #else - hp = ( status_get_lv(src) + status_get_int(src) ) / 8 * (4 + ( skill_id == AB_HIGHNESSHEAL ? ( sd ? pc_checkskill(sd,AL_HEAL) : 10 ) : skill_lv ) * 8); - #endif - if( sd && ((skill = pc_checkskill(sd, HP_MEDITATIO)) > 0) ) - hp += hp * skill * 2 / 100; - else if( src->type == BL_HOM && (skill = merc_hom_checkskill(((TBL_HOM*)src), HLIF_BRAIN)) > 0 ) - hp += hp * skill * 2 / 100; - break; - } - - if( ( (target && target->type == BL_MER) || !heal ) && skill_id != NPC_EVILLAND ) - hp >>= 1; - - if( sd && (skill = pc_skillheal_bonus(sd, skill_id)) ) - hp += hp*skill/100; - - if( tsd && (skill = pc_skillheal2_bonus(tsd, skill_id)) ) - hp += hp*skill/100; - - sc = status_get_sc(target); - if( sc && sc->count ) { - if( sc->data[SC_CRITICALWOUND] && heal ) // Critical Wound has no effect on offensive heal. [Inkfish] - hp -= hp * sc->data[SC_CRITICALWOUND]->val2/100; - if( sc->data[SC_DEATHHURT] && heal ) - hp -= hp * 20/100; - if( sc->data[SC_INCHEALRATE] && skill_id != NPC_EVILLAND && skill_id != BA_APPLEIDUN ) - hp += hp * sc->data[SC_INCHEALRATE]->val1/100; // Only affects Heal, Sanctuary and PotionPitcher.(like bHealPower) [Inkfish] - if( sc->data[SC_WATER_INSIGNIA] && sc->data[SC_WATER_INSIGNIA]->val1 == 2) - hp += hp / 10; - } - -#ifdef RENEWAL - // MATK part of the RE heal formula [malufett] - // Note: in this part matk bonuses from items or skills are not applied - switch( skill_id ) { - case BA_APPLEIDUN: case PR_SANCTUARY: - case NPC_EVILLAND: break; - default: - { - struct status_data *status = status_get_status_data(src); - int min, max, wMatk, variance; - - min = max = status_base_matk(status, status_get_lv(src)); - if( status->rhw.matk > 0 ){ - wMatk = status->rhw.matk; - variance = wMatk * status->rhw.wlv / 10; - min += wMatk - variance; - max += wMatk + variance; - } - - if( sc && sc->data[SC_RECOGNIZEDSPELL] ) - min = max; - - if( sd && sd->right_weapon.overrefine > 0 ){ - min++; - max += sd->right_weapon.overrefine - 1; - } - - if(max > min) - hp += min+rnd()%(max-min); - else - hp += min; - } - } -#endif - return hp; -} - -// Making plagiarize check its own function [Aru] -int can_copy (struct map_session_data *sd, uint16 skill_id, struct block_list* bl) -{ - // Never copy NPC/Wedding Skills - if (skill_get_inf2(skill_id)&(INF2_NPC_SKILL|INF2_WEDDING_SKILL)) - return 0; - - // High-class skills - if((skill_id >= LK_AURABLADE && skill_id <= ASC_CDP) || (skill_id >= ST_PRESERVE && skill_id <= CR_CULTIVATION)) - { - if(battle_config.copyskill_restrict == 2) - return 0; - else if(battle_config.copyskill_restrict) - return (sd->status.class_ == JOB_STALKER); - } - - //Added so plagarize can't copy agi/bless if you're undead since it damages you - if ((skill_id == AL_INCAGI || skill_id == AL_BLESSING || - skill_id == CASH_BLESSING || skill_id == CASH_INCAGI || - skill_id == MER_INCAGI || skill_id == MER_BLESSING)) - return 0; - - // Couldn't preserve 3rd Class skills except only when using Reproduce skill. [Jobbie] - if( !(sd->sc.data[SC__REPRODUCE]) && (skill_id >= RK_ENCHANTBLADE && skill_id <= SR_RIDEINLIGHTNING) ) - return 0; - // Reproduce will only copy skills according on the list. [Jobbie] - else if( sd->sc.data[SC__REPRODUCE] && !skill_reproduce_db[skill_id] ) - return 0; - - return 1; -} - -// [MouseJstr] - skill ok to cast? and when? -int skillnotok (uint16 skill_id, struct map_session_data *sd) -{ - int16 idx,m; - nullpo_retr (1, sd); - m = sd->bl.m; - idx = skill_get_index(skill_id); - - if (idx == 0) - return 1; // invalid skill id - - if (pc_has_permission(sd, PC_PERM_SKILL_UNCONDITIONAL)) - return 0; // can do any damn thing they want - - if( skill_id == AL_TELEPORT && sd->skillitem == skill_id && sd->skillitemlv > 2 ) - return 0; // Teleport lv 3 bypasses this check.[Inkfish] - - // Epoque: - // This code will compare the player's attack motion value which is influenced by ASPD before - // allowing a skill to be cast. This is to prevent no-delay ACT files from spamming skills such as - // AC_DOUBLE which do not have a skill delay and are not regarded in terms of attack motion. - if( !sd->state.autocast && sd->skillitem != skill_id && sd->canskill_tick && - DIFF_TICK(gettick(), sd->canskill_tick) < (sd->battle_status.amotion * (battle_config.skill_amotion_leniency) / 100) ) - {// attempted to cast a skill before the attack motion has finished - return 1; - } - - if (sd->blockskill[idx] > 0){ - clif_skill_fail(sd, skill_id, USESKILL_FAIL_SKILLINTERVAL, 0); - return 1; - } - /** - * It has been confirmed on a official server (thanks to Yommy) that item-cast skills bypass all the restrictions above - * Also, without this check, an exploit where an item casting + healing (or any other kind buff) isn't deleted after used on a restricted map - **/ - if( sd->skillitem == skill_id ) - return 0; - // Check skill restrictions [Celest] - if( (!map_flag_vs(m) && skill_get_nocast (skill_id) & 1) || - (map[m].flag.pvp && skill_get_nocast (skill_id) & 2) || - (map_flag_gvg(m) && skill_get_nocast (skill_id) & 4) || - (map[m].flag.battleground && skill_get_nocast (skill_id) & 8) || - (map[m].flag.restricted && map[m].zone && skill_get_nocast (skill_id) & (8*map[m].zone)) ){ - clif_msg(sd, 0x536); // This skill cannot be used within this area - return 1; - } - - if( sd->sc.option&OPTION_MOUNTING ) - return 1;//You can't use skills while in the new mounts (The client doesn't let you, this is to make cheat-safe) - - switch (skill_id) { - case AL_WARP: - case RETURN_TO_ELDICASTES: - case ALL_GUARDIAN_RECALL: - if(map[m].flag.nowarp) { - clif_skill_teleportmessage(sd,0); - return 1; - } - return 0; - case AL_TELEPORT: - case SC_FATALMENACE: - case SC_DIMENSIONDOOR: - if(map[m].flag.noteleport) { - clif_skill_teleportmessage(sd,0); - return 1; - } - return 0; // gonna be checked in 'skill_castend_nodamage_id' - case WE_CALLPARTNER: - case WE_CALLPARENT: - case WE_CALLBABY: - if (map[m].flag.nomemo) { - clif_skill_teleportmessage(sd,1); - return 1; - } - break; - case MC_VENDING: - case MC_IDENTIFY: - case ALL_BUYING_STORE: - return 0; // always allowed - case WZ_ICEWALL: - // noicewall flag [Valaris] - if (map[m].flag.noicewall) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 1; - } - break; - case GC_DARKILLUSION: - if( map_flag_gvg(m) ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 1; - } - break; - case GD_EMERGENCYCALL: - if ( - !(battle_config.emergency_call&((agit_flag || agit2_flag)?2:1)) || - !(battle_config.emergency_call&(map[m].flag.gvg || map[m].flag.gvg_castle?8:4)) || - (battle_config.emergency_call&16 && map[m].flag.nowarpto && !map[m].flag.gvg_castle) - ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 1; - } - break; - case BS_GREED: - case WS_CARTBOOST: - case BS_HAMMERFALL: - case BS_ADRENALINE: - case MC_CARTREVOLUTION: - case MC_MAMMONITE: - case WS_MELTDOWN: - case MG_SIGHT: - case TF_HIDING: - /** - * These skills cannot be used while in mado gear (credits to Xantara) - **/ - if( pc_ismadogear(sd) ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 1; - } - break; - - case WM_SIRCLEOFNATURE: - case WM_SOUND_OF_DESTRUCTION: - case SC_MANHOLE: - case WM_LULLABY_DEEPSLEEP: - case WM_SATURDAY_NIGHT_FEVER: - if( !map_flag_vs(m) ) { - clif_skill_teleportmessage(sd,2); // This skill uses this msg instead of skill fails. - return 1; - } - break; - - } - return (map[m].flag.noskill); -} - -int skillnotok_hom(uint16 skill_id, struct homun_data *hd) -{ - uint16 idx = skill_get_index(skill_id); - nullpo_retr(1,hd); - - if (idx == 0) - return 1; // invalid skill id - - if (hd->blockskill[idx] > 0) - return 1; - switch(skill_id){ - case MH_LIGHT_OF_REGENE: - if(hd->homunculus.intimacy <= 750) //if not cordial - return 1; - break; - case MH_OVERED_BOOST: - if(hd->homunculus.hunger <= 1) //if we starving - return 1; - case MH_GOLDENE_FERSE: //can be used with angriff - if(hd->sc.data[SC_ANGRIFFS_MODUS]) - return 1; - case MH_ANGRIFFS_MODUS: - if(hd->sc.data[SC_GOLDENE_FERSE]) - return 1; - break; - } - - //Use master's criteria. - return skillnotok(skill_id, hd->master); -} - -int skillnotok_mercenary(uint16 skill_id, struct mercenary_data *md) -{ - uint16 idx = skill_get_index(skill_id); - nullpo_retr(1,md); - - if( idx == 0 ) - return 1; // Invalid Skill ID - if( md->blockskill[idx] > 0 ) - return 1; - - return skillnotok(skill_id, md->master); -} - -struct s_skill_unit_layout* skill_get_unit_layout (uint16 skill_id, uint16 skill_lv, struct block_list* src, int x, int y) -{ - int pos = skill_get_unit_layout_type(skill_id,skill_lv); - uint8 dir; - - if (pos < -1 || pos >= MAX_SKILL_UNIT_LAYOUT) { - ShowError("skill_get_unit_layout: unsupported layout type %d for skill %d (level %d)\n", pos, skill_id, skill_lv); - pos = cap_value(pos, 0, MAX_SQUARE_LAYOUT); // cap to nearest square layout - } - - if (pos != -1) // simple single-definition layout - return &skill_unit_layout[pos]; - - dir = (src->x == x && src->y == y) ? 6 : map_calc_dir(src,x,y); // 6 - default aegis direction - - if (skill_id == MG_FIREWALL) - return &skill_unit_layout [firewall_unit_pos + dir]; - else if (skill_id == WZ_ICEWALL) - return &skill_unit_layout [icewall_unit_pos + dir]; - else if( skill_id == WL_EARTHSTRAIN ) //Warlock - return &skill_unit_layout [earthstrain_unit_pos + dir]; - - ShowError("skill_get_unit_layout: unknown unit layout for skill %d (level %d)\n", skill_id, skill_lv); - return &skill_unit_layout[0]; // default 1x1 layout -} - -/*========================================== - * - *------------------------------------------*/ -int skill_additional_effect (struct block_list* src, struct block_list *bl, uint16 skill_id, uint16 skill_lv, int attack_type, int dmg_lv, unsigned int tick) -{ - struct map_session_data *sd, *dstsd; - struct mob_data *md, *dstmd; - struct status_data *sstatus, *tstatus; - struct status_change *sc, *tsc; - - enum sc_type status; - int skill; - int rate; - - nullpo_ret(src); - nullpo_ret(bl); - - if(skill_id > 0 && !skill_lv) return 0; // don't forget auto attacks! - celest - - if( dmg_lv < ATK_BLOCK ) // Don't apply effect if miss. - return 0; - - sd = BL_CAST(BL_PC, src); - md = BL_CAST(BL_MOB, src); - dstsd = BL_CAST(BL_PC, bl); - dstmd = BL_CAST(BL_MOB, bl); - - sc = status_get_sc(src); - tsc = status_get_sc(bl); - sstatus = status_get_status_data(src); - tstatus = status_get_status_data(bl); - if (!tsc) //skill additional effect is about adding effects to the target... - //So if the target can't be inflicted with statuses, this is pointless. - return 0; - - if( sd ) - { // These statuses would be applied anyway even if the damage was blocked by some skills. [Inkfish] - if( skill_id != WS_CARTTERMINATION && skill_id != AM_DEMONSTRATION && skill_id != CR_REFLECTSHIELD && skill_id != MS_REFLECTSHIELD && skill_id != ASC_BREAKER ) - { // Trigger status effects - enum sc_type type; - int i; - for( i = 0; i < ARRAYLENGTH(sd->addeff) && sd->addeff[i].flag; i++ ) - { - rate = sd->addeff[i].rate; - if( attack_type&BF_LONG ) // Any ranged physical attack takes status arrows into account (Grimtooth...) [DracoRPG] - rate += sd->addeff[i].arrow_rate; - if( !rate ) continue; - - if( (sd->addeff[i].flag&(ATF_WEAPON|ATF_MAGIC|ATF_MISC)) != (ATF_WEAPON|ATF_MAGIC|ATF_MISC) ) - { // Trigger has attack type consideration. - if( (sd->addeff[i].flag&ATF_WEAPON && attack_type&BF_WEAPON) || - (sd->addeff[i].flag&ATF_MAGIC && attack_type&BF_MAGIC) || - (sd->addeff[i].flag&ATF_MISC && attack_type&BF_MISC) ) ; - else - continue; - } - - if( (sd->addeff[i].flag&(ATF_LONG|ATF_SHORT)) != (ATF_LONG|ATF_SHORT) ) - { // Trigger has range consideration. - if((sd->addeff[i].flag&ATF_LONG && !(attack_type&BF_LONG)) || - (sd->addeff[i].flag&ATF_SHORT && !(attack_type&BF_SHORT))) - continue; //Range Failed. - } - - type = sd->addeff[i].id; - skill = skill_get_time2(status_sc2skill(type),7); - - if (sd->addeff[i].flag&ATF_TARGET) - status_change_start(bl,type,rate,7,0,0,0,skill,0); - - if (sd->addeff[i].flag&ATF_SELF) - status_change_start(src,type,rate,7,0,0,0,skill,0); - } - } - - if( skill_id ) - { // Trigger status effects on skills - enum sc_type type; - int i; - for( i = 0; i < ARRAYLENGTH(sd->addeff3) && sd->addeff3[i].skill; i++ ) - { - if( skill_id != sd->addeff3[i].skill || !sd->addeff3[i].rate ) - continue; - type = sd->addeff3[i].id; - skill = skill_get_time2(status_sc2skill(type),7); - - if( sd->addeff3[i].target&ATF_TARGET ) - status_change_start(bl,type,sd->addeff3[i].rate,7,0,0,0,skill,0); - if( sd->addeff3[i].target&ATF_SELF ) - status_change_start(src,type,sd->addeff3[i].rate,7,0,0,0,skill,0); - } - } - } - - if( dmg_lv < ATK_DEF ) // no damage, return; - return 0; - - switch(skill_id) - { - case 0: // Normal attacks (no skill used) - { - if( attack_type&BF_SKILL ) - break; // If a normal attack is a skill, it's splash damage. [Inkfish] - if(sd) { - // Automatic trigger of Blitz Beat - if (pc_isfalcon(sd) && sd->status.weapon == W_BOW && (skill=pc_checkskill(sd,HT_BLITZBEAT))>0 && - rnd()%1000 <= sstatus->luk*10/3+1 ) { - rate=(sd->status.job_level+9)/10; - skill_castend_damage_id(src,bl,HT_BLITZBEAT,(skill<rate)?skill:rate,tick,SD_LEVEL); - } - // Automatic trigger of Warg Strike [Jobbie] - if( pc_iswug(sd) && (sd->status.weapon == W_BOW || sd->status.weapon == W_FIST) && (skill=pc_checkskill(sd,RA_WUGSTRIKE)) > 0 && rnd()%1000 <= sstatus->luk*10/3+1 ) - skill_castend_damage_id(src,bl,RA_WUGSTRIKE,skill,tick,0); - // Gank - if(dstmd && sd->status.weapon != W_BOW && - (skill=pc_checkskill(sd,RG_SNATCHER)) > 0 && - (skill*15 + 55) + pc_checkskill(sd,TF_STEAL)*10 > rnd()%1000) { - if(pc_steal_item(sd,bl,pc_checkskill(sd,TF_STEAL))) - clif_skill_nodamage(src,bl,TF_STEAL,skill,1); - else - clif_skill_fail(sd,RG_SNATCHER,USESKILL_FAIL_LEVEL,0); - } - // Chance to trigger Taekwon kicks [Dralnu] - if(sc && !sc->data[SC_COMBO]) { - if(sc->data[SC_READYSTORM] && - sc_start(src,SC_COMBO, 15, TK_STORMKICK, - (2000 - 4*sstatus->agi - 2*sstatus->dex))) - ; //Stance triggered - else if(sc->data[SC_READYDOWN] && - sc_start(src,SC_COMBO, 15, TK_DOWNKICK, - (2000 - 4*sstatus->agi - 2*sstatus->dex))) - ; //Stance triggered - else if(sc->data[SC_READYTURN] && - sc_start(src,SC_COMBO, 15, TK_TURNKICK, - (2000 - 4*sstatus->agi - 2*sstatus->dex))) - ; //Stance triggered - else if (sc->data[SC_READYCOUNTER]) { //additional chance from SG_FRIEND [Komurka] - rate = 20; - if (sc->data[SC_SKILLRATE_UP] && sc->data[SC_SKILLRATE_UP]->val1 == TK_COUNTER) { - rate += rate*sc->data[SC_SKILLRATE_UP]->val2/100; - status_change_end(src, SC_SKILLRATE_UP, INVALID_TIMER); - } - sc_start2(src, SC_COMBO, rate, TK_COUNTER, bl->id, - (2000 - 4*sstatus->agi - 2*sstatus->dex)); - } - } - if(sc && sc->data[SC_PYROCLASTIC] && (rnd() % 1000 <= sstatus->luk * 10 / 3 + 1) ) - skill_castend_pos2(src, bl->x, bl->y, BS_HAMMERFALL,sc->data[SC_PYROCLASTIC]->val1, tick, 0); - } - - if (sc) { - struct status_change_entry *sce; - // Enchant Poison gives a chance to poison attacked enemies - if((sce=sc->data[SC_ENCPOISON])) //Don't use sc_start since chance comes in 1/10000 rate. - status_change_start(bl,SC_POISON,sce->val2, sce->val1,src->id,0,0, - skill_get_time2(AS_ENCHANTPOISON,sce->val1),0); - // Enchant Deadly Poison gives a chance to deadly poison attacked enemies - if((sce=sc->data[SC_EDP])) - sc_start4(bl,SC_DPOISON,sce->val2, sce->val1,src->id,0,0, - skill_get_time2(ASC_EDP,sce->val1)); - } - } - break; - - case SM_BASH: - if( sd && skill_lv > 5 && pc_checkskill(sd,SM_FATALBLOW)>0 ){ - //TODO: How much % per base level it actually is? - sc_start(bl,SC_STUN,(5*(skill_lv-5)+(int)sd->status.base_level/10), - skill_lv,skill_get_time2(SM_FATALBLOW,skill_lv)); - } - break; - - case MER_CRASH: - sc_start(bl,SC_STUN,(6*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - - case AS_VENOMKNIFE: - if (sd) //Poison chance must be that of Envenom. [Skotlex] - skill_lv = pc_checkskill(sd, TF_POISON); - case TF_POISON: - case AS_SPLASHER: - if(!sc_start2(bl,SC_POISON,(4*skill_lv+10),skill_lv,src->id,skill_get_time2(skill_id,skill_lv)) - && sd && skill_id==TF_POISON - ) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - - case AS_SONICBLOW: - sc_start(bl,SC_STUN,(2*skill_lv+10),skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - - case WZ_FIREPILLAR: - unit_set_walkdelay(bl, tick, skill_get_time2(skill_id, skill_lv), 1); - break; - - case MG_FROSTDIVER: -#ifndef RENEWAL - case WZ_FROSTNOVA: -#endif - sc_start(bl,SC_FREEZE,skill_lv*3+35,skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - -#ifdef RENEWAL - case WZ_FROSTNOVA: - sc_start(bl,SC_FREEZE,skill_lv*5+33,skill_lv,skill_get_time2(skill_id,skill_lv)); - break; -#endif - - case WZ_STORMGUST: - /** - * Storm Gust counter was dropped in renewal - **/ - #ifdef RENEWAL - sc_start(bl,SC_FREEZE,65-(5*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); - #else - //Tharis pointed out that this is normal freeze chance with a base of 300% - if(tsc->sg_counter >= 3 && - sc_start(bl,SC_FREEZE,300,skill_lv,skill_get_time2(skill_id,skill_lv))) - tsc->sg_counter = 0; - /** - * being it only resets on success it'd keep stacking and eventually overflowing on mvps, so we reset at a high value - **/ - else if( tsc->sg_counter > 250 ) - tsc->sg_counter = 0; - #endif - break; - - case WZ_METEOR: - sc_start(bl,SC_STUN,3*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - - case WZ_VERMILION: - sc_start(bl,SC_BLIND,4*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - - case HT_FREEZINGTRAP: - case MA_FREEZINGTRAP: - sc_start(bl,SC_FREEZE,(3*skill_lv+35),skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - - case HT_FLASHER: - sc_start(bl,SC_BLIND,(10*skill_lv+30),skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - - case HT_LANDMINE: - case MA_LANDMINE: - sc_start(bl,SC_STUN,(5*skill_lv+30),skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - - case HT_SHOCKWAVE: - status_percent_damage(src, bl, 0, 15*skill_lv+5, false); - break; - - case HT_SANDMAN: - case MA_SANDMAN: - sc_start(bl,SC_SLEEP,(10*skill_lv+40),skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - - case TF_SPRINKLESAND: - sc_start(bl,SC_BLIND,20,skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - - case TF_THROWSTONE: - sc_start(bl,SC_STUN,3,skill_lv,skill_get_time(skill_id,skill_lv)); - sc_start(bl,SC_BLIND,3,skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - - case NPC_DARKCROSS: - case CR_HOLYCROSS: - sc_start(bl,SC_BLIND,3*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - - case CR_GRANDCROSS: - case NPC_GRANDDARKNESS: - //Chance to cause blind status vs demon and undead element, but not against players - if(!dstsd && (battle_check_undead(tstatus->race,tstatus->def_ele) || tstatus->race == RC_DEMON)) - sc_start(bl,SC_BLIND,100,skill_lv,skill_get_time2(skill_id,skill_lv)); - attack_type |= BF_WEAPON; - break; - - case AM_ACIDTERROR: - sc_start(bl,SC_BLEEDING,(skill_lv*3),skill_lv,skill_get_time2(skill_id,skill_lv)); - if (skill_break_equip(bl, EQP_ARMOR, 100*skill_get_time(skill_id,skill_lv), BCT_ENEMY)) - clif_emotion(bl,E_OMG); - break; - - case AM_DEMONSTRATION: - skill_break_equip(bl, EQP_WEAPON, 100*skill_lv, BCT_ENEMY); - break; - - case CR_SHIELDCHARGE: - sc_start(bl,SC_STUN,(15+skill_lv*5),skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - - case PA_PRESSURE: - status_percent_damage(src, bl, 0, 15+5*skill_lv, false); - break; - - case RG_RAID: - sc_start(bl,SC_STUN,(10+3*skill_lv),skill_lv,skill_get_time(skill_id,skill_lv)); - sc_start(bl,SC_BLIND,(10+3*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); - -#ifdef RENEWAL - sc_start(bl,SC_RAID,100,7,5000); - break; - - case RG_BACKSTAP: - sc_start(bl,SC_STUN,(5+2*skill_lv),skill_lv,skill_get_time(skill_id,skill_lv)); -#endif - break; - - case BA_FROSTJOKER: - sc_start(bl,SC_FREEZE,(15+5*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - - case DC_SCREAM: - sc_start(bl,SC_STUN,(25+5*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - - case BD_LULLABY: - sc_start(bl,SC_SLEEP,15,skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - - case DC_UGLYDANCE: - rate = 5+5*skill_lv; - if(sd && (skill=pc_checkskill(sd,DC_DANCINGLESSON))) - rate += 5+skill; - status_zap(bl, 0, rate); - break; - case SL_STUN: - if (tstatus->size==SZ_MEDIUM) //Only stuns mid-sized mobs. - sc_start(bl,SC_STUN,(30+10*skill_lv),skill_lv,skill_get_time(skill_id,skill_lv)); - break; - - case NPC_PETRIFYATTACK: - sc_start4(bl,status_skill2sc(skill_id),50+10*skill_lv, - skill_lv,0,0,skill_get_time(skill_id,skill_lv), - skill_get_time2(skill_id,skill_lv)); - break; - case NPC_CURSEATTACK: - case NPC_SLEEPATTACK: - case NPC_BLINDATTACK: - case NPC_POISON: - case NPC_SILENCEATTACK: - case NPC_STUNATTACK: - case NPC_HELLPOWER: - sc_start(bl,status_skill2sc(skill_id),50+10*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - case NPC_ACIDBREATH: - case NPC_ICEBREATH: - sc_start(bl,status_skill2sc(skill_id),70,skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - case NPC_BLEEDING: - sc_start(bl,SC_BLEEDING,(20*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - case NPC_MENTALBREAKER: - { //Based on observations by Tharis, Mental Breaker should do SP damage - //equal to Matk*skLevel. - rate = sstatus->matk_min; - if (rate < sstatus->matk_max) - rate += rnd()%(sstatus->matk_max - sstatus->matk_min); - rate*=skill_lv; - status_zap(bl, 0, rate); - break; - } - // Equipment breaking monster skills [Celest] - case NPC_WEAPONBRAKER: - skill_break_equip(bl, EQP_WEAPON, 150*skill_lv, BCT_ENEMY); - break; - case NPC_ARMORBRAKE: - skill_break_equip(bl, EQP_ARMOR, 150*skill_lv, BCT_ENEMY); - break; - case NPC_HELMBRAKE: - skill_break_equip(bl, EQP_HELM, 150*skill_lv, BCT_ENEMY); - break; - case NPC_SHIELDBRAKE: - skill_break_equip(bl, EQP_SHIELD, 150*skill_lv, BCT_ENEMY); - break; - - case CH_TIGERFIST: - sc_start(bl,SC_STOP,(10+skill_lv*10),0,skill_get_time2(skill_id,skill_lv)); - break; - - case LK_SPIRALPIERCE: - case ML_SPIRALPIERCE: - sc_start(bl,SC_STOP,(15+skill_lv*5),0,skill_get_time2(skill_id,skill_lv)); - break; - - case ST_REJECTSWORD: - sc_start(bl,SC_AUTOCOUNTER,(skill_lv*15),skill_lv,skill_get_time(skill_id,skill_lv)); - break; - - case PF_FOGWALL: - if (src != bl && !tsc->data[SC_DELUGE]) - sc_start(bl,SC_BLIND,100,skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - - case LK_HEADCRUSH: //Headcrush has chance of causing Bleeding status, except on demon and undead element - if (!(battle_check_undead(tstatus->race, tstatus->def_ele) || tstatus->race == RC_DEMON)) - sc_start(bl, SC_BLEEDING,50, skill_lv, skill_get_time2(skill_id,skill_lv)); - break; - - case LK_JOINTBEAT: - status = status_skill2sc(skill_id); - if (tsc->jb_flag) { - sc_start2(bl,status,(5*skill_lv+5),skill_lv,tsc->jb_flag&BREAK_FLAGS,skill_get_time2(skill_id,skill_lv)); - tsc->jb_flag = 0; - } - break; - case ASC_METEORASSAULT: - //Any enemies hit by this skill will receive Stun, Darkness, or external bleeding status ailment with a 5%+5*skill_lv% chance. - switch(rnd()%3) { - case 0: - sc_start(bl,SC_BLIND,(5+skill_lv*5),skill_lv,skill_get_time2(skill_id,1)); - break; - case 1: - sc_start(bl,SC_STUN,(5+skill_lv*5),skill_lv,skill_get_time2(skill_id,2)); - break; - default: - sc_start(bl,SC_BLEEDING,(5+skill_lv*5),skill_lv,skill_get_time2(skill_id,3)); - } - break; - - case HW_NAPALMVULCAN: - sc_start(bl,SC_CURSE,5*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - - case WS_CARTTERMINATION: // Cart termination - sc_start(bl,SC_STUN,5*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - - case CR_ACIDDEMONSTRATION: - skill_break_equip(bl, EQP_WEAPON|EQP_ARMOR, 100*skill_lv, BCT_ENEMY); - break; - - case TK_DOWNKICK: - sc_start(bl,SC_STUN,100,skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - - case TK_JUMPKICK: - if( dstsd && dstsd->class_ != MAPID_SOUL_LINKER && !tsc->data[SC_PRESERVE] ) - {// debuff the following statuses - status_change_end(bl, SC_SPIRIT, INVALID_TIMER); - status_change_end(bl, SC_ADRENALINE2, INVALID_TIMER); - status_change_end(bl, SC_KAITE, INVALID_TIMER); - status_change_end(bl, SC_KAAHI, INVALID_TIMER); - status_change_end(bl, SC_ONEHAND, INVALID_TIMER); - status_change_end(bl, SC_ASPDPOTION2, INVALID_TIMER); - } - break; - case TK_TURNKICK: - case MO_BALKYOUNG: //Note: attack_type is passed as BF_WEAPON for the actual target, BF_MISC for the splash-affected mobs. - if(attack_type&BF_MISC) //70% base stun chance... - sc_start(bl,SC_STUN,70,skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - case GS_BULLSEYE: //0.1% coma rate. - if(tstatus->race == RC_BRUTE || tstatus->race == RC_DEMIHUMAN) - status_change_start(bl,SC_COMA,10,skill_lv,0,src->id,0,0,0); - break; - case GS_PIERCINGSHOT: - sc_start(bl,SC_BLEEDING,(skill_lv*3),skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - case NJ_HYOUSYOURAKU: - sc_start(bl,SC_FREEZE,(10+10*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - case GS_FLING: - sc_start(bl,SC_FLING,100, sd?sd->spiritball_old:5,skill_get_time(skill_id,skill_lv)); - break; - case GS_DISARM: - rate = 3*skill_lv; - if (sstatus->dex > tstatus->dex) - rate += (sstatus->dex - tstatus->dex)/5; //TODO: Made up formula - skill_strip_equip(bl, EQP_WEAPON, rate, skill_lv, skill_get_time(skill_id,skill_lv)); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - break; - case NPC_EVILLAND: - sc_start(bl,SC_BLIND,5*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - case NPC_HELLJUDGEMENT: - sc_start(bl,SC_CURSE,100,skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - case NPC_CRITICALWOUND: - sc_start(bl,SC_CRITICALWOUND,100,skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - case RK_HUNDREDSPEAR: - if( !sd || pc_checkskill(sd,KN_SPEARBOOMERANG) == 0 ) - break; // Spear Boomerang auto cast chance only works if you have mastered Spear Boomerang. - rate = 10 + 3 * skill_lv; - if( rnd()%100 < rate ) - skill_castend_damage_id(src,bl,KN_SPEARBOOMERANG,1,tick,0); - break; - case RK_WINDCUTTER: - sc_start(bl,SC_FEAR,3+2*skill_lv,skill_lv,skill_get_time(skill_id,skill_lv)); - break; - case RK_DRAGONBREATH: - sc_start4(bl,SC_BURNING,5+5*skill_lv,skill_lv,1000,src->id,0,skill_get_time(skill_id,skill_lv)); - break; - case AB_ADORAMUS: - if( tsc && !tsc->data[SC_DECREASEAGI] ) //Prevent duplicate agi-down effect. - sc_start(bl, SC_ADORAMUS, 100, skill_lv, skill_get_time(skill_id, skill_lv)); - break; - case WL_CRIMSONROCK: - sc_start(bl, SC_STUN, 40, skill_lv, skill_get_time(skill_id, skill_lv)); - break; - case WL_COMET: - sc_start4(bl,SC_BURNING,100,skill_lv,1000,src->id,0,skill_get_time(skill_id,skill_lv)); - break; - case WL_EARTHSTRAIN: - { - int rate = 0, i; - const int pos[5] = { EQP_WEAPON, EQP_HELM, EQP_SHIELD, EQP_ARMOR, EQP_ACC }; - rate = 6 * skill_lv + sstatus->dex / 10 + (sd? sd->status.job_level / 4 : 0) - tstatus->dex /5;// The tstatus->dex / 5 part is unofficial, but players gotta have some kind of way to have resistance. [Rytech] - //rate -= rate * tstatus->dex / 200; // Disabled until official resistance is found. - - for( i = 0; i < skill_lv; i++ ) - skill_strip_equip(bl,pos[i],rate,skill_lv,skill_get_time2(skill_id,skill_lv)); - } - break; - case WL_JACKFROST: - sc_start(bl,SC_FREEZE,100,skill_lv,skill_get_time(skill_id,skill_lv)); - break; - case RA_WUGBITE: - sc_start(bl, SC_BITE, (sd ? pc_checkskill(sd,RA_TOOTHOFWUG)*2 : 0), skill_lv, (skill_get_time(skill_id,skill_lv) + (sd ? pc_checkskill(sd,RA_TOOTHOFWUG)*500 : 0)) ); - break; - case RA_SENSITIVEKEEN: - if( rnd()%100 < 8 * skill_lv ) - skill_castend_damage_id(src, bl, RA_WUGBITE, sd ? pc_checkskill(sd, RA_WUGBITE):skill_lv, tick, SD_ANIMATION); - break; - case RA_FIRINGTRAP: - case RA_ICEBOUNDTRAP: - sc_start(bl, (skill_id == RA_FIRINGTRAP) ? SC_BURNING:SC_FREEZING, 40 + 10 * skill_lv, skill_lv, skill_get_time2(skill_id, skill_lv)); - break; - case NC_PILEBUNKER: - if( rnd()%100 < 5 + 15*skill_lv ) - { //Deactivatable Statuses: Kyrie Eleison, Auto Guard, Steel Body, Assumptio, and Millennium Shield - status_change_end(bl, SC_KYRIE, INVALID_TIMER); - status_change_end(bl, SC_AUTOGUARD, INVALID_TIMER); - status_change_end(bl, SC_STEELBODY, INVALID_TIMER); - status_change_end(bl, SC_ASSUMPTIO, INVALID_TIMER); - status_change_end(bl, SC_MILLENNIUMSHIELD, INVALID_TIMER); - } - break; - case NC_FLAMELAUNCHER: - sc_start4(bl, SC_BURNING, 50 + 10 * skill_lv, skill_lv, 1000, src->id, 0, skill_get_time2(skill_id, skill_lv)); - break; - case NC_COLDSLOWER: - sc_start(bl, SC_FREEZE, 10 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); - sc_start(bl, SC_FREEZING, 20 + 10 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); - break; - case NC_POWERSWING: - sc_start(bl, SC_STUN, 5*skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); - if( rnd()%100 < 5*skill_lv ) - skill_castend_damage_id(src, bl, NC_AXEBOOMERANG, pc_checkskill(sd, NC_AXEBOOMERANG), tick, 1); - break; - case GC_WEAPONCRUSH: - skill_castend_nodamage_id(src,bl,skill_id,skill_lv,tick,BCT_ENEMY); - break; - case LG_SHIELDPRESS: - sc_start(bl, SC_STUN, 30 + 8 * skill_lv, skill_lv, skill_get_time(skill_id,skill_lv)); - break; - case LG_PINPOINTATTACK: - rate = 30 + (((5 * (sd?pc_checkskill(sd,LG_PINPOINTATTACK):skill_lv)) + (sstatus->agi + status_get_lv(src))) / 10); - switch( skill_lv ) { - case 1: - sc_start(bl,SC_BLEEDING,rate,skill_lv,skill_get_time(skill_id,skill_lv)); - break; - case 2: - if( dstsd && dstsd->spiritball && rnd()%100 < rate ) - pc_delspiritball(dstsd, dstsd->spiritball, 0); - break; - default: - skill_break_equip(bl,(skill_lv == 3) ? EQP_SHIELD : (skill_lv == 4) ? EQP_ARMOR : EQP_WEAPON,rate * 100,BCT_ENEMY); - break; - } - break; - case LG_MOONSLASHER: - rate = 32 + 8 * skill_lv; - if( rnd()%100 < rate && dstsd ) // Uses skill_addtimerskill to avoid damage and setsit packet overlaping. Officially clif_setsit is received about 500 ms after damage packet. - skill_addtimerskill(src,tick+500,bl->id,0,0,skill_id,skill_lv,BF_WEAPON,0); - else if( dstmd && !is_boss(bl) ) - sc_start(bl,SC_STOP,100,skill_lv,skill_get_time(skill_id,skill_lv)); - break; - case LG_RAYOFGENESIS: // 50% chance to cause Blind on Undead and Demon monsters. - if ( battle_check_undead(tstatus->race, tstatus->def_ele) || tstatus->race == RC_DEMON ) - sc_start(bl, SC_BLIND,50, skill_lv, skill_get_time(skill_id,skill_lv)); - break; - case LG_EARTHDRIVE: - skill_break_equip(src, EQP_SHIELD, 500, BCT_SELF); - sc_start(bl, SC_EARTHDRIVE, 100, skill_lv, skill_get_time(skill_id, skill_lv)); - break; - case SR_DRAGONCOMBO: - sc_start(bl, SC_STUN, 1 + skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); - break; - case SR_FALLENEMPIRE: - sc_start(bl, SC_STOP, 100, skill_lv, skill_get_time(skill_id, skill_lv)); - break; - case SR_WINDMILL: - if( dstsd ) - skill_addtimerskill(src,tick+status_get_amotion(src),bl->id,0,0,skill_id,skill_lv,BF_WEAPON,0); - else if( dstmd && !is_boss(bl) ) - sc_start(bl, SC_STUN, 100, skill_lv, 1000 + 1000 * (rnd() %3)); - break; - case SR_GENTLETOUCH_QUIET: // [(Skill Level x 5) + (Caster?s DEX + Caster?s Base Level) / 10] - sc_start(bl, SC_SILENCE, 5 * skill_lv + (sstatus->dex + status_get_lv(src)) / 10, skill_lv, skill_get_time(skill_id, skill_lv)); - break; - case SR_EARTHSHAKER: - sc_start(bl,SC_STUN, 25 + 5 * skill_lv,skill_lv,skill_get_time(skill_id,skill_lv)); - break; - case SR_HOWLINGOFLION: - sc_start(bl, SC_FEAR, 5 + 5 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); - break; - case WM_SOUND_OF_DESTRUCTION: - if( rnd()%100 < 5 + 5 * skill_lv ) { // Temporarly Check Until We Get the Official Formula - status_change_end(bl, SC_DANCING, INVALID_TIMER); - status_change_end(bl, SC_RICHMANKIM, INVALID_TIMER); - status_change_end(bl, SC_ETERNALCHAOS, INVALID_TIMER); - status_change_end(bl, SC_DRUMBATTLE, INVALID_TIMER); - status_change_end(bl, SC_NIBELUNGEN, INVALID_TIMER); - status_change_end(bl, SC_INTOABYSS, INVALID_TIMER); - status_change_end(bl, SC_SIEGFRIED, INVALID_TIMER); - status_change_end(bl, SC_WHISTLE, INVALID_TIMER); - status_change_end(bl, SC_ASSNCROS, INVALID_TIMER); - status_change_end(bl, SC_POEMBRAGI, INVALID_TIMER); - status_change_end(bl, SC_APPLEIDUN, INVALID_TIMER); - status_change_end(bl, SC_HUMMING, INVALID_TIMER); - status_change_end(bl, SC_FORTUNE, INVALID_TIMER); - status_change_end(bl, SC_SERVICE4U, INVALID_TIMER); - status_change_end(bl, SC_LONGING, INVALID_TIMER); - status_change_end(bl, SC_SWINGDANCE, INVALID_TIMER); - status_change_end(bl, SC_SYMPHONYOFLOVER, INVALID_TIMER); - status_change_end(bl, SC_MOONLITSERENADE, INVALID_TIMER); - status_change_end(bl, SC_RUSHWINDMILL, INVALID_TIMER); - status_change_end(bl, SC_ECHOSONG, INVALID_TIMER); - status_change_end(bl, SC_HARMONIZE, INVALID_TIMER); - status_change_end(bl, SC_WINKCHARM, INVALID_TIMER); - status_change_end(bl, SC_SONGOFMANA, INVALID_TIMER); - status_change_end(bl, SC_DANCEWITHWUG, INVALID_TIMER); - status_change_end(bl, SC_LERADSDEW, INVALID_TIMER); - status_change_end(bl, SC_MELODYOFSINK, INVALID_TIMER); - status_change_end(bl, SC_BEYONDOFWARCRY, INVALID_TIMER); - status_change_end(bl, SC_UNLIMITEDHUMMINGVOICE, INVALID_TIMER); - } - break; - case SO_EARTHGRAVE: - sc_start(bl, SC_BLEEDING, 5 * skill_lv, skill_lv, skill_get_time2(skill_id, skill_lv)); // Need official rate. [LimitLine] - break; - case SO_DIAMONDDUST: - rate = 5 + 5 * skill_lv; - if( sc && sc->data[SC_COOLER_OPTION] ) - rate += rate * sc->data[SC_COOLER_OPTION]->val2 / 100; - sc_start(bl, SC_CRYSTALIZE, rate, skill_lv, skill_get_time2(skill_id, skill_lv)); - break; - case SO_VARETYR_SPEAR: - sc_start(bl, SC_STUN, 5 + 5 * skill_lv, skill_lv, skill_get_time2(skill_id, skill_lv)); - break; - case GN_SLINGITEM_RANGEMELEEATK: - if( sd ) { - switch( sd->itemid ) { // Starting SCs here instead of do it in skill_additional_effect to simplify the code. - case 13261: - sc_start(bl, SC_STUN, 100, skill_lv, skill_get_time2(GN_SLINGITEM, skill_lv)); - sc_start(bl, SC_BLEEDING, 100, skill_lv, skill_get_time2(GN_SLINGITEM, skill_lv)); - break; - case 13262: - sc_start(bl, SC_MELON_BOMB, 100, skill_lv, skill_get_time(GN_SLINGITEM, skill_lv)); // Reduces ASPD and moviment speed - break; - case 13264: - sc_start(bl, SC_BANANA_BOMB, 100, skill_lv, skill_get_time(GN_SLINGITEM, skill_lv)); // Reduces LUK ??Needed confirm it, may be it's bugged in kRORE? - sc_start(bl, SC_BANANA_BOMB_SITDOWN, 75, skill_lv, skill_get_time(GN_SLINGITEM_RANGEMELEEATK,skill_lv)); // Sitdown for 3 seconds. - break; - } - sd->itemid = -1; - } - break; - case GN_HELLS_PLANT_ATK: - sc_start(bl, SC_STUN, 5 + 5 * skill_lv, skill_lv, skill_get_time2(skill_id, skill_lv)); - sc_start(bl, SC_BLEEDING, 20 + 10 * skill_lv, skill_lv, skill_get_time2(skill_id, skill_lv)); - break; - case EL_WIND_SLASH: // Non confirmed rate. - sc_start(bl, SC_BLEEDING, 25, skill_lv, skill_get_time(skill_id,skill_lv)); - break; - case EL_STONE_HAMMER: - rate = 10 * skill_lv; - sc_start(bl, SC_STUN, rate, skill_lv, skill_get_time(skill_id,skill_lv)); - break; - case EL_ROCK_CRUSHER: - case EL_ROCK_CRUSHER_ATK: - sc_start(bl,status_skill2sc(skill_id),50,skill_lv,skill_get_time(EL_ROCK_CRUSHER,skill_lv)); - break; - case EL_TYPOON_MIS: - sc_start(bl,SC_SILENCE,10*skill_lv,skill_lv,skill_get_time(skill_id,skill_lv)); - break; - case KO_JYUMONJIKIRI: // needs more info - sc_start(bl,SC_JYUMONJIKIRI,25,skill_lv,skill_get_time(skill_id,skill_lv)); - break; - case KO_MAKIBISHI: - sc_start(bl, SC_STUN, 100, skill_lv, skill_get_time2(skill_id,skill_lv)); - break; - case MH_LAVA_SLIDE: - if (tsc && !tsc->data[SC_BURNING]) sc_start4(bl, SC_BURNING, 10 * skill_lv, skill_lv, 1000, src->id, 0, skill_get_time(skill_id, skill_lv)); - break; - case MH_STAHL_HORN: - sc_start(bl, SC_STUN, (20 + 4 * (skill_lv-1)), skill_lv, skill_get_time(skill_id, skill_lv)); - break; - case MH_NEEDLE_OF_PARALYZE: - sc_start(bl, SC_PARALYSIS, 40 + (5*skill_lv), skill_lv, skill_get_time(skill_id, skill_lv)); - break; - } - - if (md && battle_config.summons_trigger_autospells && md->master_id && md->special_state.ai) - { //Pass heritage to Master for status causing effects. [Skotlex] - sd = map_id2sd(md->master_id); - src = sd?&sd->bl:src; - } - - if( attack_type&BF_WEAPON ) - { // Coma, Breaking Equipment - if( sd && sd->special_state.bonus_coma ) - { - rate = sd->weapon_coma_ele[tstatus->def_ele]; - rate += sd->weapon_coma_race[tstatus->race]; - rate += sd->weapon_coma_race[tstatus->mode&MD_BOSS?RC_BOSS:RC_NONBOSS]; - if (rate) - status_change_start(bl, SC_COMA, rate, 0, 0, src->id, 0, 0, 0); - } - if( sd && battle_config.equip_self_break_rate ) - { // Self weapon breaking - rate = battle_config.equip_natural_break_rate; - if( sc ) - { - if(sc->data[SC_OVERTHRUST]) - rate += 10; - if(sc->data[SC_MAXOVERTHRUST]) - rate += 10; - } - if( rate ) - skill_break_equip(src, EQP_WEAPON, rate, BCT_SELF); - } - if( battle_config.equip_skill_break_rate && skill_id != WS_CARTTERMINATION && skill_id != ITM_TOMAHAWK ) - { // Cart Termination/Tomahawk won't trigger breaking data. Why? No idea, go ask Gravity. - // Target weapon breaking - rate = 0; - if( sd ) - rate += sd->bonus.break_weapon_rate; - if( sc && sc->data[SC_MELTDOWN] ) - rate += sc->data[SC_MELTDOWN]->val2; - if( rate ) - skill_break_equip(bl, EQP_WEAPON, rate, BCT_ENEMY); - - // Target armor breaking - rate = 0; - if( sd ) - rate += sd->bonus.break_armor_rate; - if( sc && sc->data[SC_MELTDOWN] ) - rate += sc->data[SC_MELTDOWN]->val3; - if( rate ) - skill_break_equip(bl, EQP_ARMOR, rate, BCT_ENEMY); - } - } - - if( sd && sd->ed && sc && !status_isdead(bl) && !skill_id ){ - struct unit_data *ud = unit_bl2ud(src); - - if( sc->data[SC_WILD_STORM_OPTION] ) - skill = sc->data[SC_WILD_STORM_OPTION]->val2; - else if( sc->data[SC_UPHEAVAL_OPTION] ) - skill = sc->data[SC_UPHEAVAL_OPTION]->val2; - else if( sc->data[SC_TROPIC_OPTION] ) - skill = sc->data[SC_TROPIC_OPTION]->val3; - else if( sc->data[SC_CHILLY_AIR_OPTION] ) - skill = sc->data[SC_CHILLY_AIR_OPTION]->val3; - else - skill = 0; - - if ( rnd()%100 < 25 && skill ){ - skill_castend_damage_id(src, bl, skill, 5, tick, 0); - - if (ud) { - rate = skill_delayfix(src, skill, skill_lv); - if (DIFF_TICK(ud->canact_tick, tick + rate) < 0){ - ud->canact_tick = tick+rate; - if ( battle_config.display_status_timers ) - clif_status_change(src, SI_ACTIONDELAY, 1, rate, 0, 0, 0); - } - } - } - } - - // Autospell when attacking - if( sd && !status_isdead(bl) && sd->autospell[0].id ) - { - struct block_list *tbl; - struct unit_data *ud; - int i, skill_lv, type, notok; - - for (i = 0; i < ARRAYLENGTH(sd->autospell) && sd->autospell[i].id; i++) { - - if(!(sd->autospell[i].flag&attack_type&BF_WEAPONMASK && - sd->autospell[i].flag&attack_type&BF_RANGEMASK && - sd->autospell[i].flag&attack_type&BF_SKILLMASK)) - continue; // one or more trigger conditions were not fulfilled - - skill = (sd->autospell[i].id > 0) ? sd->autospell[i].id : -sd->autospell[i].id; - - sd->state.autocast = 1; - notok = skillnotok(skill, sd); - sd->state.autocast = 0; - - if ( notok ) - continue; - - skill_lv = sd->autospell[i].lv?sd->autospell[i].lv:1; - if (skill_lv < 0) skill_lv = 1+rnd()%(-skill_lv); - - rate = (!sd->state.arrow_atk) ? sd->autospell[i].rate : sd->autospell[i].rate / 2; - - if (rnd()%1000 >= rate) - continue; - - tbl = (sd->autospell[i].id < 0) ? src : bl; - - if( (type = skill_get_casttype(skill)) == CAST_GROUND ) { - int maxcount = 0; - if( !(BL_PC&battle_config.skill_reiteration) && - skill_get_unit_flag(skill)&UF_NOREITERATION && - skill_check_unit_range(src,tbl->x,tbl->y,skill,skill_lv) - ) { - continue; - } - if( BL_PC&battle_config.skill_nofootset && - skill_get_unit_flag(skill)&UF_NOFOOTSET && - skill_check_unit_range2(src,tbl->x,tbl->y,skill,skill_lv) - ) { - continue; - } - if( BL_PC&battle_config.land_skill_limit && - (maxcount = skill_get_maxcount(skill, skill_lv)) > 0 - ) { - int v; - for(v=0;v<MAX_SKILLUNITGROUP && sd->ud.skillunit[v] && maxcount;v++) { - if(sd->ud.skillunit[v]->skill_id == skill) - maxcount--; - } - if( maxcount == 0 ) { - continue; - } - } - } - if( battle_config.autospell_check_range && - !battle_check_range(src, tbl, skill_get_range2(src, skill,skill_lv) + (skill == RG_CLOSECONFINE?0:1)) ) - continue; - - if (skill == AS_SONICBLOW) - pc_stop_attack(sd); //Special case, Sonic Blow autospell should stop the player attacking. - if (skill == PF_SPIDERWEB) //Special case, due to its nature of coding. - type = CAST_GROUND; - - sd->state.autocast = 1; - skill_consume_requirement(sd,skill,skill_lv,1); - skill_toggle_magicpower(src, skill); - switch (type) { - case CAST_GROUND: - skill_castend_pos2(src, tbl->x, tbl->y, skill, skill_lv, tick, 0); - break; - case CAST_NODAMAGE: - skill_castend_nodamage_id(src, tbl, skill, skill_lv, tick, 0); - break; - case CAST_DAMAGE: - skill_castend_damage_id(src, tbl, skill, skill_lv, tick, 0); - break; - } - sd->state.autocast = 0; - //Set canact delay. [Skotlex] - ud = unit_bl2ud(src); - if (ud) { - rate = skill_delayfix(src, skill, skill_lv); - if (DIFF_TICK(ud->canact_tick, tick + rate) < 0){ - ud->canact_tick = tick+rate; - if ( battle_config.display_status_timers && sd ) - clif_status_change(src, SI_ACTIONDELAY, 1, rate, 0, 0, 0); - } - } - } - } - - //Autobonus when attacking - if( sd && sd->autobonus[0].rate ) - { - int i; - for( i = 0; i < ARRAYLENGTH(sd->autobonus); i++ ) - { - if( rnd()%1000 >= sd->autobonus[i].rate ) - continue; - if( sd->autobonus[i].active != INVALID_TIMER ) - continue; - if(!(sd->autobonus[i].atk_type&attack_type&BF_WEAPONMASK && - sd->autobonus[i].atk_type&attack_type&BF_RANGEMASK && - sd->autobonus[i].atk_type&attack_type&BF_SKILLMASK)) - continue; // one or more trigger conditions were not fulfilled - pc_exeautobonus(sd,&sd->autobonus[i]); - } - } - - //Polymorph - if(sd && sd->bonus.classchange && attack_type&BF_WEAPON && - dstmd && !(tstatus->mode&MD_BOSS) && - (rnd()%10000 < sd->bonus.classchange)) - { - struct mob_db *mob; - int class_; - skill = 0; - do { - do { - class_ = rnd() % MAX_MOB_DB; - } while (!mobdb_checkid(class_)); - - rate = rnd() % 1000000; - mob = mob_db(class_); - } while ( - (mob->status.mode&(MD_BOSS|MD_PLANT) || mob->summonper[0] <= rate) && - (skill++) < 2000); - if (skill < 2000) - mob_class_change(dstmd,class_); - } - - return 0; -} - -int skill_onskillusage(struct map_session_data *sd, struct block_list *bl, uint16 skill_id, unsigned int tick) { - int skill, skill_lv, i, type, notok; - struct block_list *tbl; - - if( sd == NULL || !skill_id ) - return 0; - - for( i = 0; i < ARRAYLENGTH(sd->autospell3) && sd->autospell3[i].flag; i++ ) { - if( sd->autospell3[i].flag != skill_id ) - continue; - - if( sd->autospell3[i].lock ) - continue; // autospell already being executed - - skill = (sd->autospell3[i].id > 0) ? sd->autospell3[i].id : -sd->autospell3[i].id; - - sd->state.autocast = 1; - notok = skillnotok(skill, sd); - sd->state.autocast = 0; - - if ( notok ) - continue; - - skill_lv = sd->autospell3[i].lv ? sd->autospell3[i].lv : 1; - if( skill_lv < 0 ) skill_lv = 1 + rnd()%(-skill_lv); - - if( sd->autospell3[i].id >= 0 && bl == NULL ) - continue; // No target - if( rnd()%1000 >= sd->autospell3[i].rate ) - continue; - - tbl = (sd->autospell3[i].id < 0) ? &sd->bl : bl; - - if( (type = skill_get_casttype(skill)) == CAST_GROUND ) { - int maxcount = 0; - if( !(BL_PC&battle_config.skill_reiteration) && - skill_get_unit_flag(skill)&UF_NOREITERATION && - skill_check_unit_range(&sd->bl,tbl->x,tbl->y,skill,skill_lv) - ) { - continue; - } - if( BL_PC&battle_config.skill_nofootset && - skill_get_unit_flag(skill)&UF_NOFOOTSET && - skill_check_unit_range2(&sd->bl,tbl->x,tbl->y,skill,skill_lv) - ) { - continue; - } - if( BL_PC&battle_config.land_skill_limit && - (maxcount = skill_get_maxcount(skill, skill_lv)) > 0 - ) { - int v; - for(v=0;v<MAX_SKILLUNITGROUP && sd->ud.skillunit[v] && maxcount;v++) { - if(sd->ud.skillunit[v]->skill_id == skill) - maxcount--; - } - if( maxcount == 0 ) { - continue; - } - } - } - if( battle_config.autospell_check_range && - !battle_check_range(&sd->bl, tbl, skill_get_range2(&sd->bl, skill,skill_lv) + (skill == RG_CLOSECONFINE?0:1)) ) - continue; - - sd->state.autocast = 1; - sd->autospell3[i].lock = true; - skill_consume_requirement(sd,skill,skill_lv,1); - switch( type ) - { - case CAST_GROUND: skill_castend_pos2(&sd->bl, tbl->x, tbl->y, skill, skill_lv, tick, 0); break; - case CAST_NODAMAGE: skill_castend_nodamage_id(&sd->bl, tbl, skill, skill_lv, tick, 0); break; - case CAST_DAMAGE: skill_castend_damage_id(&sd->bl, tbl, skill, skill_lv, tick, 0); break; - } - sd->autospell3[i].lock = false; - sd->state.autocast = 0; - } - - if( sd && sd->autobonus3[0].rate ) - { - for( i = 0; i < ARRAYLENGTH(sd->autobonus3); i++ ) - { - if( rnd()%1000 >= sd->autobonus3[i].rate ) - continue; - if( sd->autobonus3[i].active != INVALID_TIMER ) - continue; - if( sd->autobonus3[i].atk_type != skill_id ) - continue; - pc_exeautobonus(sd,&sd->autobonus3[i]); - } - } - - return 1; -} - -/* Splitted off from skill_additional_effect, which is never called when the - * attack skill kills the enemy. Place in this function counter status effects - * when using skills (eg: Asura's sp regen penalty, or counter-status effects - * from cards) that will take effect on the source, not the target. [Skotlex] - * Note: Currently this function only applies to Extremity Fist and BF_WEAPON - * type of skills, so not every instance of skill_additional_effect needs a call - * to this one. - */ -int skill_counter_additional_effect (struct block_list* src, struct block_list *bl, uint16 skill_id, uint16 skill_lv, int attack_type, unsigned int tick) -{ - int rate; - struct map_session_data *sd=NULL; - struct map_session_data *dstsd=NULL; - - nullpo_ret(src); - nullpo_ret(bl); - - if(skill_id > 0 && !skill_lv) return 0; // don't forget auto attacks! - celest - - sd = BL_CAST(BL_PC, src); - dstsd = BL_CAST(BL_PC, bl); - - if(dstsd && attack_type&BF_WEAPON) - { //Counter effects. - enum sc_type type; - int i, time; - for(i=0; i < ARRAYLENGTH(dstsd->addeff2) && dstsd->addeff2[i].flag; i++) - { - rate = dstsd->addeff2[i].rate; - if (attack_type&BF_LONG) - rate+=dstsd->addeff2[i].arrow_rate; - if (!rate) continue; - - if ((dstsd->addeff2[i].flag&(ATF_LONG|ATF_SHORT)) != (ATF_LONG|ATF_SHORT)) - { //Trigger has range consideration. - if((dstsd->addeff2[i].flag&ATF_LONG && !(attack_type&BF_LONG)) || - (dstsd->addeff2[i].flag&ATF_SHORT && !(attack_type&BF_SHORT))) - continue; //Range Failed. - } - type = dstsd->addeff2[i].id; - time = skill_get_time2(status_sc2skill(type),7); - - if (dstsd->addeff2[i].flag&ATF_TARGET) - status_change_start(src,type,rate,7,0,0,0,time,0); - - if (dstsd->addeff2[i].flag&ATF_SELF && !status_isdead(bl)) - status_change_start(bl,type,rate,7,0,0,0,time,0); - } - } - - switch(skill_id){ - case MO_EXTREMITYFIST: - sc_start(src,SC_EXTREMITYFIST,100,skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - case GS_FULLBUSTER: - sc_start(src,SC_BLIND,2*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - case HFLI_SBR44: //[orn] - case HVAN_EXPLOSION: - if(src->type == BL_HOM){ - TBL_HOM *hd = (TBL_HOM*)src; - hd->homunculus.intimacy = 200; - if (hd->master) - clif_send_homdata(hd->master,SP_INTIMATE,hd->homunculus.intimacy/100); - } - break; - case CR_GRANDCROSS: - case NPC_GRANDDARKNESS: - attack_type |= BF_WEAPON; - break; - } - - if(sd && (sd->class_&MAPID_UPPERMASK) == MAPID_STAR_GLADIATOR && - rnd()%10000 < battle_config.sg_miracle_skill_ratio) //SG_MIRACLE [Komurka] - sc_start(src,SC_MIRACLE,100,1,battle_config.sg_miracle_skill_duration); - - if(sd && skill_id && attack_type&BF_MAGIC && status_isdead(bl) && - !(skill_get_inf(skill_id)&(INF_GROUND_SKILL|INF_SELF_SKILL)) && - (rate=pc_checkskill(sd,HW_SOULDRAIN))>0 - ){ //Soul Drain should only work on targetted spells [Skotlex] - if (pc_issit(sd)) pc_setstand(sd); //Character stuck in attacking animation while 'sitting' fix. [Skotlex] - clif_skill_nodamage(src,bl,HW_SOULDRAIN,rate,1); - status_heal(src, 0, status_get_lv(bl)*(95+15*rate)/100, 2); - } - - if( sd && status_isdead(bl) ) { - int sp = 0, hp = 0; - if( attack_type&BF_WEAPON ) { - sp += sd->bonus.sp_gain_value; - sp += sd->sp_gain_race[status_get_race(bl)]; - sp += sd->sp_gain_race[is_boss(bl)?RC_BOSS:RC_NONBOSS]; - hp += sd->bonus.hp_gain_value; - } - if( attack_type&BF_MAGIC ) { - sp += sd->bonus.magic_sp_gain_value; - hp += sd->bonus.magic_hp_gain_value; - if( skill_id == WZ_WATERBALL ) {//(bugreport:5303) - struct status_change *sc = NULL; - if( ( sc = status_get_sc(src) ) ) { - if(sc->data[SC_SPIRIT] && - sc->data[SC_SPIRIT]->val2 == SL_WIZARD && - sc->data[SC_SPIRIT]->val3 == WZ_WATERBALL) - sc->data[SC_SPIRIT]->val3 = 0; //Clear bounced spell check. - } - } - } - if( hp || sp ) { // updated to force healing to allow healing through berserk - status_heal(src, hp, sp, battle_config.show_hp_sp_gain ? 3 : 1); - } - } - - // Trigger counter-spells to retaliate against damage causing skills. - if(dstsd && !status_isdead(bl) && dstsd->autospell2[0].id && - !(skill_id && skill_get_nk(skill_id)&NK_NO_DAMAGE)) - { - struct block_list *tbl; - struct unit_data *ud; - int i, skill_id, skill_lv, rate, type, notok; - - for (i = 0; i < ARRAYLENGTH(dstsd->autospell2) && dstsd->autospell2[i].id; i++) { - - if(!(dstsd->autospell2[i].flag&attack_type&BF_WEAPONMASK && - dstsd->autospell2[i].flag&attack_type&BF_RANGEMASK && - dstsd->autospell2[i].flag&attack_type&BF_SKILLMASK)) - continue; // one or more trigger conditions were not fulfilled - - skill_id = (dstsd->autospell2[i].id > 0) ? dstsd->autospell2[i].id : -dstsd->autospell2[i].id; - skill_lv = dstsd->autospell2[i].lv?dstsd->autospell2[i].lv:1; - if (skill_lv < 0) skill_lv = 1+rnd()%(-skill_lv); - - rate = dstsd->autospell2[i].rate; - if (attack_type&BF_LONG) - rate>>=1; - - dstsd->state.autocast = 1; - notok = skillnotok(skill_id, dstsd); - dstsd->state.autocast = 0; - - if ( notok ) - continue; - - if (rnd()%1000 >= rate) - continue; - - tbl = (dstsd->autospell2[i].id < 0) ? bl : src; - - if( (type = skill_get_casttype(skill_id)) == CAST_GROUND ) { - int maxcount = 0; - if( !(BL_PC&battle_config.skill_reiteration) && - skill_get_unit_flag(skill_id)&UF_NOREITERATION && - skill_check_unit_range(bl,tbl->x,tbl->y,skill_id,skill_lv) - ) { - continue; - } - if( BL_PC&battle_config.skill_nofootset && - skill_get_unit_flag(skill_id)&UF_NOFOOTSET && - skill_check_unit_range2(bl,tbl->x,tbl->y,skill_id,skill_lv) - ) { - continue; - } - if( BL_PC&battle_config.land_skill_limit && - (maxcount = skill_get_maxcount(skill_id, skill_lv)) > 0 - ) { - int v; - for(v=0;v<MAX_SKILLUNITGROUP && dstsd->ud.skillunit[v] && maxcount;v++) { - if(dstsd->ud.skillunit[v]->skill_id == skill_id) - maxcount--; - } - if( maxcount == 0 ) { - continue; - } - } - } - - if( !battle_check_range(src, tbl, skill_get_range2(src, skill_id,skill_lv) + (skill_id == RG_CLOSECONFINE?0:1)) && battle_config.autospell_check_range ) - continue; - - dstsd->state.autocast = 1; - skill_consume_requirement(dstsd,skill_id,skill_lv,1); - switch (type) { - case CAST_GROUND: - skill_castend_pos2(bl, tbl->x, tbl->y, skill_id, skill_lv, tick, 0); - break; - case CAST_NODAMAGE: - skill_castend_nodamage_id(bl, tbl, skill_id, skill_lv, tick, 0); - break; - case CAST_DAMAGE: - skill_castend_damage_id(bl, tbl, skill_id, skill_lv, tick, 0); - break; - } - dstsd->state.autocast = 0; - //Set canact delay. [Skotlex] - ud = unit_bl2ud(bl); - if (ud) { - rate = skill_delayfix(bl, skill_id, skill_lv); - if (DIFF_TICK(ud->canact_tick, tick + rate) < 0){ - ud->canact_tick = tick+rate; - if ( battle_config.display_status_timers && dstsd ) - clif_status_change(bl, SI_ACTIONDELAY, 1, rate, 0, 0, 0); - } - } - } - } - - //Autobonus when attacked - if( dstsd && !status_isdead(bl) && dstsd->autobonus2[0].rate && !(skill_id && skill_get_nk(skill_id)&NK_NO_DAMAGE) ) - { - int i; - for( i = 0; i < ARRAYLENGTH(dstsd->autobonus2); i++ ) - { - if( rnd()%1000 >= dstsd->autobonus2[i].rate ) - continue; - if( dstsd->autobonus2[i].active != INVALID_TIMER ) - continue; - if(!(dstsd->autobonus2[i].atk_type&attack_type&BF_WEAPONMASK && - dstsd->autobonus2[i].atk_type&attack_type&BF_RANGEMASK && - dstsd->autobonus2[i].atk_type&attack_type&BF_SKILLMASK)) - continue; // one or more trigger conditions were not fulfilled - pc_exeautobonus(dstsd,&dstsd->autobonus2[i]); - } - } - - return 0; -} -/*========================================================================= - Breaks equipment. On-non players causes the corresponding strip effect. - - rate goes from 0 to 10000 (100.00%) - - flag is a BCT_ flag to indicate which type of adjustment should be used - (BCT_ENEMY/BCT_PARTY/BCT_SELF) are the valid values. ---------------------------------------------------------------------------*/ -int skill_break_equip (struct block_list *bl, unsigned short where, int rate, int flag) -{ - const int where_list[4] = {EQP_WEAPON, EQP_ARMOR, EQP_SHIELD, EQP_HELM}; - const enum sc_type scatk[4] = {SC_STRIPWEAPON, SC_STRIPARMOR, SC_STRIPSHIELD, SC_STRIPHELM}; - const enum sc_type scdef[4] = {SC_CP_WEAPON, SC_CP_ARMOR, SC_CP_SHIELD, SC_CP_HELM}; - struct status_change *sc = status_get_sc(bl); - int i,j; - TBL_PC *sd; - sd = BL_CAST(BL_PC, bl); - if (sc && !sc->count) - sc = NULL; - - if (sd) { - if (sd->bonus.unbreakable_equip) - where &= ~sd->bonus.unbreakable_equip; - if (sd->bonus.unbreakable) - rate -= rate*sd->bonus.unbreakable/100; - if (where&EQP_WEAPON) { - switch (sd->status.weapon) { - case W_FIST: //Bare fists should not break :P - case W_1HAXE: - case W_2HAXE: - case W_MACE: // Axes and Maces can't be broken [DracoRPG] - case W_2HMACE: - case W_STAFF: - case W_2HSTAFF: - case W_BOOK: //Rods and Books can't be broken [Skotlex] - case W_HUUMA: - where &= ~EQP_WEAPON; - } - } - } - if (flag&BCT_ENEMY) { - if (battle_config.equip_skill_break_rate != 100) - rate = rate*battle_config.equip_skill_break_rate/100; - } else if (flag&(BCT_PARTY|BCT_SELF)) { - if (battle_config.equip_self_break_rate != 100) - rate = rate*battle_config.equip_self_break_rate/100; - } - - for (i = 0; i < 4; i++) { - if (where&where_list[i]) { - if (sc && sc->count && sc->data[scdef[i]]) - where&=~where_list[i]; - else if (rnd()%10000 >= rate) - where&=~where_list[i]; - else if (!sd && !(status_get_mode(bl)&MD_BOSS)) //Cause Strip effect. - sc_start(bl,scatk[i],100,0,skill_get_time(status_sc2skill(scatk[i]),1)); - } - } - if (!where) //Nothing to break. - return 0; - if (sd) { - for (i = 0; i < EQI_MAX; i++) { - j = sd->equip_index[i]; - if (j < 0 || sd->status.inventory[j].attribute == 1 || !sd->inventory_data[j]) - continue; - - switch(i) { - case EQI_HEAD_TOP: //Upper Head - flag = (where&EQP_HELM); - break; - case EQI_ARMOR: //Body - flag = (where&EQP_ARMOR); - break; - case EQI_HAND_R: //Left/Right hands - case EQI_HAND_L: - flag = ( - (where&EQP_WEAPON && sd->inventory_data[j]->type == IT_WEAPON) || - (where&EQP_SHIELD && sd->inventory_data[j]->type == IT_ARMOR)); - break; - case EQI_SHOES: - flag = (where&EQP_SHOES); - break; - case EQI_GARMENT: - flag = (where&EQP_GARMENT); - break; - default: - continue; - } - if (flag) { - sd->status.inventory[j].attribute = 1; - pc_unequipitem(sd, j, 3); - } - } - clif_equiplist(sd); - } - - return where; //Return list of pieces broken. -} - -int skill_strip_equip(struct block_list *bl, unsigned short where, int rate, int lv, int time) -{ - struct status_change *sc; - const int pos[5] = {EQP_WEAPON, EQP_SHIELD, EQP_ARMOR, EQP_HELM, EQP_ACC}; - const enum sc_type sc_atk[5] = {SC_STRIPWEAPON, SC_STRIPSHIELD, SC_STRIPARMOR, SC_STRIPHELM, SC__STRIPACCESSORY}; - const enum sc_type sc_def[5] = {SC_CP_WEAPON, SC_CP_SHIELD, SC_CP_ARMOR, SC_CP_HELM, 0}; - int i; - - if (rnd()%100 >= rate) - return 0; - - sc = status_get_sc(bl); - if (!sc || sc->option&OPTION_MADOGEAR ) //Mado Gear cannot be divested [Ind] - return 0; - - for (i = 0; i < ARRAYLENGTH(pos); i++) { - if (where&pos[i] && sc->data[sc_def[i]]) - where&=~pos[i]; - } - if (!where) return 0; - - for (i = 0; i < ARRAYLENGTH(pos); i++) { - if (where&pos[i] && !sc_start(bl, sc_atk[i], 100, lv, time)) - where&=~pos[i]; - } - return where?1:0; -} -//Early declaration -static int skill_area_temp[8]; -/*========================================================================= - Used to knock back players, monsters, traps, etc - - 'count' is the number of squares to knock back - - 'direction' indicates the way OPPOSITE to the knockback direction (or -1 for default behavior) - - if 'flag&0x1', position update packets must not be sent. - - if 'flag&0x2', skill blown ignores players' special_state.no_knockback - -------------------------------------------------------------------------*/ -int skill_blown(struct block_list* src, struct block_list* target, int count, int8 dir, int flag) -{ - int dx = 0, dy = 0; - struct skill_unit* su = NULL; - - nullpo_ret(src); - - if (src != target && (map_flag_gvg(target->m) || map[target->m].flag.battleground)) - return 0; //No knocking back in WoE - if (count == 0) - return 0; //Actual knockback distance is 0. - - switch (target->type) { - case BL_MOB: { - struct mob_data* md = BL_CAST(BL_MOB, target); - if( md->class_ == MOBID_EMPERIUM ) - return 0; - if(src != target && is_boss(target)) //Bosses can't be knocked-back - return 0; - } - break; - case BL_PC: { - struct map_session_data *sd = BL_CAST(BL_PC, target); - if( sd->sc.data[SC_BASILICA] && sd->sc.data[SC_BASILICA]->val4 == sd->bl.id && !is_boss(src)) - return 0; // Basilica caster can't be knocked-back by normal monsters. - if( !(flag&0x2) && src != target && sd->special_state.no_knockback ) - return 0; - } - break; - case BL_SKILL: - su = (struct skill_unit *)target; - if( su && su->group && su->group->unit_id == UNT_ANKLESNARE ) - return 0; // ankle snare cannot be knocked back - break; - } - - if (dir == -1) // <optimized>: do the computation here instead of outside - dir = map_calc_dir(target, src->x, src->y); // direction from src to target, reversed - - if (dir >= 0 && dir < 8) - { // take the reversed 'direction' and reverse it - dx = -dirx[dir]; - dy = -diry[dir]; - } - - return unit_blown(target, dx, dy, count, flag); // send over the proper flag -} - - -//Checks if 'bl' should reflect back a spell cast by 'src'. -//type is the type of magic attack: 0: indirect (aoe), 1: direct (targetted) -static int skill_magic_reflect(struct block_list* src, struct block_list* bl, int type) -{ - struct status_change *sc = status_get_sc(bl); - struct map_session_data* sd = BL_CAST(BL_PC, bl); - - if( sc && sc->data[SC_KYOMU] ) // Nullify reflecting ability - return 0; - - // item-based reflection - if( sd && sd->bonus.magic_damage_return && type && rnd()%100 < sd->bonus.magic_damage_return ) - return 1; - - if( is_boss(src) ) - return 0; - - // status-based reflection - if( !sc || sc->count == 0 ) - return 0; - - if( sc->data[SC_MAGICMIRROR] && rnd()%100 < sc->data[SC_MAGICMIRROR]->val2 ) - return 1; - - if( sc->data[SC_KAITE] && (src->type == BL_PC || status_get_lv(src) <= 80) ) - {// Kaite only works against non-players if they are low-level. - clif_specialeffect(bl, 438, AREA); - if( --sc->data[SC_KAITE]->val2 <= 0 ) - status_change_end(bl, SC_KAITE, INVALID_TIMER); - return 2; - } - - return 0; -} - -/* - * ========================================================================= - * Does a skill attack with the given properties. - * src is the master behind the attack (player/mob/pet) - * dsrc is the actual originator of the damage, can be the same as src, or a BL_SKILL - * bl is the target to be attacked. - * flag can hold a bunch of information: - * flag&0xFFF is passed to the underlying battle_calc_attack for processing - * (usually holds number of targets, or just 1 for simple splash attacks) - * flag&0x1000 is used to tag that this is a splash-attack (so the damage - * packet shouldn't display a skill animation) - * flag&0x2000 is used to signal that the skill_lv should be passed as -1 to the - * client (causes player characters to not scream skill name) - *-------------------------------------------------------------------------*/ -int skill_attack (int attack_type, struct block_list* src, struct block_list *dsrc, struct block_list *bl, uint16 skill_id, uint16 skill_lv, unsigned int tick, int flag) -{ - struct Damage dmg; - struct status_data *sstatus, *tstatus; - struct status_change *sc; - struct map_session_data *sd, *tsd; - int type,damage,rdamage=0; - int8 rmdamage=0;//magic reflected - - if(skill_id > 0 && !skill_lv) return 0; - - nullpo_ret(src); //Source is the master behind the attack (player/mob/pet) - nullpo_ret(dsrc); //dsrc is the actual originator of the damage, can be the same as src, or a skill casted by src. - nullpo_ret(bl); //Target to be attacked. - - if (src != dsrc) { - //When caster is not the src of attack, this is a ground skill, and as such, do the relevant target checking. [Skotlex] - if (!status_check_skilluse(battle_config.skill_caster_check?src:NULL, bl, skill_id, 2)) - return 0; - } else if ((flag&SD_ANIMATION) && skill_get_nk(skill_id)&NK_SPLASH) { - //Note that splash attacks often only check versus the targetted mob, those around the splash area normally don't get checked for being hidden/cloaked/etc. [Skotlex] - if (!status_check_skilluse(src, bl, skill_id, 2)) - return 0; - } - - sd = BL_CAST(BL_PC, src); - tsd = BL_CAST(BL_PC, bl); - - sstatus = status_get_status_data(src); - tstatus = status_get_status_data(bl); - sc= status_get_sc(bl); - if (sc && !sc->count) sc = NULL; //Don't need it. - - // Is this check really needed? FrostNova won't hurt you if you step right where the caster is? - if(skill_id == WZ_FROSTNOVA && dsrc->x == bl->x && dsrc->y == bl->y) - return 0; - //Trick Dead protects you from damage, but not from buffs and the like, hence it's placed here. - if (sc && sc->data[SC_TRICKDEAD] && !(sstatus->mode&MD_BOSS)) - return 0; - - dmg = battle_calc_attack(attack_type,src,bl,skill_id,skill_lv,flag&0xFFF); - - //Skotlex: Adjusted to the new system - if(src->type==BL_PET) - { // [Valaris] - struct pet_data *pd = (TBL_PET*)src; - if (pd->a_skill && pd->a_skill->div_ && pd->a_skill->id == skill_id) - { - int element = skill_get_ele(skill_id, skill_lv); - /*if (skill_id == -1) Does it ever worked? - element = sstatus->rhw.ele;*/ - if (element != ELE_NEUTRAL || !(battle_config.attack_attr_none&BL_PET)) - dmg.damage=battle_attr_fix(src, bl, skill_lv, element, tstatus->def_ele, tstatus->ele_lv); - else - dmg.damage= skill_lv; - dmg.damage2=0; - dmg.div_= pd->a_skill->div_; - } - } - - if( dmg.flag&BF_MAGIC && ( skill_id != NPC_EARTHQUAKE || (battle_config.eq_single_target_reflectable && (flag&0xFFF) == 1) ) ) - { // Earthquake on multiple targets is not counted as a target skill. [Inkfish] - if( (dmg.damage || dmg.damage2) && (type = skill_magic_reflect(src, bl, src==dsrc)) ) - { //Magic reflection, switch caster/target - struct block_list *tbl = bl; - rmdamage = 1; - bl = src; - src = tbl; - sd = BL_CAST(BL_PC, src); - tsd = BL_CAST(BL_PC, bl); - sc = status_get_sc(bl); - if (sc && !sc->count) - sc = NULL; //Don't need it. - /* bugreport:2564 flag&2 disables double casting trigger */ - flag |= 2; - - //Spirit of Wizard blocks Kaite's reflection - if( type == 2 && sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_WIZARD ) - { //Consume one Fragment per hit of the casted skill? [Skotlex] - type = tsd?pc_search_inventory (tsd, 7321):0; - if (type >= 0) { - if ( tsd ) pc_delitem(tsd, type, 1, 0, 1, LOG_TYPE_CONSUME); - dmg.damage = dmg.damage2 = 0; - dmg.dmg_lv = ATK_MISS; - sc->data[SC_SPIRIT]->val3 = skill_id; - sc->data[SC_SPIRIT]->val4 = dsrc->id; - } - } - - /** - * Official Magic Reflection Behavior : damage reflected depends on gears caster wears, not target - **/ - #if MAGIC_REFLECTION_TYPE - if( dmg.dmg_lv != ATK_MISS )//Wiz SL cancelled and consumed fragment - dmg = battle_calc_attack(BF_MAGIC,bl,bl,skill_id,skill_lv,flag&0xFFF); - #endif - } - if(sc && sc->data[SC_MAGICROD] && src == dsrc) { - int sp = skill_get_sp(skill_id,skill_lv); - dmg.damage = dmg.damage2 = 0; - dmg.dmg_lv = ATK_MISS; //This will prevent skill additional effect from taking effect. [Skotlex] - sp = sp * sc->data[SC_MAGICROD]->val2 / 100; - if(skill_id == WZ_WATERBALL && skill_lv > 1) - sp = sp/((skill_lv|1)*(skill_lv|1)); //Estimate SP cost of a single water-ball - status_heal(bl, 0, sp, 2); - } - } - - damage = dmg.damage + dmg.damage2; - - if( (skill_id == AL_INCAGI || skill_id == AL_BLESSING || - skill_id == CASH_BLESSING || skill_id == CASH_INCAGI || - skill_id == MER_INCAGI || skill_id == MER_BLESSING) && tsd->sc.data[SC_CHANGEUNDEAD] ) - damage = 1; - - if( damage > 0 && (( dmg.flag&BF_WEAPON && src != bl && ( src == dsrc || ( dsrc->type == BL_SKILL && ( skill_id == SG_SUN_WARM || skill_id == SG_MOON_WARM || skill_id == SG_STAR_WARM ) ) )) - || (sc && sc->data[SC_REFLECTDAMAGE])) ) - rdamage = battle_calc_return_damage(bl,src, &damage, dmg.flag, skill_id); - - if( damage && sc && sc->data[SC_GENSOU] && dmg.flag&BF_MAGIC ){ - struct block_list *nbl = NULL; - nbl = battle_getenemyarea(bl,bl->x,bl->y,2,BL_CHAR,bl->id); - if( nbl ){ // Only one target is chosen. - damage = damage / 2; // Deflect half of the damage to a target nearby - clif_skill_damage(bl, nbl, tick, status_get_amotion(src), 0, status_fix_damage(bl,nbl,damage,0), dmg.div_, OB_OBOROGENSOU_TRANSITION_ATK, -1, 6); - } - } - - //Skill hit type - type=(skill_id==0)?5:skill_get_hit(skill_id); - - if(damage < dmg.div_ - //Only skills that knockback even when they miss. [Skotlex] - && skill_id != CH_PALMSTRIKE) - dmg.blewcount = 0; - - if(skill_id == CR_GRANDCROSS||skill_id == NPC_GRANDDARKNESS) { - if(battle_config.gx_disptype) dsrc = src; - if(src == bl) type = 4; - else flag|=SD_ANIMATION; - } - if(skill_id == NJ_TATAMIGAESHI) { - dsrc = src; //For correct knockback. - flag|=SD_ANIMATION; - } - - if(sd) { - int flag = 0; //Used to signal if this skill can be combo'ed later on. - struct status_change_entry *sce; - if ((sce = sd->sc.data[SC_COMBO])) {//End combo state after skill is invoked. [Skotlex] - switch (skill_id) { - case TK_TURNKICK: - case TK_STORMKICK: - case TK_DOWNKICK: - case TK_COUNTER: - if (pc_famerank(sd->status.char_id,MAPID_TAEKWON)) {//Extend combo time. - sce->val1 = skill_id; //Update combo-skill - sce->val3 = skill_id; - if( sce->timer != INVALID_TIMER ) - delete_timer(sce->timer, status_change_timer); - sce->timer = add_timer(tick+sce->val4, status_change_timer, src->id, SC_COMBO); - break; - } - unit_cancel_combo(src); // Cancel combo wait - break; - default: - if( src == dsrc ) // Ground skills are exceptions. [Inkfish] - status_change_end(src, SC_COMBO, INVALID_TIMER); - } - } - switch(skill_id) { - case MO_TRIPLEATTACK: - if (pc_checkskill(sd, MO_CHAINCOMBO) > 0 || pc_checkskill(sd, SR_DRAGONCOMBO) > 0) - flag=1; - break; - case MO_CHAINCOMBO: - if(pc_checkskill(sd, MO_COMBOFINISH) > 0 && sd->spiritball > 0) - flag=1; - break; - case MO_COMBOFINISH: - if (sd->status.party_id>0) //bonus from SG_FRIEND [Komurka] - party_skill_check(sd, sd->status.party_id, MO_COMBOFINISH, skill_lv); - if (pc_checkskill(sd, CH_TIGERFIST) > 0 && sd->spiritball > 0) - flag=1; - case CH_TIGERFIST: - if (!flag && pc_checkskill(sd, CH_CHAINCRUSH) > 0 && sd->spiritball > 1) - flag=1; - case CH_CHAINCRUSH: - if (!flag && pc_checkskill(sd, MO_EXTREMITYFIST) > 0 && sd->spiritball > 0 && sd->sc.data[SC_EXPLOSIONSPIRITS]) - flag=1; - break; - case AC_DOUBLE: - if( (tstatus->race == RC_BRUTE || tstatus->race == RC_INSECT) && pc_checkskill(sd, HT_POWER)) - { - //TODO: This code was taken from Triple Blows, is this even how it should be? [Skotlex] - sc_start2(src,SC_COMBO,100,HT_POWER,bl->id,2000); - clif_combo_delay(src,2000); - } - break; - case TK_COUNTER: - { //bonus from SG_FRIEND [Komurka] - int level; - if(sd->status.party_id>0 && (level = pc_checkskill(sd,SG_FRIEND))) - party_skill_check(sd, sd->status.party_id, TK_COUNTER,level); - } - break; - case SL_STIN: - case SL_STUN: - if (skill_lv >= 7 && !sd->sc.data[SC_SMA]) - sc_start(src,SC_SMA,100,skill_lv,skill_get_time(SL_SMA, skill_lv)); - break; - case GS_FULLBUSTER: - //Can't attack nor use items until skill's delay expires. [Skotlex] - sd->ud.attackabletime = sd->canuseitem_tick = sd->ud.canact_tick; - break; - case SR_DRAGONCOMBO: - if( pc_checkskill(sd, SR_FALLENEMPIRE) > 0 ) - flag = 1; - break; - case SR_FALLENEMPIRE: - if( pc_checkskill(sd, SR_TIGERCANNON) > 0 || pc_checkskill(sd, SR_GATEOFHELL) > 0 ) - flag = 1; - break; - } //Switch End - if (flag) { //Possible to chain - flag = DIFF_TICK(sd->ud.canact_tick, tick); - if (flag < 1) flag = 1; - sc_start2(src,SC_COMBO,100,skill_id,bl->id,flag); - clif_combo_delay(src, flag); - } - } - - //Display damage. - switch( skill_id ) - { - case PA_GOSPEL: //Should look like Holy Cross [Skotlex] - dmg.dmotion = clif_skill_damage(dsrc,bl,tick,dmg.amotion,dmg.dmotion, damage, dmg.div_, CR_HOLYCROSS, -1, 5); - break; - //Skills that need be passed as a normal attack for the client to display correctly. - case HVAN_EXPLOSION: - case NPC_SELFDESTRUCTION: - if(src->type==BL_PC) - dmg.blewcount = 10; - dmg.amotion = 0; //Disable delay or attack will do no damage since source is dead by the time it takes effect. [Skotlex] - // fall through - case KN_AUTOCOUNTER: - case NPC_CRITICALSLASH: - case TF_DOUBLE: - case GS_CHAINACTION: - dmg.dmotion = clif_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,dmg.type,dmg.damage2); - break; - - case AS_SPLASHER: - if( flag&SD_ANIMATION ) // the surrounding targets - dmg.dmotion = clif_skill_damage(dsrc,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, -1, 5); // needs -1 as skill level - else // the central target doesn't display an animation - dmg.dmotion = clif_skill_damage(dsrc,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, -2, 5); // needs -2(!) as skill level - break; - case WL_HELLINFERNO: - case SR_EARTHSHAKER: - dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,1,skill_id,-2,6); - break; - case WL_SOULEXPANSION: - case WL_COMET: - case KO_MUCHANAGE: - case NJ_HUUMA: - dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,skill_id,skill_lv,8); - break; - case WL_CHAINLIGHTNING_ATK: - dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,1,WL_CHAINLIGHTNING,-2,6); - break; - case LG_OVERBRAND_BRANDISH: - case LG_OVERBRAND_PLUSATK: - case EL_FIRE_BOMB: - case EL_FIRE_BOMB_ATK: - case EL_FIRE_WAVE: - case EL_FIRE_WAVE_ATK: - case EL_FIRE_MANTLE: - case EL_CIRCLE_OF_FIRE: - case EL_FIRE_ARROW: - case EL_ICE_NEEDLE: - case EL_WATER_SCREW: - case EL_WATER_SCREW_ATK: - case EL_WIND_SLASH: - case EL_TIDAL_WEAPON: - case EL_ROCK_CRUSHER: - case EL_ROCK_CRUSHER_ATK: - case EL_HURRICANE: - case EL_HURRICANE_ATK: - case KO_BAKURETSU: - case GN_CRAZYWEED_ATK: - dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,skill_id,-1,5); - break; - case GN_SLINGITEM_RANGEMELEEATK: - dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,GN_SLINGITEM,-2,6); - break; - case EL_STONE_RAIN: - dmg.dmotion = clif_skill_damage(dsrc,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,skill_id,-1,(flag&1)?8:5); - break; - case WM_SEVERE_RAINSTORM_MELEE: - dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,WM_SEVERE_RAINSTORM,skill_lv,5); - break; - case WM_REVERBERATION_MELEE: - case WM_REVERBERATION_MAGIC: - dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,WM_REVERBERATION,-2,6); - break; - case HT_CLAYMORETRAP: - case HT_BLASTMINE: - case HT_FLASHER: - case HT_FREEZINGTRAP: - case RA_CLUSTERBOMB: - case RA_FIRINGTRAP: - case RA_ICEBOUNDTRAP: - dmg.dmotion = clif_skill_damage(src,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id,flag&SD_LEVEL?-1:skill_lv, 5); - if( dsrc != src ) // avoid damage display redundancy - break; - case HT_LANDMINE: - dmg.dmotion = clif_skill_damage(dsrc,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, -1, type); - break; - case WZ_SIGHTBLASTER: - dmg.dmotion = clif_skill_damage(src,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, flag&SD_LEVEL?-1:skill_lv, 5); - break; - case AB_DUPLELIGHT_MELEE: - case AB_DUPLELIGHT_MAGIC: - dmg.amotion = 300;/* makes the damage value not overlap with previous damage (when displayed by the client) */ - default: - if( flag&SD_ANIMATION && dmg.div_ < 2 ) //Disabling skill animation doesn't works on multi-hit. - type = 5; - if( bl->type == BL_SKILL ){ - TBL_SKILL *su = (TBL_SKILL*)bl; - if( su->group && skill_get_inf2(su->group->skill_id)&INF2_TRAP )// show damage on trap targets - clif_skill_damage(src,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, flag&SD_LEVEL?-1:skill_lv, 5); - } - dmg.dmotion = clif_skill_damage(dsrc,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, flag&SD_LEVEL?-1:skill_lv, type); - break; - } - - map_freeblock_lock(); - - if(damage > 0 && dmg.flag&BF_SKILL && tsd - && pc_checkskill(tsd,RG_PLAGIARISM) - && (!sc || !sc->data[SC_PRESERVE]) - && damage < tsd->battle_status.hp) - { //Updated to not be able to copy skills if the blow will kill you. [Skotlex] - int copy_skill = skill_id; - /** - * Copy Referal: dummy skills should point to their source upon copying - **/ - switch( skill_id ) { - case AB_DUPLELIGHT_MELEE: - case AB_DUPLELIGHT_MAGIC: - copy_skill = AB_DUPLELIGHT; - break; - case WL_CHAINLIGHTNING_ATK: - copy_skill = WL_CHAINLIGHTNING; - break; - case WM_REVERBERATION_MELEE: - case WM_REVERBERATION_MAGIC: - copy_skill = WM_REVERBERATION; - break; - case WM_SEVERE_RAINSTORM_MELEE: - copy_skill = WM_SEVERE_RAINSTORM; - break; - case GN_CRAZYWEED_ATK: - copy_skill = GN_CRAZYWEED; - break; - case GN_HELLS_PLANT_ATK: - copy_skill = GN_HELLS_PLANT; - break; - case LG_OVERBRAND_BRANDISH: - case LG_OVERBRAND_PLUSATK: - copy_skill = LG_OVERBRAND; - break; - } - - if ((tsd->status.skill[copy_skill].id == 0 || tsd->status.skill[copy_skill].flag == SKILL_FLAG_PLAGIARIZED) && - can_copy(tsd,copy_skill,bl)) // Split all the check into their own function [Aru] - { - int lv; - if( sc && sc->data[SC__REPRODUCE] && (lv = sc->data[SC__REPRODUCE]->val1) ) { - //Level dependent and limitation. - lv = min(lv,skill_get_max(copy_skill)); - if( tsd->reproduceskill_id && tsd->status.skill[tsd->reproduceskill_id].flag == SKILL_FLAG_PLAGIARIZED ) { - tsd->status.skill[tsd->reproduceskill_id].id = 0; - tsd->status.skill[tsd->reproduceskill_id].lv = 0; - tsd->status.skill[tsd->reproduceskill_id].flag = 0; - clif_deleteskill(tsd,tsd->reproduceskill_id); - } - - tsd->reproduceskill_id = copy_skill; - pc_setglobalreg(tsd, "REPRODUCE_SKILL", copy_skill); - pc_setglobalreg(tsd, "REPRODUCE_SKILL_LV", lv); - - tsd->status.skill[copy_skill].id = copy_skill; - tsd->status.skill[copy_skill].lv = lv; - tsd->status.skill[copy_skill].flag = SKILL_FLAG_PLAGIARIZED; - clif_addskill(tsd,copy_skill); - } else { - lv = skill_lv; - if (tsd->cloneskill_id && tsd->status.skill[tsd->cloneskill_id].flag == SKILL_FLAG_PLAGIARIZED){ - tsd->status.skill[tsd->cloneskill_id].id = 0; - tsd->status.skill[tsd->cloneskill_id].lv = 0; - tsd->status.skill[tsd->cloneskill_id].flag = 0; - clif_deleteskill(tsd,tsd->cloneskill_id); - } - - if ((type = pc_checkskill(tsd,RG_PLAGIARISM)) < lv) - lv = type; - - tsd->cloneskill_id = copy_skill; - pc_setglobalreg(tsd, "CLONE_SKILL", copy_skill); - pc_setglobalreg(tsd, "CLONE_SKILL_LV", lv); - - tsd->status.skill[skill_id].id = copy_skill; - tsd->status.skill[skill_id].lv = lv; - tsd->status.skill[skill_id].flag = SKILL_FLAG_PLAGIARIZED; - clif_addskill(tsd,skill_id); - } - } - } - - if (dmg.dmg_lv >= ATK_MISS && (type = skill_get_walkdelay(skill_id, skill_lv)) > 0) - { //Skills with can't walk delay also stop normal attacking for that - //duration when the attack connects. [Skotlex] - struct unit_data *ud = unit_bl2ud(src); - if (ud && DIFF_TICK(ud->attackabletime, tick + type) < 0) - ud->attackabletime = tick + type; - } - - if( !dmg.amotion ) - { //Instant damage - if( !sc || (!sc->data[SC_DEVOTION] && skill_id != CR_REFLECTSHIELD) ) - status_fix_damage(src,bl,damage,dmg.dmotion); //Deal damage before knockback to allow stuff like firewall+storm gust combo. - if( !status_isdead(bl) ) - skill_additional_effect(src,bl,skill_id,skill_lv,dmg.flag,dmg.dmg_lv,tick); - if( damage > 0 ) //Counter status effects [Skotlex] - skill_counter_additional_effect(src,bl,skill_id,skill_lv,dmg.flag,tick); - } - // Hell Inferno burning status only starts if Fire part hits. - if( skill_id == WL_HELLINFERNO && dmg.damage > 0 ) - sc_start4(bl,SC_BURNING,55+5*skill_lv,skill_lv,1000,src->id,0,skill_get_time(skill_id,skill_lv)); - // Apply knock back chance in SC_TRIANGLESHOT skill. - else if( skill_id == SC_TRIANGLESHOT && rnd()%100 > (1 + skill_lv) ) - dmg.blewcount = 0; - - //Only knockback if it's still alive, otherwise a "ghost" is left behind. [Skotlex] - //Reflected spells do not bounce back (bl == dsrc since it only happens for direct skills) - if (dmg.blewcount > 0 && bl!=dsrc && !status_isdead(bl)) { - int8 dir = -1; // default - switch(skill_id) {//direction - case MG_FIREWALL: - case PR_SANCTUARY: - case SC_TRIANGLESHOT: - case LG_OVERBRAND: - case SR_KNUCKLEARROW: - case GN_WALLOFTHORN: - case EL_FIRE_MANTLE: - dir = unit_getdir(bl);// backwards - break; - // This ensures the storm randomly pushes instead of exactly a cell backwards per official mechanics. - case WZ_STORMGUST: - dir = rand()%8; - break; - case WL_CRIMSONROCK: - dir = map_calc_dir(bl,skill_area_temp[4],skill_area_temp[5]); - break; - - } - //blown-specific handling - switch( skill_id ) { - case LG_OVERBRAND: - if( skill_blown(dsrc,bl,dmg.blewcount,dir,0) ) { - short dir_x, dir_y; - dir_x = dirx[(dir+4)%8]; - dir_y = diry[(dir+4)%8]; - if( map_getcell(bl->m, bl->x+dir_x, bl->y+dir_y, CELL_CHKNOPASS) != 0 ) - skill_addtimerskill(src, tick + status_get_amotion(src), bl->id, 0, 0, LG_OVERBRAND_PLUSATK, skill_lv, BF_WEAPON, flag ); - } else - skill_addtimerskill(src, tick + status_get_amotion(src), bl->id, 0, 0, LG_OVERBRAND_PLUSATK, skill_lv, BF_WEAPON, flag ); - break; - case SR_KNUCKLEARROW: - if( skill_blown(dsrc,bl,dmg.blewcount,dir,0) && !(flag&4) ) { - short dir_x, dir_y; - dir_x = dirx[(dir+4)%8]; - dir_y = diry[(dir+4)%8]; - if( map_getcell(bl->m, bl->x+dir_x, bl->y+dir_y, CELL_CHKNOPASS) != 0 ) - skill_addtimerskill(src, tick + 300 * ((flag&2) ? 1 : 2), bl->id, 0, 0, skill_id, skill_lv, BF_WEAPON, flag|4); - } - break; - case GN_WALLOFTHORN: - unit_stop_walking(bl,1); - skill_blown(dsrc,bl,dmg.blewcount,dir, 0x2 ); - clif_fixpos(bl); - break; - default: - skill_blown(dsrc,bl,dmg.blewcount,dir, 0x0 ); - if ( !dmg.blewcount && bl->type == BL_SKILL && damage > 0 ){ - TBL_SKILL *su = (TBL_SKILL*)bl; - if( su->group && su->group->skill_id == HT_BLASTMINE) - skill_blown(src, bl, 3, -1, 0); - } - break; - } - } - - //Delayed damage must be dealt after the knockback (it needs to know actual position of target) - if (dmg.amotion) - battle_delay_damage(tick, dmg.amotion,src,bl,dmg.flag,skill_id,skill_lv,damage,dmg.dmg_lv,dmg.dmotion); - - if( sc && sc->data[SC_DEVOTION] && skill_id != PA_PRESSURE ) - { - struct status_change_entry *sce = sc->data[SC_DEVOTION]; - struct block_list *d_bl = map_id2bl(sce->val1); - - if( d_bl && ( - (d_bl->type == BL_MER && ((TBL_MER*)d_bl)->master && ((TBL_MER*)d_bl)->master->bl.id == bl->id) || - (d_bl->type == BL_PC && ((TBL_PC*)d_bl)->devotion[sce->val2] == bl->id) - ) && check_distance_bl(bl, d_bl, sce->val3) ) - { - if(!rmdamage){ - clif_damage(d_bl,d_bl, gettick(), 0, 0, damage, 0, 0, 0); - status_fix_damage(NULL,d_bl, damage, 0); - } - else{//Reflected magics are done directly on the target not on paladin - //This check is only for magical skill. - //For BF_WEAPON skills types track var rdamage and function battle_calc_return_damage - clif_damage(bl,bl, gettick(), 0, 0, damage, 0, 0, 0); - status_fix_damage(bl,bl, damage, 0); - } - } - else { - status_change_end(bl, SC_DEVOTION, INVALID_TIMER); - if( !dmg.amotion ) - status_fix_damage(src,bl,damage,dmg.dmotion); - } - } - - if(damage > 0 && !(tstatus->mode&MD_BOSS)) { - if( skill_id == RG_INTIMIDATE ) { - int rate = 50 + skill_lv * 5; - rate = rate + (status_get_lv(src) - status_get_lv(bl)); - if(rnd()%100 < rate) - skill_addtimerskill(src,tick + 800,bl->id,0,0,skill_id,skill_lv,0,flag); - } else if( skill_id == SC_FATALMENACE ) - skill_addtimerskill(src,tick + 800,bl->id,skill_area_temp[4],skill_area_temp[5],skill_id,skill_lv,0,flag); - } - - if(skill_id == CR_GRANDCROSS || skill_id == NPC_GRANDDARKNESS) - dmg.flag |= BF_WEAPON; - - if( sd && src != bl && damage > 0 && ( dmg.flag&BF_WEAPON || - (dmg.flag&BF_MISC && (skill_id == RA_CLUSTERBOMB || skill_id == RA_FIRINGTRAP || skill_id == RA_ICEBOUNDTRAP || skill_id == RK_DRAGONBREATH)) ) ) - { - if (battle_config.left_cardfix_to_right) - battle_drain(sd, bl, dmg.damage, dmg.damage, tstatus->race, tstatus->mode&MD_BOSS); - else - battle_drain(sd, bl, dmg.damage, dmg.damage2, tstatus->race, tstatus->mode&MD_BOSS); - } - - if( rdamage > 0 ) { - if( sc && sc->data[SC_REFLECTDAMAGE] ) { - if( src != bl )// Don't reflect your own damage (Grand Cross) - map_foreachinshootrange(battle_damage_area,bl,skill_get_splash(LG_REFLECTDAMAGE,1),BL_CHAR,tick,bl,dmg.amotion,sstatus->dmotion,rdamage,tstatus->race); - } else { - if( dmg.amotion ) - battle_delay_damage(tick, dmg.amotion,bl,src,0,CR_REFLECTSHIELD,0,rdamage,ATK_DEF,0); - else - status_fix_damage(bl,src,rdamage,0); - clif_damage(src,src,tick, dmg.amotion,0,rdamage,1,4,0); // in aegis damage reflected is shown in single hit. - //Use Reflect Shield to signal this kind of skill trigger. [Skotlex] - if( tsd && src != bl ) - battle_drain(tsd, src, rdamage, rdamage, sstatus->race, is_boss(src)); - skill_additional_effect(bl, src, CR_REFLECTSHIELD, 1, BF_WEAPON|BF_SHORT|BF_NORMAL,ATK_DEF,tick); - } - } - if( damage > 0 ) { - /** - * Post-damage effects - **/ - switch( skill_id ) { - case RK_CRUSHSTRIKE: - skill_break_equip(src,EQP_WEAPON,2000,BCT_SELF); // 20% chance to destroy the weapon. - break; - case GC_VENOMPRESSURE: { - struct status_change *ssc = status_get_sc(src); - if( ssc && ssc->data[SC_POISONINGWEAPON] && rnd()%100 < 70 + 5*skill_lv ) { - sc_start(bl,ssc->data[SC_POISONINGWEAPON]->val2,100,ssc->data[SC_POISONINGWEAPON]->val1,skill_get_time2(GC_POISONINGWEAPON, 1)); - status_change_end(src,SC_POISONINGWEAPON,INVALID_TIMER); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - } - } - break; - case WM_METALICSOUND: - status_zap(bl, 0, damage*100/(100*(110-pc_checkskill(sd,WM_LESSON)*10))); - break; - case SR_TIGERCANNON: - status_zap(bl, 0, damage/10); // 10% of damage dealt - break; - } - if( sd ) - skill_onskillusage(sd, bl, skill_id, tick); - } - - if (!(flag&2) && - ( - skill_id == MG_COLDBOLT || skill_id == MG_FIREBOLT || skill_id == MG_LIGHTNINGBOLT - ) && - (sc = status_get_sc(src)) && - sc->data[SC_DOUBLECAST] && - rnd() % 100 < sc->data[SC_DOUBLECAST]->val2) - { -// skill_addtimerskill(src, tick + dmg.div_*dmg.amotion, bl->id, 0, 0, skill_id, skill_lv, BF_MAGIC, flag|2); - skill_addtimerskill(src, tick + dmg.amotion, bl->id, 0, 0, skill_id, skill_lv, BF_MAGIC, flag|2); - } - - map_freeblock_unlock(); - - return damage; -} - -/*========================================== - * sub fonction for recursive skill call. - * Checking bl battle flag and display dammage - * then call func with source,target,skill_id,skill_lv,tick,flag - *------------------------------------------*/ -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; - uint16 skill_id,skill_lv; - int flag; - unsigned int tick; - SkillFunc func; - - nullpo_ret(bl); - - src=va_arg(ap,struct block_list *); - 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) - { - // several splash skills need this initial dummy packet to display correctly - if (flag&SD_PREAMBLE && skill_area_temp[2] == 0) - clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - - if (flag&(SD_SPLASH|SD_PREAMBLE)) - skill_area_temp[2]++; - - return 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; - uint16 skill_id,g_skill_id; - - unit = (struct skill_unit *)bl; - - if(bl->prev == NULL || bl->type != BL_SKILL) - return 0; - - if(!unit->alive) - return 0; - - skill_id = va_arg(ap,int); - g_skill_id = unit->group->skill_id; - - switch (skill_id) { - case MH_STEINWAND: - case MG_SAFETYWALL: - case AL_PNEUMA: - case SC_MAELSTROM: - if(g_skill_id != MH_STEINWAND && g_skill_id != MG_SAFETYWALL && g_skill_id != AL_PNEUMA && g_skill_id != SC_MAELSTROM) - return 0; - break; - case AL_WARP: - case HT_SKIDTRAP: - case MA_SKIDTRAP: - case HT_LANDMINE: - case MA_LANDMINE: - case HT_ANKLESNARE: - case HT_SHOCKWAVE: - case HT_SANDMAN: - case MA_SANDMAN: - case HT_FLASHER: - case HT_FREEZINGTRAP: - case MA_FREEZINGTRAP: - case HT_BLASTMINE: - case HT_CLAYMORETRAP: - case HT_TALKIEBOX: - case HP_BASILICA: - case RA_ELECTRICSHOCKER: - case RA_CLUSTERBOMB: - case RA_MAGENTATRAP: - case RA_COBALTTRAP: - case RA_MAIZETRAP: - case RA_VERDURETRAP: - case RA_FIRINGTRAP: - case RA_ICEBOUNDTRAP: - case SC_DIMENSIONDOOR: - case SC_BLOODYLUST: - //Non stackable on themselves and traps (including venom dust which does not has the trap inf2 set) - if (skill_id != g_skill_id && !(skill_get_inf2(g_skill_id)&INF2_TRAP) && g_skill_id != AS_VENOMDUST && g_skill_id != MH_POISON_MIST) - return 0; - break; - default: //Avoid stacking with same kind of trap. [Skotlex] - if (g_skill_id != skill_id) - return 0; - break; - } - - return 1; -} - -static int skill_check_unit_range (struct block_list *bl, int x, int y, uint16 skill_id, uint16 skill_lv) -{ - //Non players do not check for the skill's splash-trigger area. - int range = bl->type==BL_PC?skill_get_unit_range(skill_id, skill_lv):0; - int layout_type = skill_get_unit_layout_type(skill_id,skill_lv); - if (layout_type==-1 || layout_type>MAX_SQUARE_LAYOUT) { - ShowError("skill_check_unit_range: unsupported layout type %d for skill %d\n",layout_type,skill_id); - return 0; - } - - range += layout_type; - return map_foreachinarea(skill_check_unit_range_sub,bl->m,x-range,y-range,x+range,y+range,BL_SKILL,skill_id); -} - -static int skill_check_unit_range2_sub (struct block_list *bl, va_list ap) -{ - uint16 skill_id; - - if(bl->prev == NULL) - return 0; - - skill_id = va_arg(ap,int); - - if( status_isdead(bl) && skill_id != AL_WARP ) - return 0; - - if( skill_id == HP_BASILICA && bl->type == BL_PC ) - return 0; - - if( skill_id == AM_DEMONSTRATION && bl->type == BL_MOB && ((TBL_MOB*)bl)->class_ == MOBID_EMPERIUM ) - return 0; //Allow casting Bomb/Demonstration Right under emperium [Skotlex] - return 1; -} - -static int skill_check_unit_range2 (struct block_list *bl, int x, int y, uint16 skill_id, uint16 skill_lv) -{ - int range, type; - - switch (skill_id) { // to be expanded later - case WZ_ICEWALL: - range = 2; - break; - default: - { - int layout_type = skill_get_unit_layout_type(skill_id,skill_lv); - if (layout_type==-1 || layout_type>MAX_SQUARE_LAYOUT) { - ShowError("skill_check_unit_range2: unsupported layout type %d for skill %d\n",layout_type,skill_id); - return 0; - } - range = skill_get_unit_range(skill_id,skill_lv) + layout_type; - } - break; - } - - // if the caster is a monster/NPC, only check for players - // otherwise just check characters - if (bl->type == BL_PC) - type = BL_CHAR; - else - type = BL_PC; - - return map_foreachinarea(skill_check_unit_range2_sub, bl->m, - x - range, y - range, x + range, y + range, - type, skill_id); -} - -int skill_guildaura_sub (struct map_session_data* sd, int id, int strvit, int agidex) -{ - if(id == sd->bl.id && battle_config.guild_aura&16) - return 0; // Do not affect guild leader - - if (sd->sc.data[SC_GUILDAURA]) { - struct status_change_entry *sce = sd->sc.data[SC_GUILDAURA]; - if( sce->val3 != strvit || sce->val4 != agidex ) { - sce->val3 = strvit; - sce->val4 = agidex; - status_calc_bl(&sd->bl, status_sc2scb_flag(SC_GUILDAURA)); - } - return 0; - } - sc_start4(&sd->bl, SC_GUILDAURA,100, 1, id, strvit, agidex, 1000); - return 1; -} - -/*========================================== - * Checks that you have the requirements for casting a skill for homunculus/mercenary. - * Flag: - * &1: finished casting the skill (invoke hp/sp/item consumption) - * &2: picked menu entry (Warp Portal, Teleport and other menu based skills) - *------------------------------------------*/ -static int skill_check_condition_mercenary(struct block_list *bl, int skill, int lv, int type) -{ - struct status_data *status; - struct map_session_data *sd = NULL; - int i, hp, sp, hp_rate, sp_rate, state, mhp; - uint16 idx; - int itemid[MAX_SKILL_ITEM_REQUIRE],amount[ARRAYLENGTH(itemid)],index[ARRAYLENGTH(itemid)]; - - if( lv < 1 || lv > MAX_SKILL_LEVEL ) - return 0; - nullpo_ret(bl); - - switch( bl->type ) - { - case BL_HOM: sd = ((TBL_HOM*)bl)->master; break; - case BL_MER: sd = ((TBL_MER*)bl)->master; break; - } - - status = status_get_status_data(bl); - if( (idx = skill_get_index(skill)) == 0 ) - return 0; - - // Requeriments - for( i = 0; i < ARRAYLENGTH(itemid); i++ ) - { - itemid[i] = skill_db[idx].itemid[i]; - amount[i] = skill_db[idx].amount[i]; - } - hp = skill_db[idx].hp[lv-1]; - sp = skill_db[idx].sp[lv-1]; - hp_rate = skill_db[idx].hp_rate[lv-1]; - sp_rate = skill_db[idx].sp_rate[lv-1]; - state = skill_db[idx].state; - if( (mhp = skill_db[idx].mhp[lv-1]) > 0 ) - hp += (status->max_hp * mhp) / 100; - if( hp_rate > 0 ) - hp += (status->hp * hp_rate) / 100; - else - hp += (status->max_hp * (-hp_rate)) / 100; - if( sp_rate > 0 ) - sp += (status->sp * sp_rate) / 100; - else - sp += (status->max_sp * (-sp_rate)) / 100; - - if( bl->type == BL_HOM ) - { // Intimacy Requeriments - struct homun_data *hd = BL_CAST(BL_HOM, bl); - switch( skill ) - { - case HFLI_SBR44: - if( hd->homunculus.intimacy <= 200 ) - return 0; - break; - case HVAN_EXPLOSION: - if( hd->homunculus.intimacy < (unsigned int)battle_config.hvan_explosion_intimate ) - return 0; - break; - } - } - - if( !(type&2) ) - { - if( hp > 0 && status->hp <= (unsigned int)hp ) - { - clif_skill_fail(sd, skill, USESKILL_FAIL_HP_INSUFFICIENT, 0); - return 0; - } - if( sp > 0 && status->sp <= (unsigned int)sp ) - { - clif_skill_fail(sd, skill, USESKILL_FAIL_SP_INSUFFICIENT, 0); - return 0; - } - } - - if( !type ) - switch( state ) - { - case ST_MOVE_ENABLE: - if( !unit_can_move(bl) ) - { - clif_skill_fail(sd, skill, USESKILL_FAIL_LEVEL, 0); - return 0; - } - break; - } - if( !(type&1) ) - return 1; - - // Check item existences - for( i = 0; i < ARRAYLENGTH(itemid); i++ ) - { - index[i] = -1; - if( itemid[i] < 1 ) continue; // No item - index[i] = pc_search_inventory(sd, itemid[i]); - if( index[i] < 0 || sd->status.inventory[index[i]].amount < amount[i] ) - { - clif_skill_fail(sd, skill, USESKILL_FAIL_LEVEL, 0); - return 0; - } - } - - // Consume items - for( i = 0; i < ARRAYLENGTH(itemid); i++ ) - { - if( index[i] >= 0 ) pc_delitem(sd, index[i], amount[i], 0, 1, LOG_TYPE_CONSUME); - } - - if( type&2 ) - return 1; - - if( sp || hp ) - status_zap(bl, hp, sp); - - return 1; -} - -/*========================================== - * - *------------------------------------------*/ -int skill_area_sub_count (struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv, unsigned int tick, int flag) -{ - return 1; -} - -/*========================================== - * - *------------------------------------------*/ -static int skill_timerskill(int tid, unsigned int tick, int id, intptr_t data) -{ - struct block_list *src = map_id2bl(id),*target; - struct unit_data *ud = unit_bl2ud(src); - struct skill_timerskill *skl = NULL; - int range; - - nullpo_ret(src); - nullpo_ret(ud); - skl = ud->skilltimerskill[data]; - nullpo_ret(skl); - ud->skilltimerskill[data] = NULL; - - do { - if(src->prev == NULL) - break; // Source not on Map - if(skl->target_id) { - target = map_id2bl(skl->target_id); - if( ( skl->skill_id == RG_INTIMIDATE || skl->skill_id == SC_FATALMENACE ) && (!target || target->prev == NULL || !check_distance_bl(src,target,AREA_SIZE)) ) - target = src; //Required since it has to warp. - if(target == NULL) - break; // Target offline? - if(target->prev == NULL) - break; // Target not on Map - if(src->m != target->m) - break; // Different Maps - if(status_isdead(src)) - break; // Caster is Dead - if(status_isdead(target) && skl->skill_id != RG_INTIMIDATE && skl->skill_id != WZ_WATERBALL) - break; - - switch(skl->skill_id) { - case RG_INTIMIDATE: - if (unit_warp(src,-1,-1,-1,CLR_TELEPORT) == 0) { - short x,y; - map_search_freecell(src, 0, &x, &y, 1, 1, 0); - if (target != src && !status_isdead(target)) - unit_warp(target, -1, x, y, CLR_TELEPORT); - } - break; - case BA_FROSTJOKER: - case DC_SCREAM: - range= skill_get_splash(skl->skill_id, skl->skill_lv); - map_foreachinarea(skill_frostjoke_scream,skl->map,skl->x-range,skl->y-range, - skl->x+range,skl->y+range,BL_CHAR,src,skl->skill_id,skl->skill_lv,tick); - break; - case NPC_EARTHQUAKE: - if( skl->type > 1 ) - skill_addtimerskill(src,tick+250,src->id,0,0,skl->skill_id,skl->skill_lv,skl->type-1,skl->flag); - skill_area_temp[0] = map_foreachinrange(skill_area_sub, src, skill_get_splash(skl->skill_id, skl->skill_lv), BL_CHAR, src, skl->skill_id, skl->skill_lv, tick, BCT_ENEMY, skill_area_sub_count); - skill_area_temp[1] = src->id; - skill_area_temp[2] = 0; - map_foreachinrange(skill_area_sub, src, skill_get_splash(skl->skill_id, skl->skill_lv), splash_target(src), src, skl->skill_id, skl->skill_lv, tick, skl->flag, skill_castend_damage_id); - break; - case WZ_WATERBALL: - skill_toggle_magicpower(src, skl->skill_id); // only the first hit will be amplify - if (!status_isdead(target)) - skill_attack(BF_MAGIC,src,src,target,skl->skill_id,skl->skill_lv,tick,skl->flag); - if (skl->type>1 && !status_isdead(target) && !status_isdead(src)) { - skill_addtimerskill(src,tick+125,target->id,0,0,skl->skill_id,skl->skill_lv,skl->type-1,skl->flag); - } else { - struct status_change *sc = status_get_sc(src); - if(sc) { - if(sc->data[SC_SPIRIT] && - sc->data[SC_SPIRIT]->val2 == SL_WIZARD && - sc->data[SC_SPIRIT]->val3 == skl->skill_id) - sc->data[SC_SPIRIT]->val3 = 0; //Clear bounced spell check. - } - } - break; - /** - * Warlock - **/ - case WL_CHAINLIGHTNING_ATK: - { - struct block_list *nbl = NULL; // Next Target of Chain - skill_attack(BF_MAGIC,src,src,target,skl->skill_id,skl->skill_lv,tick,skl->flag); // Hit a Lightning on the current Target - skill_toggle_magicpower(src, skl->skill_id); // only the first hit will be amplify - if( skl->type > 1 ) - { // Remaining Chains Hit - nbl = battle_getenemyarea(src,target->x,target->y,2,BL_CHAR|BL_SKILL,target->id); // Search for a new Target around current one... - if( nbl == NULL && skl->x > 1 ) - { - nbl = target; - skl->x--; - } - else skl->x = 3; - } - - if( nbl ) - skill_addtimerskill(src,tick+status_get_adelay(src),nbl->id,skl->x,0,WL_CHAINLIGHTNING_ATK,skl->skill_lv,skl->type-1,skl->flag); - } - break; - case WL_TETRAVORTEX_FIRE: - case WL_TETRAVORTEX_WATER: - case WL_TETRAVORTEX_WIND: - case WL_TETRAVORTEX_GROUND: - skill_attack(BF_MAGIC,src,src,target,skl->skill_id,skl->skill_lv,tick,skl->flag|SD_ANIMATION); - skill_toggle_magicpower(src, skl->skill_id); // only the first hit will be amplify - if( skl->type >= 3 ) - { // Final Hit - if( !status_isdead(target) ) - { // Final Status Effect - int effects[4] = { SC_BURNING, SC_FREEZING, SC_BLEEDING, SC_STUN }, - applyeffects[4] = { 0, 0, 0, 0 }, - i, j = 0, k = 0; - for( i = 1; i <= 8; i = i + i ) - { - if( skl->x&i ) - { - applyeffects[j] = effects[k]; - j++; - } - k++; - } - if( j ) - { - i = applyeffects[rnd()%j]; - status_change_start(target, i, 10000, skl->skill_lv, - (i == SC_BURNING ? 1000 : 0), - (i == SC_BURNING ? src->id : 0), - 0, skill_get_time(WL_TETRAVORTEX,skl->skill_lv), 0); - } - } - } - break; - case WM_REVERBERATION_MELEE: - case WM_REVERBERATION_MAGIC: - skill_castend_damage_id(src, target, skl->skill_id, skl->skill_lv, tick, skl->flag|SD_LEVEL); // damage should split among targets - break; - case SC_FATALMENACE: - if( src == target ) // Casters Part - unit_warp(src, -1, skl->x, skl->y, 3); - else { // Target's Part - short x = skl->x, y = skl->y; - map_search_freecell(NULL, target->m, &x, &y, 2, 2, 1); - unit_warp(target,-1,x,y,3); - } - break; - case LG_MOONSLASHER: - case SR_WINDMILL: - if( target->type == BL_PC ) { - struct map_session_data *tsd = NULL; - if( (tsd = ((TBL_PC*)target)) && !pc_issit(tsd) ) { - pc_setsit(tsd); - skill_sit(tsd,1); - clif_sitting(&tsd->bl); - } - } - break; - case LG_OVERBRAND_BRANDISH: - case LG_OVERBRAND_PLUSATK: - case SR_KNUCKLEARROW: - skill_attack(BF_WEAPON, src, src, target, skl->skill_id, skl->skill_lv, tick, skl->flag|SD_LEVEL); - break; - case GN_SPORE_EXPLOSION: - map_foreachinrange(skill_area_sub, target, skill_get_splash(skl->skill_id, skl->skill_lv), BL_CHAR, - src, skl->skill_id, skl->skill_lv, 0, skl->flag|1|BCT_ENEMY, skill_castend_damage_id); - break; - case CH_PALMSTRIKE: - { - struct status_change* tsc = status_get_sc(target); - struct status_change* sc = status_get_sc(src); - if( tsc && tsc->option&OPTION_HIDE || - sc && sc->option&OPTION_HIDE ){ - skill_blown(src,target,skill_get_blewcount(skl->skill_id, skl->skill_lv), -1, 0x0 ); - 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) - break; - switch( skl->skill_id ) - { - case WZ_METEOR: - if( skl->type >= 0 ) - { - int x = skl->type>>16, y = skl->type&0xFFFF; - if( path_search_long(NULL, src->m, src->x, src->y, x, y, CELL_CHKWALL) ) - skill_unitsetting(src,skl->skill_id,skl->skill_lv,x,y,skl->flag); - if( path_search_long(NULL, src->m, src->x, src->y, skl->x, skl->y, CELL_CHKWALL) ) - clif_skill_poseffect(src,skl->skill_id,skl->skill_lv,skl->x,skl->y,tick); - } - else if( path_search_long(NULL, src->m, src->x, src->y, skl->x, skl->y, CELL_CHKWALL) ) - skill_unitsetting(src,skl->skill_id,skl->skill_lv,skl->x,skl->y,skl->flag); - break; - case GN_CRAZYWEED_ATK: - { - int dummy = 1, i = skill_get_unit_range(skl->skill_id,skl->skill_lv); - map_foreachinarea(skill_cell_overlap, src->m, skl->x-i, skl->y-i, skl->x+i, skl->y+i, BL_SKILL, skl->skill_id, &dummy, src); - } - case WL_EARTHSTRAIN: - skill_unitsetting(src,skl->skill_id,skl->skill_lv,skl->x,skl->y,(skl->type<<16)|skl->flag); - break; - - } - } - } while (0); - //Free skl now that it is no longer needed. - ers_free(skill_timer_ers, skl); - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -int skill_addtimerskill (struct block_list *src, unsigned int tick, int target, int x,int y, uint16 skill_id, uint16 skill_lv, int type, int flag) -{ - int i; - struct unit_data *ud; - nullpo_retr(1, src); - if (src->prev == NULL) - return 0; - ud = unit_bl2ud(src); - nullpo_retr(1, ud); - - ARR_FIND( 0, MAX_SKILLTIMERSKILL, i, ud->skilltimerskill[i] == 0 ); - if( i == MAX_SKILLTIMERSKILL ) return 1; - - ud->skilltimerskill[i] = ers_alloc(skill_timer_ers, struct skill_timerskill); - ud->skilltimerskill[i]->timer = add_timer(tick, skill_timerskill, src->id, i); - ud->skilltimerskill[i]->src_id = src->id; - ud->skilltimerskill[i]->target_id = target; - ud->skilltimerskill[i]->skill_id = skill_id; - ud->skilltimerskill[i]->skill_lv = skill_lv; - ud->skilltimerskill[i]->map = src->m; - ud->skilltimerskill[i]->x = x; - ud->skilltimerskill[i]->y = y; - ud->skilltimerskill[i]->type = type; - ud->skilltimerskill[i]->flag = flag; - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -int skill_cleartimerskill (struct block_list *src) -{ - int i; - struct unit_data *ud; - nullpo_ret(src); - ud = unit_bl2ud(src); - nullpo_ret(ud); - - for(i=0;i<MAX_SKILLTIMERSKILL;i++) { - if(ud->skilltimerskill[i]) { - delete_timer(ud->skilltimerskill[i]->timer, skill_timerskill); - ers_free(skill_timer_ers, ud->skilltimerskill[i]); - ud->skilltimerskill[i]=NULL; - } - } - return 1; -} -static int skill_ative_reverberation( struct block_list *bl, va_list ap) { - struct skill_unit *su = (TBL_SKILL*)bl; - struct skill_unit_group *sg; - if( bl->type != BL_SKILL ) - return 0; - if( su->alive && (sg = su->group) && sg->skill_id == WM_REVERBERATION ) { - map_foreachinrange(skill_trap_splash, bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, bl, gettick()); - su->limit=DIFF_TICK(gettick(),sg->tick); - sg->unit_id = UNT_USED_TRAPS; - } - return 0; -} - -static int skill_reveal_trap (struct block_list *bl, va_list ap) -{ - TBL_SKILL *su = (TBL_SKILL*)bl; - if (su->alive && su->group && skill_get_inf2(su->group->skill_id)&INF2_TRAP) - { //Reveal trap. - //Change look is not good enough, the client ignores it as an actual trap still. [Skotlex] - //clif_changetraplook(bl, su->group->unit_id); - clif_skill_setunit(su); - return 1; - } - return 0; -} - -/*========================================== - * - * - *------------------------------------------*/ -int skill_castend_damage_id (struct block_list* src, struct block_list *bl, uint16 skill_id, uint16 skill_lv, unsigned int tick, int flag) -{ - struct map_session_data *sd = NULL; - struct status_data *tstatus; - struct status_change *sc; - - if (skill_id > 0 && !skill_lv) return 0; - - nullpo_retr(1, src); - nullpo_retr(1, bl); - - if (src->m != bl->m) - return 1; - - if (bl->prev == NULL) - return 1; - - sd = BL_CAST(BL_PC, src); - - if (status_isdead(bl)) - return 1; - - if (skill_id && skill_get_type(skill_id) == BF_MAGIC && status_isimmune(bl) == 100) - { //GTB makes all targetted magic display miss with a single bolt. - sc_type sct = status_skill2sc(skill_id); - if(sct != SC_NONE) - status_change_end(bl, sct, INVALID_TIMER); - clif_skill_damage(src, bl, tick, status_get_amotion(src), status_get_dmotion(bl), 0, 1, skill_id, skill_lv, skill_get_hit(skill_id)); - return 1; - } - - sc = status_get_sc(src); - if (sc && !sc->count) - sc = NULL; //Unneeded - - tstatus = status_get_status_data(bl); - - map_freeblock_lock(); - - switch(skill_id) - { - case MER_CRASH: - case SM_BASH: - case MS_BASH: - case MC_MAMMONITE: - case TF_DOUBLE: - case AC_DOUBLE: - case MA_DOUBLE: - case AS_SONICBLOW: - case KN_PIERCE: - case ML_PIERCE: - case KN_SPEARBOOMERANG: - case TF_POISON: - case TF_SPRINKLESAND: - case AC_CHARGEARROW: - case MA_CHARGEARROW: - case RG_INTIMIDATE: - case AM_ACIDTERROR: - case BA_MUSICALSTRIKE: - case DC_THROWARROW: - case BA_DISSONANCE: - case CR_HOLYCROSS: - case NPC_DARKCROSS: - case CR_SHIELDCHARGE: - case CR_SHIELDBOOMERANG: - case NPC_PIERCINGATT: - case NPC_MENTALBREAKER: - case NPC_RANGEATTACK: - case NPC_CRITICALSLASH: - case NPC_COMBOATTACK: - case NPC_GUIDEDATTACK: - case NPC_POISON: - case NPC_RANDOMATTACK: - case NPC_WATERATTACK: - case NPC_GROUNDATTACK: - case NPC_FIREATTACK: - case NPC_WINDATTACK: - case NPC_POISONATTACK: - case NPC_HOLYATTACK: - case NPC_DARKNESSATTACK: - case NPC_TELEKINESISATTACK: - case NPC_UNDEADATTACK: - case NPC_ARMORBRAKE: - case NPC_WEAPONBRAKER: - case NPC_HELMBRAKE: - case NPC_SHIELDBRAKE: - case NPC_BLINDATTACK: - case NPC_SILENCEATTACK: - case NPC_STUNATTACK: - case NPC_PETRIFYATTACK: - case NPC_CURSEATTACK: - case NPC_SLEEPATTACK: - case LK_AURABLADE: - case LK_SPIRALPIERCE: - case ML_SPIRALPIERCE: - case LK_HEADCRUSH: - case CG_ARROWVULCAN: - case HW_MAGICCRASHER: - case ITM_TOMAHAWK: - case MO_TRIPLEATTACK: - case CH_CHAINCRUSH: - case CH_TIGERFIST: - case PA_SHIELDCHAIN: // Shield Chain - case PA_SACRIFICE: - case WS_CARTTERMINATION: // Cart Termination - case AS_VENOMKNIFE: - case HT_PHANTASMIC: - case HT_POWER: - case TK_DOWNKICK: - case TK_COUNTER: - case GS_CHAINACTION: - case GS_TRIPLEACTION: - case GS_MAGICALBULLET: - case GS_TRACKING: - case GS_PIERCINGSHOT: - case GS_RAPIDSHOWER: - case GS_DUST: - case GS_DISARM: // Added disarm. [Reddozen] - case GS_FULLBUSTER: - case NJ_SYURIKEN: - case NJ_KUNAI: - case ASC_BREAKER: - case HFLI_MOON: //[orn] - case HFLI_SBR44: //[orn] - case NPC_BLEEDING: - case NPC_CRITICALWOUND: - case NPC_HELLPOWER: - case RK_SONICWAVE: - case RK_HUNDREDSPEAR: - case AB_DUPLELIGHT_MELEE: - case RA_AIMEDBOLT: - case NC_AXEBOOMERANG: - case NC_POWERSWING: - case GC_CROSSIMPACT: - case GC_VENOMPRESSURE: - case SC_TRIANGLESHOT: - case SC_FEINTBOMB: - case LG_BANISHINGPOINT: - case LG_SHIELDPRESS: - case LG_RAGEBURST: - case LG_RAYOFGENESIS: - case LG_HESPERUSLIT: - case SR_FALLENEMPIRE: - case SR_CRESCENTELBOW_AUTOSPELL: - case SR_GATEOFHELL: - case SR_GENTLETOUCH_QUIET: - case WM_SEVERE_RAINSTORM_MELEE: - case WM_GREAT_ECHO: - case GN_SLINGITEM_RANGEMELEEATK: - case KO_JYUMONJIKIRI: - case KO_SETSUDAN: - case KO_KAIHOU: - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); - break; - - /** - * Mechanic (MADO GEAR) - **/ - case NC_BOOSTKNUCKLE: - case NC_PILEBUNKER: - case NC_VULCANARM: - case NC_COLDSLOWER: - case NC_ARMSCANNON: - if (sd) pc_overheat(sd,1); - case RK_WINDCUTTER: - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag|SD_ANIMATION); - break; - - case LK_JOINTBEAT: // decide the ailment first (affects attack damage and effect) - switch( rnd()%6 ){ - case 0: flag |= BREAK_ANKLE; break; - case 1: flag |= BREAK_WRIST; break; - case 2: flag |= BREAK_KNEE; break; - case 3: flag |= BREAK_SHOULDER; break; - case 4: flag |= BREAK_WAIST; break; - case 5: flag |= BREAK_NECK; break; - } - //TODO: is there really no cleaner way to do this? - sc = status_get_sc(bl); - if (sc) sc->jb_flag = flag; - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); - break; - - case MO_COMBOFINISH: - if (!(flag&1) && sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_MONK) - { //Becomes a splash attack when Soul Linked. - map_foreachinrange(skill_area_sub, bl, - skill_get_splash(skill_id, skill_lv),splash_target(src), - src,skill_id,skill_lv,tick, flag|BCT_ENEMY|1, - skill_castend_damage_id); - } else - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); - break; - - case TK_STORMKICK: // Taekwon kicks [Dralnu] - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - skill_area_temp[1] = 0; - map_foreachinrange(skill_attack_area, src, - skill_get_splash(skill_id, skill_lv), splash_target(src), - BF_WEAPON, src, src, skill_id, skill_lv, tick, flag, BCT_ENEMY); - break; - - case KN_CHARGEATK: - { - bool path = path_search_long(NULL, src->m, src->x, src->y, bl->x, bl->y,CELL_CHKWALL); - unsigned int dist = distance_bl(src, bl); - uint8 dir = map_calc_dir(bl, src->x, src->y); - - // teleport to target (if not on WoE grounds) - if( !map_flag_gvg(src->m) && !map[src->m].flag.battleground && unit_movepos(src, bl->x, bl->y, 0, 1) ) - clif_slide(src, bl->x, bl->y); - - // cause damage and knockback if the path to target was a straight one - if( path ) - { - skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, dist); - skill_blown(src, bl, dist, dir, 0); - //HACK: since knockback officially defaults to the left, the client also turns to the left... therefore, - // make the caster look in the direction of the target - unit_setdir(src, (dir+4)%8); - } - - } - break; - - case NC_FLAMELAUNCHER: - if (sd) pc_overheat(sd,1); - case SN_SHARPSHOOTING: - case MA_SHARPSHOOTING: - case NJ_KAMAITACHI: - case LG_CANNONSPEAR: - //It won't shoot through walls since on castend there has to be a direct - //line of sight between caster and target. - skill_area_temp[1] = bl->id; - map_foreachinpath (skill_attack_area,src->m,src->x,src->y,bl->x,bl->y, - skill_get_splash(skill_id, skill_lv),skill_get_maxcount(skill_id,skill_lv), splash_target(src), - skill_get_type(skill_id),src,src,skill_id,skill_lv,tick,flag,BCT_ENEMY); - break; - - case NPC_ACIDBREATH: - case NPC_DARKNESSBREATH: - case NPC_FIREBREATH: - case NPC_ICEBREATH: - case NPC_THUNDERBREATH: - skill_area_temp[1] = bl->id; - map_foreachinpath(skill_attack_area,src->m,src->x,src->y,bl->x,bl->y, - skill_get_splash(skill_id, skill_lv),skill_get_maxcount(skill_id,skill_lv), splash_target(src), - skill_get_type(skill_id),src,src,skill_id,skill_lv,tick,flag,BCT_ENEMY); - break; - - case MO_INVESTIGATE: - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); - status_change_end(src, SC_BLADESTOP, INVALID_TIMER); - break; - - case RG_BACKSTAP: - { - uint8 dir = map_calc_dir(src, bl->x, bl->y), t_dir = unit_getdir(bl); - if ((!check_distance_bl(src, bl, 0) && !map_check_dir(dir, t_dir)) || bl->type == BL_SKILL) { - status_change_end(src, SC_HIDING, INVALID_TIMER); - skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); - dir = dir < 4 ? dir+4 : dir-4; // change direction [Celest] - unit_setdir(bl,dir); - } - else if (sd) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - } - break; - - case MO_FINGEROFFENSIVE: - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); - if (battle_config.finger_offensive_type && sd) { - int i; - for (i = 1; i < sd->spiritball_old; i++) - skill_addtimerskill(src, tick + i * 200, bl->id, 0, 0, skill_id, skill_lv, BF_WEAPON, flag); - } - status_change_end(src, SC_BLADESTOP, INVALID_TIMER); - break; - - case MO_CHAINCOMBO: - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); - status_change_end(src, SC_BLADESTOP, INVALID_TIMER); - break; - - case NJ_ISSEN: - status_change_end(src, SC_NEN, INVALID_TIMER); - status_change_end(src, SC_HIDING, INVALID_TIMER); - // fall through - case MO_EXTREMITYFIST: - { - short x, y, i = 2; // Move 2 cells for Issen(from target) - struct block_list *mbl = bl; - short dir = 0; - - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); - - if( skill_id == MO_EXTREMITYFIST ) - { - mbl = src; - i = 3; // for Asura(from caster) - status_set_sp(src, 0, 0); - status_change_end(src, SC_EXPLOSIONSPIRITS, INVALID_TIMER); - status_change_end(src, SC_BLADESTOP, INVALID_TIMER); -#ifdef RENEWAL - sc_start(src,SC_EXTREMITYFIST2,100,skill_lv,skill_get_time(skill_id,skill_lv)); -#endif - }else - status_set_hp(src, -#ifdef RENEWAL - max(status_get_max_hp(src)/100, 1) -#else - 1 -#endif - , 0); - - dir = map_calc_dir(src,bl->x,bl->y); - if( dir > 0 && dir < 4) x = -i; - else if( dir > 4 ) x = i; - else x = 0; - if( dir > 2 && dir < 6 ) y = -i; - else if( dir == 7 || dir < 2 ) y = i; - else y = 0; - if( (mbl == src || !map_flag_gvg(src->m) && !map[src->m].flag.battleground) && // only NJ_ISSEN don't have slide effect in GVG - unit_movepos(src, mbl->x+x, mbl->y+y, 1, 1) ) { - clif_slide(src, src->x, src->y); - //uncomment this if you want to remove MO_EXTREMITYFIST glitchy walking effect. [malufett] - //clif_fixpos(src); - } - } - break; - - //Splash attack skills. - case AS_GRIMTOOTH: - case MC_CARTREVOLUTION: - case NPC_SPLASHATTACK: - flag |= SD_PREAMBLE; // a fake packet will be sent for the first target to be hit - case AS_SPLASHER: - case SM_MAGNUM: - case MS_MAGNUM: - case HT_BLITZBEAT: - case AC_SHOWER: - case MA_SHOWER: - case MG_NAPALMBEAT: - case MG_FIREBALL: - case RG_RAID: - case HW_NAPALMVULCAN: - case NJ_HUUMA: - case NJ_BAKUENRYU: - case ASC_METEORASSAULT: - case GS_DESPERADO: - case GS_SPREADATTACK: - case NPC_EARTHQUAKE: - case NPC_PULSESTRIKE: - case NPC_HELLJUDGEMENT: - case NPC_VAMPIRE_GIFT: - case RK_IGNITIONBREAK: - case AB_JUDEX: - case WL_SOULEXPANSION: - case WL_CRIMSONROCK: - case WL_COMET: - case WL_JACKFROST: - case RA_ARROWSTORM: - case RA_WUGDASH: - case NC_SELFDESTRUCTION: - case NC_AXETORNADO: - case GC_ROLLINGCUTTER: - case GC_COUNTERSLASH: - case LG_MOONSLASHER: - case LG_EARTHDRIVE: - case SR_TIGERCANNON: - case SR_RAMPAGEBLASTER: - case SR_SKYNETBLOW: - case SR_WINDMILL: - case SR_RIDEINLIGHTNING: - case WM_SOUND_OF_DESTRUCTION: - case WM_REVERBERATION_MELEE: - case WM_REVERBERATION_MAGIC: - case SO_VARETYR_SPEAR: - case GN_CART_TORNADO: - case GN_CARTCANNON: - case KO_HAPPOKUNAI: - case KO_HUUMARANKA: - case KO_MUCHANAGE: - case KO_BAKURETSU: - if( flag&1 ) {//Recursive invocation - // skill_area_temp[0] holds number of targets in area - // skill_area_temp[1] holds the id of the original target - // skill_area_temp[2] counts how many targets have already been processed - int sflag = skill_area_temp[0] & 0xFFF, heal; - if( flag&SD_LEVEL ) - sflag |= SD_LEVEL; // -1 will be used in packets instead of the skill level - if( skill_area_temp[1] != bl->id && !(skill_get_inf2(skill_id)&INF2_NPC_SKILL) ) - sflag |= SD_ANIMATION; // original target gets no animation (as well as all NPC skills) - - heal = skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, sflag); - if( skill_id == NPC_VAMPIRE_GIFT && heal > 0 ) { - clif_skill_nodamage(NULL, src, AL_HEAL, heal, 1); - status_heal(src,heal,0,0); - } - } else { - switch ( skill_id ) { - case NJ_BAKUENRYU: - case LG_EARTHDRIVE: - case GN_CARTCANNON: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - break; - case LG_MOONSLASHER: - clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - break; - case NPC_EARTHQUAKE://FIXME: Isn't EarthQuake a ground skill after all? - skill_addtimerskill(src,tick+250,src->id,0,0,skill_id,skill_lv,2,flag|BCT_ENEMY|SD_SPLASH|1); - default: - break; - } - - skill_area_temp[0] = 0; - skill_area_temp[1] = bl->id; - skill_area_temp[2] = 0; - if( skill_id == WL_CRIMSONROCK ) { - skill_area_temp[4] = bl->x; - skill_area_temp[5] = bl->y; - } - if( skill_id == WM_REVERBERATION_MELEE || skill_id == WM_REVERBERATION_MAGIC ) - skill_area_temp[1] = 0; - // if skill damage should be split among targets, count them - //SD_LEVEL -> Forced splash damage for Auto Blitz-Beat -> count targets - //special case: Venom Splasher uses a different range for searching than for splashing - if( flag&SD_LEVEL || skill_get_nk(skill_id)&NK_SPLASHSPLIT ) - skill_area_temp[0] = map_foreachinrange(skill_area_sub, bl, (skill_id == AS_SPLASHER)?1:skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, BCT_ENEMY, skill_area_sub_count); - - // recursive invocation of skill_castend_damage_id() with flag|1 - map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), ( skill_id == WM_REVERBERATION_MELEE || skill_id == WM_REVERBERATION_MAGIC )?BL_CHAR:splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_damage_id); - } - break; - - case KN_BRANDISHSPEAR: - case ML_BRANDISH: - //Coded apart for it needs the flag passed to the damage calculation. - if (skill_area_temp[1] != bl->id) - skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag|SD_ANIMATION); - else - skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); - break; - - case KN_BOWLINGBASH: - case MS_BOWLINGBASH: - if(flag&1){ - if(bl->id==skill_area_temp[1]) - break; - //two hits for 500% - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,SD_ANIMATION); - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,SD_ANIMATION); - } else { - int i,c; - c = skill_get_blewcount(skill_id,skill_lv); - // keep moving target in the direction that src is looking, square by square - for(i=0;i<c;i++){ - if (!skill_blown(src,bl,1,(unit_getdir(src)+4)%8,0x1)) - break; //Can't knockback - skill_area_temp[0] = map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY, skill_area_sub_count); - if( skill_area_temp[0] > 1 ) break; // collision - } - clif_blown(bl); //Update target pos. - if (i!=c) { //Splash - skill_area_temp[1] = bl->id; - map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_damage_id); - } - //Weirdo dual-hit property, two attacks for 500% - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,0); - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,0); - } - break; - - case KN_SPEARSTAB: - if(flag&1) { - if (bl->id==skill_area_temp[1]) - break; - if (skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,SD_ANIMATION)) - skill_blown(src,bl,skill_area_temp[2],-1,0); - } else { - int x=bl->x,y=bl->y,i,dir; - dir = map_calc_dir(bl,src->x,src->y); - skill_area_temp[1] = bl->id; - skill_area_temp[2] = skill_get_blewcount(skill_id,skill_lv); - // all the enemies between the caster and the target are hit, as well as the target - if (skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,0)) - skill_blown(src,bl,skill_area_temp[2],-1,0); - for (i=0;i<4;i++) { - map_foreachincell(skill_area_sub,bl->m,x,y,BL_CHAR, - src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); - x += dirx[dir]; - y += diry[dir]; - } - } - break; - - case TK_TURNKICK: - case MO_BALKYOUNG: //Active part of the attack. Skill-attack [Skotlex] - { - skill_area_temp[1] = bl->id; //NOTE: This is used in skill_castend_nodamage_id to avoid affecting the target. - if (skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag)) - map_foreachinrange(skill_area_sub,bl, - skill_get_splash(skill_id, skill_lv),BL_CHAR, - src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1, - skill_castend_nodamage_id); - } - break; - case CH_PALMSTRIKE: // Palm Strike takes effect 1sec after casting. [Skotlex] - // clif_skill_nodamage(src,bl,skill_id,skill_lv,0); //Can't make this one display the correct attack animation delay :/ - clif_damage(src,bl,tick,status_get_amotion(src),0,-1,1,4,0); //Display an absorbed damage attack. - skill_addtimerskill(src, tick + (1000+status_get_amotion(src)), bl->id, 0, 0, skill_id, skill_lv, BF_WEAPON, flag); - break; - - case PR_TURNUNDEAD: - case ALL_RESURRECTION: - if (!battle_check_undead(tstatus->race, tstatus->def_ele)) - break; - skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); - break; - - case MG_SOULSTRIKE: - case NPC_DARKSTRIKE: - case MG_COLDBOLT: - case MG_FIREBOLT: - case MG_LIGHTNINGBOLT: - case WZ_EARTHSPIKE: - case AL_HEAL: - case AL_HOLYLIGHT: - case WZ_JUPITEL: - case NPC_DARKTHUNDER: - case PR_ASPERSIO: - case MG_FROSTDIVER: - case WZ_SIGHTBLASTER: - case WZ_SIGHTRASHER: - case NJ_KOUENKA: - case NJ_HYOUSENSOU: - case NJ_HUUJIN: - case AB_ADORAMUS: - case AB_RENOVATIO: - case AB_HIGHNESSHEAL: - case AB_DUPLELIGHT_MAGIC: - case WM_METALICSOUND: - case MH_ERASER_CUTTER: - skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); - break; - - case NPC_MAGICALATTACK: - skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); - sc_start(src,status_skill2sc(skill_id),100,skill_lv,skill_get_time(skill_id,skill_lv)); - break; - - case HVAN_CAPRICE: //[blackhole89] - { - int ran=rnd()%4; - int sid = 0; - switch(ran) - { - case 0: sid=MG_COLDBOLT; break; - case 1: sid=MG_FIREBOLT; break; - case 2: sid=MG_LIGHTNINGBOLT; break; - case 3: sid=WZ_EARTHSPIKE; break; - } - skill_attack(BF_MAGIC,src,src,bl,sid,skill_lv,tick,flag|SD_LEVEL); - } - break; - case WZ_WATERBALL: - { - int range = skill_lv / 2; - int maxlv = skill_get_max(skill_id); // learnable level - int count = 0; - int x, y; - struct skill_unit* unit; - - if( skill_lv > maxlv ) - { - if( src->type == BL_MOB && skill_lv == 10 ) - range = 4; - else - range = maxlv / 2; - } - - for( y = src->y - range; y <= src->y + range; ++y ) - for( x = src->x - range; x <= src->x + range; ++x ) - { - if( !map_find_skill_unit_oncell(src,x,y,SA_LANDPROTECTOR,NULL,1) ) - { - if( src->type != BL_PC || map_getcell(src->m,x,y,CELL_CHKWATER) ) // non-players bypass the water requirement - count++; // natural water cell - else if( (unit = map_find_skill_unit_oncell(src,x,y,SA_DELUGE,NULL,1)) != NULL || (unit = map_find_skill_unit_oncell(src,x,y,NJ_SUITON,NULL,1)) != NULL ) - { - count++; // skill-induced water cell - skill_delunit(unit); // consume cell - } - } - } - - if( count > 1 ) // queue the remaining count - 1 timerskill Waterballs - skill_addtimerskill(src,tick+150,bl->id,0,0,skill_id,skill_lv,count-1,flag); - } - skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); - break; - - case PR_BENEDICTIO: - //Should attack undead and demons. [Skotlex] - if (battle_check_undead(tstatus->race, tstatus->def_ele) || tstatus->race == RC_DEMON) - skill_attack(BF_MAGIC, src, src, bl, skill_id, skill_lv, tick, flag); - break; - - case SL_SMA: - status_change_end(src, SC_SMA, INVALID_TIMER); - case SL_STIN: - case SL_STUN: - if (sd && !battle_config.allow_es_magic_pc && bl->type != BL_MOB) { - status_change_start(src,SC_STUN,10000,skill_lv,0,0,0,500,10); - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); - break; - - case NPC_DARKBREATH: - clif_emotion(src,E_AG); - case SN_FALCONASSAULT: - case PA_PRESSURE: - case CR_ACIDDEMONSTRATION: - case TF_THROWSTONE: - case NPC_SMOKING: - case GS_FLING: - case NJ_ZENYNAGE: - case GN_THORNS_TRAP: - case GN_HELLS_PLANT_ATK: - skill_attack(BF_MISC,src,src,bl,skill_id,skill_lv,tick,flag); - break; - /** - * Rune Knight - **/ - case RK_DRAGONBREATH: { - struct status_change *tsc = NULL; - if( (tsc = status_get_sc(bl)) && (tsc->data[SC_HIDING] )) { - clif_skill_nodamage(src,src,skill_id,skill_lv,1); - } else - skill_attack(BF_MISC,src,src,bl,skill_id,skill_lv,tick,flag); - } - break; - - case NPC_SELFDESTRUCTION: { - struct status_change *tsc = NULL; - if( (tsc = status_get_sc(bl)) && tsc->data[SC_HIDING] ) - break; - } - case HVAN_EXPLOSION: - if (src != bl) - skill_attack(BF_MISC,src,src,bl,skill_id,skill_lv,tick,flag); - break; - - // Celest - case PF_SOULBURN: - if (rnd()%100 < (skill_lv < 5 ? 30 + skill_lv * 10 : 70)) { - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - if (skill_lv == 5) - skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); - status_percent_damage(src, bl, 0, 100, false); - } else { - clif_skill_nodamage(src,src,skill_id,skill_lv,1); - if (skill_lv == 5) - skill_attack(BF_MAGIC,src,src,src,skill_id,skill_lv,tick,flag); - status_percent_damage(src, src, 0, 100, false); - } - break; - - case NPC_BLOODDRAIN: - case NPC_ENERGYDRAIN: - { - int heal = skill_attack( (skill_id == NPC_BLOODDRAIN) ? BF_WEAPON : BF_MAGIC, - src, src, bl, skill_id, skill_lv, tick, flag); - if (heal > 0){ - clif_skill_nodamage(NULL, src, AL_HEAL, heal, 1); - status_heal(src, heal, 0, 0); - } - } - break; - - case GS_BULLSEYE: - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); - break; - - case NJ_KASUMIKIRI: - if (skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag) > 0) - sc_start(src,SC_HIDING,100,skill_lv,skill_get_time(skill_id,skill_lv)); - break; - case NJ_KIRIKAGE: - if( !map_flag_gvg(src->m) && !map[src->m].flag.battleground ) - { //You don't move on GVG grounds. - short x, y; - map_search_freecell(bl, 0, &x, &y, 1, 1, 0); - if (unit_movepos(src, x, y, 0, 0)) - clif_slide(src,src->x,src->y); - } - status_change_end(src, SC_HIDING, INVALID_TIMER); - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); - break; - case RK_PHANTOMTHRUST: - unit_setdir(src,map_calc_dir(src, bl->x, bl->y)); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - - skill_blown(src,bl,distance_bl(src,bl)-1,unit_getdir(src),0); - if( battle_check_target(src,bl,BCT_ENEMY) ) - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); - break; - - case RK_STORMBLAST: - case RK_CRUSHSTRIKE: - if( sd ) { - if( pc_checkskill(sd,RK_RUNEMASTERY) >= ( skill_id == RK_CRUSHSTRIKE ? 7 : 3 ) ) - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); - else - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - } else //non-sd support - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); - break; - case GC_DARKILLUSION: - { - short x, y; - short dir = map_calc_dir(src,bl->x,bl->y); - - if( dir > 0 && dir < 4) x = 2; - else if( dir > 4 ) x = -2; - else x = 0; - if( dir > 2 && dir < 6 ) y = 2; - else if( dir == 7 || dir < 2 ) y = -2; - else y = 0; - - if( unit_movepos(src, bl->x+x, bl->y+y, 1, 1) ) - { - clif_slide(src,bl->x+x,bl->y+y); - clif_fixpos(src); // the official server send these two packts. - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); - if( rnd()%100 < 4 * skill_lv ) - skill_castend_damage_id(src,bl,GC_CROSSIMPACT,skill_lv,tick,flag); - } - - } - break; - - case GC_WEAPONCRUSH: - if( sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == GC_WEAPONBLOCKING ) - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); - else if( sd ) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_GC_WEAPONBLOCKING,0); - break; - - case GC_CROSSRIPPERSLASHER: - if( sd && !(sc && sc->data[SC_ROLLINGCUTTER]) ) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_CONDITION,0); - else - { - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); - status_change_end(src,SC_ROLLINGCUTTER,INVALID_TIMER); - } - break; - - case GC_PHANTOMMENACE: - if( flag&1 ) - { // Only Hits Invisible Targets - struct status_change *tsc = status_get_sc(bl); - if(tsc && (tsc->option&(OPTION_HIDE|OPTION_CLOAK|OPTION_CHASEWALK) || tsc->data[SC__INVISIBILITY]) ) - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); - } - break; - case WL_CHAINLIGHTNING: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - skill_addtimerskill(src,tick + 150,bl->id,3,0,WL_CHAINLIGHTNING_ATK,skill_lv,4+skill_lv,flag); - break; - case WL_DRAINLIFE: - { - int heal = skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); - int rate = 70 + 5 * skill_lv; - - heal = heal * (5 + 5 * skill_lv) / 100; - - if( bl->type == BL_SKILL ) - heal = 0; // Don't absorb heal from Ice Walls or other skill units. - - if( heal && rnd()%100 < rate ) - { - status_heal(src, heal, 0, 0); - clif_skill_nodamage(NULL, src, AL_HEAL, heal, 1); - } - } - break; - - case WL_TETRAVORTEX: - if( sd ) - { - int spheres[5] = { 0, 0, 0, 0, 0 }, - positions[5] = {-1,-1,-1,-1,-1 }, - i, j = 0, k, subskill = 0; - - for( i = SC_SPHERE_1; i <= SC_SPHERE_5; i++ ) - if( sc && sc->data[i] ) - { - spheres[j] = i; - positions[j] = sc->data[i]->val2; - j++; // - } - - if( j < 4 ) - { // Need 4 spheres minimum - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - - // Sphere Sort, this time from new to old - for( i = 0; i <= j - 2; i++ ) - for( k = i + 1; k <= j - 1; k++ ) - if( positions[i] < positions[k] ) - { - swap(positions[i],positions[k]); - swap(spheres[i],spheres[k]); - } - - k = 0; - for( i = 0; i < 4; i++ ) - { - switch( sc->data[spheres[i]]->val1 ) - { - case WLS_FIRE: subskill = WL_TETRAVORTEX_FIRE; k |= 1; break; - case WLS_WIND: subskill = WL_TETRAVORTEX_WIND; k |= 4; break; - case WLS_WATER: subskill = WL_TETRAVORTEX_WATER; k |= 2; break; - case WLS_STONE: subskill = WL_TETRAVORTEX_GROUND; k |= 8; break; - } - skill_addtimerskill(src, tick + i * 200, bl->id, k, 0, subskill, skill_lv, i, flag); - clif_skill_nodamage(src, bl, subskill, skill_lv, 1); - status_change_end(src, spheres[i], INVALID_TIMER); - } - } - break; - - case WL_RELEASE: - if( sd ) - { - int i; - // Priority is to release SpellBook - if( sc && sc->data[SC_READING_SB] ) - { // SpellBook - uint16 skill_id, skill_lv, point, s = 0; - int spell[SC_MAXSPELLBOOK-SC_SPELLBOOK1 + 1]; - - for(i = SC_MAXSPELLBOOK; i >= SC_SPELLBOOK1; i--) // List all available spell to be released - if( sc->data[i] ) spell[s++] = i; - - if ( s == 0 ) - break; - - i = spell[s==1?0:rand()%s];// Random select of spell to be released. - if( s && sc->data[i] ){// Now extract the data from the preserved spell - skill_id = sc->data[i]->val1; - skill_lv = sc->data[i]->val2; - point = sc->data[i]->val3; - status_change_end(src, (sc_type)i, INVALID_TIMER); - }else //something went wrong :( - break; - - if( sc->data[SC_READING_SB]->val2 > point ) - sc->data[SC_READING_SB]->val2 -= point; - else // Last spell to be released - status_change_end(src, SC_READING_SB, INVALID_TIMER); - - clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); - if( !skill_check_condition_castbegin(sd, skill_id, skill_lv) ) - break; - - switch( skill_get_casttype(skill_id) ) - { - case CAST_GROUND: - skill_castend_pos2(src, bl->x, bl->y, skill_id, skill_lv, tick, 0); - break; - case CAST_NODAMAGE: - skill_castend_nodamage_id(src, bl, skill_id, skill_lv, tick, 0); - break; - case CAST_DAMAGE: - skill_castend_damage_id(src, bl, skill_id, skill_lv, tick, 0); - break; - } - - sd->ud.canact_tick = tick + skill_delayfix(src, skill_id, skill_lv); - clif_status_change(src, SI_ACTIONDELAY, 1, skill_delayfix(src, skill_id, skill_lv), 0, 0, 0); - } - else - { // Summon Balls - int j = 0, k, skele; - int spheres[5] = { 0, 0, 0, 0, 0 }, - positions[5] = {-1,-1,-1,-1,-1 }; - - for( i = SC_SPHERE_1; i <= SC_SPHERE_5; i++ ) - if( sc && sc->data[i] ) - { - spheres[j] = i; - positions[j] = sc->data[i]->val2; - sc->data[i]->val2--; // Prepares for next position - j++; - } - - if( j == 0 ) - { // No Spheres - clif_skill_fail(sd,skill_id,USESKILL_FAIL_SUMMON_NONE,0); - break; - } - - // Sphere Sort - for( i = 0; i <= j - 2; i++ ) - for( k = i + 1; k <= j - 1; k++ ) - if( positions[i] > positions[k] ) - { - swap(positions[i],positions[k]); - swap(spheres[i],spheres[k]); - } - - if( skill_lv == 1 ) j = 1; // Limit only to one ball - for( i = 0; i < j; i++ ) - { - skele = WL_RELEASE - 5 + sc->data[spheres[i]]->val1 - WLS_FIRE; // Convert Ball Element into Skill ATK for balls - // WL_SUMMON_ATK_FIRE, WL_SUMMON_ATK_WIND, WL_SUMMON_ATK_WATER, WL_SUMMON_ATK_GROUND - skill_addtimerskill(src,tick+status_get_adelay(src)*i,bl->id,0,0,skele,sc->data[spheres[i]]->val3,BF_MAGIC,flag|SD_LEVEL); - status_change_end(src, spheres[i], INVALID_TIMER); // Eliminate ball - } - clif_skill_nodamage(src,bl,skill_id,0,1); - } - } - break; - case WL_FROSTMISTY: - // Causes Freezing status through walls. - sc_start(bl,status_skill2sc(skill_id),20+12*skill_lv+(sd ? sd->status.job_level : 50)/5,skill_lv,skill_get_time(skill_id,skill_lv)); - // Doesn't deal damage through non-shootable walls. - if( path_search(NULL,src->m,src->x,src->y,bl->x,bl->y,1,CELL_CHKWALL) ) - skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag|SD_ANIMATION); - break; - case WL_HELLINFERNO: - skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); - skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag|ELE_DARK); - break; - case RA_WUGSTRIKE: - if( sd && pc_isridingwug(sd) ){ - short x[8]={0,-1,-1,-1,0,1,1,1}; - short y[8]={1,1,0,-1,-1,-1,0,1}; - uint8 dir = map_calc_dir(bl, src->x, src->y); - - if( unit_movepos(src, bl->x+x[dir], bl->y+y[dir], 1, 1) ) - { - clif_slide(src, bl->x+x[dir], bl->y+y[dir]); - clif_fixpos(src); - skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); - } - break; - } - case RA_WUGBITE: - if( path_search(NULL,src->m,src->x,src->y,bl->x,bl->y,1,CELL_CHKNOREACH) ) { - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); - }else if( sd && skill_id == RA_WUGBITE ) // Only RA_WUGBITE has the skill fail message. - clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); - - break; - - case RA_SENSITIVEKEEN: - if( bl->type != BL_SKILL ) { // Only Hits Invisible Targets - struct status_change * tsc = status_get_sc(bl); - if( tsc && tsc->option&(OPTION_HIDE|OPTION_CLOAK) ){ - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); - status_change_end(bl, SC_CLOAKINGEXCEED, INVALID_TIMER); - } - } - else - { - struct skill_unit *su = BL_CAST(BL_SKILL,bl); - struct skill_unit_group* sg; - - if( su && (sg=su->group) && skill_get_inf2(sg->skill_id)&INF2_TRAP ) - { - if( !(sg->unit_id == UNT_USED_TRAPS || (sg->unit_id == UNT_ANKLESNARE && sg->val2 != 0 )) ) - { - struct item item_tmp; - memset(&item_tmp,0,sizeof(item_tmp)); - item_tmp.nameid = sg->item_id?sg->item_id:ITEMID_TRAP; - item_tmp.identify = 1; - if( item_tmp.nameid ) - map_addflooritem(&item_tmp,1,bl->m,bl->x,bl->y,0,0,0,0); - } - skill_delunit(su); - } - } - break; - case NC_INFRAREDSCAN: - if( flag&1 ) - { //TODO: Need a confirmation if the other type of hidden status is included to be scanned. [Jobbie] - if( rnd()%100 < 50 ) - sc_start(bl, SC_INFRAREDSCAN, 10000, skill_lv, skill_get_time(skill_id, skill_lv)); - status_change_end(bl, SC_HIDING, INVALID_TIMER); - status_change_end(bl, SC_CLOAKING, INVALID_TIMER); - status_change_end(bl, SC_CLOAKINGEXCEED, INVALID_TIMER); // Need confirm it. - } - else - { - map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_damage_id); - clif_skill_damage(src,src,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - if( sd ) pc_overheat(sd,1); - } - break; - - case NC_MAGNETICFIELD: - sc_start2(bl,SC_MAGNETICFIELD,100,skill_lv,src->id,skill_get_time(skill_id,skill_lv)); - break; - case SC_FATALMENACE: - if( flag&1 ) - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); - else - { - short x, y; - map_search_freecell(src, 0, &x, &y, -1, -1, 0); - // Destination area - skill_area_temp[4] = x; - skill_area_temp[5] = y; - map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_damage_id); - skill_addtimerskill(src,tick + 800,src->id,x,y,skill_id,skill_lv,0,flag); // To teleport Self - clif_skill_damage(src,src,tick,status_get_amotion(src),0,-30000,1,skill_id,skill_lv,6); - } - break; - case LG_PINPOINTATTACK: - if( !map_flag_gvg(src->m) && !map[src->m].flag.battleground && unit_movepos(src, bl->x, bl->y, 1, 1) ) - clif_slide(src,bl->x,bl->y); - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); - break; - - case LG_SHIELDSPELL: - // flag&1: Phisycal Attack, flag&2: Magic Attack. - skill_attack((flag&1)?BF_WEAPON:BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); - break; - - case LG_OVERBRAND: - skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag|SD_LEVEL); - break; - - case LG_OVERBRAND_BRANDISH: - skill_addtimerskill(src, tick + status_get_amotion(src)*8/10, bl->id, 0, 0, skill_id, skill_lv, BF_WEAPON, flag|SD_LEVEL); - break; - case SR_DRAGONCOMBO: - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); - break; - - case SR_KNUCKLEARROW: - if( !map_flag_gvg(src->m) && !map[src->m].flag.battleground && unit_movepos(src, bl->x, bl->y, 1, 1) ) { - clif_slide(src,bl->x,bl->y); - clif_fixpos(src); // Aegis send this packet too. - } - - if( flag&1 ) - skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag|SD_LEVEL); - else - skill_addtimerskill(src, tick + 300, bl->id, 0, 0, skill_id, skill_lv, BF_WEAPON, flag|SD_LEVEL|2); - break; - - case SR_HOWLINGOFLION: - status_change_end(bl, SC_SWINGDANCE, INVALID_TIMER); - status_change_end(bl, SC_SYMPHONYOFLOVER, INVALID_TIMER); - status_change_end(bl, SC_MOONLITSERENADE, INVALID_TIMER); - status_change_end(bl, SC_RUSHWINDMILL, INVALID_TIMER); - status_change_end(bl, SC_ECHOSONG, INVALID_TIMER); - status_change_end(bl, SC_HARMONIZE, INVALID_TIMER); - status_change_end(bl, SC_SIRCLEOFNATURE, INVALID_TIMER); - status_change_end(bl, SC_SATURDAYNIGHTFEVER, INVALID_TIMER); - status_change_end(bl, SC_DANCEWITHWUG, INVALID_TIMER); - status_change_end(bl, SC_LERADSDEW, INVALID_TIMER); - status_change_end(bl, SC_MELODYOFSINK, INVALID_TIMER); - status_change_end(bl, SC_BEYONDOFWARCRY, INVALID_TIMER); - status_change_end(bl, SC_UNLIMITEDHUMMINGVOICE, INVALID_TIMER); - skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag|SD_ANIMATION); - break; - - case SR_EARTHSHAKER: - if( flag&1 ) { //by default cloaking skills are remove by aoe skills so no more checking/removing except hiding and cloaking exceed. - skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); - status_change_end(bl, SC_HIDING, INVALID_TIMER); - status_change_end(bl, SC_CLOAKINGEXCEED, INVALID_TIMER); - } else{ - map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_damage_id); - clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - } - break; - - case WM_LULLABY_DEEPSLEEP: - if( bl != src && rnd()%100 < 88 + 2 * skill_lv ) - sc_start(bl,status_skill2sc(skill_id),100,skill_lv,skill_get_time(skill_id,skill_lv)); - break; - - case SO_POISON_BUSTER: { - struct status_change *tsc = status_get_sc(bl); - if( tsc && tsc->data[SC_POISON] ) { - skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); - status_change_end(bl, SC_POISON, INVALID_TIMER); - } - else if( sd ) - clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); - } - break; - - case GN_SPORE_EXPLOSION: - if( flag&1 ) - skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); - else { - clif_skill_nodamage(src, bl, skill_id, 0, 1); - skill_addtimerskill(src, gettick() + skill_get_time(skill_id, skill_lv) - 1000, bl->id, 0, 0, skill_id, skill_lv, 0, 0); - } - break; - - case EL_FIRE_BOMB: - case EL_FIRE_WAVE: - case EL_WATER_SCREW: - case EL_HURRICANE: - case EL_TYPOON_MIS: - if( flag&1 ) - skill_attack(skill_get_type(skill_id+1),src,src,bl,skill_id+1,skill_lv,tick,flag); - else { - int i = skill_get_splash(skill_id,skill_lv); - clif_skill_nodamage(src,battle_get_master(src),skill_id,skill_lv,1); - clif_skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - if( rnd()%100 < 30 ) - map_foreachinrange(skill_area_sub,bl,i,BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); - else - skill_attack(skill_get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); - } - break; - - case EL_ROCK_CRUSHER: - clif_skill_nodamage(src,battle_get_master(src),skill_id,skill_lv,1); - clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - if( rnd()%100 < 50 ) - skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); - else - skill_attack(BF_WEAPON,src,src,bl,EL_ROCK_CRUSHER_ATK,skill_lv,tick,flag); - break; - - case EL_STONE_RAIN: - if( flag&1 ) - skill_attack(skill_get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); - else { - int i = skill_get_splash(skill_id,skill_lv); - clif_skill_nodamage(src,battle_get_master(src),skill_id,skill_lv,1); - clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - if( rnd()%100 < 30 ) - map_foreachinrange(skill_area_sub,bl,i,BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); - else - skill_attack(skill_get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); - } - break; - - case EL_FIRE_ARROW: - case EL_ICE_NEEDLE: - case EL_WIND_SLASH: - case EL_STONE_HAMMER: - clif_skill_nodamage(src,battle_get_master(src),skill_id,skill_lv,1); - clif_skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - skill_attack(skill_get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); - break; - - case EL_TIDAL_WEAPON: - if( src->type == BL_ELEM ) { - struct elemental_data *ele = BL_CAST(BL_ELEM,src); - struct status_change *sc = status_get_sc(&ele->bl); - struct status_change *tsc = status_get_sc(bl); - sc_type type = status_skill2sc(skill_id), type2; - type2 = type-1; - - clif_skill_nodamage(src,battle_get_master(src),skill_id,skill_lv,1); - clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - if( (sc && sc->data[type2]) || (tsc && tsc->data[type]) ) { - elemental_clean_single_effect(ele, skill_id); - } - if( rnd()%100 < 50 ) - skill_attack(skill_get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); - else { - sc_start(src,type2,100,skill_lv,skill_get_time(skill_id,skill_lv)); - sc_start(battle_get_master(src),type,100,ele->bl.id,skill_get_time(skill_id,skill_lv)); - } - clif_skill_nodamage(src,src,skill_id,skill_lv,1); - } - break; - - - //recursive homon skill - case MH_MAGMA_FLOW: - case MH_XENO_SLASHER: - case MH_HEILIGE_STANGE: - if(flag & 1) - skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); - else { - map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag | BCT_ENEMY | SD_SPLASH | 1, skill_castend_damage_id); - } - break; - - case MH_STAHL_HORN: - case MH_NEEDLE_OF_PARALYZE: - skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); - break; - case MH_TINDER_BREAKER: - if (unit_movepos(src, bl->x, bl->y, 1, 1)) { -#if PACKETVER >= 20111005 - clif_snap(src, bl->x, bl->y); -#else - clif_skill_poseffect(src,skill_id,skill_lv,bl->x,bl->y,tick); -#endif - } - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start4(bl,SC_CLOSECONFINE2,100,skill_lv,src->id,0,0,skill_get_time(skill_id,skill_lv))); - skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); - break; - - case 0:/* no skill - basic/normal attack */ - if(sd) { - if (flag & 3){ - if (bl->id != skill_area_temp[1]) - skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, SD_LEVEL|flag); - } else { - skill_area_temp[1] = bl->id; - map_foreachinrange(skill_area_sub, bl, - sd->bonus.splash_range, BL_CHAR, - src, skill_id, skill_lv, tick, flag | BCT_ENEMY | 1, - skill_castend_damage_id); - flag|=1; //Set flag to 1 so ammo is not double-consumed. [Skotlex] - } - } - break; - - default: - ShowWarning("skill_castend_damage_id: Unknown skill used:%d\n",skill_id); - clif_skill_damage(src, bl, tick, status_get_amotion(src), tstatus->dmotion, - 0, abs(skill_get_num(skill_id, skill_lv)), - skill_id, skill_lv, skill_get_hit(skill_id)); - map_freeblock_unlock(); - return 1; - } - - if( sc && sc->data[SC_CURSEDCIRCLE_ATKER] ) //Should only remove after the skill has been casted. - status_change_end(src,SC_CURSEDCIRCLE_ATKER,INVALID_TIMER); - - map_freeblock_unlock(); - - if( sd && !(flag&1) ) - {// ensure that the skill last-cast tick is recorded - sd->canskill_tick = gettick(); - - if( sd->state.arrow_atk ) - {// consume arrow on last invocation to this skill. - battle_consume_ammo(sd, skill_id, skill_lv); - } - - // perform skill requirement consumption - skill_consume_requirement(sd,skill_id,skill_lv,2); - } - - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, uint16 skill_id, uint16 skill_lv, unsigned int tick, int flag) -{ - struct map_session_data *sd, *dstsd; - struct mob_data *md, *dstmd; - struct homun_data *hd; - struct mercenary_data *mer; - struct status_data *sstatus, *tstatus; - struct status_change *tsc; - struct status_change_entry *tsce; - - int i = 0; - enum sc_type type; - - if(skill_id > 0 && !skill_lv) return 0; // celest - - nullpo_retr(1, src); - nullpo_retr(1, bl); - - if (src->m != bl->m) - return 1; - - sd = BL_CAST(BL_PC, src); - hd = BL_CAST(BL_HOM, src); - md = BL_CAST(BL_MOB, src); - mer = BL_CAST(BL_MER, src); - - dstsd = BL_CAST(BL_PC, bl); - dstmd = BL_CAST(BL_MOB, bl); - - if(bl->prev == NULL) - return 1; - if(status_isdead(src)) - return 1; - - if( src != bl && status_isdead(bl) ) { - /** - * Skills that may be cast on dead targets - **/ - switch( skill_id ) { - case NPC_WIDESOULDRAIN: - case PR_REDEMPTIO: - case ALL_RESURRECTION: - case WM_DEADHILLHERE: - break; - default: - return 1; - } - } - - tstatus = status_get_status_data(bl); - sstatus = status_get_status_data(src); - - //Check for undead skills that convert a no-damage skill into a damage one. [Skotlex] - switch (skill_id) { - case HLIF_HEAL: //[orn] - if (bl->type != BL_HOM) { - if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0) ; - break ; - } - case AL_HEAL: - case ALL_RESURRECTION: - case PR_ASPERSIO: - /** - * Arch Bishop - **/ - case AB_RENOVATIO: - case AB_HIGHNESSHEAL: - //Apparently only player casted skills can be offensive like this. - if (sd && battle_check_undead(tstatus->race,tstatus->def_ele)) { - if (battle_check_target(src, bl, BCT_ENEMY) < 1) { - //Offensive heal does not works on non-enemies. [Skotlex] - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - return skill_castend_damage_id (src, bl, skill_id, skill_lv, tick, flag); - } - break; - case NPC_SMOKING: //Since it is a self skill, this one ends here rather than in damage_id. [Skotlex] - return skill_castend_damage_id (src, bl, skill_id, skill_lv, tick, flag); - case MH_STEINWAND: { - struct block_list *s_src = battle_get_master(src); - short ret = 0; - if(!skill_check_unit_range(src, src->x, src->y, skill_id, skill_lv)) //prevent reiteration - ret = skill_castend_pos2(src,src->x,src->y,skill_id,skill_lv,tick,flag); //cast on homon - if(s_src && !skill_check_unit_range(s_src, s_src->x, s_src->y, skill_id, skill_lv)) - ret |= skill_castend_pos2(s_src,s_src->x,s_src->y,skill_id,skill_lv,tick,flag); //cast on master - if (hd) - skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); - return ret; - } - break; - default: - //Skill is actually ground placed. - if (src == bl && skill_get_unit_id(skill_id,0)) - return skill_castend_pos2(src,bl->x,bl->y,skill_id,skill_lv,tick,0); - } - - type = status_skill2sc(skill_id); - tsc = status_get_sc(bl); - tsce = (tsc && type != -1)?tsc->data[type]:NULL; - - if (src!=bl && type > -1 && - (i = skill_get_ele(skill_id, skill_lv)) > ELE_NEUTRAL && - skill_get_inf(skill_id) != INF_SUPPORT_SKILL && - battle_attr_fix(NULL, NULL, 100, i, tstatus->def_ele, tstatus->ele_lv) <= 0) - return 1; //Skills that cause an status should be blocked if the target element blocks its element. - - map_freeblock_lock(); - switch(skill_id) - { - case HLIF_HEAL: //[orn] - case AL_HEAL: - /** - * Arch Bishop - **/ - case AB_HIGHNESSHEAL: - { - int heal = skill_calc_heal(src, bl, (skill_id == AB_HIGHNESSHEAL)?AL_HEAL:skill_id, (skill_id == AB_HIGHNESSHEAL)?10:skill_lv, true); - int heal_get_jobexp; - //Highness Heal: starts at 1.5 boost + 0.5 for each level - if( skill_id == AB_HIGHNESSHEAL ) { - heal = heal * ( 15 + 5 * skill_lv ) / 10; - } - if( status_isimmune(bl) || - (dstmd && (dstmd->class_ == MOBID_EMPERIUM || mob_is_battleground(dstmd))) || - (dstsd && pc_ismadogear(dstsd)) )//Mado is immune to heal - heal=0; - - if( sd && dstsd && sd->status.partner_id == dstsd->status.char_id && (sd->class_&MAPID_UPPERMASK) == MAPID_SUPER_NOVICE && sd->status.sex == 0 ) - heal = heal*2; - - if( tsc && tsc->count ) - { - if( tsc->data[SC_KAITE] && !(sstatus->mode&MD_BOSS) ) - { //Bounce back heal - if (--tsc->data[SC_KAITE]->val2 <= 0) - status_change_end(bl, SC_KAITE, INVALID_TIMER); - if (src == bl) - heal=0; //When you try to heal yourself under Kaite, the heal is voided. - else { - bl = src; - dstsd = sd; - } - } - else if (tsc->data[SC_BERSERK] || tsc->data[SC_SATURDAYNIGHTFEVER] || tsc->data[SC__BLOODYLUST]) - heal = 0; //Needed so that it actually displays 0 when healing. - } - clif_skill_nodamage (src, bl, skill_id, heal, 1); - if( tsc && tsc->data[SC_AKAITSUKI] && heal && skill_id != HLIF_HEAL ) - heal = ~heal + 1; - heal_get_jobexp = status_heal(bl,heal,0,0); - - if(sd && dstsd && heal > 0 && sd != dstsd && 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 (sd, bl, 0, heal_get_jobexp, false); - } - } - break; - - case PR_REDEMPTIO: - if (sd && !(flag&1)) { - if (sd->status.party_id == 0) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - skill_area_temp[0] = 0; - party_foreachsamemap(skill_area_sub, - sd,skill_get_splash(skill_id, skill_lv), - src,skill_id,skill_lv,tick, flag|BCT_PARTY|1, - skill_castend_nodamage_id); - if (skill_area_temp[0] == 0) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - skill_area_temp[0] = 5 - skill_area_temp[0]; // The actual penalty... - if (skill_area_temp[0] > 0 && !map[src->m].flag.noexppenalty) { //Apply penalty - sd->status.base_exp -= min(sd->status.base_exp, pc_nextbaseexp(sd) * skill_area_temp[0] * 2/1000); //0.2% penalty per each. - sd->status.job_exp -= min(sd->status.job_exp, pc_nextjobexp(sd) * skill_area_temp[0] * 2/1000); - clif_updatestatus(sd,SP_BASEEXP); - clif_updatestatus(sd,SP_JOBEXP); - } - status_set_hp(src, 1, 0); - status_set_sp(src, 0, 0); - break; - } else if (status_isdead(bl) && flag&1) { //Revive - skill_area_temp[0]++; //Count it in, then fall-through to the Resurrection code. - skill_lv = 3; //Resurrection level 3 is used - } else //Invalid target, skip resurrection. - break; - - case ALL_RESURRECTION: - if(sd && (map_flag_gvg(bl->m) || map[bl->m].flag.battleground)) - { //No reviving in WoE grounds! - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - if (!status_isdead(bl)) - break; - { - int per = 0, sper = 0; - if (tsc && tsc->data[SC_HELLPOWER]) - break; - - if (map[bl->m].flag.pvp && dstsd && dstsd->pvp_point < 0) - break; - - switch(skill_lv){ - case 1: per=10; break; - case 2: per=30; break; - case 3: per=50; break; - case 4: per=80; break; - } - if(dstsd && dstsd->special_state.restart_full_recover) - per = sper = 100; - if (status_revive(bl, per, sper)) - { - clif_skill_nodamage(src,bl,ALL_RESURRECTION,skill_lv,1); //Both Redemptio and Res show this skill-animation. - if(sd && dstsd && battle_config.resurrection_exp > 0) - { - int exp = 0,jexp = 0; - int lv = dstsd->status.base_level - sd->status.base_level, jlv = dstsd->status.job_level - sd->status.job_level; - if(lv > 0 && pc_nextbaseexp(dstsd)) { - exp = (int)((double)dstsd->status.base_exp * (double)lv * (double)battle_config.resurrection_exp / 1000000.); - if (exp < 1) exp = 1; - } - if(jlv > 0 && pc_nextjobexp(dstsd)) { - jexp = (int)((double)dstsd->status.job_exp * (double)lv * (double)battle_config.resurrection_exp / 1000000.); - if (jexp < 1) jexp = 1; - } - if(exp > 0 || jexp > 0) - pc_gainexp (sd, bl, exp, jexp, false); - } - } - } - break; - - case AL_DECAGI: - case MER_DECAGI: - clif_skill_nodamage (src, bl, skill_id, skill_lv, - sc_start(bl, type, (40 + skill_lv * 2 + (status_get_lv(src) + sstatus->int_)/5), skill_lv, skill_get_time(skill_id,skill_lv))); - break; - - case AL_CRUCIS: - if (flag&1) - sc_start(bl,type, 23+skill_lv*4 +status_get_lv(src) -status_get_lv(bl), skill_lv,skill_get_time(skill_id,skill_lv)); - else { - map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_CHAR, - src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); - clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); - } - break; - - case PR_LEXDIVINA: - case MER_LEXDIVINA: - if( tsce ) - status_change_end(bl,type, INVALID_TIMER); - else - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); - clif_skill_nodamage (src, bl, skill_id, skill_lv, 1); - break; - - case SA_ABRACADABRA: - { - int abra_skill_id = 0, abra_skill_lv; - do { - i = rnd() % MAX_SKILL_ABRA_DB; - abra_skill_id = skill_abra_db[i].skill_id; - } while (abra_skill_id == 0 || - skill_abra_db[i].req_lv > skill_lv || //Required lv for it to appear - rnd()%10000 >= skill_abra_db[i].per - ); - abra_skill_lv = min(skill_lv, skill_get_max(abra_skill_id)); - clif_skill_nodamage (src, bl, skill_id, skill_lv, 1); - - if( sd ) - {// player-casted - sd->state.abra_flag = 1; - sd->skillitem = abra_skill_id; - sd->skillitemlv = abra_skill_lv; - clif_item_skill(sd, abra_skill_id, abra_skill_lv); - } - else - {// mob-casted - struct unit_data *ud = unit_bl2ud(src); - int inf = skill_get_inf(abra_skill_id); - int target_id = 0; - if (!ud) break; - if (inf&INF_SELF_SKILL || inf&INF_SUPPORT_SKILL) { - if (src->type == BL_PET) - bl = (struct block_list*)((TBL_PET*)src)->msd; - if (!bl) bl = src; - unit_skilluse_id(src, bl->id, abra_skill_id, abra_skill_lv); - } else { //Assume offensive skills - if (ud->target) - target_id = ud->target; - else switch (src->type) { - case BL_MOB: target_id = ((TBL_MOB*)src)->target_id; break; - case BL_PET: target_id = ((TBL_PET*)src)->target_id; break; - } - if (!target_id) - break; - if (skill_get_casttype(abra_skill_id) == CAST_GROUND) { - bl = map_id2bl(target_id); - if (!bl) bl = src; - unit_skilluse_pos(src, bl->x, bl->y, abra_skill_id, abra_skill_lv); - } else - unit_skilluse_id(src, target_id, abra_skill_id, abra_skill_lv); - } - } - } - break; - - case SA_COMA: - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,type,100,skill_lv,skill_get_time2(skill_id,skill_lv))); - break; - case SA_FULLRECOVERY: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - if (status_isimmune(bl)) - break; - status_percent_heal(bl, 100, 100); - break; - case NPC_ALLHEAL: - { - int heal; - if( status_isimmune(bl) ) - break; - heal = status_percent_heal(bl, 100, 0); - clif_skill_nodamage(NULL, bl, AL_HEAL, heal, 1); - if( dstmd ) - { // Reset Damage Logs - memset(dstmd->dmglog, 0, sizeof(dstmd->dmglog)); - dstmd->tdmg = 0; - } - } - break; - case SA_SUMMONMONSTER: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - if (sd) mob_once_spawn(sd, src->m, src->x, src->y," --ja--", -1, 1, "", SZ_SMALL, AI_NONE); - break; - case SA_LEVELUP: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - if (sd && pc_nextbaseexp(sd)) pc_gainexp(sd, NULL, pc_nextbaseexp(sd) * 10 / 100, 0, false); - break; - case SA_INSTANTDEATH: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - status_set_hp(bl,1,0); - break; - case SA_QUESTION: - case SA_GRAVITY: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - break; - case SA_CLASSCHANGE: - case SA_MONOCELL: - if (dstmd) - { - int class_; - if ( sd && dstmd->status.mode&MD_BOSS ) - { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - class_ = skill_id==SA_MONOCELL?1002:mob_get_random_id(4, 1, 0); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - mob_class_change(dstmd,class_); - if( tsc && dstmd->status.mode&MD_BOSS ) - { - const enum sc_type scs[] = { SC_QUAGMIRE, SC_PROVOKE, SC_ROKISWEIL, SC_GRAVITATION, SC_SUITON, SC_STRIPWEAPON, SC_STRIPSHIELD, SC_STRIPARMOR, SC_STRIPHELM, SC_BLADESTOP }; - for (i = SC_COMMON_MIN; i <= SC_COMMON_MAX; i++) - if (tsc->data[i]) status_change_end(bl, (sc_type)i, INVALID_TIMER); - for (i = 0; i < ARRAYLENGTH(scs); i++) - if (tsc->data[scs[i]]) status_change_end(bl, scs[i], INVALID_TIMER); - } - } - break; - case SA_DEATH: - if ( sd && dstmd && dstmd->status.mode&MD_BOSS ) - { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - status_kill(bl); - break; - case SA_REVERSEORCISH: - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id, skill_lv))); - break; - case SA_FORTUNE: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - if(sd) pc_getzeny(sd,status_get_lv(bl)*100,LOG_TYPE_STEAL,NULL); - break; - case SA_TAMINGMONSTER: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - if (sd && dstmd) { - ARR_FIND( 0, MAX_PET_DB, i, dstmd->class_ == pet_db[i].class_ ); - if( i < MAX_PET_DB ) - pet_catch_process1(sd, dstmd->class_); - } - break; - - case CR_PROVIDENCE: - if(sd && dstsd){ //Check they are not another crusader [Skotlex] - if ((dstsd->class_&MAPID_UPPERMASK) == MAPID_CRUSADER) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - map_freeblock_unlock(); - return 1; - } - } - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - break; - - case CG_MARIONETTE: - { - struct status_change* sc = status_get_sc(src); - - if( sd && dstsd && (dstsd->class_&MAPID_UPPERMASK) == MAPID_BARDDANCER && dstsd->status.sex == sd->status.sex ) - {// Cannot cast on another bard/dancer-type class of the same gender as caster - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - map_freeblock_unlock(); - return 1; - } - - if( sc && tsc ) - { - if( !sc->data[SC_MARIONETTE] && !tsc->data[SC_MARIONETTE2] ) - { - sc_start(src,SC_MARIONETTE,100,bl->id,skill_get_time(skill_id,skill_lv)); - sc_start(bl,SC_MARIONETTE2,100,src->id,skill_get_time(skill_id,skill_lv)); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - } - else - if( sc->data[SC_MARIONETTE ] && sc->data[SC_MARIONETTE ]->val1 == bl->id && - tsc->data[SC_MARIONETTE2] && tsc->data[SC_MARIONETTE2]->val1 == src->id ) - { - status_change_end(src, SC_MARIONETTE, INVALID_TIMER); - status_change_end(bl, SC_MARIONETTE2, INVALID_TIMER); - } - else - { - if( sd ) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - - map_freeblock_unlock(); - return 1; - } - } - } - break; - - case RG_CLOSECONFINE: - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start4(bl,type,100,skill_lv,src->id,0,0,skill_get_time(skill_id,skill_lv))); - 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 (dstsd) { - if(dstsd->status.weapon == W_FIST || - (dstsd->sc.count && !dstsd->sc.data[type] && - ( //Allow re-enchanting to lenghten time. [Skotlex] - dstsd->sc.data[SC_FIREWEAPON] || - dstsd->sc.data[SC_WATERWEAPON] || - dstsd->sc.data[SC_WINDWEAPON] || - dstsd->sc.data[SC_EARTHWEAPON] || - dstsd->sc.data[SC_SHADOWWEAPON] || - dstsd->sc.data[SC_GHOSTWEAPON] || - dstsd->sc.data[SC_ENCPOISON] - )) - ) { - if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - clif_skill_nodamage(src,bl,skill_id,skill_lv,0); - break; - } - } - // 100% success rate at lv4 & 5, but lasts longer at lv5 - if(!clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(bl,type,(60+skill_lv*10),skill_lv, skill_get_time(skill_id,skill_lv)))) { - if (sd) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - if (skill_break_equip(bl, EQP_WEAPON, 10000, BCT_PARTY) && sd && sd != dstsd) - clif_displaymessage(sd->fd, msg_txt(669)); - } - break; - - case PR_ASPERSIO: - if (sd && dstmd) { - clif_skill_nodamage(src,bl,skill_id,skill_lv,0); - break; - } - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - break; - - case ITEM_ENCHANTARMS: - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start2(bl,type,100,skill_lv, - skill_get_ele(skill_id,skill_lv), skill_get_time(skill_id,skill_lv))); - break; - - case TK_SEVENWIND: - switch(skill_get_ele(skill_id,skill_lv)) { - case ELE_EARTH : type = SC_EARTHWEAPON; break; - case ELE_WIND : type = SC_WINDWEAPON; break; - case ELE_WATER : type = SC_WATERWEAPON; break; - case ELE_FIRE : type = SC_FIREWEAPON; break; - case ELE_GHOST : type = SC_GHOSTWEAPON; break; - case ELE_DARK : type = SC_SHADOWWEAPON; break; - case ELE_HOLY : type = SC_ASPERSIO; break; - } - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - - sc_start(bl,SC_SEVENWIND,100,skill_lv,skill_get_time(skill_id,skill_lv)); - - break; - - case PR_KYRIE: - case MER_KYRIE: - clif_skill_nodamage(bl,bl,skill_id,skill_lv, - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - break; - //Passive Magnum, should had been casted on yourself. - case SM_MAGNUM: - case MS_MAGNUM: - skill_area_temp[1] = 0; - map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_SKILL|BL_CHAR, - src,skill_id,skill_lv,tick, flag|BCT_ENEMY|1, skill_castend_damage_id); - clif_skill_nodamage (src,src,skill_id,skill_lv,1); - // Initiate 10% of your damage becomes fire element. - sc_start4(src,SC_WATK_ELEMENT,100,3,20,0,0,skill_get_time2(skill_id, skill_lv)); - if( sd ) - skill_blockpc_start(sd, skill_id, skill_get_time(skill_id, skill_lv)); - else if( bl->type == BL_MER ) - skill_blockmerc_start((TBL_MER*)bl, skill_id, skill_get_time(skill_id, skill_lv)); - break; - - case TK_JUMPKICK: - /* Check if the target is an enemy; if not, skill should fail so the character doesn't unit_movepos (exploitable) */ - if( battle_check_target(src, bl, BCT_ENEMY) > 0 ) - { - if( unit_movepos(src, bl->x, bl->y, 1, 1) ) - { - skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); - clif_slide(src,bl->x,bl->y); - } - } - else - clif_skill_fail(sd,skill_id,USESKILL_FAIL,0); - break; - - case AL_INCAGI: - case AL_BLESSING: - case MER_INCAGI: - case MER_BLESSING: - if (dstsd != NULL && tsc->data[SC_CHANGEUNDEAD]) { - skill_attack(BF_MISC,src,src,bl,skill_id,skill_lv,tick,flag); - break; - } - case PR_SLOWPOISON: - case PR_IMPOSITIO: - case PR_LEXAETERNA: - case PR_SUFFRAGIUM: - case PR_BENEDICTIO: - case LK_BERSERK: - case MS_BERSERK: - case KN_AUTOCOUNTER: - case KN_TWOHANDQUICKEN: - case KN_ONEHAND: - case MER_QUICKEN: - case CR_SPEARQUICKEN: - case CR_REFLECTSHIELD: - case MS_REFLECTSHIELD: - case AS_POISONREACT: - case MC_LOUD: - case MG_ENERGYCOAT: - case MO_EXPLOSIONSPIRITS: - case MO_STEELBODY: - case MO_BLADESTOP: - case LK_AURABLADE: - case LK_PARRYING: - case MS_PARRYING: - case LK_CONCENTRATION: - case WS_CARTBOOST: - case SN_SIGHT: - case WS_MELTDOWN: - case WS_OVERTHRUSTMAX: - case ST_REJECTSWORD: - case HW_MAGICPOWER: - case PF_MEMORIZE: - case PA_SACRIFICE: - case ASC_EDP: - case PF_DOUBLECASTING: - case SG_SUN_COMFORT: - case SG_MOON_COMFORT: - case SG_STAR_COMFORT: - case NPC_HALLUCINATION: - case GS_MADNESSCANCEL: - case GS_ADJUSTMENT: - case GS_INCREASING: - case NJ_KASUMIKIRI: - case NJ_UTSUSEMI: - case NJ_NEN: - case NPC_DEFENDER: - case NPC_MAGICMIRROR: - case ST_PRESERVE: - case NPC_INVINCIBLE: - case NPC_INVINCIBLEOFF: - case RK_DEATHBOUND: - case AB_RENOVATIO: - case AB_EXPIATIO: - case AB_DUPLELIGHT: - case AB_SECRAMENT: - case NC_ACCELERATION: - case NC_HOVERING: - case NC_SHAPESHIFT: - case WL_RECOGNIZEDSPELL: - case GC_VENOMIMPRESS: - case SC_DEADLYINFECT: - case LG_EXEEDBREAK: - case LG_PRESTIGE: - case SR_CRESCENTELBOW: - case SR_LIGHTNINGWALK: - case SR_GENTLETOUCH_ENERGYGAIN: - case GN_CARTBOOST: - case KO_MEIKYOUSISUI: - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - break; - - case SO_STRIKING: - if (sd) { - int bonus = 25 + 10 * skill_lv; - bonus += (pc_checkskill(sd, SA_FLAMELAUNCHER)+pc_checkskill(sd, SA_FROSTWEAPON)+pc_checkskill(sd, SA_LIGHTNINGLOADER)+pc_checkskill(sd, SA_SEISMICWEAPON))*5; - clif_skill_nodamage( src, bl, skill_id, skill_lv, - battle_check_target(src,bl,BCT_PARTY) ? - sc_start2(bl, type, 100, skill_lv, bonus, skill_get_time(skill_id,skill_lv)) : - 0 - ); - } - break; - - case NPC_STOP: - if( clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start2(bl,type,100,skill_lv,src->id,skill_get_time(skill_id,skill_lv)) ) ) - sc_start2(src,type,100,skill_lv,bl->id,skill_get_time(skill_id,skill_lv)); - break; - case HP_ASSUMPTIO: - if( sd && dstmd ) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - else - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - break; - case MG_SIGHT: - case MER_SIGHT: - case AL_RUWACH: - case WZ_SIGHTBLASTER: - case NPC_WIDESIGHT: - case NPC_STONESKIN: - case NPC_ANTIMAGIC: - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start2(bl,type,100,skill_lv,skill_id,skill_get_time(skill_id,skill_lv))); - break; - case HLIF_AVOID: - case HAMI_DEFENCE: - i = skill_get_time(skill_id,skill_lv); - clif_skill_nodamage(bl,bl,skill_id,skill_lv,sc_start(bl,type,100,skill_lv,i)); // Master - clif_skill_nodamage(src,src,skill_id,skill_lv,sc_start(src,type,100,skill_lv,i)); // Homunc - break; - case NJ_BUNSINJYUTSU: - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - status_change_end(bl, SC_NEN, INVALID_TIMER); - break; -/* Was modified to only affect targetted char. [Skotlex] - case HP_ASSUMPTIO: - if (flag&1) - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); - else - { - map_foreachinrange(skill_area_sub, bl, - skill_get_splash(skill_id, skill_lv), BL_PC, - src, skill_id, skill_lv, tick, flag|BCT_ALL|1, - skill_castend_nodamage_id); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - } - break; -*/ - case SM_ENDURE: - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - if (sd) - skill_blockpc_start (sd, skill_id, skill_get_time2(skill_id,skill_lv)); - break; - - case AS_ENCHANTPOISON: // Prevent spamming [Valaris] - if (sd && dstsd && dstsd->sc.count) { - if (dstsd->sc.data[SC_FIREWEAPON] || - dstsd->sc.data[SC_WATERWEAPON] || - dstsd->sc.data[SC_WINDWEAPON] || - dstsd->sc.data[SC_EARTHWEAPON] || - dstsd->sc.data[SC_SHADOWWEAPON] || - dstsd->sc.data[SC_GHOSTWEAPON] - // dstsd->sc.data[SC_ENCPOISON] //People say you should be able to recast to lengthen the timer. [Skotlex] - ) { - clif_skill_nodamage(src,bl,skill_id,skill_lv,0); - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - } - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - break; - - case LK_TENSIONRELAX: - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start4(bl,type,100,skill_lv,0,0,skill_get_time2(skill_id,skill_lv), - skill_get_time(skill_id,skill_lv))); - break; - - case MC_CHANGECART: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - break; - - case TK_MISSION: - if (sd) { - int id; - if (sd->mission_mobid && (sd->mission_count || rnd()%100)) { //Cannot change target when already have one - clif_mission_info(sd, sd->mission_mobid, sd->mission_count); - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - id = mob_get_random_id(0,0xF, sd->status.base_level); - if (!id) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - sd->mission_mobid = id; - sd->mission_count = 0; - pc_setglobalreg(sd,"TK_MISSION_ID", id); - clif_mission_info(sd, id, 0); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - } - break; - - case AC_CONCENTRATION: - { - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - map_foreachinrange( status_change_timer_sub, src, - skill_get_splash(skill_id, skill_lv), BL_CHAR, - src,NULL,type,tick); - } - break; - - case SM_PROVOKE: - case SM_SELFPROVOKE: - case MER_PROVOKE: - if( (tstatus->mode&MD_BOSS) || battle_check_undead(tstatus->race,tstatus->def_ele) ) - { - map_freeblock_unlock(); - return 1; - } - //TODO: How much does base level affects? Dummy value of 1% per level difference used. [Skotlex] - clif_skill_nodamage(src,bl,skill_id == SM_SELFPROVOKE ? SM_PROVOKE : skill_id,skill_lv, - (i = sc_start(bl,type, skill_id == SM_SELFPROVOKE ? 100:( 50 + 3*skill_lv + status_get_lv(src) - status_get_lv(bl)), skill_lv, skill_get_time(skill_id,skill_lv)))); - if( !i ) - { - if( sd ) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - map_freeblock_unlock(); - return 0; - } - unit_skillcastcancel(bl, 2); - - if( tsc && tsc->count ) - { - status_change_end(bl, SC_FREEZE, INVALID_TIMER); - if( tsc->data[SC_STONE] && tsc->opt1 == OPT1_STONE ) - status_change_end(bl, SC_STONE, INVALID_TIMER); - status_change_end(bl, SC_SLEEP, INVALID_TIMER); - status_change_end(bl, SC_TRICKDEAD, INVALID_TIMER); - } - - if( dstmd ) - { - dstmd->state.provoke_flag = src->id; - mob_target(dstmd, src, skill_get_range2(src,skill_id,skill_lv)); - } - break; - - case ML_DEVOTION: - case CR_DEVOTION: - { - int count, lv; - if( !dstsd || (!sd && !mer) ) - { // Only players can be devoted - if( sd ) - clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); - break; - } - - if( (lv = status_get_lv(src) - dstsd->status.base_level) < 0 ) - lv = -lv; - if( lv > battle_config.devotion_level_difference || // Level difference requeriments - (dstsd->sc.data[type] && dstsd->sc.data[type]->val1 != src->id) || // Cannot Devote a player devoted from another source - (skill_id == ML_DEVOTION && (!mer || mer != dstsd->md)) || // Mercenary only can devote owner - (dstsd->class_&MAPID_UPPERMASK) == MAPID_CRUSADER || // Crusader Cannot be devoted - (dstsd->sc.data[SC_HELLPOWER])) // Players affected by SC_HELLPOWERR cannot be devoted. - { - if( sd ) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - map_freeblock_unlock(); - return 1; - } - - i = 0; - count = (sd)? min(skill_lv,5) : 1; // Mercenary only can Devote owner - if( sd ) - { // Player Devoting Player - ARR_FIND(0, count, i, sd->devotion[i] == bl->id ); - if( i == count ) - { - ARR_FIND(0, count, i, sd->devotion[i] == 0 ); - if( i == count ) - { // No free slots, skill Fail - clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); - map_freeblock_unlock(); - return 1; - } - } - - sd->devotion[i] = bl->id; - } - else - mer->devotion_flag = 1; // Mercenary Devoting Owner - - clif_skill_nodamage(src, bl, skill_id, skill_lv, - sc_start4(bl, type, 100, src->id, i, skill_get_range2(src,skill_id,skill_lv),0, skill_get_time2(skill_id, skill_lv))); - clif_devotion(src, NULL); - } - break; - - case MO_CALLSPIRITS: - if(sd) { - int limit = skill_lv; - if( sd->sc.data[SC_RAISINGDRAGON] ) - limit += sd->sc.data[SC_RAISINGDRAGON]->val1; - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - pc_addspiritball(sd,skill_get_time(skill_id,skill_lv),limit); - } - break; - - case CH_SOULCOLLECT: - if(sd) { - int limit = 5; - if( sd->sc.data[SC_RAISINGDRAGON] ) - limit += sd->sc.data[SC_RAISINGDRAGON]->val1; - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - for (i = 0; i < limit; i++) - pc_addspiritball(sd,skill_get_time(skill_id,skill_lv),limit); - } - break; - - case MO_KITRANSLATION: - if(dstsd && (dstsd->class_&MAPID_BASEMASK)!=MAPID_GUNSLINGER) { - pc_addspiritball(dstsd,skill_get_time(skill_id,skill_lv),5); - } - break; - - case TK_TURNKICK: - case MO_BALKYOUNG: //Passive part of the attack. Splash knock-back+stun. [Skotlex] - if (skill_area_temp[1] != bl->id) { - skill_blown(src,bl,skill_get_blewcount(skill_id,skill_lv),-1,0); - skill_additional_effect(src,bl,skill_id,skill_lv,BF_MISC,ATK_DEF,tick); //Use Misc rather than weapon to signal passive pushback - } - break; - - case MO_ABSORBSPIRITS: - i = 0; - if (dstsd && dstsd->spiritball && (sd == dstsd || map_flag_vs(src->m)) && (dstsd->class_&MAPID_BASEMASK)!=MAPID_GUNSLINGER) - { // split the if for readability, and included gunslingers in the check so that their coins cannot be removed [Reddozen] - i = dstsd->spiritball * 7; - pc_delspiritball(dstsd,dstsd->spiritball,0); - } else if (dstmd && !(tstatus->mode&MD_BOSS) && rnd() % 100 < 20) - { // check if target is a monster and not a Boss, for the 20% chance to absorb 2 SP per monster's level [Reddozen] - i = 2 * dstmd->level; - mob_target(dstmd,src,0); - } - if (i) status_heal(src, 0, i, 3); - clif_skill_nodamage(src,bl,skill_id,skill_lv,i?1:0); - break; - - case AC_MAKINGARROW: - if(sd) { - clif_arrow_create_list(sd); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - } - break; - - case AM_PHARMACY: - if(sd) { - clif_skill_produce_mix_list(sd,skill_id,22); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - } - break; - - case SA_CREATECON: - if(sd) { - clif_elementalconverter_list(sd); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - } - break; - - case BS_HAMMERFALL: - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,SC_STUN,(20 + 10 * skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv))); - break; - case RG_RAID: - skill_area_temp[1] = 0; - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - map_foreachinrange(skill_area_sub, bl, - skill_get_splash(skill_id, skill_lv), splash_target(src), - src,skill_id,skill_lv,tick, flag|BCT_ENEMY|1, - skill_castend_damage_id); - status_change_end(src, SC_HIDING, INVALID_TIMER); - break; - - case ASC_METEORASSAULT: - case GS_SPREADATTACK: - case RK_STORMBLAST: - case NC_AXETORNADO: - case GC_COUNTERSLASH: - case SR_SKYNETBLOW: - case SR_RAMPAGEBLASTER: - case SR_HOWLINGOFLION: - case KO_HAPPOKUNAI: - skill_area_temp[1] = 0; - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - i = map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), - src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_damage_id); - if( !i && ( skill_id == NC_AXETORNADO || skill_id == SR_SKYNETBLOW || skill_id == KO_HAPPOKUNAI ) ) - clif_skill_damage(src,src,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - break; - - case NC_EMERGENCYCOOL: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - status_change_end(src,SC_OVERHEAT_LIMITPOINT,INVALID_TIMER); - status_change_end(src,SC_OVERHEAT,INVALID_TIMER); - break; - case SR_WINDMILL: - case GN_CART_TORNADO: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - case SR_EARTHSHAKER: - case NC_INFRAREDSCAN: - case NPC_EARTHQUAKE: - case NPC_VAMPIRE_GIFT: - case NPC_HELLJUDGEMENT: - case NPC_PULSESTRIKE: - case LG_MOONSLASHER: - skill_castend_damage_id(src, src, skill_id, skill_lv, tick, flag); - break; - - case KN_BRANDISHSPEAR: - case ML_BRANDISH: - skill_brandishspear(src, bl, skill_id, skill_lv, tick, flag); - break; - - case WZ_SIGHTRASHER: - //Passive side of the attack. - status_change_end(src, SC_SIGHT, INVALID_TIMER); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - map_foreachinrange(skill_area_sub,src, - skill_get_splash(skill_id, skill_lv),BL_CHAR|BL_SKILL, - src,skill_id,skill_lv,tick, flag|BCT_ENEMY|1, - skill_castend_damage_id); - break; - - case NJ_HYOUSYOURAKU: - case NJ_RAIGEKISAI: - case WZ_FROSTNOVA: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - skill_area_temp[1] = 0; - map_foreachinrange(skill_attack_area, src, - skill_get_splash(skill_id, skill_lv), splash_target(src), - BF_MAGIC, src, src, skill_id, skill_lv, tick, flag, BCT_ENEMY); - break; - - case HVAN_EXPLOSION: //[orn] - case NPC_SELFDESTRUCTION: - //Self Destruction hits everyone in range (allies+enemies) - //Except for Summoned Marine spheres on non-versus maps, where it's just enemy. - i = ((!md || md->special_state.ai == 2) && !map_flag_vs(src->m))? - BCT_ENEMY:BCT_ALL; - clif_skill_nodamage(src, src, skill_id, -1, 1); - map_delblock(src); //Required to prevent chain-self-destructions hitting back. - map_foreachinrange(skill_area_sub, bl, - skill_get_splash(skill_id, skill_lv), splash_target(src), - src, skill_id, skill_lv, tick, flag|i, - skill_castend_damage_id); - map_addblock(src); - status_damage(src, src, sstatus->max_hp,0,0,1); - break; - - case AL_ANGELUS: - case PR_MAGNIFICAT: - case PR_GLORIA: - case SN_WINDWALK: - case CASH_BLESSING: - case CASH_INCAGI: - case CASH_ASSUMPTIO: - if( sd == NULL || sd->status.party_id == 0 || (flag & 1) ) - clif_skill_nodamage(bl, bl, skill_id, skill_lv, sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - else if( sd ) - party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); - break; - case MER_MAGNIFICAT: - if( mer != NULL ) - { - clif_skill_nodamage(bl, bl, skill_id, skill_lv, sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - if( mer->master && mer->master->status.party_id != 0 && !(flag&1) ) - party_foreachsamemap(skill_area_sub, mer->master, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); - else if( mer->master && !(flag&1) ) - clif_skill_nodamage(src, &mer->master->bl, skill_id, skill_lv, sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - } - break; - - case BS_ADRENALINE: - case BS_ADRENALINE2: - case BS_WEAPONPERFECT: - case BS_OVERTHRUST: - if (sd == NULL || sd->status.party_id == 0 || (flag & 1)) { - clif_skill_nodamage(bl,bl,skill_id,skill_lv, - sc_start2(bl,type,100,skill_lv,(src == bl)? 1:0,skill_get_time(skill_id,skill_lv))); - } else if (sd) { - party_foreachsamemap(skill_area_sub, - sd,skill_get_splash(skill_id, skill_lv), - src,skill_id,skill_lv,tick, flag|BCT_PARTY|1, - skill_castend_nodamage_id); - } - break; - - case BS_MAXIMIZE: - case NV_TRICKDEAD: - case CR_DEFENDER: - case ML_DEFENDER: - case CR_AUTOGUARD: - case ML_AUTOGUARD: - case TK_READYSTORM: - case TK_READYDOWN: - case TK_READYTURN: - case TK_READYCOUNTER: - case TK_DODGE: - case CR_SHRINK: - case SG_FUSION: - case GS_GATLINGFEVER: - if( tsce ) - { - clif_skill_nodamage(src,bl,skill_id,skill_lv,status_change_end(bl, type, INVALID_TIMER)); - map_freeblock_unlock(); - return 0; - } - clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - break; - case SL_KAITE: - case SL_KAAHI: - case SL_KAIZEL: - case SL_KAUPE: - if (sd) { - if (!dstsd || !( - (sd->sc.data[SC_SPIRIT] && sd->sc.data[SC_SPIRIT]->val2 == SL_SOULLINKER) || - (dstsd->class_&MAPID_UPPERMASK) == MAPID_SOUL_LINKER || - dstsd->status.char_id == sd->status.char_id || - dstsd->status.char_id == sd->status.partner_id || - dstsd->status.char_id == sd->status.child - )) { - status_change_start(src,SC_STUN,10000,skill_lv,0,0,0,500,8); - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - } - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id, skill_lv))); - break; - case SM_AUTOBERSERK: - case MER_AUTOBERSERK: - if( tsce ) - i = status_change_end(bl, type, INVALID_TIMER); - else - i = sc_start(bl,type,100,skill_lv,60000); - clif_skill_nodamage(src,bl,skill_id,skill_lv,i); - break; - case TF_HIDING: - case ST_CHASEWALK: - case KO_YAMIKUMO: - if (tsce) - { - clif_skill_nodamage(src,bl,skill_id,-1,status_change_end(bl, type, INVALID_TIMER)); //Hide skill-scream animation. - map_freeblock_unlock(); - return 0; - } else if( tsc && tsc->option&OPTION_MADOGEAR ) { - //Mado Gear cannot hide - if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - map_freeblock_unlock(); - return 0; - } - clif_skill_nodamage(src,bl,skill_id,-1,sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - break; - case TK_RUN: - if (tsce) - { - clif_skill_nodamage(src,bl,skill_id,skill_lv,status_change_end(bl, type, INVALID_TIMER)); - map_freeblock_unlock(); - return 0; - } - clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start4(bl,type,100,skill_lv,unit_getdir(bl),0,0,0)); - if (sd) // If the client receives a skill-use packet inmediately before a walkok packet, it will discard the walk packet! [Skotlex] - clif_walkok(sd); // So aegis has to resend the walk ok. - break; - case AS_CLOAKING: - case GC_CLOAKINGEXCEED: - case LG_FORCEOFVANGUARD: - case SC_REPRODUCE: - case SC_INVISIBILITY: - if (tsce) { - i = status_change_end(bl, type, INVALID_TIMER); - if( i ) - clif_skill_nodamage(src,bl,skill_id,( skill_id == LG_FORCEOFVANGUARD ) ? skill_lv : -1,i); - else if( sd ) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - map_freeblock_unlock(); - return 0; - } - case RA_CAMOUFLAGE: - i = sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); - if( i ) - clif_skill_nodamage(src,bl,skill_id,( skill_id == LG_FORCEOFVANGUARD ) ? skill_lv : -1,i); - else if( sd ) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - - case BD_ADAPTATION: - if(tsc && tsc->data[SC_DANCING]){ - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - status_change_end(bl, SC_DANCING, INVALID_TIMER); - } - break; - - case BA_FROSTJOKER: - case DC_SCREAM: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - skill_addtimerskill(src,tick+2000,bl->id,src->x,src->y,skill_id,skill_lv,0,flag); - - if (md) { - // custom hack to make the mob display the skill, because these skills don't show the skill use text themselves - //NOTE: mobs don't have the sprite animation that is used when performing this skill (will cause glitches) - char temp[70]; - snprintf(temp, sizeof(temp), "%s : %s !!",md->name,skill_db[skill_id].desc); - clif_message(&md->bl,temp); - } - break; - - case BA_PANGVOICE: - clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(bl,SC_CONFUSION,50,7,skill_get_time(skill_id,skill_lv))); - break; - - case DC_WINKCHARM: - if( dstsd ) - clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(bl,SC_CONFUSION,30,7,skill_get_time2(skill_id,skill_lv))); - else - if( dstmd ) - { - if( status_get_lv(src) > status_get_lv(bl) - && (tstatus->race == RC_DEMON || tstatus->race == RC_DEMIHUMAN || tstatus->race == RC_ANGEL) - && !(tstatus->mode&MD_BOSS) ) - clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start2(bl,type,70,skill_lv,src->id,skill_get_time(skill_id,skill_lv))); - else - { - clif_skill_nodamage(src,bl,skill_id,skill_lv,0); - if(sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - } - } - break; - - case TF_STEAL: - if(sd) { - if(pc_steal_item(sd,bl,skill_lv)) - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - else - clif_skill_fail(sd,skill_id,USESKILL_FAIL,0); - } - break; - - case RG_STEALCOIN: - if(sd) { - if(pc_steal_coin(sd,bl)) - { - dstmd->state.provoke_flag = src->id; - mob_target(dstmd, src, skill_get_range2(src,skill_id,skill_lv)); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - - } - else - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - } - break; - - case MG_STONECURSE: - { - int brate = 0; - if (tstatus->mode&MD_BOSS) { - if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - if(status_isimmune(bl) || !tsc) - break; - - if (sd && sd->sc.data[SC_PETROLOGY_OPTION]) - brate = sd->sc.data[SC_PETROLOGY_OPTION]->val3; - - if (tsc->data[SC_STONE]) { - status_change_end(bl, SC_STONE, INVALID_TIMER); - if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - if (sc_start4(bl,SC_STONE,(skill_lv*4+20)+brate, - skill_lv, 0, 0, skill_get_time(skill_id, skill_lv), - skill_get_time2(skill_id,skill_lv))) - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - else if(sd) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - // Level 6-10 doesn't consume a red gem if it fails [celest] - if (skill_lv > 5) - { // not to consume items - map_freeblock_unlock(); - return 0; - } - } - } - break; - - case NV_FIRSTAID: - clif_skill_nodamage(src,bl,skill_id,5,1); - status_heal(bl,5,0,0); - break; - - case AL_CURE: - if(status_isimmune(bl)) { - clif_skill_nodamage(src,bl,skill_id,skill_lv,0); - break; - } - status_change_end(bl, SC_SILENCE, INVALID_TIMER); - status_change_end(bl, SC_BLIND, INVALID_TIMER); - status_change_end(bl, SC_CONFUSION, INVALID_TIMER); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - break; - - case TF_DETOXIFY: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - status_change_end(bl, SC_POISON, INVALID_TIMER); - status_change_end(bl, SC_DPOISON, INVALID_TIMER); - break; - - case PR_STRECOVERY: - if(status_isimmune(bl)) { - clif_skill_nodamage(src,bl,skill_id,skill_lv,0); - break; - } - if (tsc && tsc->opt1) { - status_change_end(bl, SC_FREEZE, INVALID_TIMER); - status_change_end(bl, SC_STONE, INVALID_TIMER); - status_change_end(bl, SC_SLEEP, INVALID_TIMER); - status_change_end(bl, SC_STUN, INVALID_TIMER); - status_change_end(bl, SC_WHITEIMPRISON, INVALID_TIMER); - } - //Is this equation really right? It looks so... special. - if(battle_check_undead(tstatus->race,tstatus->def_ele)) - { - status_change_start(bl, SC_BLIND, - 100*(100-(tstatus->int_/2+tstatus->vit/3+tstatus->luk/10)), - 1,0,0,0, - skill_get_time2(skill_id, skill_lv) * (100-(tstatus->int_+tstatus->vit)/2)/100,0); - } - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - if(dstmd) - mob_unlocktarget(dstmd,tick); - break; - - // Mercenary Supportive Skills - case MER_BENEDICTION: - status_change_end(bl, SC_CURSE, INVALID_TIMER); - status_change_end(bl, SC_BLIND, INVALID_TIMER); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - break; - case MER_COMPRESS: - status_change_end(bl, SC_BLEEDING, INVALID_TIMER); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - break; - case MER_MENTALCURE: - status_change_end(bl, SC_CONFUSION, INVALID_TIMER); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - break; - case MER_RECUPERATE: - status_change_end(bl, SC_POISON, INVALID_TIMER); - status_change_end(bl, SC_SILENCE, INVALID_TIMER); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - break; - case MER_REGAIN: - status_change_end(bl, SC_SLEEP, INVALID_TIMER); - status_change_end(bl, SC_STUN, INVALID_TIMER); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - break; - case MER_TENDER: - status_change_end(bl, SC_FREEZE, INVALID_TIMER); - status_change_end(bl, SC_STONE, INVALID_TIMER); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - break; - - case MER_SCAPEGOAT: - if( mer && mer->master ) - { - status_heal(&mer->master->bl, mer->battle_status.hp, 0, 2); - status_damage(src, src, mer->battle_status.max_hp, 0, 0, 1); - } - break; - - case MER_ESTIMATION: - if( !mer ) - break; - sd = mer->master; - case WZ_ESTIMATION: - if( sd == NULL ) - break; - if( dstsd ) - { // Fail on Players - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - if( dstmd && dstmd->class_ == MOBID_EMPERIUM ) - break; // Cannot be Used on Emperium - - clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); - clif_skill_estimation(sd, bl); - if( skill_id == MER_ESTIMATION ) - sd = NULL; - break; - - case BS_REPAIRWEAPON: - if(sd && dstsd) - clif_item_repair_list(sd,dstsd,skill_lv); - break; - - case MC_IDENTIFY: - if(sd) - clif_item_identify_list(sd); - break; - - // Weapon Refining [Celest] - case WS_WEAPONREFINE: - if(sd) - clif_item_refine_list(sd); - break; - - case MC_VENDING: - if(sd) - { //Prevent vending of GMs with unnecessary Level to trade/drop. [Skotlex] - if ( !pc_can_give_items(sd) ) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - else { - sd->state.prevend = 1; - clif_openvendingreq(sd,2+skill_lv); - } - } - break; - - case AL_TELEPORT: - if(sd) - { - if (map[bl->m].flag.noteleport && skill_lv <= 2) { - clif_skill_teleportmessage(sd,0); - break; - } - if(!battle_config.duel_allow_teleport && sd->duel_group && skill_lv <= 2) { // duel restriction [LuzZza] - char output[128]; sprintf(output, msg_txt(365), skill_get_name(AL_TELEPORT)); - clif_displaymessage(sd->fd, output); //"Duel: Can't use %s in duel." - break; - } - - if( sd->state.autocast || ( (sd->skillitem == AL_TELEPORT || battle_config.skip_teleport_lv1_menu) && skill_lv == 1 ) || skill_lv == 3 ) - { - if( skill_lv == 1 ) - pc_randomwarp(sd,CLR_TELEPORT); - else - pc_setpos(sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,CLR_TELEPORT); - break; - } - - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - if( skill_lv == 1 ) - clif_skill_warppoint(sd,skill_id,skill_lv, (unsigned short)-1,0,0,0); - else - clif_skill_warppoint(sd,skill_id,skill_lv, (unsigned short)-1,sd->status.save_point.map,0,0); - } else - unit_warp(bl,-1,-1,-1,CLR_TELEPORT); - break; - - case NPC_EXPULSION: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - unit_warp(bl,-1,-1,-1,CLR_TELEPORT); - break; - - case AL_HOLYWATER: - if(sd) { - if (skill_produce_mix(sd, skill_id, 523, 0, 0, 0, 1)) - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - else - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - } - break; - - case TF_PICKSTONE: - if(sd) { - int eflag; - struct item item_tmp; - struct block_list tbl; - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - memset(&item_tmp,0,sizeof(item_tmp)); - memset(&tbl,0,sizeof(tbl)); // [MouseJstr] - item_tmp.nameid = ITEMID_STONE; - item_tmp.identify = 1; - tbl.id = 0; - clif_takeitem(&sd->bl,&tbl); - eflag = pc_additem(sd,&item_tmp,1,LOG_TYPE_PRODUCE); - if(eflag) { - clif_additem(sd,0,0,eflag); - map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); - } - } - break; - case ASC_CDP: - if(sd) { - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - skill_produce_mix(sd, skill_id, 678, 0, 0, 0, 1); //Produce a Deadly Poison Bottle. - } - break; - - case RG_STRIPWEAPON: - case RG_STRIPSHIELD: - case RG_STRIPARMOR: - case RG_STRIPHELM: - case ST_FULLSTRIP: - case GC_WEAPONCRUSH: - case SC_STRIPACCESSARY: { - unsigned short location = 0; - int d = 0; - - //Rate in percent - if ( skill_id == ST_FULLSTRIP ) { - i = 5 + 2*skill_lv + (sstatus->dex - tstatus->dex)/5; - } else if( skill_id == SC_STRIPACCESSARY ) { - i = 12 + 2 * skill_lv + (sstatus->dex - tstatus->dex)/5; - } else { - i = 5 + 5*skill_lv + (sstatus->dex - tstatus->dex)/5; - } - - if (i < 5) i = 5; //Minimum rate 5% - - //Duration in ms - if( skill_id == GC_WEAPONCRUSH){ - d = skill_get_time(skill_id,skill_lv); - if(bl->type == BL_PC) - d += skill_lv * 15 + (sstatus->dex - tstatus->dex); - else - d += skill_lv * 30 + (sstatus->dex - tstatus->dex) / 2; - }else - d = skill_get_time(skill_id,skill_lv) + (sstatus->dex - tstatus->dex)*500; - - if (d < 0) d = 0; //Minimum duration 0ms - - switch (skill_id) { - case RG_STRIPWEAPON: - case GC_WEAPONCRUSH: - location = EQP_WEAPON; - break; - case RG_STRIPSHIELD: - location = EQP_SHIELD; - break; - case RG_STRIPARMOR: - location = EQP_ARMOR; - break; - case RG_STRIPHELM: - location = EQP_HELM; - break; - case ST_FULLSTRIP: - location = EQP_WEAPON|EQP_SHIELD|EQP_ARMOR|EQP_HELM; - break; - case SC_STRIPACCESSARY: - location = EQP_ACC; - break; - } - - //Special message when trying to use strip on FCP [Jobbie] - if( sd && skill_id == ST_FULLSTRIP && tsc && tsc->data[SC_CP_WEAPON] && tsc->data[SC_CP_HELM] && tsc->data[SC_CP_ARMOR] && tsc->data[SC_CP_SHIELD]) - { - clif_gospel_info(sd, 0x28); - break; - } - - //Attempts to strip at rate i and duration d - if( (i = skill_strip_equip(bl, location, i, skill_lv, d)) || (skill_id != ST_FULLSTRIP && skill_id != GC_WEAPONCRUSH ) ) - clif_skill_nodamage(src,bl,skill_id,skill_lv,i); - - //Nothing stripped. - if( sd && !i ) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - - case AM_BERSERKPITCHER: - case AM_POTIONPITCHER: { - int i,x,hp = 0,sp = 0,bonus=100; - if( dstmd && dstmd->class_ == MOBID_EMPERIUM ) { - map_freeblock_unlock(); - return 1; - } - if( sd ) { - x = skill_lv%11 - 1; - i = pc_search_inventory(sd,skill_db[skill_id].itemid[x]); - if( i < 0 || skill_db[skill_id].itemid[x] <= 0 ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - map_freeblock_unlock(); - return 1; - } - if(sd->inventory_data[i] == NULL || sd->status.inventory[i].amount < skill_db[skill_id].amount[x]) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - map_freeblock_unlock(); - return 1; - } - if( skill_id == AM_BERSERKPITCHER ) { - if( dstsd && dstsd->status.base_level < (unsigned int)sd->inventory_data[i]->elv ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - map_freeblock_unlock(); - return 1; - } - } - potion_flag = 1; - potion_hp = potion_sp = potion_per_hp = potion_per_sp = 0; - potion_target = bl->id; - run_script(sd->inventory_data[i]->script,0,sd->bl.id,0); - potion_flag = potion_target = 0; - if( sd->sc.data[SC_SPIRIT] && sd->sc.data[SC_SPIRIT]->val2 == SL_ALCHEMIST ) - bonus += sd->status.base_level; - if( potion_per_hp > 0 || potion_per_sp > 0 ) { - hp = tstatus->max_hp * potion_per_hp / 100; - hp = hp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000; - if( dstsd ) { - sp = dstsd->status.max_sp * potion_per_sp / 100; - sp = sp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000; - } - } else { - if( potion_hp > 0 ) { - hp = potion_hp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000; - hp = hp * (100 + (tstatus->vit<<1)) / 100; - if( dstsd ) - hp = hp * (100 + pc_checkskill(dstsd,SM_RECOVERY)*10) / 100; - } - if( potion_sp > 0 ) { - sp = potion_sp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000; - sp = sp * (100 + (tstatus->int_<<1)) / 100; - if( dstsd ) - sp = sp * (100 + pc_checkskill(dstsd,MG_SRECOVERY)*10) / 100; - } - } - - if (sd->itemgrouphealrate[IG_POTION]>0) { - hp += hp * sd->itemgrouphealrate[IG_POTION] / 100; - sp += sp * sd->itemgrouphealrate[IG_POTION] / 100; - } - - if( (i = pc_skillheal_bonus(sd, skill_id)) ) { - hp += hp * i / 100; - sp += sp * i / 100; - } - } else { - hp = (1 + rnd()%400) * (100 + skill_lv*10) / 100; - hp = hp * (100 + (tstatus->vit<<1)) / 100; - if( dstsd ) - hp = hp * (100 + pc_checkskill(dstsd,SM_RECOVERY)*10) / 100; - } - if( dstsd && (i = pc_skillheal2_bonus(dstsd, skill_id)) ) { - hp += hp * i / 100; - sp += sp * i / 100; - } - if( tsc && tsc->count ) { - if( tsc->data[SC_CRITICALWOUND] ) { - hp -= hp * tsc->data[SC_CRITICALWOUND]->val2 / 100; - sp -= sp * tsc->data[SC_CRITICALWOUND]->val2 / 100; - } - if( tsc->data[SC_DEATHHURT] ) { - hp -= hp * 20 / 100; - sp -= sp * 20 / 100; - } - if( tsc->data[SC_WATER_INSIGNIA] && tsc->data[SC_WATER_INSIGNIA]->val1 == 2 ) { - hp += hp / 10; - sp += sp / 10; - } - } - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - if( hp > 0 || (skill_id == AM_POTIONPITCHER && sp <= 0) ) - clif_skill_nodamage(NULL,bl,AL_HEAL,hp,1); - if( sp > 0 ) - clif_skill_nodamage(NULL,bl,MG_SRECOVERY,sp,1); -#ifdef RENEWAL - if( tsc && tsc->data[SC_EXTREMITYFIST2] ) - sp = 0; -#endif - status_heal(bl,hp,sp,0); - } - break; - case AM_CP_WEAPON: - case AM_CP_SHIELD: - case AM_CP_ARMOR: - case AM_CP_HELM: - { - unsigned int equip[] = {EQP_WEAPON, EQP_SHIELD, EQP_ARMOR, EQP_HEAD_TOP}; - - if( sd && ( bl->type != BL_PC || ( dstsd && pc_checkequip(dstsd,equip[skill_id - AM_CP_WEAPON]) < 0 ) ) ){ - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - map_freeblock_unlock(); // Don't consume item requirements - return 0; - } - - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - } - break; - case AM_TWILIGHT1: - if (sd) { - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - //Prepare 200 White Potions. - if (!skill_produce_mix(sd, skill_id, 504, 0, 0, 0, 200)) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - } - break; - case AM_TWILIGHT2: - if (sd) { - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - //Prepare 200 Slim White Potions. - if (!skill_produce_mix(sd, skill_id, 547, 0, 0, 0, 200)) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - } - break; - case AM_TWILIGHT3: - if (sd) { - int ebottle = pc_search_inventory(sd,713); - if( ebottle >= 0 ) - ebottle = sd->status.inventory[ebottle].amount; - //check if you can produce all three, if not, then fail: - if (!skill_can_produce_mix(sd,970,-1, 100) //100 Alcohol - || !skill_can_produce_mix(sd,7136,-1, 50) //50 Acid Bottle - || !skill_can_produce_mix(sd,7135,-1, 50) //50 Flame Bottle - || ebottle < 200 //200 empty bottle are required at total. - ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - skill_produce_mix(sd, skill_id, 970, 0, 0, 0, 100); - skill_produce_mix(sd, skill_id, 7136, 0, 0, 0, 50); - skill_produce_mix(sd, skill_id, 7135, 0, 0, 0, 50); - } - break; - case SA_DISPELL: - if (flag&1 || (i = skill_get_splash(skill_id, skill_lv)) < 1) - { - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - if((dstsd && (dstsd->class_&MAPID_UPPERMASK) == MAPID_SOUL_LINKER) - || (tsc && tsc->data[SC_SPIRIT] && tsc->data[SC_SPIRIT]->val2 == SL_ROGUE) //Rogue's spirit defends againt dispel. - || rnd()%100 >= 50+10*skill_lv - || ( tsc && tsc->option&OPTION_MADOGEAR ) )//Mado Gear is immune to dispell according to bug report 49 [Ind] - { - if (sd) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - if(status_isimmune(bl) || !tsc || !tsc->count) - break; - for(i=0;i<SC_MAX;i++) - { - if (!tsc->data[i]) - continue; - switch (i) { - case SC_WEIGHT50: case SC_WEIGHT90: case SC_HALLUCINATION: - 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: case SC_COMBO: - case SC_STRFOOD: case SC_AGIFOOD: case SC_VITFOOD: - case SC_INTFOOD: case SC_DEXFOOD: case SC_LUKFOOD: - case SC_HITFOOD: case SC_FLEEFOOD: case SC_BATKFOOD: - case SC_WATKFOOD: case SC_MATKFOOD: case SC_DANCING: - case SC_EDP: case SC_AUTOBERSERK: - case SC_CARTBOOST: case SC_MELTDOWN: case SC_SAFETYWALL: - case SC_SMA: case SC_SPEEDUP0: case SC_NOCHAT: - case SC_ANKLE: case SC_SPIDERWEB: case SC_JAILED: - case SC_ITEMBOOST: case SC_EXPBOOST: case SC_LIFEINSURANCE: - case SC_BOSSMAPINFO: case SC_PNEUMA: case SC_AUTOSPELL: - case SC_INCHITRATE: case SC_INCATKRATE: case SC_NEN: - case SC_READYSTORM: case SC_READYDOWN: case SC_READYTURN: - case SC_READYCOUNTER: case SC_DODGE: case SC_WARM: - case SC_SPEEDUP1: case SC_AUTOTRADE: case SC_CRITICALWOUND: - case SC_JEXPBOOST: case SC_INVINCIBLE: case SC_INVINCIBLEOFF: - case SC_HELLPOWER: case SC_MANU_ATK: case SC_MANU_DEF: - case SC_SPL_ATK: case SC_SPL_DEF: case SC_MANU_MATK: - case SC_SPL_MATK: case SC_RICHMANKIM: case SC_ETERNALCHAOS: - case SC_DRUMBATTLE: case SC_NIBELUNGEN: case SC_ROKISWEIL: - case SC_INTOABYSS: case SC_SIEGFRIED: case SC_FOOD_STR_CASH: - case SC_FOOD_AGI_CASH: case SC_FOOD_VIT_CASH: case SC_FOOD_DEX_CASH: - case SC_FOOD_INT_CASH: case SC_FOOD_LUK_CASH: case SC_SEVENWIND: - case SC_MIRACLE: case SC_S_LIFEPOTION: case SC_L_LIFEPOTION: - case SC_INCHEALRATE: case SC_ELECTRICSHOCKER: case SC__STRIPACCESSORY: - //case SC_SAVAGE_STEAK: case SC_COCKTAIL_WARG_BLOOD: case SC_MINOR_BBQ: - //case SC_SIROMA_ICE_TEA: case SC_DROCERA_HERB_STEAMED: case SC_PUTTI_TAILS_NOODLES: - case SC_NEUTRALBARRIER_MASTER: case SC_NEUTRALBARRIER: case SC_STEALTHFIELD_MASTER: - case SC_STEALTHFIELD: case SC_GIANTGROWTH: case SC_MILLENNIUMSHIELD: - case SC_REFRESH: case SC_STONEHARDSKIN: case SC_VITALITYACTIVATION: - case SC_FIGHTINGSPIRIT: case SC_ABUNDANCE: case SC__SHADOWFORM: - case SC_LEADERSHIP: case SC_GLORYWOUNDS: case SC_SOULCOLD: - case SC_HAWKEYES: case SC_GUILDAURA: case SC_PUSH_CART: - case SC_RAISINGDRAGON: case SC_GT_ENERGYGAIN: case SC_GT_CHANGE: - case SC_GT_REVITALIZE: case SC_REFLECTDAMAGE: case SC_INSPIRATION: - case SC_EXEEDBREAK: case SC_FORCEOFVANGUARD: case SC_BANDING: - case SC_DUPLELIGHT: case SC_EXPIATIO: case SC_LAUDAAGNUS: - case SC_LAUDARAMUS: case SC_GATLINGFEVER: case SC_INCREASING: - case SC_ADJUSTMENT: case SC_MADNESSCANCEL: -#ifdef RENEWAL - case SC_EXTREMITYFIST2: -#endif - continue; - /** - * bugreport:4888 these songs may only be dispelled if you're not in their song area anymore - **/ - case SC_WHISTLE: - case SC_ASSNCROS: - case SC_POEMBRAGI: - case SC_APPLEIDUN: - case SC_HUMMING: - case SC_DONTFORGETME: - case SC_FORTUNE: - case SC_SERVICE4U: - if( tsc->data[i]->val4 ) //val4 = out-of-song-area - continue; - break; - case SC_ASSUMPTIO: - if( bl->type == BL_MOB ) - continue; - break; - } - if(i==SC_BERSERK || i==SC_SATURDAYNIGHTFEVER) tsc->data[i]->val2=0; //Mark a dispelled berserk to avoid setting hp to 100 by setting hp penalty to 0. - status_change_end(bl, (sc_type)i, INVALID_TIMER); - } - break; - } - //Affect all targets on splash area. - map_foreachinrange(skill_area_sub, bl, i, BL_CHAR, - src, skill_id, skill_lv, tick, flag|1, - skill_castend_damage_id); - break; - - case TF_BACKSLIDING: //This is the correct implementation as per packet logging information. [Skotlex] - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - skill_blown(src,bl,skill_get_blewcount(skill_id,skill_lv),unit_getdir(bl),0); - break; - - case TK_HIGHJUMP: - { - int x,y, dir = unit_getdir(src); - - //Fails on noteleport maps, except for GvG and BG maps [Skotlex] - if( map[src->m].flag.noteleport && - !(map[src->m].flag.battleground || map_flag_gvg2(src->m) ) - ) { - x = src->x; - y = src->y; - } else { - x = src->x + dirx[dir]*skill_lv*2; - y = src->y + diry[dir]*skill_lv*2; - } - - clif_skill_nodamage(src,bl,TK_HIGHJUMP,skill_lv,1); - if(!map_count_oncell(src->m,x,y,BL_PC|BL_NPC|BL_MOB) && map_getcell(src->m,x,y,CELL_CHKREACH)) { - clif_slide(src,x,y); - unit_movepos(src, x, y, 1, 0); - } - } - break; - - case SA_CASTCANCEL: - case SO_SPELLFIST: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - unit_skillcastcancel(src,1); - if(sd) { - int sp = skill_get_sp(sd->skill_id_old,sd->skill_lv_old); - if( skill_id == SO_SPELLFIST ){ - sc_start4(src,type,100,skill_lv+1,skill_lv,sd->skill_id_old,sd->skill_lv_old,skill_get_time(skill_id,skill_lv)); - sd->skill_id_old = sd->skill_lv_old = 0; - break; - } - sp = sp * (90 - (skill_lv-1)*20) / 100; - if(sp < 0) sp = 0; - status_zap(src, 0, sp); - } - break; - case SA_SPELLBREAKER: - { - int sp; - if(tsc && tsc->data[SC_MAGICROD]) { - sp = skill_get_sp(skill_id,skill_lv); - sp = sp * tsc->data[SC_MAGICROD]->val2 / 100; - if(sp < 1) sp = 1; - status_heal(bl,0,sp,2); - status_percent_damage(bl, src, 0, -20, false); //20% max SP damage. - } else { - struct unit_data *ud = unit_bl2ud(bl); - int bl_skill_id=0,bl_skill_lv=0,hp = 0; - if (!ud || ud->skilltimer == INVALID_TIMER) - break; //Nothing to cancel. - bl_skill_id = ud->skill_id; - bl_skill_lv = ud->skill_lv; - if (tstatus->mode & MD_BOSS) - { //Only 10% success chance against bosses. [Skotlex] - if (rnd()%100 < 90) - { - if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - } else if (!dstsd || map_flag_vs(bl->m)) //HP damage only on pvp-maps when against players. - hp = tstatus->max_hp/50; //Recover 2% HP [Skotlex] - - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - unit_skillcastcancel(bl,0); - sp = skill_get_sp(bl_skill_id,bl_skill_lv); - status_zap(bl, hp, sp); - - if (hp && skill_lv >= 5) - hp>>=1; //Recover half damaged HP at level 5 [Skotlex] - else - hp = 0; - - if (sp) //Recover some of the SP used - sp = sp*(25*(skill_lv-1))/100; - - if(hp || sp) - status_heal(src, hp, sp, 2); - } - } - break; - case SA_MAGICROD: - clif_skill_nodamage(src,src,SA_MAGICROD,skill_lv,1); - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); - break; - case SA_AUTOSPELL: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - if(sd) - clif_autospell(sd,skill_lv); - else { - int maxlv=1,spellid=0; - static const int spellarray[3] = { MG_COLDBOLT,MG_FIREBOLT,MG_LIGHTNINGBOLT }; - if(skill_lv >= 10) { - spellid = MG_FROSTDIVER; -// if (tsc && tsc->data[SC_SPIRIT] && tsc->data[SC_SPIRIT]->val2 == SA_SAGE) -// maxlv = 10; -// else - maxlv = skill_lv - 9; - } - else if(skill_lv >=8) { - spellid = MG_FIREBALL; - maxlv = skill_lv - 7; - } - else if(skill_lv >=5) { - spellid = MG_SOULSTRIKE; - maxlv = skill_lv - 4; - } - else if(skill_lv >=2) { - int i = rnd()%3; - spellid = spellarray[i]; - maxlv = skill_lv - 1; - } - else if(skill_lv > 0) { - spellid = MG_NAPALMBEAT; - maxlv = 3; - } - if(spellid > 0) - sc_start4(src,SC_AUTOSPELL,100,skill_lv,spellid,maxlv,0, - skill_get_time(SA_AUTOSPELL,skill_lv)); - } - break; - - case BS_GREED: - if(sd){ - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - map_foreachinrange(skill_greed,bl, - skill_get_splash(skill_id, skill_lv),BL_ITEM,bl); - } - break; - - case SA_ELEMENTWATER: - case SA_ELEMENTFIRE: - case SA_ELEMENTGROUND: - case SA_ELEMENTWIND: - if(sd && !dstmd) //Only works on monsters. - break; - if(tstatus->mode&MD_BOSS) - 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: - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start2(bl, type, 100, skill_lv, skill_get_ele(skill_id,skill_lv), - skill_get_time(skill_id, skill_lv))); - break; - case NPC_CHANGEUNDEAD: - //This skill should fail if target is wearing bathory/evil druid card [Brainstorm] - //TO-DO This is ugly, fix it - if(tstatus->def_ele==ELE_UNDEAD || tstatus->def_ele==ELE_DARK) break; - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start2(bl, type, 100, skill_lv, skill_get_ele(skill_id,skill_lv), - skill_get_time(skill_id, skill_lv))); - break; - - case NPC_PROVOCATION: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - if (md) mob_unlocktarget(md, tick); - break; - - case NPC_KEEPING: - case NPC_BARRIER: - { - int skill_time = skill_get_time(skill_id,skill_lv); - struct unit_data *ud = unit_bl2ud(bl); - if (clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,type,100,skill_lv,skill_time)) - && ud) { //Disable attacking/acting/moving for skill's duration. - ud->attackabletime = - ud->canact_tick = - ud->canmove_tick = tick + skill_time; - } - } - break; - - case NPC_REBIRTH: - if( md && md->state.rebirth ) - break; // only works once - sc_start(bl,type,100,skill_lv,-1); - break; - - case NPC_DARKBLESSING: - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start2(bl,type,(50+skill_lv*5),skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv))); - break; - - case NPC_LICK: - status_zap(bl, 0, 100); - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,type,(skill_lv*5),skill_lv,skill_get_time2(skill_id,skill_lv))); - break; - - case NPC_SUICIDE: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - status_kill(src); //When suiciding, neither exp nor drops is given. - break; - - case NPC_SUMMONSLAVE: - case NPC_SUMMONMONSTER: - if(md && md->skill_idx >= 0) - mob_summonslave(md,md->db->skill[md->skill_idx].val,skill_lv,skill_id); - break; - - case NPC_CALLSLAVE: - mob_warpslave(src,MOB_SLAVEDISTANCE); - break; - - case NPC_RANDOMMOVE: - if (md) { - md->next_walktime = tick - 1; - mob_randomwalk(md,tick); - } - break; - - case NPC_SPEEDUP: - { - // or does it increase casting rate? just a guess xD - int i = SC_ASPDPOTION0 + skill_lv - 1; - if (i > SC_ASPDPOTION3) - i = SC_ASPDPOTION3; - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,(sc_type)i,100,skill_lv,skill_lv * 60000)); - } - break; - - case NPC_REVENGE: - // not really needed... but adding here anyway ^^ - if (md && md->master_id > 0) { - struct block_list *mbl, *tbl; - if ((mbl = map_id2bl(md->master_id)) == NULL || - (tbl = battle_gettargeted(mbl)) == NULL) - break; - md->state.provoke_flag = tbl->id; - mob_target(md, tbl, sstatus->rhw.range); - } - break; - - case NPC_RUN: - { - const int mask[8][2] = {{0,-1},{1,-1},{1,0},{1,1},{0,1},{-1,1},{-1,0},{-1,-1}}; - uint8 dir = (bl == src)?unit_getdir(src):map_calc_dir(src,bl->x,bl->y); //If cast on self, run forward, else run away. - unit_stop_attack(src); - //Run skillv tiles overriding the can-move check. - if (unit_walktoxy(src, src->x + skill_lv * mask[dir][0], src->y + skill_lv * mask[dir][1], 2) && md) - md->state.skillstate = MSS_WALK; //Otherwise it isn't updated in the ai. - } - break; - - case NPC_TRANSFORMATION: - case NPC_METAMORPHOSIS: - if(md && md->skill_idx >= 0) { - int class_ = mob_random_class (md->db->skill[md->skill_idx].val,0); - if (skill_lv > 1) //Multiply the rest of mobs. [Skotlex] - mob_summonslave(md,md->db->skill[md->skill_idx].val,skill_lv-1,skill_id); - if (class_) mob_class_change(md, class_); - } - break; - - case NPC_EMOTION_ON: - case NPC_EMOTION: - //va[0] is the emotion to use. - //NPC_EMOTION & NPC_EMOTION_ON can change a mob's mode 'permanently' [Skotlex] - //val[1] 'sets' the mode - //val[2] adds to the current mode - //val[3] removes from the current mode - //val[4] if set, asks to delete the previous mode change. - if(md && md->skill_idx >= 0 && tsc) - { - clif_emotion(bl, md->db->skill[md->skill_idx].val[0]); - if(md->db->skill[md->skill_idx].val[4] && tsce) - status_change_end(bl, type, INVALID_TIMER); - - if(md->db->skill[md->skill_idx].val[1] || md->db->skill[md->skill_idx].val[2]) - sc_start4(src, type, 100, skill_lv, - md->db->skill[md->skill_idx].val[1], - md->db->skill[md->skill_idx].val[2], - md->db->skill[md->skill_idx].val[3], - skill_get_time(skill_id, skill_lv)); - } - break; - - case NPC_POWERUP: - sc_start(bl,SC_INCATKRATE,100,200,skill_get_time(skill_id, skill_lv)); - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,type,100,100,skill_get_time(skill_id, skill_lv))); - break; - - case NPC_AGIUP: - sc_start(bl,SC_SPEEDUP1,100,skill_lv,skill_get_time(skill_id, skill_lv)); - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,type,100,100,skill_get_time(skill_id, skill_lv))); - break; - - case NPC_INVISIBLE: - //Have val4 passed as 6 is for "infinite cloak" (do not end on attack/skill use). - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start4(bl,type,100,skill_lv,0,0,6,skill_get_time(skill_id,skill_lv))); - break; - - case NPC_SIEGEMODE: - // not sure what it does - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - break; - - case WE_MALE: - { - int hp_rate=(!skill_lv)? 0:skill_db[skill_id].hp_rate[skill_lv-1]; - int gain_hp= tstatus->max_hp*abs(hp_rate)/100; // The earned is the same % of the target HP than it costed the caster. [Skotlex] - clif_skill_nodamage(src,bl,skill_id,status_heal(bl, gain_hp, 0, 0),1); - } - break; - case WE_FEMALE: - { - int sp_rate=(!skill_lv)? 0:skill_db[skill_id].sp_rate[skill_lv-1]; - int gain_sp=tstatus->max_sp*abs(sp_rate)/100;// The earned is the same % of the target SP than it costed the caster. [Skotlex] - clif_skill_nodamage(src,bl,skill_id,status_heal(bl, 0, gain_sp, 0),1); - } - break; - - // parent-baby skills - case WE_BABY: - if(sd){ - struct map_session_data *f_sd = pc_get_father(sd); - struct map_session_data *m_sd = pc_get_mother(sd); - // if neither was found - if(!f_sd && !m_sd){ - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - map_freeblock_unlock(); - return 0; - } - status_change_start(bl,SC_STUN,10000,skill_lv,0,0,0,skill_get_time2(skill_id,skill_lv),8); - if (f_sd) sc_start(&f_sd->bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); - if (m_sd) sc_start(&m_sd->bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); - } - break; - - case PF_HPCONVERSION: - { - int hp, sp; - hp = sstatus->max_hp/10; - sp = hp * 10 * skill_lv / 100; - if (!status_charge(src,hp,0)) { - if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); - status_heal(bl,0,sp,2); - } - break; - - case MA_REMOVETRAP: - case HT_REMOVETRAP: - { - struct skill_unit* su; - struct skill_unit_group* sg; - su = BL_CAST(BL_SKILL, bl); - - // Mercenaries can remove any trap - // Players can only remove their own traps or traps on Vs maps. - if( su && (sg = su->group) && (src->type == BL_MER || sg->src_id == src->id || map_flag_vs(bl->m)) && (skill_get_inf2(sg->skill_id)&INF2_TRAP) ) - { - clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); - if( sd && !(sg->unit_id == UNT_USED_TRAPS || (sg->unit_id == UNT_ANKLESNARE && sg->val2 != 0 )) ) - { // prevent picking up expired traps - if( battle_config.skill_removetrap_type ) - { // get back all items used to deploy the trap - for( i = 0; i < 10; i++ ) - { - if( skill_db[su->group->skill_id].itemid[i] > 0 ) - { - int flag; - struct item item_tmp; - 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],LOG_TYPE_OTHER)) ) - { - 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,0,0,0,0); - } - } - } - } - else - { // get back 1 trap - struct item item_tmp; - memset(&item_tmp,0,sizeof(item_tmp)); - item_tmp.nameid = su->group->item_id?su->group->item_id:ITEMID_TRAP; - item_tmp.identify = 1; - if( item_tmp.nameid && (flag=pc_additem(sd,&item_tmp,1,LOG_TYPE_OTHER)) ) - { - clif_additem(sd,0,0,flag); - map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); - } - } - } - skill_delunit(su); - }else if(sd) - clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); - - } - break; - case HT_SPRINGTRAP: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - { - struct skill_unit *su=NULL; - if((bl->type==BL_SKILL) && (su=(struct skill_unit *)bl) && (su->group) ){ - switch(su->group->unit_id){ - case UNT_ANKLESNARE: // ankle snare - if (su->group->val2 != 0) - // if it is already trapping something don't spring it, - // remove trap should be used instead - break; - // otherwise fallthrough to below - case UNT_BLASTMINE: - case UNT_SKIDTRAP: - case UNT_LANDMINE: - case UNT_SHOCKWAVE: - case UNT_SANDMAN: - case UNT_FLASHER: - case UNT_FREEZINGTRAP: - case UNT_CLAYMORETRAP: - case UNT_TALKIEBOX: - su->group->unit_id = UNT_USED_TRAPS; - clif_changetraplook(bl, UNT_USED_TRAPS); - 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,skill_id,skill_lv,1); - if(sd) - unit_skilluse_id(src,src->id,sd->skill_id_dance,sd->skill_lv_dance); - break; - - case AS_SPLASHER: - if(tstatus->mode&MD_BOSS - /** - * Renewal dropped the 3/4 hp requirement - **/ - #ifndef RENEWAL - || tstatus-> hp > tstatus->max_hp*3/4 - #endif - ) { - if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - map_freeblock_unlock(); - return 1; - } - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start4(bl,type,100,skill_lv,skill_id,src->id,skill_get_time(skill_id,skill_lv),1000)); -#ifndef RENEWAL - if (sd) skill_blockpc_start (sd, skill_id, skill_get_time(skill_id, skill_lv)+3000); -#endif - break; - - case PF_MINDBREAKER: - { - if(tstatus->mode&MD_BOSS || battle_check_undead(tstatus->race,tstatus->def_ele)) - { - map_freeblock_unlock(); - return 1; - } - - if (tsce) - { //HelloKitty2 (?) explained that this silently fails when target is - //already inflicted. [Skotlex] - map_freeblock_unlock(); - return 1; - } - - //Has a 55% + skill_lv*5% success chance. - if (!clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,type,55+5*skill_lv,skill_lv,skill_get_time(skill_id,skill_lv)))) - { - if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - map_freeblock_unlock(); - return 0; - } - - unit_skillcastcancel(bl,0); - - if(tsc && tsc->count){ - status_change_end(bl, SC_FREEZE, INVALID_TIMER); - if(tsc->data[SC_STONE] && tsc->opt1 == OPT1_STONE) - status_change_end(bl, SC_STONE, INVALID_TIMER); - status_change_end(bl, SC_SLEEP, INVALID_TIMER); - } - - if(dstmd) - mob_target(dstmd,src,skill_get_range2(src,skill_id,skill_lv)); - } - break; - - case PF_SOULCHANGE: - { - unsigned int sp1 = 0, sp2 = 0; - if (dstmd) { - if (dstmd->state.soul_change_flag) { - if(sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - dstmd->state.soul_change_flag = 1; - sp2 = sstatus->max_sp * 3 /100; - status_heal(src, 0, sp2, 2); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - break; - } - sp1 = sstatus->sp; - sp2 = tstatus->sp; - #ifdef RENEWAL - sp1 = sp1 / 2; - sp2 = sp2 / 2; - if( tsc && tsc->data[SC_EXTREMITYFIST2] ) - sp1 = tstatus->sp; - #endif - status_set_sp(src, sp2, 3); - status_set_sp(bl, sp1, 3); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - } - break; - - // Slim Pitcher - case CR_SLIMPITCHER: - // Updated to block Slim Pitcher from working on barricades and guardian stones. - if( dstmd && (dstmd->class_ == MOBID_EMPERIUM || (dstmd->class_ >= MOBID_BARRICADE1 && dstmd->class_ <= MOBID_GUARIDAN_STONE2)) ) - break; - if (potion_hp || potion_sp) { - int hp = potion_hp, sp = potion_sp; - hp = hp * (100 + (tstatus->vit<<1))/100; - sp = sp * (100 + (tstatus->int_<<1))/100; - if (dstsd) { - if (hp) - hp = hp * (100 + pc_checkskill(dstsd,SM_RECOVERY)*10 + pc_skillheal2_bonus(dstsd, skill_id))/100; - if (sp) - sp = sp * (100 + pc_checkskill(dstsd,MG_SRECOVERY)*10 + pc_skillheal2_bonus(dstsd, skill_id))/100; - } - if( tsc && tsc->count ) { - if (tsc->data[SC_CRITICALWOUND]) { - hp -= hp * tsc->data[SC_CRITICALWOUND]->val2 / 100; - sp -= sp * tsc->data[SC_CRITICALWOUND]->val2 / 100; - } - if (tsc->data[SC_DEATHHURT]) { - hp -= hp * 20 / 100; - sp -= sp * 20 / 100; - } - if( tsc->data[SC_WATER_INSIGNIA] && tsc->data[SC_WATER_INSIGNIA]->val1 == 2) { - hp += hp / 10; - sp += sp / 10; - } - } - if(hp > 0) - clif_skill_nodamage(NULL,bl,AL_HEAL,hp,1); - if(sp > 0) - clif_skill_nodamage(NULL,bl,MG_SRECOVERY,sp,1); - status_heal(bl,hp,sp,0); - } - break; - // Full Chemical Protection - case CR_FULLPROTECTION: - { - unsigned int equip[] = {EQP_WEAPON, EQP_SHIELD, EQP_ARMOR, EQP_HEAD_TOP}; - int i, s = 0, skilltime = skill_get_time(skill_id,skill_lv); - - for (i=0 ; i<4; i++) { - if( bl->type != BL_PC || ( dstsd && pc_checkequip(dstsd,equip[i]) < 0 ) ) - continue; - sc_start(bl,(sc_type)(SC_CP_WEAPON + i),100,skill_lv,skilltime); - s++; - } - if( sd && !s ){ - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - map_freeblock_unlock(); // Don't consume item requirements - return 0; - } - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - } - break; - - case RG_CLEANER: //AppleGirl - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - break; - - case CG_LONGINGFREEDOM: - { - if (tsc && !tsce && (tsce=tsc->data[SC_DANCING]) && tsce->val4 - && (tsce->val1&0xFFFF) != CG_MOONLIT) //Can't use Longing for Freedom while under Moonlight Petals. [Skotlex] - { - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - } - } - break; - - case CG_TAROTCARD: - { - int eff, count = -1; - if( rnd() % 100 > skill_lv * 8 || (dstmd && ((dstmd->guardian_data && dstmd->class_ == MOBID_EMPERIUM) || mob_is_battleground(dstmd))) ) - { - if( sd ) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - - map_freeblock_unlock(); - return 0; - } - status_zap(src,0,skill_db[skill_get_index(skill_id)].sp[skill_lv]); // consume sp only if succeeded [Inkfish] - do { - eff = rnd() % 14; - clif_specialeffect(bl, 523 + eff, AREA); - switch (eff) - { - case 0: // heals SP to 0 - status_percent_damage(src, bl, 0, 100, false); - break; - case 1: // matk halved - sc_start(bl,SC_INCMATKRATE,100,-50,skill_get_time2(skill_id,skill_lv)); - break; - case 2: // all buffs removed - status_change_clear_buffs(bl,1); - break; - case 3: // 1000 damage, random armor destroyed - { - int where[] = { EQP_ARMOR, EQP_SHIELD, EQP_HELM, EQP_SHOES, EQP_GARMENT }; - status_fix_damage(src, bl, 1000, 0); - clif_damage(src,bl,tick,0,0,1000,0,0,0); - if( !status_isdead(bl) ) - skill_break_equip(bl, where[rnd()%5], 10000, BCT_ENEMY); - } - break; - case 4: // atk halved - sc_start(bl,SC_INCATKRATE,100,-50,skill_get_time2(skill_id,skill_lv)); - break; - case 5: // 2000HP heal, random teleported - status_heal(src, 2000, 0, 0); - if( !map_flag_vs(bl->m) ) - unit_warp(bl, -1,-1,-1, CLR_TELEPORT); - break; - case 6: // random 2 other effects - if (count == -1) - count = 3; - else - count++; //Should not retrigger this one. - break; - case 7: // stop freeze or stoned - { - enum sc_type sc[] = { SC_STOP, SC_FREEZE, SC_STONE }; - sc_start(bl,sc[rnd()%3],100,skill_lv,skill_get_time2(skill_id,skill_lv)); - } - break; - case 8: // curse coma and poison - sc_start(bl,SC_COMA,100,skill_lv,skill_get_time2(skill_id,skill_lv)); - sc_start(bl,SC_CURSE,100,skill_lv,skill_get_time2(skill_id,skill_lv)); - sc_start(bl,SC_POISON,100,skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - case 9: // confusion - sc_start(bl,SC_CONFUSION,100,skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - case 10: // 6666 damage, atk matk halved, cursed - status_fix_damage(src, bl, 6666, 0); - clif_damage(src,bl,tick,0,0,6666,0,0,0); - sc_start(bl,SC_INCATKRATE,100,-50,skill_get_time2(skill_id,skill_lv)); - sc_start(bl,SC_INCMATKRATE,100,-50,skill_get_time2(skill_id,skill_lv)); - sc_start(bl,SC_CURSE,skill_lv,100,skill_get_time2(skill_id,skill_lv)); - break; - case 11: // 4444 damage - status_fix_damage(src, bl, 4444, 0); - clif_damage(src,bl,tick,0,0,4444,0,0,0); - break; - case 12: // stun - sc_start(bl,SC_STUN,100,skill_lv,5000); - break; - case 13: // atk,matk,hit,flee,def reduced - sc_start(bl,SC_INCATKRATE,100,-20,skill_get_time2(skill_id,skill_lv)); - sc_start(bl,SC_INCMATKRATE,100,-20,skill_get_time2(skill_id,skill_lv)); - sc_start(bl,SC_INCHITRATE,100,-20,skill_get_time2(skill_id,skill_lv)); - sc_start(bl,SC_INCFLEERATE,100,-20,skill_get_time2(skill_id,skill_lv)); - sc_start(bl,SC_INCDEFRATE,100,-20,skill_get_time2(skill_id,skill_lv)); - break; - default: - break; - } - } while ((--count) > 0); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - } - break; - - case SL_ALCHEMIST: - case SL_ASSASIN: - case SL_BARDDANCER: - case SL_BLACKSMITH: - case SL_CRUSADER: - case SL_HUNTER: - case SL_KNIGHT: - case SL_MONK: - case SL_PRIEST: - case SL_ROGUE: - case SL_SAGE: - case SL_SOULLINKER: - case SL_STAR: - case SL_SUPERNOVICE: - case SL_WIZARD: - //NOTE: here, 'type' has the value of the associated MAPID, not of the SC_SPIRIT constant. - if (sd && !(dstsd && (dstsd->class_&MAPID_UPPERMASK) == type)) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - if (skill_id == SL_SUPERNOVICE && dstsd && dstsd->die_counter && !(rnd()%100)) - { //Erase death count 1% of the casts - dstsd->die_counter = 0; - pc_setglobalreg(dstsd,"PC_DIE_COUNTER", 0); - clif_specialeffect(bl, 0x152, AREA); - //SC_SPIRIT invokes status_calc_pc for us. - } - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start4(bl,SC_SPIRIT,100,skill_lv,skill_id,0,0,skill_get_time(skill_id,skill_lv))); - sc_start(src,SC_SMA,100,skill_lv,skill_get_time(SL_SMA,skill_lv)); - break; - case SL_HIGH: - if (sd && !(dstsd && (dstsd->class_&JOBL_UPPER) && !(dstsd->class_&JOBL_2) && dstsd->status.base_level < 70)) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start4(bl,type,100,skill_lv,skill_id,0,0,skill_get_time(skill_id,skill_lv))); - sc_start(src,SC_SMA,100,skill_lv,skill_get_time(SL_SMA,skill_lv)); - break; - - case SL_SWOO: - if (tsce) { - if(sd) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - status_change_start(src,SC_STUN,10000,skill_lv,0,0,0,10000,8); - status_change_end(bl, SC_SWOO, INVALID_TIMER); - break; - } - case SL_SKA: // [marquis007] - case SL_SKE: - if (sd && !battle_config.allow_es_magic_pc && bl->type != BL_MOB) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - status_change_start(src,SC_STUN,10000,skill_lv,0,0,0,500,10); - break; - } - clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - if (skill_id == SL_SKE) - sc_start(src,SC_SMA,100,skill_lv,skill_get_time(SL_SMA,skill_lv)); - break; - - // New guild skills [Celest] - case GD_BATTLEORDER: - if(flag&1) { - if (status_get_guild_id(src) == status_get_guild_id(bl)) - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id, skill_lv)); - } else if (status_get_guild_id(src)) { - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - map_foreachinrange(skill_area_sub, src, - skill_get_splash(skill_id, skill_lv), BL_PC, - src,skill_id,skill_lv,tick, flag|BCT_GUILD|1, - skill_castend_nodamage_id); - if (sd) - guild_block_skill(sd,skill_get_time2(skill_id,skill_lv)); - } - break; - case GD_REGENERATION: - if(flag&1) { - if (status_get_guild_id(src) == status_get_guild_id(bl)) - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id, skill_lv)); - } else if (status_get_guild_id(src)) { - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - map_foreachinrange(skill_area_sub, src, - skill_get_splash(skill_id, skill_lv), BL_PC, - src,skill_id,skill_lv,tick, flag|BCT_GUILD|1, - skill_castend_nodamage_id); - if (sd) - guild_block_skill(sd,skill_get_time2(skill_id,skill_lv)); - } - break; - case GD_RESTORE: - if(flag&1) { - if (status_get_guild_id(src) == status_get_guild_id(bl)) - clif_skill_nodamage(src,bl,AL_HEAL,status_percent_heal(bl,90,90),1); - } else if (status_get_guild_id(src)) { - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - map_foreachinrange(skill_area_sub, src, - skill_get_splash(skill_id, skill_lv), BL_PC, - src,skill_id,skill_lv,tick, flag|BCT_GUILD|1, - skill_castend_nodamage_id); - if (sd) - guild_block_skill(sd,skill_get_time2(skill_id,skill_lv)); - } - break; - case GD_EMERGENCYCALL: - { - int dx[9]={-1, 1, 0, 0,-1, 1,-1, 1, 0}; - int dy[9]={ 0, 0, 1,-1, 1,-1,-1, 1, 0}; - int j = 0; - struct guild *g = NULL; - // i don't know if it actually summons in a circle, but oh well. ;P - g = sd?sd->state.gmaster_flag:guild_search(status_get_guild_id(src)); - if (!g) - break; - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - for(i = 0; i < g->max_member; i++, j++) { - if (j>8) j=0; - if ((dstsd = g->member[i].sd) != NULL && sd != dstsd && !dstsd->state.autotrade && !pc_isdead(dstsd)) { - if (map[dstsd->bl.m].flag.nowarp && !map_flag_gvg2(dstsd->bl.m)) - continue; - if(map_getcell(src->m,src->x+dx[j],src->y+dy[j],CELL_CHKNOREACH)) - dx[j] = dy[j] = 0; - pc_setpos(dstsd, map_id2index(src->m), src->x+dx[j], src->y+dy[j], CLR_RESPAWN); - } - } - if (sd) - guild_block_skill(sd,skill_get_time2(skill_id,skill_lv)); - } - break; - - case SG_FEEL: - //AuronX reported you CAN memorize the same map as all three. [Skotlex] - if (sd) { - if(!sd->feel_map[skill_lv-1].index) - clif_feel_req(sd->fd,sd, skill_lv); - else - clif_feel_info(sd, skill_lv-1, 1); - } - break; - - case SG_HATE: - if (sd) { - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - if (!pc_set_hate_mob(sd, skill_lv-1, bl)) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - } - break; - - case GS_GLITTERING: - if(sd) { - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - if(rnd()%100 < (20+10*skill_lv)) - pc_addspiritball(sd,skill_get_time(skill_id,skill_lv),10); - else if(sd->spiritball > 0) - pc_delspiritball(sd,1,0); - } - break; - - case GS_CRACKER: - /* per official standards, this skill works on players and mobs. */ - if (sd && (dstsd || dstmd)) - { - i =65 -5*distance_bl(src,bl); //Base rate - if (i < 30) i = 30; - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - sc_start(bl,SC_STUN, i,skill_lv,skill_get_time2(skill_id,skill_lv)); - } - break; - - case AM_CALLHOMUN: //[orn] - if (sd && !merc_call_homunculus(sd)) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - - case AM_REST: - if (sd) { - if (merc_hom_vaporize(sd,1)) - clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); - else - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - } - break; - - case HAMI_CASTLE: //[orn] - if(rnd()%100 < 20*skill_lv && src != bl) - { - int x,y; - x = src->x; - y = src->y; - if (hd) - skill_blockhomun_start(hd, skill_id, skill_get_time2(skill_id,skill_lv)); - - if (unit_movepos(src,bl->x,bl->y,0,0)) { - clif_skill_nodamage(src,src,skill_id,skill_lv,1); // Homunc - clif_slide(src,bl->x,bl->y) ; - if (unit_movepos(bl,x,y,0,0)) - { - clif_skill_nodamage(bl,bl,skill_id,skill_lv,1); // Master - clif_slide(bl,x,y) ; - } - - //TODO: Shouldn't also players and the like switch targets? - map_foreachinrange(skill_chastle_mob_changetarget,src, - AREA_SIZE, BL_MOB, bl, src); - } - } - // Failed - else if (hd && hd->master) - clif_skill_fail(hd->master, skill_id, USESKILL_FAIL_LEVEL, 0); - else if (sd) - clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); - break; - case HVAN_CHAOTIC: //[orn] - { - static const int per[5][2]={{20,50},{50,60},{25,75},{60,64},{34,67}}; - int r = rnd()%100; - i = (skill_lv-1)%5; - if(r<per[i][0]) //Self - bl = src; - else if(r<per[i][1]) //Master - bl = battle_get_master(src); - else //Enemy - bl = map_id2bl(battle_gettarget(src)); - - if (!bl) bl = src; - i = skill_calc_heal(src, bl, skill_id, 1+rnd()%skill_lv, true); - //Eh? why double skill packet? - clif_skill_nodamage(src,bl,AL_HEAL,i,1); - clif_skill_nodamage(src,bl,skill_id,i,1); - status_heal(bl, i, 0, 0); - } - break; - //Homun single-target support skills [orn] - case HAMI_BLOODLUST: - case HFLI_FLEET: - case HFLI_SPEED: - case HLIF_CHANGE: - case MH_ANGRIFFS_MODUS: - case MH_GOLDENE_FERSE: - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - if (hd) - skill_blockhomun_start(hd, skill_id, skill_get_time2(skill_id,skill_lv)); - break; - - case NPC_DRAGONFEAR: - if (flag&1) { - const enum sc_type sc[] = { SC_STUN, SC_SILENCE, SC_CONFUSION, SC_BLEEDING }; - int j; - j = i = rnd()%ARRAYLENGTH(sc); - while ( !sc_start(bl,sc[i],100,skill_lv,skill_get_time2(skill_id,i+1)) ) { - i++; - if ( i == ARRAYLENGTH(sc) ) - i = 0; - if (i == j) - break; - } - break; - } - case NPC_WIDEBLEEDING: - case NPC_WIDECONFUSE: - case NPC_WIDECURSE: - case NPC_WIDEFREEZE: - case NPC_WIDESLEEP: - case NPC_WIDESILENCE: - case NPC_WIDESTONE: - case NPC_WIDESTUN: - case NPC_SLOWCAST: - case NPC_WIDEHELLDIGNITY: - if (flag&1) - sc_start(bl,type,100,skill_lv,skill_get_time2(skill_id,skill_lv)); - else { - skill_area_temp[2] = 0; //For SD_PREAMBLE - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - map_foreachinrange(skill_area_sub, bl, - skill_get_splash(skill_id, skill_lv),BL_CHAR, - src,skill_id,skill_lv,tick, flag|BCT_ENEMY|SD_PREAMBLE|1, - skill_castend_nodamage_id); - } - break; - case NPC_WIDESOULDRAIN: - if (flag&1) - status_percent_damage(src,bl,0,((skill_lv-1)%5+1)*20,false); - else { - skill_area_temp[2] = 0; //For SD_PREAMBLE - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - map_foreachinrange(skill_area_sub, bl, - skill_get_splash(skill_id, skill_lv),BL_CHAR, - src,skill_id,skill_lv,tick, flag|BCT_ENEMY|SD_PREAMBLE|1, - skill_castend_nodamage_id); - } - break; - case ALL_PARTYFLEE: - if( sd && !(flag&1) ) - { - if( !sd->status.party_id ) - { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); - } - else - clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - break; - case NPC_TALK: - case ALL_WEWISH: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - break; - case ALL_BUYING_STORE: - if( sd ) - {// players only, skill allows 5 buying slots - clif_skill_nodamage(src, bl, skill_id, skill_lv, buyingstore_setup(sd, MAX_BUYINGSTORE_SLOTS)); - } - break; - case RK_ENCHANTBLADE: - clif_skill_nodamage(src,bl,skill_id,skill_lv,// formula not confirmed - sc_start2(bl,type,100,skill_lv,100+20*skill_lv/*+sstatus->int_/2+status_get_lv(bl)/10*/,skill_get_time(skill_id,skill_lv))); - break; - case RK_DRAGONHOWLING: - if( flag&1) - sc_start(bl,type,50 + 6 * skill_lv,skill_lv,skill_get_time(skill_id,skill_lv)); - else - { - skill_area_temp[2] = 0; - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - map_foreachinrange(skill_area_sub, src, - skill_get_splash(skill_id,skill_lv),BL_CHAR, - src,skill_id,skill_lv,tick,flag|BCT_ENEMY|SD_PREAMBLE|1, - skill_castend_nodamage_id); - } - break; - case RK_IGNITIONBREAK: - case LG_EARTHDRIVE: - clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - i = skill_get_splash(skill_id,skill_lv); - if( skill_id == LG_EARTHDRIVE ) { - int dummy = 1; - map_foreachinarea(skill_cell_overlap, src->m, src->x-i, src->y-i, src->x+i, src->y+i, BL_SKILL, LG_EARTHDRIVE, &dummy, src); - } - map_foreachinrange(skill_area_sub, bl,i,BL_CHAR, - src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); - break; - case RK_STONEHARDSKIN: - if( sd && pc_checkskill(sd,RK_RUNEMASTERY) >= 4 ) - { - int heal = sstatus->hp / 4; // 25% HP - if( status_charge(bl,heal,0) ) - clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start2(bl,type,100,skill_lv,heal,skill_get_time(skill_id,skill_lv))); - else - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - } - break; - case RK_REFRESH: - if( sd && pc_checkskill(sd,RK_RUNEMASTERY) >= 8 ) - { - int heal = status_get_max_hp(bl) * 25 / 100; - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - status_heal(bl,heal,0,1); - status_change_clear_buffs(bl,4); - } - break; - - case RK_MILLENNIUMSHIELD: - if( sd && pc_checkskill(sd,RK_RUNEMASTERY) >= 9 ) - { - short shields = (rnd()%100<50) ? 4 : ((rnd()%100<80) ? 3 : 2); - sc_start4(bl,type,100,skill_lv,shields,1000,0,skill_get_time(skill_id,skill_lv)); - clif_millenniumshield(sd,shields); - clif_skill_nodamage(src,bl,skill_id,1,1); - } - break; - - case RK_GIANTGROWTH: - case RK_VITALITYACTIVATION: - case RK_ABUNDANCE: - case RK_CRUSHSTRIKE: - if( sd ) - { - int lv = 1; // RK_GIANTGROWTH - if( skill_id == RK_VITALITYACTIVATION ) - lv = 2; - else if( skill_id == RK_ABUNDANCE ) - lv = 6; - else if( skill_id == RK_CRUSHSTRIKE ) - lv = 7; - if( pc_checkskill(sd,RK_RUNEMASTERY) >= lv ) - clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - } - break; - - case RK_FIGHTINGSPIRIT: - if( flag&1 ) { - if( src == bl ) - sc_start2(bl,type,100,skill_area_temp[5],10*(sd?pc_checkskill(sd,RK_RUNEMASTERY):10),skill_get_time(skill_id,skill_lv)); - else - sc_start(bl,type,100,skill_area_temp[5]/4,skill_get_time(skill_id,skill_lv)); - } else if( sd && pc_checkskill(sd,RK_RUNEMASTERY) >= 5 ) { - if( sd->status.party_id ) { - i = party_foreachsamemap(skill_area_sub,sd,skill_get_splash(skill_id,skill_lv),src,skill_id,skill_lv,tick,BCT_PARTY,skill_area_sub_count); - skill_area_temp[5] = 7 * i; // ATK - party_foreachsamemap(skill_area_sub,sd,skill_get_splash(skill_id,skill_lv),src,skill_id,skill_lv,tick,flag|BCT_PARTY|1,skill_castend_nodamage_id); - } else - sc_start2(bl,type,100,7,5,skill_get_time(skill_id,skill_lv)); - } - clif_skill_nodamage(src,bl,skill_id,1,1); - break; - /** - * Guilotine Cross - **/ - case GC_ROLLINGCUTTER: - { - short count = 1; - skill_area_temp[2] = 0; - map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|SD_PREAMBLE|SD_SPLASH|1,skill_castend_damage_id); - if( tsc && tsc->data[SC_ROLLINGCUTTER] ) - { // Every time the skill is casted the status change is reseted adding a counter. - count += (short)tsc->data[SC_ROLLINGCUTTER]->val1; - if( count > 10 ) - count = 10; // Max coounter - status_change_end(bl, SC_ROLLINGCUTTER, INVALID_TIMER); - } - sc_start(bl,SC_ROLLINGCUTTER,100,count,skill_get_time(skill_id,skill_lv)); - clif_skill_nodamage(src,src,skill_id,skill_lv,1); - } - break; - - case GC_WEAPONBLOCKING: - if( tsc && tsc->data[SC_WEAPONBLOCKING] ) - status_change_end(bl, SC_WEAPONBLOCKING, INVALID_TIMER); - else - sc_start(bl,SC_WEAPONBLOCKING,100,skill_lv,skill_get_time(skill_id,skill_lv)); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - break; - - case GC_CREATENEWPOISON: - if( sd ) - { - clif_skill_produce_mix_list(sd,skill_id,25); - clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); - } - break; - - case GC_POISONINGWEAPON: - if( sd ) { - clif_poison_list(sd,skill_lv); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - } - break; - - case GC_ANTIDOTE: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - if( tsc ) - { - status_change_end(bl, SC_PARALYSE, INVALID_TIMER); - status_change_end(bl, SC_PYREXIA, INVALID_TIMER); - status_change_end(bl, SC_DEATHHURT, INVALID_TIMER); - status_change_end(bl, SC_LEECHESEND, INVALID_TIMER); - status_change_end(bl, SC_VENOMBLEED, INVALID_TIMER); - status_change_end(bl, SC_MAGICMUSHROOM, INVALID_TIMER); - status_change_end(bl, SC_TOXIN, INVALID_TIMER); - status_change_end(bl, SC_OBLIVIONCURSE, INVALID_TIMER); - } - break; - - case GC_PHANTOMMENACE: - clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),BL_CHAR, - src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); - break; - - case GC_HALLUCINATIONWALK: - { - int heal = status_get_max_hp(bl) / 10; - if( status_get_hp(bl) < heal ) { // if you haven't enough HP skill fails. - if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_HP_INSUFFICIENT,0); - break; - } - if( !status_charge(bl,heal,0) ) - { - if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_HP_INSUFFICIENT,0); - break; - } - clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - } - break; - /** - * Arch Bishop - **/ - case AB_ANCILLA: - if( sd ) { - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - skill_produce_mix(sd, skill_id, ITEMID_ANCILLA, 0, 0, 0, 1); - } - break; - - case AB_CLEMENTIA: - case AB_CANTO: - { - int bless_lv = pc_checkskill(sd,AL_BLESSING) + (sd->status.job_level / 10); - int agi_lv = pc_checkskill(sd,AL_INCAGI) + (sd->status.job_level / 10); - if( sd == NULL || sd->status.party_id == 0 || flag&1 ) - clif_skill_nodamage(bl, bl, skill_id, skill_lv, sc_start(bl,type,100, - (skill_id == AB_CLEMENTIA)? bless_lv : (skill_id == AB_CANTO)? agi_lv : skill_lv, skill_get_time(skill_id,skill_lv))); - else if( sd ) - party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); - } - break; - - case AB_PRAEFATIO: - if( sd == NULL || sd->status.party_id == 0 || flag&1 ) - clif_skill_nodamage(bl, bl, skill_id, skill_lv, sc_start4(bl, type, 100, skill_lv, 0, 0, 1, skill_get_time(skill_id, skill_lv))); - else if( sd ) - party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); - break; - - case AB_CHEAL: - if( sd == NULL || sd->status.party_id == 0 || flag&1 ) - { - if( sd && tstatus && !battle_check_undead(tstatus->race, tstatus->def_ele) ) - { - i = skill_calc_heal(src, bl, AL_HEAL, pc_checkskill(sd, AL_HEAL), true); - - if( (dstsd && pc_ismadogear(dstsd)) || status_isimmune(bl)) - i = 0; // Should heal by 0 or won't do anything?? in iRO it breaks the healing to members.. [malufett] - - clif_skill_nodamage(bl, bl, skill_id, i, 1); - if( tsc && tsc->data[SC_AKAITSUKI] && i ) - i = ~i + 1; - status_heal(bl, i, 0, 0); - } - } - else if( sd ) - party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); - break; - - case AB_ORATIO: - if( flag&1 ) - sc_start(bl, type, 40 + 5 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); - else - { - map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_CHAR, - src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); - clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); - } - break; - - case AB_LAUDAAGNUS: - if( flag&1 || sd == NULL ) { - if( tsc && (tsc->data[SC_FREEZE] || tsc->data[SC_STONE] || tsc->data[SC_BLIND] || - tsc->data[SC_BURNING] || tsc->data[SC_FREEZING] || tsc->data[SC_CRYSTALIZE])) { - // Success Chance: (40 + 10 * Skill Level) % - if( rnd()%100 > 40+10*skill_lv ) break; - status_change_end(bl, SC_FREEZE, INVALID_TIMER); - status_change_end(bl, SC_STONE, INVALID_TIMER); - status_change_end(bl, SC_BLIND, INVALID_TIMER); - status_change_end(bl, SC_BURNING, INVALID_TIMER); - status_change_end(bl, SC_FREEZING, INVALID_TIMER); - status_change_end(bl, SC_CRYSTALIZE, INVALID_TIMER); - }else //Success rate only applies to the curing effect and not stat bonus. Bonus status only applies to non infected targets - clif_skill_nodamage(bl, bl, skill_id, skill_lv, - sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); - } else if( sd ) - party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), - src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); - break; - - case AB_LAUDARAMUS: - if( flag&1 || sd == NULL ) { - if( tsc && (tsc->data[SC_SLEEP] || tsc->data[SC_STUN] || tsc->data[SC_MANDRAGORA] || tsc->data[SC_SILENCE]) ){ - // Success Chance: (40 + 10 * Skill Level) % - if( rnd()%100 > 40+10*skill_lv ) break; - status_change_end(bl, SC_SLEEP, INVALID_TIMER); - status_change_end(bl, SC_STUN, INVALID_TIMER); - status_change_end(bl, SC_MANDRAGORA, INVALID_TIMER); - status_change_end(bl, SC_SILENCE, INVALID_TIMER); - }else // Success rate only applies to the curing effect and not stat bonus. Bonus status only applies to non infected targets - clif_skill_nodamage(bl, bl, skill_id, skill_lv, - sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); - } else if( sd ) - party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), - src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); - break; - - case AB_CLEARANCE: - if( flag&1 || (i = skill_get_splash(skill_id, skill_lv)) < 1 ) - { //As of the behavior in official server Clearance is just a super version of Dispell skill. [Jobbie] - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - if((dstsd && (dstsd->class_&MAPID_UPPERMASK) == MAPID_SOUL_LINKER) || rnd()%100 >= 30 + 10 * skill_lv) - { - if (sd) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - if(status_isimmune(bl) || !tsc || !tsc->count) - break; - for(i=0;i<SC_MAX;i++) - { - if (!tsc->data[i]) - continue; - switch (i) { - case SC_WEIGHT50: case SC_WEIGHT90: case SC_HALLUCINATION: - 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: case SC_COMBO: - case SC_STRFOOD: case SC_AGIFOOD: case SC_VITFOOD: - case SC_INTFOOD: case SC_DEXFOOD: case SC_LUKFOOD: - case SC_HITFOOD: case SC_FLEEFOOD: case SC_BATKFOOD: - case SC_WATKFOOD: case SC_MATKFOOD: case SC_DANCING: - case SC_SPIRIT: case SC_AUTOBERSERK: - case SC_CARTBOOST: case SC_MELTDOWN: case SC_SAFETYWALL: - case SC_SMA: case SC_SPEEDUP0: case SC_NOCHAT: - case SC_ANKLE: case SC_SPIDERWEB: case SC_JAILED: - case SC_ITEMBOOST: case SC_EXPBOOST: case SC_LIFEINSURANCE: - case SC_BOSSMAPINFO: case SC_PNEUMA: case SC_AUTOSPELL: - case SC_INCHITRATE: case SC_INCATKRATE: case SC_NEN: - case SC_READYSTORM: case SC_READYDOWN: case SC_READYTURN: - case SC_READYCOUNTER:case SC_DODGE: case SC_WARM: - case SC_SPEEDUP1: case SC_AUTOTRADE: case SC_CRITICALWOUND: - case SC_JEXPBOOST: case SC_INVINCIBLE: case SC_INVINCIBLEOFF: - case SC_HELLPOWER: case SC_MANU_ATK: case SC_MANU_DEF: - case SC_SPL_ATK: case SC_SPL_DEF: case SC_MANU_MATK: - case SC_SPL_MATK: case SC_RICHMANKIM: case SC_ETERNALCHAOS: - case SC_DRUMBATTLE: case SC_NIBELUNGEN: case SC_ROKISWEIL: - case SC_INTOABYSS: case SC_SIEGFRIED: case SC_WHISTLE: - case SC_ASSNCROS: case SC_POEMBRAGI: case SC_APPLEIDUN: - case SC_HUMMING: case SC_DONTFORGETME: case SC_FORTUNE: - case SC_SERVICE4U: case SC_FOOD_STR_CASH: case SC_FOOD_AGI_CASH: - case SC_FOOD_VIT_CASH: case SC_FOOD_DEX_CASH: case SC_FOOD_INT_CASH: - case SC_FOOD_LUK_CASH: case SC_ELECTRICSHOCKER: case SC_BITE: - case SC__STRIPACCESSORY: case SC__ENERVATION: case SC__GROOMY: - case SC__IGNORANCE: case SC__LAZINESS: case SC__UNLUCKY: - case SC__WEAKNESS: //case SC_SAVAGE_STEAK: case SC_COCKTAIL_WARG_BLOOD: - case SC_MAGNETICFIELD://case SC_MINOR_BBQ: case SC_SIROMA_ICE_TEA: - //case SC_DROCERA_HERB_STEAMED: case SC_PUTTI_TAILS_NOODLES: - case SC_NEUTRALBARRIER_MASTER: case SC_NEUTRALBARRIER: - case SC_STEALTHFIELD_MASTER: case SC_STEALTHFIELD: - case SC_LEADERSHIP: case SC_GLORYWOUNDS: case SC_SOULCOLD: - case SC_HAWKEYES: case SC_GUILDAURA: case SC_PUSH_CART: - case SC_PARTYFLEE: case SC_GT_REVITALIZE: - case SC_RAISINGDRAGON: case SC_GT_ENERGYGAIN: case SC_GT_CHANGE: -#ifdef RENEWAL - case SC_EXTREMITYFIST2: -#endif - continue; - case SC_ASSUMPTIO: - if( bl->type == BL_MOB ) - continue; - break; - } - if(i==SC_BERSERK || i==SC_SATURDAYNIGHTFEVER) tsc->data[i]->val2=0; //Mark a dispelled berserk to avoid setting hp to 100 by setting hp penalty to 0. - status_change_end(bl,(sc_type)i,INVALID_TIMER); - } - break; - } - map_foreachinrange(skill_area_sub, bl, i, BL_CHAR, src, skill_id, skill_lv, tick, flag|1, skill_castend_damage_id); - break; - - case AB_SILENTIUM: - // Should the level of Lex Divina be equivalent to the level of Silentium or should the highest level learned be used? [LimitLine] - map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_CHAR, - src, PR_LEXDIVINA, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); - clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); - break; - /** - * Warlock - **/ - case WL_STASIS: - if( flag&1 ) - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); - else - { - map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id, skill_lv),BL_CHAR,src,skill_id,skill_lv,tick,(map_flag_vs(src->m)?BCT_ALL:BCT_ENEMY|BCT_SELF)|flag|1,skill_castend_nodamage_id); - clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); - } - break; - - case WL_WHITEIMPRISON: - if( (src == bl || battle_check_target(src, bl, BCT_ENEMY)) && !is_boss(bl) )// Should not work with bosses. - { - int rate = ( sd? sd->status.job_level : 50 ) / 4; - - if( src == bl ) rate = 100; // Success Chance: On self, 100% - else if(bl->type == BL_PC) rate += 20 + 10 * skill_lv; // On Players, (20 + 10 * Skill Level) % - else rate += 40 + 10 * skill_lv; // On Monsters, (40 + 10 * Skill Level) % - - if( sd ) - skill_blockpc_start(sd,skill_id,4000); - - if( !(tsc && tsc->data[type]) ){ - i = sc_start2(bl,type,rate,skill_lv,src->id,(src == bl)?5000:(bl->type == BL_PC)?skill_get_time(skill_id,skill_lv):skill_get_time2(skill_id, skill_lv)); - clif_skill_nodamage(src,bl,skill_id,skill_lv,i); - if( !i ) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - } - }else - if( sd ) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_TOTARGET,0); - break; - - case WL_FROSTMISTY: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - map_foreachinrange(skill_area_sub,bl,skill_get_splash(skill_id,skill_lv),BL_CHAR|BL_SKILL,src,skill_id,skill_lv,tick,flag|BCT_ENEMY,skill_castend_damage_id); - break; - - case WL_JACKFROST: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - map_foreachinshootrange(skill_area_sub,bl,skill_get_splash(skill_id,skill_lv),BL_CHAR|BL_SKILL,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); - break; - - case WL_MARSHOFABYSS: - // Should marsh of abyss still apply half reduction to players after the 28/10 patch? [LimitLine] - clif_skill_nodamage(src, bl, skill_id, skill_lv, - sc_start4(bl, type, 100, skill_lv, status_get_int(src), sd ? sd->status.job_level : 50, 0, - skill_get_time(skill_id, skill_lv))); - break; - - case WL_SIENNAEXECRATE: - if( status_isimmune(bl) || !tsc ) - break; - - if( flag&1 ) { - if( bl->id == skill_area_temp[1] ) - break; // Already work on this target - - if( tsc && tsc->data[SC_STONE] ) - status_change_end(bl,SC_STONE,INVALID_TIMER); - else - status_change_start(bl,SC_STONE,10000,skill_lv,0,0,1000,skill_get_time(skill_id, skill_lv),2); - } else { - int rate = 40 + 8 * skill_lv + ( sd? sd->status.job_level : 50 ) / 4; - // IroWiki says Rate should be reduced by target stats, but currently unknown - if( rnd()%100 < rate ) { // Success on First Target - if( !tsc->data[SC_STONE] ) - rate = status_change_start(bl,SC_STONE,10000,skill_lv,0,0,1000,skill_get_time(skill_id, skill_lv),2); - else { - rate = 1; - status_change_end(bl,SC_STONE,INVALID_TIMER); - } - - if( rate ) { - skill_area_temp[1] = bl->id; - map_foreachinrange(skill_area_sub,bl,skill_get_splash(skill_id,skill_lv),BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_nodamage_id); - } - // Doesn't send failure packet if it fails on defense. - } - else if( sd ) // Failure on Rate - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - } - break; - - case WL_SUMMONFB: - case WL_SUMMONBL: - case WL_SUMMONWB: - case WL_SUMMONSTONE: - { - short element = 0, sctype = 0, pos = -1; - struct status_change *sc = status_get_sc(src); - if( !sc ) break; - - for( i = SC_SPHERE_1; i <= SC_SPHERE_5; i++ ) - { - if( !sctype && !sc->data[i] ) - sctype = i; // Take the free SC - if( sc->data[i] ) - pos = max(sc->data[i]->val2,pos); - } - - if( !sctype ) - { - if( sd ) // No free slots to put SC - clif_skill_fail(sd,skill_id,USESKILL_FAIL_SUMMON,0); - break; - } - - pos++; // Used in val2 for SC. Indicates the order of this ball - switch( skill_id ) - { // Set val1. The SC element for this ball - case WL_SUMMONFB: element = WLS_FIRE; break; - case WL_SUMMONBL: element = WLS_WIND; break; - case WL_SUMMONWB: element = WLS_WATER; break; - case WL_SUMMONSTONE: element = WLS_STONE; break; - } - - sc_start4(src,sctype,100,element,pos,skill_lv,0,skill_get_time(skill_id,skill_lv)); - clif_skill_nodamage(src,bl,skill_id,0,0); - } - break; - - case WL_READING_SB: - if( sd ) { - struct status_change *sc = status_get_sc(bl); - - for( i = SC_SPELLBOOK1; i <= SC_MAXSPELLBOOK; i++) - if( sc && !sc->data[i] ) - break; - if( i == SC_MAXSPELLBOOK ) { - clif_skill_fail(sd, WL_READING_SB, USESKILL_FAIL_SPELLBOOK_READING, 0); - break; - } - - sc_start(bl, SC_STOP, 100, skill_lv, INVALID_TIMER); //Can't move while selecting a spellbook. - clif_spellbook_list(sd); - clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); - } - break; - /** - * Ranger - **/ - case RA_FEARBREEZE: - clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); - break; - - case RA_WUGMASTERY: - if( sd ) { - if( !pc_iswug(sd) ) - pc_setoption(sd,sd->sc.option|OPTION_WUG); - else - pc_setoption(sd,sd->sc.option&~OPTION_WUG); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - } - break; - - case RA_WUGRIDER: - if( sd ) { - if( !pc_isridingwug(sd) && pc_iswug(sd) ) { - pc_setoption(sd,sd->sc.option&~OPTION_WUG); - pc_setoption(sd,sd->sc.option|OPTION_WUGRIDER); - } else if( pc_isridingwug(sd) ) { - pc_setoption(sd,sd->sc.option&~OPTION_WUGRIDER); - pc_setoption(sd,sd->sc.option|OPTION_WUG); - } - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - } - break; - - case RA_WUGDASH: - if( tsce ) { - clif_skill_nodamage(src,bl,skill_id,skill_lv,status_change_end(bl, type, INVALID_TIMER)); - map_freeblock_unlock(); - return 0; - } - if( sd && pc_isridingwug(sd) ) { - clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start4(bl,type,100,skill_lv,unit_getdir(bl),0,0,1)); - clif_walkok(sd); - } - break; - - case RA_SENSITIVEKEEN: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - clif_skill_damage(src,src,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),BL_CHAR|BL_SKILL,src,skill_id,skill_lv,tick,flag|BCT_ENEMY,skill_castend_damage_id); - break; - /** - * Mechanic - **/ - case NC_F_SIDESLIDE: - case NC_B_SIDESLIDE: - { - uint8 dir = (skill_id == NC_F_SIDESLIDE) ? (unit_getdir(src)+4)%8 : unit_getdir(src); - skill_blown(src,bl,skill_get_blewcount(skill_id,skill_lv),dir,0x1); - clif_slide(src,src->x,src->y); - clif_fixpos(src); //Aegis sent this packet - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - } - break; - - case NC_SELFDESTRUCTION: - if( sd ) { - if( pc_ismadogear(sd) ) - pc_setmadogear(sd, 0); - clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); - skill_castend_damage_id(src, src, skill_id, skill_lv, tick, flag); - status_set_sp(src, 0, 0); - } - break; - - case NC_ANALYZE: - clif_skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - clif_skill_nodamage(src, bl, skill_id, skill_lv, - sc_start(bl,type, 30 + 12 * skill_lv,skill_lv,skill_get_time(skill_id,skill_lv))); - if( sd ) pc_overheat(sd,1); - break; - - case NC_MAGNETICFIELD: - if( (i = sc_start2(bl,type,100,skill_lv,src->id,skill_get_time(skill_id,skill_lv))) ) - { - map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),splash_target(src),src,skill_id,skill_lv,tick,flag|BCT_ENEMY|SD_SPLASH|1,skill_castend_damage_id);; - clif_skill_damage(src,src,tick,status_get_amotion(src),0,-30000,1,skill_id,skill_lv,6); - if (sd) pc_overheat(sd,1); - } - clif_skill_nodamage(src,src,skill_id,skill_lv,i); - break; - - case NC_REPAIR: - if( sd ) - { - int heal; - if( dstsd && pc_ismadogear(dstsd) ) - { - heal = dstsd->status.max_hp * (3+3*skill_lv) / 100; - status_heal(bl,heal,0,2); - } else { - heal = sd->status.max_hp * (3+3*skill_lv) / 100; - status_heal(src,heal,0,2); - } - - clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - clif_skill_nodamage(src, bl, skill_id, skill_lv, heal); - } - break; - - case NC_DISJOINT: - { - if( bl->type != BL_MOB ) break; - md = map_id2md(bl->id); - if( md && md->class_ >= MOBID_SILVERSNIPER && md->class_ <= MOBID_MAGICDECOY_WIND ) - status_kill(bl); - clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); - } - break; - case SC_AUTOSHADOWSPELL: - if( sd ) { - if( sd->status.skill[sd->reproduceskill_id].id || sd->status.skill[sd->cloneskill_id].id ) { - sc_start(src,SC_STOP,100,skill_lv,-1);// The skill_lv is stored in val1 used in skill_select_menu to determine the used skill lvl [Xazax] - clif_autoshadowspell_list(sd); - clif_skill_nodamage(src,bl,skill_id,1,1); - } - else - clif_skill_fail(sd,skill_id,USESKILL_FAIL_IMITATION_SKILL_NONE,0); - } - break; - - case SC_SHADOWFORM: - if( sd && dstsd && src != bl && !dstsd->shadowform_id ) { - if( clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start4(src,type,100,skill_lv,bl->id,4+skill_lv,0,skill_get_time(skill_id, skill_lv))) ) - dstsd->shadowform_id = src->id; - } - else if( sd ) - clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); - break; - - case SC_BODYPAINT: - if( flag&1 ) { - if( tsc && (tsc->data[SC_HIDING] || tsc->data[SC_CLOAKING] || - tsc->data[SC_CHASEWALK] || tsc->data[SC_CLOAKINGEXCEED] || - tsc->data[SC__INVISIBILITY]) ) { - status_change_end(bl, SC_HIDING, INVALID_TIMER); - status_change_end(bl, SC_CLOAKING, INVALID_TIMER); - status_change_end(bl, SC_CHASEWALK, INVALID_TIMER); - status_change_end(bl, SC_CLOAKINGEXCEED, INVALID_TIMER); - status_change_end(bl, SC__INVISIBILITY, INVALID_TIMER); - - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); - sc_start(bl,SC_BLIND,53 + 2 * skill_lv,skill_lv,skill_get_time(skill_id,skill_lv)); - } - } else { - clif_skill_nodamage(src, bl, skill_id, 0, 1); - map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, - src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); - } - break; - - case SC_ENERVATION: - case SC_GROOMY: - case SC_LAZINESS: - case SC_UNLUCKY: - case SC_WEAKNESS: - if( !(tsc && tsc->data[type]) ) { - //((rand(myDEX / 12, myDEX / 4) + myJobLevel + 10 * skLevel) + myLevel / 10) - (targetLevel / 10 + targetLUK / 10 + (targetMaxWeight - targetWeight) / 1000 + rand(targetAGI / 6, targetAGI / 3)) - int rate = rnd_value(sstatus->dex/12,sstatus->dex/4) + 10*skill_lv + (sd?sd->status.job_level:0) + status_get_lv(src)/10 - - status_get_lv(bl)/10 - tstatus->luk/10 - (dstsd?(dstsd->max_weight-dstsd->weight)/10000:0) - rnd_value(tstatus->agi/6,tstatus->agi/3); - rate = cap_value(rate, skill_lv+sstatus->dex/20, 100); - clif_skill_nodamage(src,bl,skill_id,0,sc_start(bl,type,rate,skill_lv,skill_get_time(skill_id,skill_lv))); - } else if( sd ) - clif_skill_fail(sd,skill_id,0,0); - break; - - case SC_IGNORANCE: - if( !(tsc && tsc->data[type]) ) { - int rate = rnd_value(sstatus->dex/12,sstatus->dex/4) + 10*skill_lv + (sd?sd->status.job_level:0) + status_get_lv(src)/10 - - status_get_lv(bl)/10 - tstatus->luk/10 - (dstsd?(dstsd->max_weight-dstsd->weight)/10000:0) - rnd_value(tstatus->agi/6,tstatus->agi/3); - rate = cap_value(rate, skill_lv+sstatus->dex/20, 100); - if (clif_skill_nodamage(src,bl,skill_id,0,sc_start(bl,type,rate,skill_lv,skill_get_time(skill_id,skill_lv)))) { - int sp = 200 * skill_lv; - if( dstmd ) sp = dstmd->level * 2; - if( status_zap(bl,0,sp) ) - status_heal(src,0,sp/2,3); - } - else if( sd ) clif_skill_fail(sd,skill_id,0,0); - } else if( sd ) - clif_skill_fail(sd,skill_id,0,0); - break; - - case LG_TRAMPLE: - clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - map_foreachinrange(skill_destroy_trap,bl,skill_get_splash(skill_id,skill_lv),BL_SKILL,tick); - break; - - case LG_REFLECTDAMAGE: - if( tsc && tsc->data[type] ) - status_change_end(bl,type,INVALID_TIMER); - else - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - break; - - case LG_SHIELDSPELL: - if( flag&1 ) { - int duration = (sd) ? sd->bonus.shieldmdef * 2000 : 10000; - sc_start(bl,SC_SILENCE,100,skill_lv,duration); - } else if( sd ) { - int opt = skill_lv; - int rate = rnd()%100; - int val, brate; - switch( skill_lv ) { - case 1: - { - struct item_data *shield_data = sd->inventory_data[sd->equip_index[EQI_HAND_L]]; - if( !shield_data || shield_data->type != IT_ARMOR ) { // No shield? - clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); - break; - } - brate = shield_data->def * 10; - if( rate < 50 ) - opt = 1; - else if( rate < 75 ) - opt = 2; - else - opt = 3; - - switch( opt ) { - case 1: - sc_start(bl,SC_SHIELDSPELL_DEF,100,opt,-1); - clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - if( rate < brate ) - map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); - status_change_end(bl,SC_SHIELDSPELL_DEF,INVALID_TIMER); - break; - case 2: - val = shield_data->def / 10; // % Reflected damage. - sc_start2(bl,SC_SHIELDSPELL_DEF,brate,opt,val,shield_data->def * 1000); - break; - case 3: - val = shield_data->def; // Attack increase. - sc_start2(bl,SC_SHIELDSPELL_DEF,brate,opt,val,shield_data->def * 3000); - break; - } - } - break; - - case 2: - brate = sd->bonus.shieldmdef * 20; - if( rate < 30 ) - opt = 1; - else if( rate < 60 ) - opt = 2; - else - opt = 3; - switch( opt ) { - case 1: - sc_start(bl,SC_SHIELDSPELL_MDEF,100,opt,-1); - clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - if( rate < brate ) - map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|2,skill_castend_damage_id); - status_change_end(bl,SC_SHIELDSPELL_MDEF,INVALID_TIMER); - break; - case 2: - sc_start(bl,SC_SHIELDSPELL_MDEF,100,opt,-1); - clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - if( rate < brate ) - map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_nodamage_id); - break; - case 3: - if( sc_start(bl,SC_SHIELDSPELL_MDEF,brate,opt,sd->bonus.shieldmdef * 30000) ) - clif_skill_nodamage(src,bl,PR_MAGNIFICAT,skill_lv, - sc_start(bl,SC_MAGNIFICAT,100,1,sd->bonus.shieldmdef * 30000)); - break; - } - break; - - case 3: - { - struct item *it = &sd->status.inventory[sd->equip_index[EQI_HAND_L]]; - if( !it ) { // No shield? - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - brate = it->refine * 5; - if( rate < 25 ) - opt = 1; - else if( rate < 50 ) - opt = 2; - else - opt = 3; - switch( opt ) { - case 1: - val = 105 * it->refine / 10; - sc_start2(bl,SC_SHIELDSPELL_REF,brate,opt,val,skill_get_time(skill_id,skill_lv)); - break; - case 2: case 3: - if( rate < brate ) - { - val = sstatus->max_hp * (11 + it->refine) / 100; - status_heal(bl, val, 0, 3); - } - break; - /*case 3: - // Full protection. I need confirm what effect should be here. Moved to case 2 to until we got it. - break;*/ - } - } - break; - } - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - } - break; - - case LG_PIETY: - if( flag&1 ) - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); - else { - skill_area_temp[2] = 0; - map_foreachinrange(skill_area_sub,bl,skill_get_splash(skill_id,skill_lv),BL_PC,src,skill_id,skill_lv,tick,flag|SD_PREAMBLE|BCT_PARTY|BCT_SELF|1,skill_castend_nodamage_id); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - } - break; - - case LG_INSPIRATION: - if( sd && !map[sd->bl.m].flag.noexppenalty && sd->status.base_level != MAX_LEVEL ) { - sd->status.base_exp -= min(sd->status.base_exp, pc_nextbaseexp(sd) * 1 / 100); // 1% penalty. - sd->status.job_exp -= min(sd->status.job_exp, pc_nextjobexp(sd) * 1 / 100); - clif_updatestatus(sd,SP_BASEEXP); - clif_updatestatus(sd,SP_JOBEXP); - } - clif_skill_nodamage(bl,src,skill_id,skill_lv, - sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); - break; - case SR_CURSEDCIRCLE: - if( flag&1 ) { - if( is_boss(bl) ) break; - if( sc_start2(bl, type, 100, skill_lv, src->id, skill_get_time(skill_id, skill_lv))) { - if( bl->type == BL_MOB ) - mob_unlocktarget((TBL_MOB*)bl,gettick()); - unit_stop_attack(bl); - clif_bladestop(src, bl->id, 1); - map_freeblock_unlock(); - return 1; - } - } else { - int count = 0; - clif_skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - count = map_forcountinrange(skill_area_sub, src, skill_get_splash(skill_id,skill_lv), (sd)?sd->spiritball_old:15, // Assume 15 spiritballs in non-charactors - BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); - if( sd ) pc_delspiritball(sd, count, 0); - clif_skill_nodamage(src, src, skill_id, skill_lv, - sc_start2(src, SC_CURSEDCIRCLE_ATKER, 100, skill_lv, count, skill_get_time(skill_id,skill_lv))); - } - break; - - case SR_RAISINGDRAGON: - if( sd ) { - short max = 5 + skill_lv; - sc_start(bl, SC_EXPLOSIONSPIRITS, 100, skill_lv, skill_get_time(skill_id, skill_lv)); - for( i = 0; i < max; i++ ) // Don't call more than max available spheres. - pc_addspiritball(sd, skill_get_time(skill_id, skill_lv), max); - clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(bl, type, 100, skill_lv,skill_get_time(skill_id, skill_lv))); - } - break; - - case SR_ASSIMILATEPOWER: - if( flag&1 ) { - i = 0; - if( dstsd && dstsd->spiritball && (sd == dstsd || map_flag_vs(src->m)) && (dstsd->class_&MAPID_BASEMASK)!=MAPID_GUNSLINGER ) - { - i = dstsd->spiritball; //1%sp per spiritball. - pc_delspiritball(dstsd, dstsd->spiritball, 0); - } - if( i ) status_percent_heal(src, 0, i); - clif_skill_nodamage(src, bl, skill_id, skill_lv, i ? 1:0); - } else { - clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|BCT_SELF|SD_SPLASH|1, skill_castend_nodamage_id); - } - break; - - case SR_POWERVELOCITY: - if( !dstsd ) - break; - if( sd && dstsd->spiritball <= 5 ) { - for(i = 0; i <= 5; i++) { - pc_addspiritball(dstsd, skill_get_time(MO_CALLSPIRITS, pc_checkskill(sd,MO_CALLSPIRITS)), i); - pc_delspiritball(sd, sd->spiritball, 0); - } - } - clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); - break; - - case SR_GENTLETOUCH_CURE: - { - int heal; - - if( status_isimmune(bl) ) - { - clif_skill_nodamage(src,bl,skill_id,skill_lv,0); - break; - } - - heal = 120 * skill_lv + status_get_max_hp(bl) * (2 + skill_lv) / 100; - status_heal(bl, heal, 0, 0); - - if( (tsc && tsc->opt1) && (rnd()%100 < ((skill_lv * 5) + (status_get_dex(src) + status_get_lv(src)) / 4) - (1 + (rnd() % 10))) ) - { - status_change_end(bl, SC_STONE, INVALID_TIMER); - status_change_end(bl, SC_FREEZE, INVALID_TIMER); - status_change_end(bl, SC_STUN, INVALID_TIMER); - status_change_end(bl, SC_POISON, INVALID_TIMER); - status_change_end(bl, SC_SILENCE, INVALID_TIMER); - status_change_end(bl, SC_BLIND, INVALID_TIMER); - status_change_end(bl, SC_HALLUCINATION, INVALID_TIMER); - status_change_end(bl, SC_BURNING, INVALID_TIMER); - status_change_end(bl, SC_FREEZING, INVALID_TIMER); - } - - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - } - break; - case SR_GENTLETOUCH_CHANGE: - case SR_GENTLETOUCH_REVITALIZE: - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start2(bl,type,100,skill_lv,src->id,skill_get_time(skill_id,skill_lv))); - break; - case WA_SWING_DANCE: - case WA_MOONLIT_SERENADE: - if( sd == NULL || sd->status.party_id == 0 || (flag & 1) ) - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); - else if( sd ) { // Only shows effects on caster. - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); - } - break; - - case WA_SYMPHONY_OF_LOVER: - case MI_RUSH_WINDMILL: - case MI_ECHOSONG: - if( sd == NULL || sd->status.party_id == 0 || (flag & 1) ) - sc_start4(bl,type,100,skill_lv,6*skill_lv,(sd?pc_checkskill(sd,WM_LESSON):0),(sd?sd->status.job_level:0),skill_get_time(skill_id,skill_lv)); - else if( sd ) { // Only shows effects on caster. - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); - } - break; - - case MI_HARMONIZE: - if( src != bl ) - clif_skill_nodamage(src, src, skill_id, skill_lv, sc_start(src, type, 100, skill_lv, skill_get_time(skill_id,skill_lv))); - clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id,skill_lv))); - break; - - case WM_DEADHILLHERE: - if( bl->type == BL_PC ) { - if( !status_isdead(bl) ) - break; - - if( rnd()%100 < 88 + 2 * skill_lv ) { - int heal = tstatus->sp; - if( heal <= 0 ) - heal = 1; - tstatus->hp = heal; - tstatus->sp -= tstatus->sp * ( 120 - 20 * skill_lv ) / 100; - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - pc_revive((TBL_PC*)bl,heal,0); - clif_resurrection(bl,1); - } - } - break; - - case WM_SIRCLEOFNATURE: - flag |= BCT_SELF|BCT_PARTY|BCT_GUILD; - case WM_VOICEOFSIREN: - if( skill_id != WM_SIRCLEOFNATURE ) - flag &= ~BCT_SELF; - if( flag&1 ) { - sc_start2(bl,type,(skill_id==WM_VOICEOFSIREN)?20+10*skill_lv:100,skill_lv,(skill_id==WM_VOICEOFSIREN)?src->id:0,skill_get_time(skill_id,skill_lv)); - } else { - map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id,skill_lv),(skill_id==WM_VOICEOFSIREN)?BL_CHAR|BL_SKILL:BL_PC, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - } - break; - - case WM_GLOOMYDAY: - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - if( dstsd && ( pc_checkskill(dstsd,KN_BRANDISHSPEAR) || pc_checkskill(dstsd,LK_SPIRALPIERCE) || - pc_checkskill(dstsd,CR_SHIELDCHARGE) || pc_checkskill(dstsd,CR_SHIELDBOOMERANG) || - pc_checkskill(dstsd,PA_SHIELDCHAIN) || pc_checkskill(dstsd,LG_SHIELDPRESS) ) ) - { - sc_start(bl,SC_GLOOMYDAY_SK,100,skill_lv,skill_get_time(skill_id,skill_lv)); - break; - } - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); - break; - - case WM_SATURDAY_NIGHT_FEVER: - if( flag&1 ) { // Affect to all targets arround the caster and caster too. - if( !(tsc && tsc->data[type]) ) - sc_start(bl, type, 100, skill_lv,skill_get_time(skill_id, skill_lv)); - } else if( flag&2 ) { - if( src->id != bl->id && battle_check_target(src,bl,BCT_ENEMY) > 0 ) - status_fix_damage(src,bl,9999,clif_damage(src,bl,tick,0,0,9999,0,0,0)); - } else if( sd ) { - short chance = sstatus->int_/6 + sd->status.job_level/5 + skill_lv*4; - if( !sd->status.party_id || (rnd()%100 > chance)) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_NEED_HELPER,0); - break; - } - if( map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id,skill_lv), - BL_PC, src, skill_id, skill_lv, tick, BCT_ENEMY, skill_area_sub_count) > 7 ) - flag |= 2; - else - flag |= 1; - map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id,skill_lv),BL_PC, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|BCT_SELF, skill_castend_nodamage_id); - clif_skill_nodamage(src, bl, skill_id, skill_lv, - sc_start(src,SC_STOP,100,skill_lv,skill_get_time2(skill_id,skill_lv))); - if( flag&2 ) // Dealed here to prevent conflicts - status_fix_damage(src,bl,9999,clif_damage(src,bl,tick,0,0,9999,0,0,0)); - } - break; - - case WM_SONG_OF_MANA: - case WM_DANCE_WITH_WUG: - case WM_LERADS_DEW: - if( flag&1 ) { // These affect to to all party members near the caster. - struct status_change *sc = status_get_sc(src); - if( sc && sc->data[type] ) { - sc_start2(bl,type,100,skill_lv,sc->data[type]->val2,skill_get_time(skill_id,skill_lv)); - } - } else if( sd ) { - short lv = (short)skill_lv; - int count = skill_check_pc_partner(sd,skill_id,&lv,skill_get_splash(skill_id,skill_lv),1); - if( sc_start2(bl,type,100,skill_lv,count,skill_get_time(skill_id,skill_lv)) ) - party_foreachsamemap(skill_area_sub,sd,skill_get_splash(skill_id,skill_lv),src,skill_id,skill_lv,tick,flag|BCT_PARTY|1,skill_castend_nodamage_id); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - - } - break; - - case WM_MELODYOFSINK: - case WM_BEYOND_OF_WARCRY: - case WM_UNLIMITED_HUMMING_VOICE: - if( flag&1 ) { - sc_start2(bl,type,100,skill_lv,skill_area_temp[0],skill_get_time(skill_id,skill_lv)); - } else { // These affect to all targets arround the caster. - short lv = (short)skill_lv; - skill_area_temp[0] = (sd) ? skill_check_pc_partner(sd,skill_id,&lv,skill_get_splash(skill_id,skill_lv),1) : 50; // 50% chance in non BL_PC (clones). - map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id,skill_lv),BL_PC, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - } - break; - - case WM_RANDOMIZESPELL: { - int improv_skill_id = 0, improv_skill_lv; - do { - i = rnd() % MAX_SKILL_IMPROVISE_DB; - improv_skill_id = skill_improvise_db[i].skill_id; - } while( improv_skill_id == 0 || rnd()%10000 >= skill_improvise_db[i].per ); - improv_skill_lv = 4 + skill_lv; - clif_skill_nodamage (src, bl, skill_id, skill_lv, 1); - - if( sd ) { - sd->state.abra_flag = 2; - sd->skillitem = improv_skill_id; - sd->skillitemlv = improv_skill_lv; - clif_item_skill(sd, improv_skill_id, improv_skill_lv); - } else { - struct unit_data *ud = unit_bl2ud(src); - int inf = skill_get_inf(improv_skill_id); - int target_id = 0; - if (!ud) break; - if (inf&INF_SELF_SKILL || inf&INF_SUPPORT_SKILL) { - if (src->type == BL_PET) - bl = (struct block_list*)((TBL_PET*)src)->msd; - if (!bl) bl = src; - unit_skilluse_id(src, bl->id, improv_skill_id, improv_skill_lv); - } else { - if (ud->target) - target_id = ud->target; - else switch (src->type) { - case BL_MOB: target_id = ((TBL_MOB*)src)->target_id; break; - case BL_PET: target_id = ((TBL_PET*)src)->target_id; break; - } - if (!target_id) - break; - if (skill_get_casttype(improv_skill_id) == CAST_GROUND) { - bl = map_id2bl(target_id); - if (!bl) bl = src; - unit_skilluse_pos(src, bl->x, bl->y, improv_skill_id, improv_skill_lv); - } else - unit_skilluse_id(src, target_id, improv_skill_id, improv_skill_lv); - } - } - } - break; - - - case RETURN_TO_ELDICASTES: - case ALL_GUARDIAN_RECALL: - if( sd ) - { - short x, y; // Destiny position. - unsigned short mapindex; - - if( skill_id == RETURN_TO_ELDICASTES) - { - x = 198; - y = 187; - mapindex = mapindex_name2id(MAP_DICASTES); - } - else - { - x = 44; - y = 151; - mapindex = mapindex_name2id(MAP_MORA); - } - - if(!mapindex) - { //Given map not found? - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - map_freeblock_unlock(); - return 0; - } - pc_setpos(sd, mapindex, x, y, CLR_TELEPORT); - } - break; - - case GM_SANDMAN: - if( tsc ) { - if( tsc->opt1 == OPT1_SLEEP ) - tsc->opt1 = 0; - else - tsc->opt1 = OPT1_SLEEP; - clif_changeoption(bl); - clif_skill_nodamage (src, bl, skill_id, skill_lv, 1); - } - break; - - case SO_ARRULLO: - if( flag&1 ) - sc_start2(bl, type, 88 + 2 * skill_lv, skill_lv, 1, skill_get_time(skill_id, skill_lv)); - else { - clif_skill_nodamage(src, bl, skill_id, 0, 1); - map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, - src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); - } - break; - - case SO_SUMMON_AGNI: - case SO_SUMMON_AQUA: - case SO_SUMMON_VENTUS: - case SO_SUMMON_TERA: - if( sd ) { - int elemental_class = skill_get_elemental_type(skill_id,skill_lv); - - // Remove previous elemental fisrt. - if( sd->ed ) - elemental_delete(sd->ed,0); - - // Summoning the new one. - if( !elemental_create(sd,elemental_class,skill_get_time(skill_id,skill_lv)) ) { - clif_skill_fail(sd,skill_id,0,0); - break; - } - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - } - break; - - case SO_EL_CONTROL: - if( sd ) { - int mode = EL_MODE_PASSIVE; // Standard mode. - - if( !sd->ed ) break; - - if( skill_lv == 4 ) {// At level 4 delete elementals. - elemental_delete(sd->ed, 0); - break; - } - switch( skill_lv ) {// Select mode bassed on skill level used. - case 2: mode = EL_MODE_ASSIST; break; - case 3: mode = EL_MODE_AGGRESSIVE; break; - } - if( !elemental_change_mode(sd->ed,mode) ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - } - break; - - case SO_EL_ACTION: - if( sd ) { - int duration = 3000; - if( !sd->ed ) break; - sd->skill_id_old = skill_id; - elemental_action(sd->ed, bl, tick); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - switch(sd->ed->db->class_){ - case 2115:case 2124: - case 2118:case 2121: - duration = 6000; - break; - case 2116:case 2119: - case 2122:case 2125: - duration = 9000; - break; - } - skill_blockpc_start(sd, skill_id, duration); - } - break; - - case SO_EL_CURE: - if( sd ) { - struct elemental_data *ed = sd->ed; - int s_hp = sd->battle_status.hp * 10 / 100, s_sp = sd->battle_status.sp * 10 / 100; - int e_hp, e_sp; - - if( !ed ) break; - if( !status_charge(&sd->bl,s_hp,s_sp) ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - e_hp = ed->battle_status.max_hp * 10 / 100; - e_sp = ed->battle_status.max_sp * 10 / 100; - status_heal(&ed->bl,e_hp,e_sp,3); - clif_skill_nodamage(src,&ed->bl,skill_id,skill_lv,1); - } - break; - - case GN_CHANGEMATERIAL: - case SO_EL_ANALYSIS: - if( sd ) { - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - clif_skill_itemlistwindow(sd,skill_id,skill_lv); - } - break; - - case GN_BLOOD_SUCKER: - { - struct status_change *sc = status_get_sc(src); - - if( sc && sc->bs_counter < skill_get_maxcount( skill_id , skill_lv) ) { - if( tsc && tsc->data[type] ){ - (sc->bs_counter)--; - status_change_end(src, type, INVALID_TIMER); // the first one cancels and the last one will take effect resetting the timer - } - clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); - sc_start2(bl, type, 100, skill_lv, src->id, skill_get_time(skill_id,skill_lv)); - (sc->bs_counter)++; - } else if( sd ) { - clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); - break; - } - } - break; - - case GN_MANDRAGORA: - if( flag&1 ) { - if ( clif_skill_nodamage(bl, src, skill_id, skill_lv, - sc_start(bl, type, 25 + 10 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv))) ) - status_zap(bl, 0, status_get_max_sp(bl) * (25 + 5 * skill_lv) / 100); - } else - map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, - src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); - break; - - case GN_SLINGITEM: - if( sd ) { - short ammo_id; - i = sd->equip_index[EQI_AMMO]; - if( i <= 0 ) - break; // No ammo. - ammo_id = sd->inventory_data[i]->nameid; - if( ammo_id <= 0 ) - break; - sd->itemid = ammo_id; - if( itemdb_is_GNbomb(ammo_id) ) { - if(battle_check_target(src,bl,BCT_ENEMY) > 0) {// Only attack if the target is an enemy. - if( ammo_id == 13263 ) - map_foreachincell(skill_area_sub,bl->m,bl->x,bl->y,BL_CHAR,src,GN_SLINGITEM_RANGEMELEEATK,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); - else - skill_attack(BF_WEAPON,src,src,bl,GN_SLINGITEM_RANGEMELEEATK,skill_lv,tick,flag); - } else //Otherwise, it fails, shows animation and removes items. - clif_skill_fail(sd,GN_SLINGITEM_RANGEMELEEATK,0xa,0); - } else if( itemdb_is_GNthrowable(ammo_id) ){ - struct script_code *script = sd->inventory_data[i]->script; - if( !script ) - break; - if( dstsd ) - run_script(script,0,dstsd->bl.id,fake_nd->bl.id); - else - run_script(script,0,src->id,0); - } - } - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1);// This packet is received twice actually, I think it is to show the animation. - break; - - case GN_MIX_COOKING: - case GN_MAKEBOMB: - case GN_S_PHARMACY: - if( sd ) { - int qty = 1; - sd->skill_id_old = skill_id; - sd->skill_lv_old = skill_lv; - if( skill_id != GN_S_PHARMACY && skill_lv > 1 ) - qty = 10; - clif_cooking_list(sd,(skill_id - GN_MIX_COOKING) + 27,skill_id,qty,skill_id==GN_MAKEBOMB?5:6); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - } - break; - case EL_CIRCLE_OF_FIRE: - case EL_PYROTECHNIC: - case EL_HEATER: - case EL_TROPIC: - case EL_AQUAPLAY: - case EL_COOLER: - case EL_CHILLY_AIR: - case EL_GUST: - case EL_BLAST: - case EL_WILD_STORM: - case EL_PETROLOGY: - case EL_CURSED_SOIL: - case EL_UPHEAVAL: - case EL_FIRE_CLOAK: - case EL_WATER_DROP: - case EL_WIND_CURTAIN: - case EL_SOLID_SKIN: - case EL_STONE_SHIELD: - case EL_WIND_STEP: { - struct elemental_data *ele = BL_CAST(BL_ELEM, src); - if( ele ) { - sc_type type2 = type-1; - struct status_change *sc = status_get_sc(&ele->bl); - - if( (sc && sc->data[type2]) || (tsc && tsc->data[type]) ) { - elemental_clean_single_effect(ele, skill_id); - } else { - clif_skill_nodamage(src,src,skill_id,skill_lv,1); - clif_skill_damage(src, ( skill_id == EL_GUST || skill_id == EL_BLAST || skill_id == EL_WILD_STORM )?src:bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - if( skill_id == EL_WIND_STEP ) // There aren't teleport, just push the master away. - skill_blown(src,bl,(rnd()%skill_get_blewcount(skill_id,skill_lv))+1,rand()%8,0); - sc_start(src,type2,100,skill_lv,skill_get_time(skill_id,skill_lv)); - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); - } - } - } - break; - - case EL_FIRE_MANTLE: - case EL_WATER_BARRIER: - case EL_ZEPHYR: - case EL_POWER_OF_GAIA: - clif_skill_nodamage(src,src,skill_id,skill_lv,1); - clif_skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - skill_unitsetting(src,skill_id,skill_lv,bl->x,bl->y,0); - break; - - case EL_WATER_SCREEN: { - struct elemental_data *ele = BL_CAST(BL_ELEM, src); - if( ele ) { - struct status_change *sc = status_get_sc(&ele->bl); - sc_type type2 = type-1; - - clif_skill_nodamage(src,src,skill_id,skill_lv,1); - if( (sc && sc->data[type2]) || (tsc && tsc->data[type]) ) { - elemental_clean_single_effect(ele, skill_id); - } else { - // This not heals at the end. - clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - sc_start(src,type2,100,skill_lv,skill_get_time(skill_id,skill_lv)); - sc_start(bl,type,100,src->id,skill_get_time(skill_id,skill_lv)); - } - } - } - break; - - case KO_KAHU_ENTEN: - case KO_HYOUHU_HUBUKI: - case KO_KAZEHU_SEIRAN: - case KO_DOHU_KOUKAI: - if(sd) { - int ttype = skill_get_ele(skill_id, skill_lv); - clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); - pc_add_talisman(sd, skill_get_time(skill_id, skill_lv), 10, ttype); - } - break; - - case KO_ZANZOU: - if(sd){ - struct mob_data *md; - - md = mob_once_spawn_sub(src, src->m, src->x, src->y, status_get_name(src), 2308, "", SZ_SMALL, AI_NONE); - if( md ) - { - md->master_id = src->id; - md->special_state.ai = AI_ZANZOU; - if( md->deletetimer != INVALID_TIMER ) - delete_timer(md->deletetimer, mob_timer_delete); - md->deletetimer = add_timer (gettick() + skill_get_time(skill_id, skill_lv), mob_timer_delete, md->bl.id, 0); - mob_spawn( md ); - pc_setinvincibletimer(sd,500);// unlock target lock - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - skill_blown(src,bl,skill_get_blewcount(skill_id,skill_lv),unit_getdir(bl),0); - } - } - break; - - case KO_KYOUGAKU: - if( dstsd && tsc && !tsc->data[type] && rand()%100 < tstatus->int_/2 ){ - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - }else if( sd ) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - - case KO_JYUSATSU: - if( dstsd && tsc && !tsc->data[type] && - rand()%100 < ((45+5*skill_lv) + skill_lv*5 - status_get_int(bl)/2) ){//[(Base chance of success) + (Skill Level x 5) - (int / 2)]%. - clif_skill_nodamage(src,bl,skill_id,skill_lv, - status_change_start(bl,type,10000,skill_lv,0,0,0,skill_get_time(skill_id,skill_lv),1)); - status_zap(bl, tstatus->max_hp*skill_lv*5/100 , 0); - if( status_get_lv(bl) <= status_get_lv(src) ) - status_change_start(bl,SC_COMA,10,skill_lv,0,src->id,0,0,0); - }else if( sd ) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - - case KO_GENWAKU: - if ( !map_flag_gvg(src->m) && ( dstsd || dstmd ) && battle_check_target(src,bl,BCT_ENEMY) > 0 ) { - int x = src->x, y = src->y; - - if( sd && rnd()%100 > ((45+5*skill_lv) - status_get_int(bl)/10) ){//[(Base chance of success) - (Intelligence Objectives / 10)]%. - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - - if (unit_movepos(src,bl->x,bl->y,0,0)) { - clif_skill_nodamage(src,src,skill_id,skill_lv,1); - clif_slide(src,bl->x,bl->y) ; - sc_start(src,SC_CONFUSION,80,skill_lv,skill_get_time(skill_id,skill_lv)); - if (unit_movepos(bl,x,y,0,0)) - { - clif_skill_damage(bl,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, -1, 6); - if( bl->type == BL_PC && pc_issit((TBL_PC*)bl)) - clif_sitting(bl); //Avoid sitting sync problem - clif_slide(bl,x,y) ; - sc_start(bl,SC_CONFUSION,80,skill_lv,skill_get_time(skill_id,skill_lv)); - } - } - } - break; - - case OB_AKAITSUKI: - case OB_OBOROGENSOU: - if( sd && ( (skill_id == OB_OBOROGENSOU && bl->type == BL_MOB) // This skill does not work on monsters. - || is_boss(bl) ) ){ // Does not work on Boss monsters. - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - case KO_IZAYOI: - case OB_ZANGETSU: - case KG_KYOMU: - case KG_KAGEMUSYA: - clif_skill_nodamage(src,bl,skill_id,skill_lv, - sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); - clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - break; - - case KG_KAGEHUMI: - if( flag&1 ){ - if(tsc && ( tsc->option&(OPTION_CLOAK|OPTION_HIDE) || - tsc->data[SC_CAMOUFLAGE] || tsc->data[SC__SHADOWFORM] || - tsc->data[SC_MARIONETTE] || tsc->data[SC_HARMONIZE])){ - sc_start(src, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); - sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); - status_change_end(bl, SC_HIDING, INVALID_TIMER); - status_change_end(bl, SC_CLOAKING, INVALID_TIMER); - status_change_end(bl, SC_CLOAKINGEXCEED, INVALID_TIMER); - status_change_end(bl, SC_CAMOUFLAGE, INVALID_TIMER); - status_change_end(bl, SC__SHADOWFORM, INVALID_TIMER); - status_change_end(bl, SC_MARIONETTE, INVALID_TIMER); - status_change_end(bl, SC_HARMONIZE, INVALID_TIMER); - } - if( skill_area_temp[2] == 1 ){ - clif_skill_damage(src,src,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - sc_start(src, SC_STOP, 100, skill_lv, skill_get_time(skill_id, skill_lv)); - } - }else{ - skill_area_temp[2] = 0; - map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_nodamage_id); - } - break; - - case MH_SILENT_BREEZE: { - struct status_change *ssc = status_get_sc(src); - struct block_list *m_bl = battle_get_master(src); - const enum sc_type scs[] = { - SC_MANDRAGORA, SC_HARMONIZE, SC_DEEPSLEEP, SC_VOICEOFSIREN, SC_SLEEP, SC_CONFUSION, SC_HALLUCINATION - }; - int heal; - if(tsc){ - for (i = 0; i < ARRAYLENGTH(scs); i++) { - if (tsc->data[scs[i]]) status_change_end(bl, scs[i], INVALID_TIMER); - } - if (!tsc->data[SC_SILENCE]) //put inavoidable silence on target - status_change_start(bl, SC_SILENCE, 100, skill_lv, 0,0,0, skill_get_time(skill_id, skill_lv),1|2|8); - } - heal = status_get_matk_min(src)*4; - status_heal(bl, heal, 0, 7); - - //now inflict silence on everyone - if(ssc && !ssc->data[SC_SILENCE]) //put inavoidable silence on homun - status_change_start(src, SC_SILENCE, 100, skill_lv, 0,0,0, skill_get_time(skill_id, skill_lv),1|2|8); - if(m_bl){ - struct status_change *msc = status_get_sc(m_bl); - if(msc && !msc->data[SC_SILENCE]) //put inavoidable silence on master - status_change_start(m_bl, SC_SILENCE, 100, skill_lv, 0,0,0, skill_get_time(skill_id, skill_lv),1|2|8); - } - if (hd) - skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); - } - break; - case MH_OVERED_BOOST: - if (hd){ - struct block_list *s_bl = battle_get_master(src); - if(hd->homunculus.hunger>50) //reduce hunger - hd->homunculus.hunger = hd->homunculus.hunger/2; - else - hd->homunculus.hunger = min(1,hd->homunculus.hunger); - if(s_bl && s_bl->type==BL_PC){ - status_set_sp(s_bl,status_get_max_sp(s_bl)/2,0); //master drain 50% sp - clif_send_homdata(((TBL_PC *)s_bl), SP_HUNGRY, hd->homunculus.hunger); //refresh hunger info - sc_start(s_bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); //gene bonus - } - sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); - skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); - } - break; - case MH_GRANITIC_ARMOR: - case MH_PYROCLASTIC: { - struct block_list *s_bl = battle_get_master(src); - if(s_bl) sc_start2(s_bl, type, 100, skill_lv, hd->homunculus.level, skill_get_time(skill_id, skill_lv)); //start on master - sc_start2(bl, type, 100, skill_lv, hd->homunculus.level, skill_get_time(skill_id, skill_lv)); - if (hd) skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); - } - break; - - case MH_LIGHT_OF_REGENE: - if(hd){ - hd->homunculus.intimacy = 251; //change to neutral (can't be cast if < 750) - if(sd) clif_send_homdata(sd, SP_INTIMATE, hd->homunculus.intimacy); //refresh intimacy info - } - //don't break need to start status and start block timer - case MH_STYLE_CHANGE: - case MH_MAGMA_FLOW: - case MH_PAIN_KILLER: - sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); - if (hd) - skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); - break; - case MH_SUMMON_LEGION: - { - int summons[5] = {1004, 1303, 1303, 1994, 1994}; - int qty[5] = {3 , 3 , 4 , 4 , 5}; - struct mob_data *md; - int i; - - for(i=0; i<qty[skill_lv - 1]; i++){ //easy way - md = mob_once_spawn_sub(src, src->m, src->x, src->y, status_get_name(src), summons[skill_lv - 1], "", SZ_SMALL, AI_ATTACK); - if (md) { - md->master_id = src->id; - if (md->deletetimer != INVALID_TIMER) - delete_timer(md->deletetimer, mob_timer_delete); - md->deletetimer = add_timer(gettick() + skill_get_time(skill_id, skill_lv), mob_timer_delete, md->bl.id, 0); - mob_spawn(md); //Now it is ready for spawning. - sc_start4(&md->bl, SC_MODECHANGE, 100, 1, 0, MD_ASSIST, 0, 60000); - } - } - if (hd) - skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); - } - break; - default: - ShowWarning("skill_castend_nodamage_id: Unknown skill used:%d\n",skill_id); - clif_skill_nodamage(src,bl,skill_id,skill_lv,1); - map_freeblock_unlock(); - return 1; - } - - if(skill_id != SR_CURSEDCIRCLE){ - struct status_change *sc = status_get_sc(src); - if( sc && sc->data[SC_CURSEDCIRCLE_ATKER] )//Should only remove after the skill had been casted. - status_change_end(src,SC_CURSEDCIRCLE_ATKER,INVALID_TIMER); - } - - if (dstmd) { //Mob skill event for no damage skills (damage ones are handled in battle_calc_damage) [Skotlex] - mob_log_damage(dstmd, src, 0); //Log interaction (counts as 'attacker' for the exp bonus) - mobskill_event(dstmd, src, tick, MSC_SKILLUSED|(skill_id<<16)); - } - - if( sd && !(flag&1) ) - {// ensure that the skill last-cast tick is recorded - sd->canskill_tick = gettick(); - - if( sd->state.arrow_atk ) - {// consume arrow on last invocation to this skill. - battle_consume_ammo(sd, skill_id, skill_lv); - } - skill_onskillusage(sd, bl, skill_id, tick); - // perform skill requirement consumption - skill_consume_requirement(sd,skill_id,skill_lv,2); - } - - map_freeblock_unlock(); - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -int skill_castend_id(int tid, unsigned int tick, int id, intptr_t data) -{ - struct block_list *target, *src; - struct map_session_data *sd; - struct mob_data *md; - struct unit_data *ud; - struct status_change *sc = NULL; - int inf,inf2,flag = 0; - - src = map_id2bl(id); - if( src == NULL ) - { - ShowDebug("skill_castend_id: src == NULL (tid=%d, id=%d)\n", tid, id); - return 0;// not found - } - - ud = unit_bl2ud(src); - if( ud == NULL ) - { - ShowDebug("skill_castend_id: ud == NULL (tid=%d, id=%d)\n", tid, id); - return 0;// ??? - } - - sd = BL_CAST(BL_PC, src); - md = BL_CAST(BL_MOB, src); - - if( src->prev == NULL ) { - ud->skilltimer = INVALID_TIMER; - return 0; - } - - if(ud->skill_id != SA_CASTCANCEL && ud->skill_id != SO_SPELLFIST) {// otherwise handled in unit_skillcastcancel() - if( ud->skilltimer != tid ) { - ShowError("skill_castend_id: Timer mismatch %d!=%d!\n", ud->skilltimer, tid); - ud->skilltimer = INVALID_TIMER; - return 0; - } - - if( sd && ud->skilltimer != INVALID_TIMER && (pc_checkskill(sd,SA_FREECAST) > 0 || ud->skill_id == LG_EXEEDBREAK) ) - {// restore original walk speed - ud->skilltimer = INVALID_TIMER; - status_calc_bl(&sd->bl, SCB_SPEED); - } - - ud->skilltimer = INVALID_TIMER; - } - - if (ud->skilltarget == id) - target = src; - else - target = map_id2bl(ud->skilltarget); - - // Use a do so that you can break out of it when the skill fails. - do { - if(!target || target->prev==NULL) break; - - if(src->m != target->m || status_isdead(src)) break; - - switch (ud->skill_id) { - //These should become skill_castend_pos - case WE_CALLPARTNER: - if(sd) clif_callpartner(sd); - case WE_CALLPARENT: - case WE_CALLBABY: - case AM_RESURRECTHOMUN: - case PF_SPIDERWEB: - //Find a random spot to place the skill. [Skotlex] - inf2 = skill_get_splash(ud->skill_id, ud->skill_lv); - ud->skillx = target->x + inf2; - ud->skilly = target->y + inf2; - if (inf2 && !map_random_dir(target, &ud->skillx, &ud->skilly)) { - ud->skillx = target->x; - ud->skilly = target->y; - } - ud->skilltimer=tid; - return skill_castend_pos(tid,tick,id,data); - case GN_WALLOFTHORN: - ud->skillx = target->x; - ud->skilly = target->y; - ud->skilltimer = tid; - return skill_castend_pos(tid,tick,id,data); - } - - if(ud->skill_id == RG_BACKSTAP) { - uint8 dir = map_calc_dir(src,target->x,target->y),t_dir = unit_getdir(target); - if(check_distance_bl(src, target, 0) || map_check_dir(dir,t_dir)) { - break; - } - } - - if( ud->skill_id == PR_TURNUNDEAD ) - { - struct status_data *tstatus = status_get_status_data(target); - if( !battle_check_undead(tstatus->race, tstatus->def_ele) ) - break; - } - - if( ud->skill_id == RA_WUGSTRIKE ){ - if( !path_search(NULL,src->m,src->x,src->y,target->x,target->y,1,CELL_CHKNOREACH)) - break; - } - - if( ud->skill_id == PR_LEXDIVINA || ud->skill_id == MER_LEXDIVINA ) - { - sc = status_get_sc(target); - if( battle_check_target(src,target, BCT_ENEMY) <= 0 && (!sc || !sc->data[SC_SILENCE]) ) - { //If it's not an enemy, and not silenced, you can't use the skill on them. [Skotlex] - clif_skill_nodamage (src, target, ud->skill_id, ud->skill_lv, 0); - break; - } - } - else - { // Check target validity. - inf = skill_get_inf(ud->skill_id); - inf2 = skill_get_inf2(ud->skill_id); - - if(inf&INF_ATTACK_SKILL || - (inf&INF_SELF_SKILL && inf2&INF2_NO_TARGET_SELF) //Combo skills - ) // Casted through combo. - inf = BCT_ENEMY; //Offensive skill. - else if(inf2&INF2_NO_ENEMY) - inf = BCT_NOENEMY; - else - inf = 0; - - if(inf2 & (INF2_PARTY_ONLY|INF2_GUILD_ONLY) && src != target) - { - inf |= - (inf2&INF2_PARTY_ONLY?BCT_PARTY:0)| - (inf2&INF2_GUILD_ONLY?BCT_GUILD:0); - //Remove neutral targets (but allow enemy if skill is designed to be so) - inf &= ~BCT_NEUTRAL; - } - - if( ud->skill_id >= SL_SKE && ud->skill_id <= SL_SKA && target->type == BL_MOB ) - { - if( ((TBL_MOB*)target)->class_ == MOBID_EMPERIUM ) - break; - } - else if (inf && battle_check_target(src, target, inf) <= 0){ - if (sd) clif_skill_fail(sd,ud->skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - - if(inf&BCT_ENEMY && (sc = status_get_sc(target)) && - sc->data[SC_FOGWALL] && - rnd() % 100 < 75) { //Fogwall makes all offensive-type targetted skills fail at 75% - if (sd) clif_skill_fail(sd, ud->skill_id, USESKILL_FAIL_LEVEL, 0); - break; - } - } - - //Avoid doing double checks for instant-cast skills. - if (tid != INVALID_TIMER && !status_check_skilluse(src, target, ud->skill_id, 1)) - break; - - if(md) { - md->last_thinktime=tick +MIN_MOBTHINKTIME; - if(md->skill_idx >= 0 && md->db->skill[md->skill_idx].emotion >= 0) - clif_emotion(src, md->db->skill[md->skill_idx].emotion); - } - - if(src != target && battle_config.skill_add_range && - !check_distance_bl(src, target, skill_get_range2(src,ud->skill_id,ud->skill_lv)+battle_config.skill_add_range)) - { - if (sd) { - clif_skill_fail(sd,ud->skill_id,USESKILL_FAIL_LEVEL,0); - if(battle_config.skill_out_range_consume) //Consume items anyway. [Skotlex] - skill_consume_requirement(sd,ud->skill_id,ud->skill_lv,3); - } - break; - } - - if( sd ) - { - if( !skill_check_condition_castend(sd, ud->skill_id, ud->skill_lv) ) - break; - else - skill_consume_requirement(sd,ud->skill_id,ud->skill_lv,1); - } -#ifdef OFFICIAL_WALKPATH - if( !path_search_long(NULL, src->m, src->x, src->y, target->x, target->y, CELL_CHKWALL) ) - break; -#endif - if( (src->type == BL_MER || src->type == BL_HOM) && !skill_check_condition_mercenary(src, ud->skill_id, ud->skill_lv, 1) ) - break; - - if (ud->state.running && ud->skill_id == TK_JUMPKICK) - { - ud->state.running = 0; - status_change_end(src, SC_RUN, INVALID_TIMER); - flag = 1; - } - - if (ud->walktimer != INVALID_TIMER && ud->skill_id != TK_RUN && ud->skill_id != RA_WUGDASH) - unit_stop_walking(src,1); - - if( !sd || sd->skillitem != ud->skill_id || skill_get_delay(ud->skill_id,ud->skill_lv) ) - ud->canact_tick = tick + skill_delayfix(src, ud->skill_id, ud->skill_lv); //Tests show wings don't overwrite the delay but skill scrolls do. [Inkfish] - if (sd) { //Cooldown application - int i, cooldown = skill_get_cooldown(ud->skill_id, ud->skill_lv); - for (i = 0; i < ARRAYLENGTH(sd->skillcooldown) && sd->skillcooldown[i].id; i++) { // Increases/Decreases cooldown of a skill by item/card bonuses. - if (sd->skillcooldown[i].id == ud->skill_id){ - cooldown += sd->skillcooldown[i].val; - break; - } - } - if(cooldown) - skill_blockpc_start(sd, ud->skill_id, cooldown); - } - if( battle_config.display_status_timers && sd ) - clif_status_change(src, SI_ACTIONDELAY, 1, skill_delayfix(src, ud->skill_id, ud->skill_lv), 0, 0, 0); - if( sd ) - { - switch( ud->skill_id ) - { - case GS_DESPERADO: - sd->canequip_tick = tick + skill_get_time(ud->skill_id, ud->skill_lv); - break; - case CR_GRANDCROSS: - case NPC_GRANDDARKNESS: - if( (sc = status_get_sc(src)) && sc->data[SC_STRIPSHIELD] ) - { - const struct TimerData *timer = get_timer(sc->data[SC_STRIPSHIELD]->timer); - if( timer && timer->func == status_change_timer && DIFF_TICK(timer->tick,gettick()+skill_get_time(ud->skill_id, ud->skill_lv)) > 0 ) - break; - } - sc_start2(src, SC_STRIPSHIELD, 100, 0, 1, skill_get_time(ud->skill_id, ud->skill_lv)); - break; - } - } - if (skill_get_state(ud->skill_id) != ST_MOVE_ENABLE) - unit_set_walkdelay(src, tick, battle_config.default_walk_delay+skill_get_walkdelay(ud->skill_id, ud->skill_lv), 1); - - if(battle_config.skill_log && battle_config.skill_log&src->type) - ShowInfo("Type %d, ID %d skill castend id [id =%d, lv=%d, target ID %d]\n", - src->type, src->id, ud->skill_id, ud->skill_lv, target->id); - - map_freeblock_lock(); - - // SC_MAGICPOWER needs to switch states before any damage is actually dealt - skill_toggle_magicpower(src, ud->skill_id); - if( ud->skill_id != RA_CAMOUFLAGE ) // only normal attack and auto cast skills benefit from its bonuses - status_change_end(src,SC_CAMOUFLAGE, INVALID_TIMER); - - if (skill_get_casttype(ud->skill_id) == CAST_NODAMAGE) - skill_castend_nodamage_id(src,target,ud->skill_id,ud->skill_lv,tick,flag); - else - skill_castend_damage_id(src,target,ud->skill_id,ud->skill_lv,tick,flag); - - sc = status_get_sc(src); - if(sc && sc->count) { - if(sc->data[SC_SPIRIT] && - sc->data[SC_SPIRIT]->val2 == SL_WIZARD && - sc->data[SC_SPIRIT]->val3 == ud->skill_id && - ud->skill_id != WZ_WATERBALL) - sc->data[SC_SPIRIT]->val3 = 0; //Clear bounced spell check. - - if( sc->data[SC_DANCING] && skill_get_inf2(ud->skill_id)&INF2_SONG_DANCE && sd ) - skill_blockpc_start(sd,BD_ADAPTATION,3000); - } - - if( sd && ud->skill_id != SA_ABRACADABRA && ud->skill_id != WM_RANDOMIZESPELL ) // they just set the data so leave it as it is.[Inkfish] - sd->skillitem = sd->skillitemlv = 0; - - if (ud->skilltimer == INVALID_TIMER) { - if(md) md->skill_idx = -1; - else ud->skill_id = 0; //mobs can't clear this one as it is used for skill condition 'afterskill' - ud->skill_lv = ud->skilltarget = 0; - } - map_freeblock_unlock(); - return 1; - } while(0); - - //Skill failed. - if (ud->skill_id == MO_EXTREMITYFIST && sd && !(sc && sc->data[SC_FOGWALL])) - { //When Asura fails... (except when it fails from Fog of Wall) - //Consume SP/spheres - skill_consume_requirement(sd,ud->skill_id, ud->skill_lv,1); - status_set_sp(src, 0, 0); - sc = &sd->sc; - if (sc->count) - { //End states - status_change_end(src, SC_EXPLOSIONSPIRITS, INVALID_TIMER); - status_change_end(src, SC_BLADESTOP, INVALID_TIMER); -#ifdef RENEWAL - sc_start(src, SC_EXTREMITYFIST2, 100, ud->skill_lv, skill_get_time(ud->skill_id, ud->skill_lv)); -#endif - } - if (target && target->m == src->m) - { //Move character to target anyway. - if (unit_movepos(src, src->x+3, src->y+3, 1, 1)) - { //Display movement + animation. - clif_slide(src,src->x,src->y); - clif_skill_damage(src,target,tick,sd->battle_status.amotion,0,0,1,ud->skill_id, ud->skill_lv, 5); - } - clif_skill_fail(sd,ud->skill_id,USESKILL_FAIL_LEVEL,0); - } - } - - ud->skill_id = ud->skill_lv = ud->skilltarget = 0; - if( !sd || sd->skillitem != ud->skill_id || skill_get_delay(ud->skill_id,ud->skill_lv) ) - ud->canact_tick = tick; - //You can't place a skill failed packet here because it would be - //sent in ALL cases, even cases where skill_check_condition fails - //which would lead to double 'skill failed' messages u.u [Skotlex] - if(sd) - sd->skillitem = sd->skillitemlv = 0; - else if(md) - md->skill_idx = -1; - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -int skill_castend_pos(int tid, unsigned int tick, int id, intptr_t data) -{ - struct block_list* src = map_id2bl(id); - int maxcount; - struct map_session_data *sd; - struct unit_data *ud = unit_bl2ud(src); - struct mob_data *md; - - nullpo_ret(ud); - - sd = BL_CAST(BL_PC , src); - md = BL_CAST(BL_MOB, src); - - if( src->prev == NULL ) { - ud->skilltimer = INVALID_TIMER; - return 0; - } - - if( ud->skilltimer != tid ) - { - ShowError("skill_castend_pos: Timer mismatch %d!=%d\n", ud->skilltimer, tid); - ud->skilltimer = INVALID_TIMER; - return 0; - } - - if( sd && ud->skilltimer != INVALID_TIMER && ( pc_checkskill(sd,SA_FREECAST) > 0 || ud->skill_id == LG_EXEEDBREAK ) ) - {// restore original walk speed - ud->skilltimer = INVALID_TIMER; - status_calc_bl(&sd->bl, SCB_SPEED); - } - ud->skilltimer = INVALID_TIMER; - - do { - if( status_isdead(src) ) - break; - - if( !(src->type&battle_config.skill_reiteration) && - skill_get_unit_flag(ud->skill_id)&UF_NOREITERATION && - skill_check_unit_range(src,ud->skillx,ud->skilly,ud->skill_id,ud->skill_lv) - ) - { - if (sd) clif_skill_fail(sd,ud->skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - if( src->type&battle_config.skill_nofootset && - skill_get_unit_flag(ud->skill_id)&UF_NOFOOTSET && - skill_check_unit_range2(src,ud->skillx,ud->skilly,ud->skill_id,ud->skill_lv) - ) - { - if (sd) clif_skill_fail(sd,ud->skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - if( src->type&battle_config.land_skill_limit && - (maxcount = skill_get_maxcount(ud->skill_id, ud->skill_lv)) > 0 - ) { - int i; - for(i=0;i<MAX_SKILLUNITGROUP && ud->skillunit[i] && maxcount;i++) { - if(ud->skillunit[i]->skill_id == ud->skill_id) - maxcount--; - } - if( maxcount == 0 ) - { - if (sd) clif_skill_fail(sd,ud->skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - } - - if(tid != INVALID_TIMER) - { //Avoid double checks on instant cast skills. [Skotlex] - if (!status_check_skilluse(src, NULL, ud->skill_id, 1)) - break; - if(battle_config.skill_add_range && - !check_distance_blxy(src, ud->skillx, ud->skilly, skill_get_range2(src,ud->skill_id,ud->skill_lv)+battle_config.skill_add_range)) { - if (sd && battle_config.skill_out_range_consume) //Consume items anyway. - skill_consume_requirement(sd,ud->skill_id,ud->skill_lv,3); - break; - } - } - - if( sd ) - { - if( !skill_check_condition_castend(sd, ud->skill_id, ud->skill_lv) ) - break; - else - skill_consume_requirement(sd,ud->skill_id,ud->skill_lv,1); - } - - if( (src->type == BL_MER || src->type == BL_HOM) && !skill_check_condition_mercenary(src, ud->skill_id, ud->skill_lv, 1) ) - break; - - if(md) { - md->last_thinktime=tick +MIN_MOBTHINKTIME; - if(md->skill_idx >= 0 && md->db->skill[md->skill_idx].emotion >= 0) - clif_emotion(src, md->db->skill[md->skill_idx].emotion); - } - - if(battle_config.skill_log && battle_config.skill_log&src->type) - ShowInfo("Type %d, ID %d skill castend pos [id =%d, lv=%d, (%d,%d)]\n", - src->type, src->id, ud->skill_id, ud->skill_lv, ud->skillx, ud->skilly); - - if (ud->walktimer != INVALID_TIMER) - unit_stop_walking(src,1); - - if( !sd || sd->skillitem != ud->skill_id || skill_get_delay(ud->skill_id,ud->skill_lv) ) - ud->canact_tick = tick + skill_delayfix(src, ud->skill_id, ud->skill_lv); - if (sd) { //Cooldown application - int i, cooldown = skill_get_cooldown(ud->skill_id, ud->skill_lv); - for (i = 0; i < ARRAYLENGTH(sd->skillcooldown) && sd->skillcooldown[i].id; i++) { // Increases/Decreases cooldown of a skill by item/card bonuses. - if (sd->skillcooldown[i].id == ud->skill_id){ - cooldown += sd->skillcooldown[i].val; - break; - } - } - if(cooldown) - skill_blockpc_start(sd, ud->skill_id, cooldown); - } - if( battle_config.display_status_timers && sd ) - clif_status_change(src, SI_ACTIONDELAY, 1, skill_delayfix(src, ud->skill_id, ud->skill_lv), 0, 0, 0); -// if( sd ) -// { -// switch( ud->skill_id ) -// { -// case ????: -// sd->canequip_tick = tick + ????; -// break; -// } -// } - unit_set_walkdelay(src, tick, battle_config.default_walk_delay+skill_get_walkdelay(ud->skill_id, ud->skill_lv), 1); - status_change_end(src,SC_CAMOUFLAGE, INVALID_TIMER);// only normal attack and auto cast skills benefit from its bonuses - map_freeblock_lock(); - skill_castend_pos2(src,ud->skillx,ud->skilly,ud->skill_id,ud->skill_lv,tick,0); - - if( sd && sd->skillitem != AL_WARP ) // Warp-Portal thru items will clear data in skill_castend_map. [Inkfish] - sd->skillitem = sd->skillitemlv = 0; - - if (ud->skilltimer == INVALID_TIMER) { - if (md) md->skill_idx = -1; - else ud->skill_id = 0; //Non mobs can't clear this one as it is used for skill condition 'afterskill' - ud->skill_lv = ud->skillx = ud->skilly = 0; - } - - map_freeblock_unlock(); - return 1; - } while(0); - - if( !sd || sd->skillitem != ud->skill_id || skill_get_delay(ud->skill_id,ud->skill_lv) ) - ud->canact_tick = tick; - ud->skill_id = ud->skill_lv = 0; - if(sd) - sd->skillitem = sd->skillitemlv = 0; - else if(md) - md->skill_idx = -1; - return 0; - -} - -/*========================================== - * - *------------------------------------------*/ -int skill_castend_pos2(struct block_list* src, int x, int y, uint16 skill_id, uint16 skill_lv, unsigned int tick, int flag) -{ - struct map_session_data* sd; - struct status_change* sc; - struct status_change_entry *sce; - struct skill_unit_group* sg; - enum sc_type type; - int i; - - //if(skill_lv <= 0) return 0; - if(skill_id > 0 && !skill_lv) return 0; // celest - - nullpo_ret(src); - - if(status_isdead(src)) - return 0; - - sd = BL_CAST(BL_PC, src); - - sc = status_get_sc(src); - type = status_skill2sc(skill_id); - sce = (sc && type != -1)?sc->data[type]:NULL; - - switch (skill_id) { //Skill effect. - case WZ_METEOR: - case MO_BODYRELOCATION: - case CR_CULTIVATION: - case HW_GANBANTEIN: - case LG_EARTHDRIVE: - break; //Effect is displayed on respective switch case. - default: - if(skill_get_inf(skill_id)&INF_SELF_SKILL) - clif_skill_nodamage(src,src,skill_id,skill_lv,1); - else - clif_skill_poseffect(src,skill_id,skill_lv,x,y,tick); - } - - // SC_MAGICPOWER needs to switch states before any damage is actually dealt - skill_toggle_magicpower(src, skill_id); - - switch(skill_id) - { - case PR_BENEDICTIO: - skill_area_temp[1] = src->id; - i = skill_get_splash(skill_id, skill_lv); - map_foreachinarea(skill_area_sub, - src->m, x-i, y-i, x+i, y+i, BL_PC, - src, skill_id, skill_lv, tick, flag|BCT_ALL|1, - skill_castend_nodamage_id); - map_foreachinarea(skill_area_sub, - src->m, x-i, y-i, x+i, y+i, BL_CHAR, - src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, - skill_castend_damage_id); - break; - - case BS_HAMMERFALL: - i = skill_get_splash(skill_id, skill_lv); - map_foreachinarea (skill_area_sub, - src->m, x-i, y-i, x+i, y+i, BL_CHAR, - src, skill_id, skill_lv, tick, flag|BCT_ENEMY|2, - skill_castend_nodamage_id); - break; - - case HT_DETECTING: - i = skill_get_splash(skill_id, skill_lv); - map_foreachinarea( status_change_timer_sub, - src->m, x-i, y-i, x+i,y+i,BL_CHAR, - src,NULL,SC_SIGHT,tick); - if(battle_config.traps_setting&1) - map_foreachinarea( skill_reveal_trap, - src->m, x-i, y-i, x+i,y+i,BL_SKILL); - break; - - case SR_RIDEINLIGHTNING: - i = skill_get_splash(skill_id, skill_lv); - map_foreachinarea(skill_area_sub, src->m, x-i, y-i, x+i, y+i, BL_CHAR, - src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_damage_id); - break; - - case SA_VOLCANO: - case SA_DELUGE: - case SA_VIOLENTGALE: - { //Does not consumes if the skill is already active. [Skotlex] - struct skill_unit_group *sg; - if ((sg= skill_locate_element_field(src)) != NULL && ( sg->skill_id == SA_VOLCANO || sg->skill_id == SA_DELUGE || sg->skill_id == SA_VIOLENTGALE )) - { - if (sg->limit - DIFF_TICK(gettick(), sg->tick) > 0) - { - skill_unitsetting(src,skill_id,skill_lv,x,y,0); - return 0; // not to consume items - } - else - sg->limit = 0; //Disable it. - } - skill_unitsetting(src,skill_id,skill_lv,x,y,0); - break; - } - case MG_SAFETYWALL: - case MG_FIREWALL: - case MG_THUNDERSTORM: - - case AL_PNEUMA: - case WZ_ICEWALL: - case WZ_FIREPILLAR: - case WZ_QUAGMIRE: - case WZ_VERMILION: - case WZ_STORMGUST: - case WZ_HEAVENDRIVE: - case PR_SANCTUARY: - case PR_MAGNUS: - case CR_GRANDCROSS: - case NPC_GRANDDARKNESS: - case HT_SKIDTRAP: - case MA_SKIDTRAP: - case HT_LANDMINE: - case MA_LANDMINE: - case HT_ANKLESNARE: - case HT_SHOCKWAVE: - case HT_SANDMAN: - case MA_SANDMAN: - case HT_FLASHER: - case HT_FREEZINGTRAP: - case MA_FREEZINGTRAP: - case HT_BLASTMINE: - case HT_CLAYMORETRAP: - case AS_VENOMDUST: - case AM_DEMONSTRATION: - case PF_FOGWALL: - case PF_SPIDERWEB: - case HT_TALKIEBOX: - case WE_CALLPARTNER: - case WE_CALLPARENT: - case WE_CALLBABY: - case AC_SHOWER: //Ground-placed skill implementation. - case MA_SHOWER: - case SA_LANDPROTECTOR: - 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: - case GS_DESPERADO: - case NJ_KAENSIN: - case NJ_BAKUENRYU: - case NJ_SUITON: - case NJ_HYOUSYOURAKU: - case NJ_RAIGEKISAI: - case NJ_KAMAITACHI: -#ifdef RENEWAL - case NJ_HUUMA: -#endif - case NPC_EVILLAND: - case RA_ELECTRICSHOCKER: - case RA_CLUSTERBOMB: - case RA_MAGENTATRAP: - case RA_COBALTTRAP: - case RA_MAIZETRAP: - case RA_VERDURETRAP: - case RA_FIRINGTRAP: - case RA_ICEBOUNDTRAP: - case SC_MANHOLE: - case SC_DIMENSIONDOOR: - case SC_CHAOSPANIC: - case SC_MAELSTROM: - case WM_REVERBERATION: - case WM_SEVERE_RAINSTORM: - case WM_POEMOFNETHERWORLD: - case SO_PSYCHIC_WAVE: - case SO_VACUUM_EXTREME: - case GN_WALLOFTHORN: - case GN_THORNS_TRAP: - case GN_DEMONIC_FIRE: - case GN_HELLS_PLANT: - case SO_EARTHGRAVE: - case SO_DIAMONDDUST: - case SO_FIRE_INSIGNIA: - case SO_WATER_INSIGNIA: - case SO_WIND_INSIGNIA: - case SO_EARTH_INSIGNIA: - case KO_HUUMARANKA: - case KO_MUCHANAGE: - case KO_BAKURETSU: - case KO_ZENKAI: - case MH_LAVA_SLIDE: - case MH_VOLCANIC_ASH: - case MH_POISON_MIST: - case MH_STEINWAND: - case MH_XENO_SLASHER: - flag|=1;//Set flag to 1 to prevent deleting ammo (it will be deleted on group-delete). - case GS_GROUNDDRIFT: //Ammo should be deleted right away. - skill_unitsetting(src,skill_id,skill_lv,x,y,0); - break; - case RG_GRAFFITI: /* Graffiti [Valaris] */ - skill_clear_unitgroup(src); - skill_unitsetting(src,skill_id,skill_lv,x,y,0); - flag|=1; - break; - case HP_BASILICA: - if( sc->data[SC_BASILICA] ) - status_change_end(src, SC_BASILICA, INVALID_TIMER); // Cancel Basilica - else - { // Create Basilica. Start SC on caster. Unit timer start SC on others. - skill_clear_unitgroup(src); - if( skill_unitsetting(src,skill_id,skill_lv,x,y,0) ) - sc_start4(src,type,100,skill_lv,0,0,src->id,skill_get_time(skill_id,skill_lv)); - flag|=1; - } - break; - case CG_HERMODE: - skill_clear_unitgroup(src); - if ((sg = skill_unitsetting(src,skill_id,skill_lv,x,y,0))) - sc_start4(src,SC_DANCING,100, - skill_id,0,skill_lv,sg->group_id,skill_get_time(skill_id,skill_lv)); - flag|=1; - break; - case RG_CLEANER: // [Valaris] - i = skill_get_splash(skill_id, skill_lv); - map_foreachinarea(skill_graffitiremover,src->m,x-i,y-i,x+i,y+i,BL_SKILL); - break; - - case SO_WARMER: - flag|= 8; - case SO_CLOUD_KILL: - skill_unitsetting(src,skill_id,skill_lv,x,y,0); - break; - - case WZ_METEOR: { - int area = skill_get_splash(skill_id, skill_lv); - short tmpx = 0, tmpy = 0, x1 = 0, y1 = 0; - - for( i = 0; i < 2 + (skill_lv>>1); i++ ) { - // Creates a random Cell in the Splash Area - tmpx = x - area + rnd()%(area * 2 + 1); - tmpy = y - area + rnd()%(area * 2 + 1); - - if( i == 0 && path_search_long(NULL, src->m, src->x, src->y, tmpx, tmpy, CELL_CHKWALL) ) - clif_skill_poseffect(src,skill_id,skill_lv,tmpx,tmpy,tick); - - if( i > 0 ) - skill_addtimerskill(src,tick+i*1000,0,tmpx,tmpy,skill_id,skill_lv,(x1<<16)|y1,0); - - x1 = tmpx; - y1 = tmpy; - } - - skill_addtimerskill(src,tick+i*1000,0,tmpx,tmpy,skill_id,skill_lv,-1,0); - } - break; - - case AL_WARP: - if(sd) - { - clif_skill_warppoint(sd, skill_id, skill_lv, sd->status.save_point.map, - (skill_lv >= 2) ? sd->status.memo_point[0].map : 0, - (skill_lv >= 3) ? sd->status.memo_point[1].map : 0, - (skill_lv >= 4) ? sd->status.memo_point[2].map : 0 - ); - } - return 0; // not to consume item. - - case MO_BODYRELOCATION: - if (unit_movepos(src, x, y, 1, 1)) { -#if PACKETVER >= 20111005 - clif_snap(src, src->x, src->y); -#else - clif_skill_poseffect(src,skill_id,skill_lv,src->x,src->y,tick); -#endif - if (sd) - skill_blockpc_start (sd, MO_EXTREMITYFIST, 2000); - } - break; - case NJ_SHADOWJUMP: - if( !map_flag_gvg(src->m) && !map[src->m].flag.battleground ) { //You don't move on GVG grounds. - unit_movepos(src, x, y, 1, 0); - clif_slide(src,x,y); - } - status_change_end(src, SC_HIDING, INVALID_TIMER); - break; - case AM_SPHEREMINE: - case AM_CANNIBALIZE: - { - int summons[5] = { 1589, 1579, 1575, 1555, 1590 }; - //int summons[5] = { 1020, 1068, 1118, 1500, 1368 }; - int class_ = skill_id==AM_SPHEREMINE?1142:summons[skill_lv-1]; - struct mob_data *md; - - // Correct info, don't change any of this! [celest] - md = mob_once_spawn_sub(src, src->m, x, y, status_get_name(src), class_, "", SZ_SMALL, AI_NONE); - if (md) { - md->master_id = src->id; - md->special_state.ai = (skill_id == AM_SPHEREMINE) ? AI_SPHERE : AI_FLORA; - if( md->deletetimer != INVALID_TIMER ) - delete_timer(md->deletetimer, mob_timer_delete); - md->deletetimer = add_timer (gettick() + skill_get_time(skill_id,skill_lv), mob_timer_delete, md->bl.id, 0); - mob_spawn (md); //Now it is ready for spawning. - } - } - break; - - // Slim Pitcher [Celest] - case CR_SLIMPITCHER: - if (sd) { - int i = skill_lv%11 - 1; - int j = pc_search_inventory(sd,skill_db[skill_id].itemid[i]); - if( j < 0 || skill_db[skill_id].itemid[i] <= 0 || sd->inventory_data[j] == NULL || sd->status.inventory[j].amount < skill_db[skill_id].amount[i] ) - { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 1; - } - potion_flag = 1; - potion_hp = 0; - potion_sp = 0; - run_script(sd->inventory_data[j]->script,0,sd->bl.id,0); - potion_flag = 0; - //Apply skill bonuses - i = pc_checkskill(sd,CR_SLIMPITCHER)*10 - + pc_checkskill(sd,AM_POTIONPITCHER)*10 - + pc_checkskill(sd,AM_LEARNINGPOTION)*5 - + pc_skillheal_bonus(sd, skill_id); - - potion_hp = potion_hp * (100+i)/100; - potion_sp = potion_sp * (100+i)/100; - - if(potion_hp > 0 || potion_sp > 0) { - i = skill_get_splash(skill_id, skill_lv); - map_foreachinarea(skill_area_sub, - src->m,x-i,y-i,x+i,y+i,BL_CHAR, - src,skill_id,skill_lv,tick,flag|BCT_PARTY|BCT_GUILD|1, - skill_castend_nodamage_id); - } - } else { - int i = skill_lv%11 - 1; - struct item_data *item; - i = skill_db[skill_id].itemid[i]; - item = itemdb_search(i); - potion_flag = 1; - potion_hp = 0; - potion_sp = 0; - run_script(item->script,0,src->id,0); - potion_flag = 0; - i = skill_get_max(CR_SLIMPITCHER)*10; - - potion_hp = potion_hp * (100+i)/100; - potion_sp = potion_sp * (100+i)/100; - - if(potion_hp > 0 || potion_sp > 0) { - i = skill_get_splash(skill_id, skill_lv); - map_foreachinarea(skill_area_sub, - src->m,x-i,y-i,x+i,y+i,BL_CHAR, - src,skill_id,skill_lv,tick,flag|BCT_PARTY|BCT_GUILD|1, - skill_castend_nodamage_id); - } - } - break; - - case HW_GANBANTEIN: - if (rnd()%100 < 80) { - int dummy = 1; - clif_skill_poseffect(src,skill_id,skill_lv,x,y,tick); - i = skill_get_splash(skill_id, skill_lv); - map_foreachinarea(skill_cell_overlap, src->m, x-i, y-i, x+i, y+i, BL_SKILL, HW_GANBANTEIN, &dummy, src); - } else { - if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 1; - } - break; - - case HW_GRAVITATION: - if ((sg = skill_unitsetting(src,skill_id,skill_lv,x,y,0))) - sc_start4(src,type,100,skill_lv,0,BCT_SELF,sg->group_id,skill_get_time(skill_id,skill_lv)); - flag|=1; - break; - - // Plant Cultivation [Celest] - case CR_CULTIVATION: - if (sd) { - if( map_count_oncell(src->m,x,y,BL_CHAR) > 0 ) - { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 1; - } - clif_skill_poseffect(src,skill_id,skill_lv,x,y,tick); - if (rnd()%100 < 50) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - } else { - TBL_MOB* md = mob_once_spawn_sub(src, src->m, x, y, "--ja--",(skill_lv < 2 ? 1084+rnd()%2 : 1078+rnd()%6),"", SZ_SMALL, AI_NONE); - int i; - if (!md) break; - if ((i = skill_get_time(skill_id, skill_lv)) > 0) - { - if( md->deletetimer != INVALID_TIMER ) - delete_timer(md->deletetimer, mob_timer_delete); - md->deletetimer = add_timer (tick + i, mob_timer_delete, md->bl.id, 0); - } - mob_spawn (md); - } - } - break; - - case SG_SUN_WARM: - case SG_MOON_WARM: - case SG_STAR_WARM: - skill_clear_unitgroup(src); - if ((sg = skill_unitsetting(src,skill_id,skill_lv,src->x,src->y,0))) - sc_start4(src,type,100,skill_lv,0,0,sg->group_id,skill_get_time(skill_id,skill_lv)); - flag|=1; - break; - - case PA_GOSPEL: - if (sce && sce->val4 == BCT_SELF) - { - status_change_end(src, SC_GOSPEL, INVALID_TIMER); - return 0; - } - else - { - sg = skill_unitsetting(src,skill_id,skill_lv,src->x,src->y,0); - if (!sg) break; - if (sce) - status_change_end(src, type, INVALID_TIMER); //Was under someone else's Gospel. [Skotlex] - sc_start4(src,type,100,skill_lv,0,sg->group_id,BCT_SELF,skill_get_time(skill_id,skill_lv)); - clif_skill_poseffect(src, skill_id, skill_lv, 0, 0, tick); // PA_GOSPEL music packet - } - break; - case NJ_TATAMIGAESHI: - if (skill_unitsetting(src,skill_id,skill_lv,src->x,src->y,0)) - sc_start(src,type,100,skill_lv,skill_get_time2(skill_id,skill_lv)); - break; - - case AM_RESURRECTHOMUN: //[orn] - if (sd) - { - if (!merc_resurrect_homunculus(sd, 20*skill_lv, x, y)) - { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - break; - } - } - break; - - case RK_WINDCUTTER: - clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - case NC_COLDSLOWER: - case NC_ARMSCANNON: - case RK_DRAGONBREATH: - case WM_LULLABY_DEEPSLEEP: - i = skill_get_splash(skill_id,skill_lv); - map_foreachinarea(skill_area_sub,src->m,x-i,y-i,x+i,y+i,splash_target(src), - src,skill_id,skill_lv,tick,flag|(skill_id==WM_LULLABY_DEEPSLEEP?BCT_ALL:BCT_ENEMY)|1,skill_castend_damage_id); - break; - /** - * Guilotine Cross - **/ - case GC_POISONSMOKE: - if( !(sc && sc->data[SC_POISONINGWEAPON]) ) { - if( sd ) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_GC_POISONINGWEAPON,0); - return 0; - } - clif_skill_damage(src,src,tick,status_get_amotion(src),0,-30000,1,skill_id,skill_lv,6); - skill_unitsetting(src, skill_id, skill_lv, x, y, flag); - //status_change_end(src,SC_POISONINGWEAPON,INVALID_TIMER); // 08/31/2011 - When using poison smoke, you no longer lose the poisoning weapon effect. - break; - /** - * Arch Bishop - **/ - case AB_EPICLESIS: - if( (sg = skill_unitsetting(src, skill_id, skill_lv, x, y, 0)) ) { - i = sg->unit->range; - map_foreachinarea(skill_area_sub, src->m, x - i, y - i, x + i, y + i, BL_CHAR, src, ALL_RESURRECTION, 1, tick, flag|BCT_NOENEMY|1,skill_castend_nodamage_id); - } - break; - /** - * Warlock - **/ - case WL_COMET: - if( sc ) { - sc->comet_x = x; - sc->comet_y = y; - } - i = skill_get_splash(skill_id,skill_lv); - map_foreachinarea(skill_area_sub,src->m,x-i,y-i,x+i,y+i,splash_target(src),src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); - break; - - case WL_EARTHSTRAIN: - { - int i, wave = skill_lv + 4, dir = map_calc_dir(src,x,y); - int sx = x = src->x, sy = y = src->y; // Store first caster's location to avoid glitch on unit setting - - for( i = 1; i <= wave; i++ ) - { - switch( dir ){ - case 0: case 1: case 7: sy = y + i; break; - case 3: case 4: case 5: sy = y - i; break; - case 2: sx = x - i; break; - case 6: sx = x + i; break; - } - skill_addtimerskill(src,gettick() + (150 * i),0,sx,sy,skill_id,skill_lv,dir,flag&2); - } - } - break; - /** - * Ranger - **/ - case RA_DETONATOR: - i = skill_get_splash(skill_id, skill_lv); - map_foreachinarea(skill_detonator, src->m, x-i, y-i, x+i, y+i, BL_SKILL, src); - clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); - break; - /** - * Mechanic - **/ - case NC_NEUTRALBARRIER: - case NC_STEALTHFIELD: - skill_clear_unitgroup(src); // To remove previous skills - cannot used combined - if( (sg = skill_unitsetting(src,skill_id,skill_lv,src->x,src->y,0)) != NULL ) { - sc_start2(src,skill_id == NC_NEUTRALBARRIER ? SC_NEUTRALBARRIER_MASTER : SC_STEALTHFIELD_MASTER,100,skill_lv,sg->group_id,skill_get_time(skill_id,skill_lv)); - if( sd ) pc_overheat(sd,1); - } - break; - - case NC_SILVERSNIPER: - { - int class_ = 2042; - struct mob_data *md; - - md = mob_once_spawn_sub(src, src->m, x, y, status_get_name(src), class_, "", SZ_SMALL, AI_NONE); - if( md ) - { - md->master_id = src->id; - md->special_state.ai = AI_FLORA; - if( md->deletetimer != INVALID_TIMER ) - delete_timer(md->deletetimer, mob_timer_delete); - md->deletetimer = add_timer (gettick() + skill_get_time(skill_id, skill_lv), mob_timer_delete, md->bl.id, 0); - mob_spawn( md ); - } - } - break; - - case NC_MAGICDECOY: - if( sd ) clif_magicdecoy_list(sd,skill_lv,x,y); - break; - - case SC_FEINTBOMB: - clif_skill_nodamage(src,src,skill_id,skill_lv,1); - skill_unitsetting(src,skill_id,skill_lv,x,y,0); // Set bomb on current Position - if( skill_blown(src,src,6,unit_getdir(src),0) ) - skill_castend_nodamage_id(src,src,TF_HIDING,1,tick,0); - break; - - case LG_OVERBRAND: - { - int width;//according to data from irowiki it actually is a square - for( width = 0; width < 7; width++ ) - for( i = 0; i < 7; i++ ) - map_foreachincell(skill_area_sub, src->m, x-2+i, y-2+width, splash_target(src), src, LG_OVERBRAND_BRANDISH, skill_lv, tick, flag|BCT_ENEMY,skill_castend_damage_id); - for( width = 0; width < 7; width++ ) - for( i = 0; i < 7; i++ ) - map_foreachincell(skill_area_sub, src->m, x-2+i, y-2+width, splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY,skill_castend_damage_id); - } - break; - - case LG_BANDING: - if( sc && sc->data[SC_BANDING] ) - status_change_end(src,SC_BANDING,INVALID_TIMER); - else if( (sg = skill_unitsetting(src,skill_id,skill_lv,src->x,src->y,0)) != NULL ) { - sc_start4(src,SC_BANDING,100,skill_lv,0,0,sg->group_id,skill_get_time(skill_id,skill_lv)); - if( sd ) pc_banding(sd,skill_lv); - } - clif_skill_nodamage(src,src,skill_id,skill_lv,1); - break; - - case LG_RAYOFGENESIS: - if( status_charge(src,status_get_max_hp(src)*3*skill_lv / 100,0) ) { - i = skill_get_splash(skill_id,skill_lv); - map_foreachinarea(skill_area_sub,src->m,x-i,y-i,x+i,y+i,splash_target(src), - src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); - } else if( sd ) - clif_skill_fail(sd,skill_id,USESKILL_FAIL,0); - break; - - case WM_DOMINION_IMPULSE: - i = skill_get_splash(skill_id, skill_lv); - map_foreachinarea( skill_ative_reverberation, - src->m, x-i, y-i, x+i,y+i,BL_SKILL); - break; - - case WM_GREAT_ECHO: - flag|=1; // Should counsume 1 item per skill usage. - map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id,skill_lv),splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY, skill_castend_damage_id); - break; - case GN_CRAZYWEED: { - int area = skill_get_splash(GN_CRAZYWEED_ATK, skill_lv); - short x1 = 0, y1 = 0; - - for( i = 0; i < 3 + (skill_lv/2); i++ ) { - x1 = x - area + rnd()%(area * 2 + 1); - y1 = y - area + rnd()%(area * 2 + 1); - skill_addtimerskill(src,tick+i*150,0,x1,y1,GN_CRAZYWEED_ATK,skill_lv,-1,0); - } - } - break; - case GN_FIRE_EXPANSION: { - int i; - struct unit_data *ud = unit_bl2ud(src); - - if( !ud ) break; - - for( i = 0; i < MAX_SKILLUNITGROUP && ud->skillunit[i]; i ++ ) { - if( ud->skillunit[i]->skill_id == GN_DEMONIC_FIRE && - distance_xy(x, y, ud->skillunit[i]->unit->bl.x, ud->skillunit[i]->unit->bl.y) < 4 ) { - switch( skill_lv ) { - case 3: - ud->skillunit[i]->unit_id = UNT_FIRE_EXPANSION_SMOKE_POWDER; - clif_changetraplook(&ud->skillunit[i]->unit->bl, UNT_FIRE_EXPANSION_SMOKE_POWDER); - break; - case 4: - ud->skillunit[i]->unit_id = UNT_FIRE_EXPANSION_TEAR_GAS; - clif_changetraplook(&ud->skillunit[i]->unit->bl, UNT_FIRE_EXPANSION_TEAR_GAS); - break; - case 5: - map_foreachinarea(skill_area_sub, src->m, - ud->skillunit[i]->unit->bl.x - 3, ud->skillunit[i]->unit->bl.y - 3, - ud->skillunit[i]->unit->bl.x + 3, ud->skillunit[i]->unit->bl.y + 3, BL_CHAR, - src, CR_ACIDDEMONSTRATION, sd ? pc_checkskill(sd, CR_ACIDDEMONSTRATION) : skill_lv, tick, flag|BCT_ENEMY|1|SD_LEVEL, skill_castend_damage_id); - skill_delunit(ud->skillunit[i]->unit); - break; - default: - ud->skillunit[i]->unit->val2 = skill_lv; - ud->skillunit[i]->unit->group->val2 = skill_lv; - break; - } - } - } - } - break; - - case SO_FIREWALK: - case SO_ELECTRICWALK: - if( sc && sc->data[type] ) - status_change_end(src,type,INVALID_TIMER); - clif_skill_nodamage(src, src ,skill_id, skill_lv, - sc_start2(src, type, 100, skill_id, skill_lv, skill_get_time(skill_id, skill_lv))); - break; - - case SC_BLOODYLUST: //set in another group so instance will move if recasted - flag |= 33; - skill_unitsetting(src, skill_id, skill_lv, x, y, 0); - break; - - case KO_MAKIBISHI: - for( i = 0; i < (skill_lv+2); i++ ) { - x = src->x - 1 + rnd()%3; - y = src->y - 1 + rnd()%3; - skill_unitsetting(src,skill_id,skill_lv,x,y,0); - } - break; - - default: - ShowWarning("skill_castend_pos2: Unknown skill used:%d\n",skill_id); - return 1; - } - - if( sc && sc->data[SC_CURSEDCIRCLE_ATKER] ) //Should only remove after the skill has been casted. - status_change_end(src,SC_CURSEDCIRCLE_ATKER,INVALID_TIMER); - - if( sd ) - {// ensure that the skill last-cast tick is recorded - sd->canskill_tick = gettick(); - - if( sd->state.arrow_atk && !(flag&1) ) - {// consume arrow if this is a ground skill - battle_consume_ammo(sd, skill_id, skill_lv); - } - - // perform skill requirement consumption - skill_consume_requirement(sd,skill_id,skill_lv,2); - } - - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -int skill_castend_map (struct map_session_data *sd, uint16 skill_id, const char *map) -{ - nullpo_ret(sd); - -//Simplify skill_failed code. -#define skill_failed(sd) { sd->menuskill_id = sd->menuskill_val = 0; } - if(skill_id != sd->menuskill_id) - return 0; - - if( sd->bl.prev == NULL || pc_isdead(sd) ) { - skill_failed(sd); - return 0; - } - - if( ( sd->sc.opt1 && sd->sc.opt1 != OPT1_BURNING ) || sd->sc.option&OPTION_HIDE ) { - skill_failed(sd); - return 0; - } - if(sd->sc.count && ( - sd->sc.data[SC_SILENCE] || - sd->sc.data[SC_ROKISWEIL] || - sd->sc.data[SC_AUTOCOUNTER] || - sd->sc.data[SC_STEELBODY] || - (sd->sc.data[SC_DANCING] && skill_id < RK_ENCHANTBLADE && !pc_checkskill(sd, WM_LESSON)) || - sd->sc.data[SC_BERSERK] || sd->sc.data[SC__BLOODYLUST] || - sd->sc.data[SC_BASILICA] || - sd->sc.data[SC_MARIONETTE] || - sd->sc.data[SC_WHITEIMPRISON] || - (sd->sc.data[SC_STASIS] && skill_block_check(&sd->bl, SC_STASIS, skill_id)) || - (sd->sc.data[SC_KAGEHUMI] && skill_block_check(&sd->bl, SC_KAGEHUMI, skill_id)) || - sd->sc.data[SC_OBLIVIONCURSE] || - sd->sc.data[SC__MANHOLE] || - (sd->sc.data[SC_ASH] && rnd()%2) //50% fail chance under ASH - )) { - skill_failed(sd); - return 0; - } - - pc_stop_attack(sd); - pc_stop_walking(sd,0); - - if(battle_config.skill_log && battle_config.skill_log&BL_PC) - ShowInfo("PC %d skill castend skill =%d map=%s\n",sd->bl.id,skill_id,map); - - if(strcmp(map,"cancel")==0) { - skill_failed(sd); - return 0; - } - - switch(skill_id) - { - case AL_TELEPORT: - if(strcmp(map,"Random")==0) - pc_randomwarp(sd,CLR_TELEPORT); - else if (sd->menuskill_val > 1) //Need lv2 to be able to warp here. - pc_setpos(sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,CLR_TELEPORT); - break; - - case AL_WARP: - { - const struct point *p[4]; - struct skill_unit_group *group; - int i, lv, wx, wy; - int maxcount=0; - int x,y; - unsigned short mapindex; - - mapindex = mapindex_name2id((char*)map); - if(!mapindex) { //Given map not found? - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - skill_failed(sd); - return 0; - } - p[0] = &sd->status.save_point; - p[1] = &sd->status.memo_point[0]; - p[2] = &sd->status.memo_point[1]; - p[3] = &sd->status.memo_point[2]; - - if((maxcount = skill_get_maxcount(skill_id, sd->menuskill_val)) > 0) { - for(i=0;i<MAX_SKILLUNITGROUP && sd->ud.skillunit[i] && maxcount;i++) { - if(sd->ud.skillunit[i]->skill_id == skill_id) - maxcount--; - } - if(!maxcount) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - skill_failed(sd); - return 0; - } - } - - lv = sd->skillitem==skill_id?sd->skillitemlv:pc_checkskill(sd,skill_id); - wx = sd->menuskill_val>>16; - wy = sd->menuskill_val&0xffff; - - if( lv <= 0 ) return 0; - if( lv > 4 ) lv = 4; // crash prevention - - // check if the chosen map exists in the memo list - ARR_FIND( 0, lv, i, mapindex == p[i]->map ); - if( i < lv ) { - x=p[i]->x; - y=p[i]->y; - } else { - skill_failed(sd); - return 0; - } - - if(!skill_check_condition_castend(sd, sd->menuskill_id, lv)) - { // This checks versus skill_id/skill_lv... - skill_failed(sd); - return 0; - } - - skill_consume_requirement(sd,sd->menuskill_id,lv,2); - sd->skillitem = sd->skillitemlv = 0; // Clear data that's skipped in 'skill_castend_pos' [Inkfish] - - if((group=skill_unitsetting(&sd->bl,skill_id,lv,wx,wy,0))==NULL) { - skill_failed(sd); - return 0; - } - - group->val1 = (group->val1<<16)|(short)0; - // record the destination coordinates - group->val2 = (x<<16)|y; - group->val3 = mapindex; - } - break; - } - - sd->menuskill_id = sd->menuskill_val = 0; - return 0; -#undef skill_failed -} - -/// transforms 'target' skill unit into dissonance (if conditions are met) -static int skill_dance_overlap_sub(struct block_list* bl, va_list ap) -{ - struct skill_unit* target = (struct skill_unit*)bl; - struct skill_unit* src = va_arg(ap, struct skill_unit*); - int flag = va_arg(ap, int); - - if (src == target) - return 0; - if (!target->group || !(target->group->state.song_dance&0x1)) - return 0; - if (!(target->val2 & src->val2 & ~UF_ENSEMBLE)) //They don't match (song + dance) is valid. - return 0; - - if (flag) //Set dissonance - target->val2 |= UF_ENSEMBLE; //Add ensemble to signal this unit is overlapping. - else //Remove dissonance - target->val2 &= ~UF_ENSEMBLE; - - clif_skill_setunit(target); //Update look of affected cell. - - return 1; -} - -//Does the song/dance overlapping -> dissonance check. [Skotlex] -//When flag is 0, this unit is about to be removed, cancel the dissonance effect -//When 1, this unit has been positioned, so start the cancel effect. -int skill_dance_overlap(struct skill_unit* unit, int flag) -{ - if (!unit || !unit->group || !(unit->group->state.song_dance&0x1)) - return 0; - if (!flag && !(unit->val2&UF_ENSEMBLE)) - return 0; //Nothing to remove, this unit is not overlapped. - - if (unit->val1 != unit->group->skill_id) - { //Reset state - unit->val1 = unit->group->skill_id; - unit->val2 &= ~UF_ENSEMBLE; - } - - return map_foreachincell(skill_dance_overlap_sub, unit->bl.m,unit->bl.x,unit->bl.y,BL_SKILL, unit,flag); -} - -/*========================================== - * Converts this group information so that it is handled as a Dissonance or Ugly Dance cell. - * Flag: 0 - Convert, 1 - Revert. - *------------------------------------------*/ -static bool skill_dance_switch(struct skill_unit* unit, int flag) -{ - static int prevflag = 1; // by default the backup is empty - static struct skill_unit_group backup; - struct skill_unit_group* group = unit->group; - - // val2&UF_ENSEMBLE is a hack to indicate dissonance - if ( !(group->state.song_dance&0x1 && unit->val2&UF_ENSEMBLE) ) - return false; - - if( flag == prevflag ) - {// protection against attempts to read an empty backup / write to a full backup - ShowError("skill_dance_switch: Attempted to %s (skill_id=%d, skill_lv=%d, src_id=%d).\n", - flag ? "read an empty backup" : "write to a full backup", - group->skill_id, group->skill_lv, group->src_id); - return false; - } - prevflag = flag; - - if( !flag ) - { //Transform - uint16 skill_id = unit->val2&UF_SONG ? BA_DISSONANCE : DC_UGLYDANCE; - - // backup - backup.skill_id = group->skill_id; - backup.skill_lv = group->skill_lv; - backup.unit_id = group->unit_id; - backup.target_flag = group->target_flag; - backup.bl_flag = group->bl_flag; - backup.interval = group->interval; - - // replace - group->skill_id = skill_id; - group->skill_lv = 1; - group->unit_id = skill_get_unit_id(skill_id,0); - group->target_flag = skill_get_unit_target(skill_id); - group->bl_flag = skill_get_unit_bl_target(skill_id); - group->interval = skill_get_unit_interval(skill_id); - } - else - { //Restore - group->skill_id = backup.skill_id; - group->skill_lv = backup.skill_lv; - group->unit_id = backup.unit_id; - group->target_flag = backup.target_flag; - group->bl_flag = backup.bl_flag; - group->interval = backup.interval; - } - - return true; -} -/** - * Upon Ice Wall cast it checks all nearby mobs to find any who may be blocked by the IW - **/ -static int skill_icewall_block(struct block_list *bl,va_list ap) { - struct block_list *target = NULL; - struct mob_data *md = ((TBL_MOB*)bl); - - nullpo_ret(bl); - nullpo_ret(md); - if( !md->target_id || ( target = map_id2bl(md->target_id) ) == NULL ) - return 0; - - if( path_search_long(NULL,bl->m,bl->x,bl->y,target->x,target->y,CELL_CHKICEWALL) ) - return 0; - - if( !check_distance_bl(bl, target, status_get_range(bl) ) ) { - mob_unlocktarget(md,gettick()); - mob_stop_walking(md,1); - } - - return 0; -} -/*========================================== - * Initializes and sets a ground skill. - * flag&1 is used to determine when the skill 'morphs' (Warp portal becomes active, or Fire Pillar becomes active) - *------------------------------------------*/ -struct skill_unit_group* skill_unitsetting (struct block_list *src, uint16 skill_id, uint16 skill_lv, int16 x, int16 y, int flag) -{ - struct skill_unit_group *group; - int i,limit,val1=0,val2=0,val3=0; - int target,interval,range,unit_flag,req_item=0; - struct s_skill_unit_layout *layout; - struct map_session_data *sd; - struct status_data *status; - struct status_change *sc; - int active_flag=1; - int subunt=0; - - nullpo_retr(NULL, src); - - limit = skill_get_time(skill_id,skill_lv); - range = skill_get_unit_range(skill_id,skill_lv); - interval = skill_get_unit_interval(skill_id); - target = skill_get_unit_target(skill_id); - unit_flag = skill_get_unit_flag(skill_id); - layout = skill_get_unit_layout(skill_id,skill_lv,src,x,y); - - sd = BL_CAST(BL_PC, src); - status = status_get_status_data(src); - sc = status_get_sc(src); // for traps, firewall and fogwall - celest - - switch( skill_id ) { - case MH_STEINWAND: - val2 = 4 + skill_lv; //nb of attack blocked - break; - case MG_SAFETYWALL: - #ifdef RENEWAL - /** - * According to data provided in RE, SW life is equal to 3 times caster's health - **/ - val2 = status_get_max_hp(src) * 3; - #else - val2 = skill_lv+1; - #endif - break; - case MG_FIREWALL: - if(sc && sc->data[SC_VIOLENTGALE]) - limit = limit*3/2; - val2=4+skill_lv; - break; - - case AL_WARP: - val1=skill_lv+6; - if(!(flag&1)) - limit=2000; - else // previous implementation (not used anymore) - { //Warp Portal morphing to active mode, extract relevant data from src. [Skotlex] - if( src->type != BL_SKILL ) return NULL; - group = ((TBL_SKILL*)src)->group; - src = map_id2bl(group->src_id); - if( !src ) return NULL; - val2 = group->val2; //Copy the (x,y) position you warp to - val3 = group->val3; //as well as the mapindex to warp to. - } - break; - case HP_BASILICA: - val1 = src->id; // Store caster id. - break; - - case PR_SANCTUARY: - case NPC_EVILLAND: - val1=(skill_lv+3)*2; - break; - - case WZ_FIREPILLAR: - if((flag&1)!=0) - limit=1000; - val1=skill_lv+2; - break; - case WZ_QUAGMIRE: //The target changes to "all" if used in a gvg map. [Skotlex] - case AM_DEMONSTRATION: - case GN_HELLS_PLANT: - if (map_flag_vs(src->m) && battle_config.vs_traps_bctall - && (src->type&battle_config.vs_traps_bctall)) - target = BCT_ALL; - break; - case HT_SHOCKWAVE: - val1=skill_lv*15+10; - case HT_SANDMAN: - case MA_SANDMAN: - case HT_CLAYMORETRAP: - case HT_SKIDTRAP: - case MA_SKIDTRAP: - case HT_LANDMINE: - case MA_LANDMINE: - case HT_ANKLESNARE: - case HT_FLASHER: - case HT_FREEZINGTRAP: - case MA_FREEZINGTRAP: - case HT_BLASTMINE: - /** - * Ranger - **/ - case RA_ELECTRICSHOCKER: - case RA_CLUSTERBOMB: - case RA_MAGENTATRAP: - case RA_COBALTTRAP: - case RA_MAIZETRAP: - case RA_VERDURETRAP: - case RA_FIRINGTRAP: - case RA_ICEBOUNDTRAP: - { - struct skill_condition req = skill_get_requirement(sd,skill_id,skill_lv); - ARR_FIND(0, MAX_SKILL_ITEM_REQUIRE, i, req.itemid[i] && (req.itemid[i] == ITEMID_TRAP || req.itemid[i] == ITEMID_TRAP_ALLOY)); - if( req.itemid[i] ) - req_item = req.itemid[i]; - if( map_flag_gvg(src->m) || map[src->m].flag.battleground ) - limit *= 4; // longer trap times in WOE [celest] - if( battle_config.vs_traps_bctall && map_flag_vs(src->m) && (src->type&battle_config.vs_traps_bctall) ) - target = BCT_ALL; - } - break; - - case SA_LANDPROTECTOR: - case SA_VOLCANO: - case SA_DELUGE: - case SA_VIOLENTGALE: - { - struct skill_unit_group *old_sg; - if ((old_sg = skill_locate_element_field(src)) != NULL) - { //HelloKitty confirmed that these are interchangeable, - //so you can change element and not consume gemstones. - if (( - old_sg->skill_id == SA_VOLCANO || - old_sg->skill_id == SA_DELUGE || - old_sg->skill_id == SA_VIOLENTGALE - ) && old_sg->limit > 0) - { //Use the previous limit (minus the elapsed time) [Skotlex] - limit = old_sg->limit - DIFF_TICK(gettick(), old_sg->tick); - if (limit < 0) //This can happen... - limit = skill_get_time(skill_id,skill_lv); - } - skill_clear_group(src,1); - } - break; - } - - case BA_DISSONANCE: - case DC_UGLYDANCE: - val1 = 10; //FIXME: This value is not used anywhere, what is it for? [Skotlex] - break; - case BA_WHISTLE: - val1 = skill_lv +status->agi/10; // Flee increase - val2 = ((skill_lv+1)/2)+status->luk/10; // Perfect dodge increase - if(sd){ - val1 += pc_checkskill(sd,BA_MUSICALLESSON); - val2 += pc_checkskill(sd,BA_MUSICALLESSON); - } - break; - case DC_HUMMING: - val1 = 2*skill_lv+status->dex/10; // Hit increase - #ifdef RENEWAL - val1 *= 2; - #endif - if(sd) - val1 += pc_checkskill(sd,DC_DANCINGLESSON); - break; - case BA_POEMBRAGI: - val1 = 3*skill_lv+status->dex/10; // Casting time reduction - //For some reason at level 10 the base delay reduction is 50%. - val2 = (skill_lv<10?3*skill_lv:50)+status->int_/5; // After-cast delay reduction - if(sd){ - val1 += 2*pc_checkskill(sd,BA_MUSICALLESSON); - val2 += 2*pc_checkskill(sd,BA_MUSICALLESSON); - } - break; - case DC_DONTFORGETME: - val1 = status->dex/10 + 3*skill_lv + 5; // ASPD decrease - val2 = status->agi/10 + 3*skill_lv + 5; // Movement speed adjustment. - if(sd){ - val1 += pc_checkskill(sd,DC_DANCINGLESSON); - val2 += pc_checkskill(sd,DC_DANCINGLESSON); - } - break; - case BA_APPLEIDUN: - val1 = 5+2*skill_lv+status->vit/10; // MaxHP percent increase - if(sd) - val1 += pc_checkskill(sd,BA_MUSICALLESSON); - break; - case DC_SERVICEFORYOU: - val1 = 15+skill_lv+(status->int_/10); // MaxSP percent increase TO-DO: this INT bonus value is guessed - val2 = 20+3*skill_lv+(status->int_/10); // SP cost reduction - if(sd){ - val1 += pc_checkskill(sd,DC_DANCINGLESSON); //TO-DO This bonus value is guessed - val2 += pc_checkskill(sd,DC_DANCINGLESSON); //TO-DO Should be half this value - } - break; - case BA_ASSASSINCROSS: - val1 = 100+(10*skill_lv)+(status->agi/10); // ASPD increase - if(sd) - val1 += 5*pc_checkskill(sd,BA_MUSICALLESSON); - break; - case DC_FORTUNEKISS: - val1 = 10+skill_lv+(status->luk/10); // Critical increase - if(sd) - val1 += pc_checkskill(sd,DC_DANCINGLESSON); - val1*=10; //Because every 10 crit is an actual cri point. - break; - case BD_DRUMBATTLEFIELD: - #ifdef RENEWAL - val1 = (skill_lv+5)*25; //Watk increase - val2 = skill_lv*10; //Def increase - #else - val1 = (skill_lv+1)*25; //Watk increase - val2 = (skill_lv+1)*2; //Def increase - #endif - break; - case BD_RINGNIBELUNGEN: - val1 = (skill_lv+2)*25; //Watk increase - break; - case BD_RICHMANKIM: - val1 = 25 + 11*skill_lv; //Exp increase bonus. - break; - case BD_SIEGFRIED: - val1 = 55 + skill_lv*5; //Elemental Resistance - val2 = skill_lv*10; //Status ailment resistance - break; - case WE_CALLPARTNER: - if (sd) val1 = sd->status.partner_id; - break; - case WE_CALLPARENT: - if (sd) { - val1 = sd->status.father; - val2 = sd->status.mother; - } - break; - case WE_CALLBABY: - if (sd) val1 = sd->status.child; - break; - case NJ_KAENSIN: - skill_clear_group(src, 1); //Delete previous Kaensins/Suitons - val2 = (skill_lv+1)/2 + 4; - break; - case NJ_SUITON: - skill_clear_group(src, 1); - break; - - case GS_GROUNDDRIFT: - { - int element[5]={ELE_WIND,ELE_DARK,ELE_POISON,ELE_WATER,ELE_FIRE}; - - val1 = status->rhw.ele; - if (!val1) - val1=element[rnd()%5]; - - switch (val1) - { - case ELE_FIRE: - subunt++; - case ELE_WATER: - subunt++; - case ELE_POISON: - subunt++; - case ELE_DARK: - subunt++; - case ELE_WIND: - break; - default: - subunt=rnd()%5; - break; - } - - break; - } - case GC_POISONSMOKE: - if( !(sc && sc->data[SC_POISONINGWEAPON]) ) - return NULL; - val2 = sc->data[SC_POISONINGWEAPON]->val2; // Type of Poison - val3 = sc->data[SC_POISONINGWEAPON]->val1; - limit = 4000 + 2000 * skill_lv; - break; - case GD_LEADERSHIP: - case GD_GLORYWOUNDS: - case GD_SOULCOLD: - case GD_HAWKEYES: - limit = 1000000;//it doesn't matter - break; - case LG_BANDING: - limit = -1; - break; - case WM_REVERBERATION: - interval = limit; - val2 = 1; - case WM_POEMOFNETHERWORLD: // Can't be placed on top of Land Protector. - if( map_getcell(src->m, x, y, CELL_CHKLANDPROTECTOR) ) - return NULL; - break; - case SO_CLOUD_KILL: - skill_clear_group(src, 4); - break; - case SO_WARMER: - skill_clear_group(src, 8); - break; - case SO_VACUUM_EXTREME: - range++; - - break; - case SC_BLOODYLUST: - skill_clear_group(src, 32); - break; - case GN_WALLOFTHORN: - if( flag&1 ) - limit = 3000; - val3 = (x<<16)|y; - break; - case KO_ZENKAI: - if( sd ){ - ARR_FIND(1, 6, i, sd->talisman[i] > 0); - if( i < 5 ){ - val1 = sd->talisman[i]; // no. of aura - val2 = i; // aura type - limit += val1 * 1000; - subunt = i - 1; - pc_del_talisman(sd, sd->talisman[i], i); - } - } - break; - } - - nullpo_retr(NULL, group=skill_initunitgroup(src,layout->count,skill_id,skill_lv,skill_get_unit_id(skill_id,flag&1)+subunt, limit, interval)); - group->val1=val1; - group->val2=val2; - group->val3=val3; - group->target_flag=target; - group->bl_flag= skill_get_unit_bl_target(skill_id); - group->state.ammo_consume = (sd && sd->state.arrow_atk && skill_id != GS_GROUNDDRIFT); //Store if this skill needs to consume ammo. - group->state.song_dance = (unit_flag&(UF_DANCE|UF_SONG)?1:0)|(unit_flag&UF_ENSEMBLE?2:0); //Signals if this is a song/dance/duet - group->state.guildaura = ( skill_id >= GD_LEADERSHIP && skill_id <= GD_HAWKEYES )?1:0; - group->item_id = req_item; - //if tick is greater than current, do not invoke onplace function just yet. [Skotlex] - if (DIFF_TICK(group->tick, gettick()) > SKILLUNITTIMER_INTERVAL) - active_flag = 0; - - if(skill_id==HT_TALKIEBOX || skill_id==RG_GRAFFITI){ - group->valstr=(char *) aMalloc(MESSAGE_SIZE*sizeof(char)); - if (sd) - safestrncpy(group->valstr, sd->message, MESSAGE_SIZE); - else //Eh... we have to write something here... even though mobs shouldn't use this. [Skotlex] - safestrncpy(group->valstr, "Boo!", MESSAGE_SIZE); - } - - if (group->state.song_dance) { - if(sd){ - sd->skill_id_dance = skill_id; - sd->skill_lv_dance = skill_lv; - } - if ( - sc_start4(src, SC_DANCING, 100, skill_id, group->group_id, skill_lv, - (group->state.song_dance&2?BCT_SELF:0), limit+1000) && - sd && group->state.song_dance&2 && skill_id != CG_HERMODE //Hermod is a encore with a warp! - ) - skill_check_pc_partner(sd, skill_id, &skill_lv, 1, 1); - } - - limit = group->limit; - for( i = 0; i < layout->count; i++ ) - { - struct skill_unit *unit; - int ux = x + layout->dx[i]; - int uy = y + layout->dy[i]; - int val1 = skill_lv; - int val2 = 0; - int alive = 1; - - if( !group->state.song_dance && !map_getcell(src->m,ux,uy,CELL_CHKREACH) ) - continue; // don't place skill units on walls (except for songs/dances/encores) - if( battle_config.skill_wall_check && skill_get_unit_flag(skill_id)&UF_PATHCHECK && !path_search_long(NULL,src->m,ux,uy,x,y,CELL_CHKWALL) ) - continue; // no path between cell and center of casting. - - switch( skill_id ) - { - case MG_FIREWALL: - case NJ_KAENSIN: - val2=group->val2; - break; - case WZ_ICEWALL: - val1 = (skill_lv <= 1) ? 500 : 200 + 200*skill_lv; - val2 = map_getcell(src->m, ux, uy, CELL_GETTYPE); - break; - case HT_LANDMINE: - case MA_LANDMINE: - case HT_ANKLESNARE: - case HT_SHOCKWAVE: - case HT_SANDMAN: - case MA_SANDMAN: - case HT_FLASHER: - case HT_FREEZINGTRAP: - case MA_FREEZINGTRAP: - case HT_TALKIEBOX: - case HT_SKIDTRAP: - case MA_SKIDTRAP: - case HT_CLAYMORETRAP: - case HT_BLASTMINE: - /** - * Ranger - **/ - case RA_ELECTRICSHOCKER: - case RA_CLUSTERBOMB: - case RA_MAGENTATRAP: - case RA_COBALTTRAP: - case RA_MAIZETRAP: - case RA_VERDURETRAP: - case RA_FIRINGTRAP: - case RA_ICEBOUNDTRAP: - val1 = 3500; - break; - case GS_DESPERADO: - val1 = abs(layout->dx[i]); - val2 = abs(layout->dy[i]); - if (val1 < 2 || val2 < 2) { //Nearby cross, linear decrease with no diagonals - if (val2 > val1) val1 = val2; - if (val1) val1--; - val1 = 36 -12*val1; - } else //Diagonal edges - val1 = 28 -4*val1 -4*val2; - if (val1 < 1) val1 = 1; - val2 = 0; - break; - case WM_REVERBERATION: - val1 = 1 + skill_lv; - break; - case GN_WALLOFTHORN: - val1 = 1000 * skill_lv; // Need official value. [LimitLine] - break; - default: - if (group->state.song_dance&0x1) - val2 = unit_flag&(UF_DANCE|UF_SONG); //Store whether this is a song/dance - break; - } - if (skill_get_unit_flag(skill_id) & UF_RANGEDSINGLEUNIT && i == (layout->count / 2)) - val2 |= UF_RANGEDSINGLEUNIT; // center. - - if( range <= 0 ) - map_foreachincell(skill_cell_overlap,src->m,ux,uy,BL_SKILL,skill_id, &alive, src); - if( !alive ) - continue; - - nullpo_retr(NULL, unit=skill_initunit(group,i,ux,uy,val1,val2)); - unit->limit=limit; - unit->range=range; - - if (skill_id == PF_FOGWALL && alive == 2) - { //Double duration of cells on top of Deluge/Suiton - unit->limit *= 2; - group->limit = unit->limit; - } - - // execute on all targets standing on this cell - if (range==0 && active_flag) - map_foreachincell(skill_unit_effect,unit->bl.m,unit->bl.x,unit->bl.y,group->bl_flag,&unit->bl,gettick(),1); - } - - if (!group->alive_count) - { //No cells? Something that was blocked completely by Land Protector? - skill_delunitgroup(group); - return NULL; - } - - //success, unit created. - switch( skill_id ) { - case WZ_ICEWALL: - map_foreachinrange(skill_icewall_block, src, AREA_SIZE, BL_MOB); - break; - case NJ_TATAMIGAESHI: //Store number of tiles. - group->val1 = group->alive_count; - break; - } - - return group; -} - -/*========================================== - * - *------------------------------------------*/ -void ext_skill_unit_onplace(struct skill_unit *src, struct block_list *bl, unsigned int tick){skill_unit_onplace(src, bl, tick);} -static 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 status_change *sc; - struct status_change_entry *sce; - enum sc_type type; - uint16 skill_id; - - nullpo_ret(src); - nullpo_ret(bl); - - if(bl->prev==NULL || !src->alive || status_isdead(bl)) - return 0; - - nullpo_ret(sg=src->group); - nullpo_ret(ss=map_id2bl(sg->src_id)); - - if( skill_get_type(sg->skill_id) == BF_MAGIC && map_getcell(bl->m, bl->x, bl->y, CELL_CHKLANDPROTECTOR) && sg->skill_id != SA_LANDPROTECTOR ) - return 0; //AoE skills are ineffective. [Skotlex] - - sc = status_get_sc(bl); - - if (sc && sc->option&OPTION_HIDE && sg->skill_id != WZ_HEAVENDRIVE && sg->skill_id != WL_EARTHSTRAIN ) - return 0; //Hidden characters are immune to AoE skills except to these. [Skotlex] - - type = status_skill2sc(sg->skill_id); - sce = (sc && type != -1)?sc->data[type]:NULL; - skill_id = sg->skill_id; //In case the group is deleted, we need to return the correct skill id, still. - switch (sg->unit_id) - { - case UNT_SPIDERWEB: - if( sc && sc->data[SC_SPIDERWEB] && sc->data[SC_SPIDERWEB]->val1 > 0 ) - { // If you are fiberlocked and can't move, it will only increase your fireweakness level. [Inkfish] - sc->data[SC_SPIDERWEB]->val2++; - break; - } - else if( sc ) - { - int sec = skill_get_time2(sg->skill_id,sg->skill_lv); - if( status_change_start(bl,type,10000,sg->skill_lv,1,sg->group_id,0,sec,8) ) - { - const struct TimerData* td = sc->data[type]?get_timer(sc->data[type]->timer):NULL; - if( td ) - sec = DIFF_TICK(td->tick, tick); - map_moveblock(bl, src->bl.x, src->bl.y, tick); - clif_fixpos(bl); - sg->val2 = bl->id; - } - else - sec = 3000; //Couldn't trap it? - sg->limit = DIFF_TICK(tick,sg->tick)+sec; - } - break; - case UNT_SAFETYWALL: - if (!sce) - sc_start4(bl,type,100,sg->skill_lv,sg->skill_id,sg->group_id,0,sg->limit); - break; - - case UNT_PNEUMA: - case UNT_CHAOSPANIC: - case UNT_MAELSTROM: - if (!sce) - sc_start4(bl,type,100,sg->skill_lv,sg->group_id,0,0,sg->limit); - break; - case UNT_BLOODYLUST: - if (sg->src_id == bl->id) - break; //Does not affect the caster. - if (!sce) { - TBL_PC *sd = BL_CAST(BL_PC, bl); //prevent fullheal exploit - if (sd && sd->bloodylust_tick && DIFF_TICK(gettick(), sd->bloodylust_tick) < skill_get_time2(SC_BLOODYLUST, 1)) - clif_skill_nodamage(&src->bl,bl,sg->skill_id,sg->skill_lv, - sc_start4(bl, type, 100, sg->skill_lv, 1, 0, 0, skill_get_time(LK_BERSERK, sg->skill_lv))); - else { - if (sd) sd->bloodylust_tick = gettick(); - clif_skill_nodamage(&src->bl,bl,sg->skill_id,sg->skill_lv, - sc_start4(bl, type, 100, sg->skill_lv, 0, 0, 0, skill_get_time(LK_BERSERK, sg->skill_lv))); - } - } - break; - - case UNT_WARP_WAITING: { - int working = sg->val1&0xffff; - - if(bl->type==BL_PC && !working){ - struct map_session_data *sd = (struct map_session_data *)bl; - if((!sd->chatID || battle_config.chat_warpportal) - && sd->ud.to_x == src->bl.x && sd->ud.to_y == src->bl.y) - { - int x = sg->val2>>16; - int y = sg->val2&0xffff; - int count = sg->val1>>16; - unsigned short m = sg->val3; - - if( --count <= 0 ) - skill_delunitgroup(sg); - - if ( map_mapindex2mapid(sg->val3) == sd->bl.m && x == sd->bl.x && y == sd->bl.y ) - working = 1;/* we break it because officials break it, lovely stuff. */ - - sg->val1 = (count<<16)|working; - - pc_setpos(sd,m,x,y,CLR_TELEPORT); - } - } else if(bl->type == BL_MOB && battle_config.mob_warp&2) { - int16 m = map_mapindex2mapid(sg->val3); - if (m < 0) break; //Map not available on this map-server. - unit_warp(bl,m,sg->val2>>16,sg->val2&0xffff,CLR_TELEPORT); - } - } - break; - - case UNT_QUAGMIRE: - if( !sce && battle_check_target(&sg->unit->bl,bl,sg->target_flag) > 0 ) - sc_start4(bl,type,100,sg->skill_lv,sg->group_id,0,0,sg->limit); - break; - - case UNT_VOLCANO: - case UNT_DELUGE: - case UNT_VIOLENTGALE: - if(!sce) - sc_start(bl,type,100,sg->skill_lv,sg->limit); - break; - - case UNT_SUITON: - if(!sce) - sc_start4(bl,type,100,sg->skill_lv, - map_flag_vs(bl->m) || battle_check_target(&src->bl,bl,BCT_ENEMY)>0?1:0, //Send val3 =1 to reduce agi. - 0,0,sg->limit); - break; - - case UNT_HERMODE: - if (sg->src_id!=bl->id && battle_check_target(&src->bl,bl,BCT_PARTY|BCT_GUILD) > 0) - status_change_clear_buffs(bl,1); //Should dispell only allies. - case UNT_RICHMANKIM: - case UNT_ETERNALCHAOS: - case UNT_DRUMBATTLEFIELD: - case UNT_RINGNIBELUNGEN: - case UNT_ROKISWEIL: - case UNT_INTOABYSS: - case UNT_SIEGFRIED: - //Needed to check when a dancer/bard leaves their ensemble area. - if (sg->src_id==bl->id && !(sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_BARDDANCER)) - return skill_id; - if (!sce) - sc_start4(bl,type,100,sg->skill_lv,sg->val1,sg->val2,0,sg->limit); - break; - case UNT_WHISTLE: - case UNT_ASSASSINCROSS: - case UNT_POEMBRAGI: - case UNT_APPLEIDUN: - case UNT_HUMMING: - case UNT_DONTFORGETME: - case UNT_FORTUNEKISS: - case UNT_SERVICEFORYOU: - if (sg->src_id==bl->id && !(sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_BARDDANCER)) - return 0; - - if (!sc) return 0; - if (!sce) - sc_start4(bl,type,100,sg->skill_lv,sg->val1,sg->val2,0,sg->limit); - else if (sce->val4 == 1) { - //Readjust timers since the effect will not last long. - sce->val4 = 0; - delete_timer(sce->timer, status_change_timer); - sce->timer = add_timer(tick+sg->limit, status_change_timer, bl->id, type); - } - break; - - case UNT_FOGWALL: - if (!sce) - { - sc_start4(bl, type, 100, sg->skill_lv, sg->val1, sg->val2, sg->group_id, sg->limit); - if (battle_check_target(&src->bl,bl,BCT_ENEMY)>0) - skill_additional_effect (ss, bl, sg->skill_id, sg->skill_lv, BF_MISC, ATK_DEF, tick); - } - break; - - case UNT_GRAVITATION: - if (!sce) - sc_start4(bl,type,100,sg->skill_lv,0,BCT_ENEMY,sg->group_id,sg->limit); - break; - -// officially, icewall has no problems existing on occupied cells [ultramage] -// case UNT_ICEWALL: //Destroy the cell. [Skotlex] -// src->val1 = 0; -// if(src->limit + sg->tick > tick + 700) -// src->limit = DIFF_TICK(tick+700,sg->tick); -// break; - - case UNT_MOONLIT: - //Knockback out of area if affected char isn't in Moonlit effect - if (sc && sc->data[SC_DANCING] && (sc->data[SC_DANCING]->val1&0xFFFF) == CG_MOONLIT) - break; - if (ss == bl) //Also needed to prevent infinite loop crash. - break; - skill_blown(ss,bl,skill_get_blewcount(sg->skill_id,sg->skill_lv),unit_getdir(bl),0); - break; - - case UNT_WALLOFTHORN: - if( status_get_mode(bl)&MD_BOSS ) - break; // iRO Wiki says that this skill don't affect to Boss monsters. - if( map_flag_vs(bl->m) || bl->id == src->bl.id || battle_check_target(&src->bl,bl, BCT_ENEMY) == 1 ) - skill_attack(skill_get_type(sg->skill_id), ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); - break; - - case UNT_VOLCANIC_ASH: - if (!sce) - sc_start(bl, SC_ASH, 100, sg->skill_lv, skill_get_time(MH_VOLCANIC_ASH, sg->skill_lv)); - break; - - case UNT_GD_LEADERSHIP: - case UNT_GD_GLORYWOUNDS: - case UNT_GD_SOULCOLD: - case UNT_GD_HAWKEYES: - if ( !sce ) - sc_start4(bl,type,100,sg->skill_lv,0,0,0,1000); - break; - } - return skill_id; -} - -/*========================================== - * - *------------------------------------------*/ -int skill_unit_onplace_timer (struct skill_unit *src, struct block_list *bl, unsigned int tick) -{ - struct skill_unit_group *sg; - struct block_list *ss; - TBL_PC* tsd; - struct status_data *tstatus; - struct status_change *tsc; - struct skill_unit_group_tickset *ts; - enum sc_type type; - uint16 skill_id; - int diff=0; - - nullpo_ret(src); - nullpo_ret(bl); - - if (bl->prev==NULL || !src->alive || status_isdead(bl)) - return 0; - - nullpo_ret(sg=src->group); - nullpo_ret(ss=map_id2bl(sg->src_id)); - tsd = BL_CAST(BL_PC, bl); - tsc = status_get_sc(bl); - - if ( tsc && tsc->data[SC_HOVERING] ) - return 0; //Under hovering characters are immune to trap and ground target skills. - - tstatus = status_get_status_data(bl); - type = status_skill2sc(sg->skill_id); - skill_id = sg->skill_id; - - if (sg->interval == -1) { - switch (sg->unit_id) { - case UNT_ANKLESNARE: //These happen when a trap is splash-triggered by multiple targets on the same cell. - case UNT_FIREPILLAR_ACTIVE: - case UNT_ELECTRICSHOCKER: - case UNT_MANHOLE: - return 0; - default: - ShowError("skill_unit_onplace_timer: interval error (unit id %x)\n", sg->unit_id); - return 0; - } - } - - if ((ts = skill_unitgrouptickset_search(bl,sg,tick))) - { //Not all have it, eg: Traps don't have it even though they can be hit by Heaven's Drive [Skotlex] - diff = DIFF_TICK(tick,ts->tick); - if (diff < 0) - return 0; - ts->tick = tick+sg->interval; - - if ((skill_id==CR_GRANDCROSS || skill_id==NPC_GRANDDARKNESS) && !battle_config.gx_allhit) - ts->tick += sg->interval*(map_count_oncell(bl->m,bl->x,bl->y,BL_CHAR)-1); - } - - switch (sg->unit_id) - { - case UNT_FIREWALL: - case UNT_KAEN: - { - int count=0; - const int x = bl->x, y = bl->y; - - if( sg->skill_id == GN_WALLOFTHORN && !map_flag_vs(bl->m) ) - break; - - //Take into account these hit more times than the timer interval can handle. - do - skill_attack(BF_MAGIC,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick+count*sg->interval,0); - while(--src->val2 && x == bl->x && y == bl->y && - ++count < SKILLUNITTIMER_INTERVAL/sg->interval && !status_isdead(bl)); - - if (src->val2<=0) - skill_delunit(src); - } - break; - - case UNT_SANCTUARY: - if( battle_check_undead(tstatus->race, tstatus->def_ele) || tstatus->race==RC_DEMON ) - { //Only damage enemies with offensive Sanctuary. [Skotlex] - if( battle_check_target(&src->bl,bl,BCT_ENEMY) > 0 && skill_attack(BF_MAGIC, ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0) ) - sg->val1 -= 2; // reduce healing count if this was meant for damaging [hekate] - } - else - { - int heal = skill_calc_heal(ss,bl,sg->skill_id,sg->skill_lv,true); - struct mob_data *md = BL_CAST(BL_MOB, bl); -#ifdef RENEWAL - if( md && md->class_ == MOBID_EMPERIUM ) - break; -#endif - if( md && mob_is_battleground(md) ) - break; - if( tstatus->hp >= tstatus->max_hp ) - break; - if( status_isimmune(bl) ) - heal = 0; - clif_skill_nodamage(&src->bl, bl, AL_HEAL, heal, 1); - if( tsc && tsc->data[SC_AKAITSUKI] && heal ) - heal = ~heal + 1; - status_heal(bl, heal, 0, 0); - if( diff >= 500 ) - sg->val1--; - } - if( sg->val1 <= 0 ) - skill_delunitgroup(sg); - break; - - case UNT_EVILLAND: - //Will heal demon and undead element monsters, but not players. - if ((bl->type == BL_PC) || (!battle_check_undead(tstatus->race, tstatus->def_ele) && tstatus->race!=RC_DEMON)) - { //Damage enemies - if(battle_check_target(&src->bl,bl,BCT_ENEMY)>0) - skill_attack(BF_MISC, ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); - } else { - int heal = skill_calc_heal(ss,bl,sg->skill_id,sg->skill_lv,true); - if (tstatus->hp >= tstatus->max_hp) - break; - if (status_isimmune(bl)) - heal = 0; - clif_skill_nodamage(&src->bl, bl, AL_HEAL, heal, 1); - status_heal(bl, heal, 0, 0); - } - break; - - case UNT_MAGNUS: - if (!battle_check_undead(tstatus->race,tstatus->def_ele) && tstatus->race!=RC_DEMON) - break; - skill_attack(BF_MAGIC,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); - break; - - case UNT_DUMMYSKILL: - switch (sg->skill_id) - { - case SG_SUN_WARM: //SG skills [Komurka] - case SG_MOON_WARM: - case SG_STAR_WARM: - { - int count = 0; - const int x = bl->x, y = bl->y; - - //If target isn't knocked back it should hit every "interval" ms [Playtester] - do - { - if( bl->type == BL_PC ) - status_zap(bl, 0, 15); // sp damage to players - else // mobs - if( status_charge(ss, 0, 2) ) // costs 2 SP per hit - { - if( !skill_attack(BF_WEAPON,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick+count*sg->interval,0) ) - status_charge(ss, 0, 8); //costs additional 8 SP if miss - } - else - { //should end when out of sp. - sg->limit = DIFF_TICK(tick,sg->tick); - break; - } - } while( x == bl->x && y == bl->y && - ++count < SKILLUNITTIMER_INTERVAL/sg->interval && !status_isdead(bl) ); - } - break; - /** - * The storm gust counter was dropped in renewal - **/ - #ifndef RENEWAL - case WZ_STORMGUST: //SG counter does not reset per stormgust. IE: One hit from a SG and two hits from another will freeze you. - if (tsc) - tsc->sg_counter++; //SG hit counter. - if (skill_attack(skill_get_type(sg->skill_id),ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0) <= 0 && tsc) - tsc->sg_counter=0; //Attack absorbed. - break; - #endif - case GS_DESPERADO: - if (rnd()%100 < src->val1) - skill_attack(BF_WEAPON,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); - break; - case GN_CRAZYWEED_ATK: - if( bl->type == BL_SKILL ){ - struct skill_unit *su = (struct skill_unit *)bl; - if( su && !(skill_get_inf2(su->group->skill_id)&INF2_TRAP) ) - break; - } - default: - skill_attack(skill_get_type(sg->skill_id),ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); - } - break; - - case UNT_FIREPILLAR_WAITING: - skill_unitsetting(ss,sg->skill_id,sg->skill_lv,src->bl.x,src->bl.y,1); - skill_delunit(src); - break; - - case UNT_SKIDTRAP: - { - skill_blown(&src->bl,bl,skill_get_blewcount(sg->skill_id,sg->skill_lv),unit_getdir(bl),0); - sg->unit_id = UNT_USED_TRAPS; - clif_changetraplook(&src->bl, UNT_USED_TRAPS); - sg->limit=DIFF_TICK(tick,sg->tick)+1500; - } - break; - - case UNT_ANKLESNARE: - case UNT_MANHOLE: - if( sg->val2 == 0 && tsc && (sg->unit_id == UNT_ANKLESNARE || bl->id != sg->src_id) ) { - int sec = skill_get_time2(sg->skill_id,sg->skill_lv); - if( status_change_start(bl,type,10000,sg->skill_lv,sg->group_id,0,0,sec, 8) ) { - const struct TimerData* td = tsc->data[type]?get_timer(tsc->data[type]->timer):NULL; - if( td ) - sec = DIFF_TICK(td->tick, tick); - unit_movepos(bl, src->bl.x, src->bl.y, 0, 0); - clif_fixpos(bl); - sg->val2 = bl->id; - } else - sec = 3000; //Couldn't trap it? - if( sg->unit_id == UNT_ANKLESNARE ) { - clif_skillunit_update(&src->bl); - /** - * If you're snared from a trap that was invisible this makes the trap be - * visible again -- being you stepped on it (w/o this the trap remains invisible and you go "WTF WHY I CANT MOVE") - * bugreport:3961 - **/ - clif_changetraplook(&src->bl, UNT_ANKLESNARE); - } - sg->limit = DIFF_TICK(tick,sg->tick)+sec; - sg->interval = -1; - src->range = 0; - } - break; - - case UNT_ELECTRICSHOCKER: - if( bl->id != ss->id ) { - if( status_get_mode(bl)&MD_BOSS ) - break; - if( status_change_start(bl,type,10000,sg->skill_lv,sg->group_id,0,0,skill_get_time2(sg->skill_id, sg->skill_lv), 8) ) { - - map_moveblock(bl, src->bl.x, src->bl.y, tick); - clif_fixpos(bl); - - } - - map_foreachinrange(skill_trap_splash, &src->bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, &src->bl, tick); - sg->unit_id = UNT_USED_TRAPS; //Changed ID so it does not invoke a for each in area again. - } - break; - - case UNT_VENOMDUST: - if(tsc && !tsc->data[type]) - status_change_start(bl,type,10000,sg->skill_lv,sg->group_id,0,0,skill_get_time2(sg->skill_id,sg->skill_lv),0); - break; - - - case UNT_MAGENTATRAP: - case UNT_COBALTTRAP: - case UNT_MAIZETRAP: - case UNT_VERDURETRAP: - if( bl->type == BL_PC )// it won't work on players - break; - case UNT_FIRINGTRAP: - case UNT_ICEBOUNDTRAP: - case UNT_CLUSTERBOMB: - if( bl->id == ss->id )// it won't trigger on caster - break; - case UNT_LANDMINE: - case UNT_CLAYMORETRAP: - case UNT_BLASTMINE: - case UNT_SHOCKWAVE: - case UNT_SANDMAN: - case UNT_FLASHER: - case UNT_FREEZINGTRAP: - case UNT_FIREPILLAR_ACTIVE: - map_foreachinrange(skill_trap_splash,&src->bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, &src->bl,tick); - if (sg->unit_id != UNT_FIREPILLAR_ACTIVE) - clif_changetraplook(&src->bl, sg->unit_id==UNT_LANDMINE?UNT_FIREPILLAR_ACTIVE:UNT_USED_TRAPS); - sg->limit=DIFF_TICK(tick,sg->tick)+1500 + - (sg->unit_id== UNT_CLUSTERBOMB || sg->unit_id== UNT_ICEBOUNDTRAP?1000:0);// Cluster Bomb/Icebound has 1s to disappear once activated. - sg->unit_id = UNT_USED_TRAPS; //Changed ID so it does not invoke a for each in area again. - break; - - case UNT_TALKIEBOX: - if (sg->src_id == bl->id) - break; - if (sg->val2 == 0){ - clif_talkiebox(&src->bl, sg->valstr); - sg->unit_id = UNT_USED_TRAPS; - clif_changetraplook(&src->bl, UNT_USED_TRAPS); - sg->limit = DIFF_TICK(tick, sg->tick) + 5000; - sg->val2 = -1; - } - break; - - case UNT_LULLABY: - if (ss->id == bl->id) - break; - skill_additional_effect(ss, bl, sg->skill_id, sg->skill_lv, BF_LONG|BF_SKILL|BF_MISC, ATK_DEF, tick); - break; - - case UNT_UGLYDANCE: //Ugly Dance [Skotlex] - if (ss->id != bl->id) - skill_additional_effect(ss, bl, sg->skill_id, sg->skill_lv, BF_LONG|BF_SKILL|BF_MISC, ATK_DEF, tick); - break; - - case UNT_DISSONANCE: - skill_attack(BF_MISC, ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); - break; - - case UNT_APPLEIDUN: //Apple of Idun [Skotlex] - { - int heal; -#ifdef RENEWAL - struct mob_data *md = BL_CAST(BL_MOB, bl); - if( md && md->class_ == MOBID_EMPERIUM ) - break; -#endif - if( sg->src_id == bl->id && !(tsc && tsc->data[SC_SPIRIT] && tsc->data[SC_SPIRIT]->val2 == SL_BARDDANCER) ) - break; // affects self only when soullinked - heal = skill_calc_heal(ss,bl,sg->skill_id, sg->skill_lv, true); - if( tsc->data[SC_AKAITSUKI] && heal ) - heal = ~heal + 1; - clif_skill_nodamage(&src->bl, bl, AL_HEAL, heal, 1); - status_heal(bl, heal, 0, 0); - break; - } - - case UNT_TATAMIGAESHI: - case UNT_DEMONSTRATION: - skill_attack(BF_WEAPON,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); - break; - - case UNT_GOSPEL: - if (rnd()%100 > sg->skill_lv*10 || ss == bl) - break; - if (battle_check_target(ss,bl,BCT_PARTY)>0) - { // Support Effect only on party, not guild - int heal; - int i = rnd()%13; // Positive buff count - int time = skill_get_time2(sg->skill_id, sg->skill_lv); //Duration - switch (i) - { - case 0: // Heal 1~9999 HP - heal = rnd() %9999+1; - clif_skill_nodamage(ss,bl,AL_HEAL,heal,1); - status_heal(bl,heal,0,0); - break; - case 1: // End all negative status - status_change_clear_buffs(bl,6); - if (tsd) clif_gospel_info(tsd, 0x15); - break; - case 2: // Immunity to all status - sc_start(bl,SC_SCRESIST,100,100,time); - if (tsd) clif_gospel_info(tsd, 0x16); - break; - case 3: // MaxHP +100% - sc_start(bl,SC_INCMHPRATE,100,100,time); - if (tsd) clif_gospel_info(tsd, 0x17); - break; - case 4: // MaxSP +100% - sc_start(bl,SC_INCMSPRATE,100,100,time); - if (tsd) clif_gospel_info(tsd, 0x18); - break; - case 5: // All stats +20 - sc_start(bl,SC_INCALLSTATUS,100,20,time); - if (tsd) clif_gospel_info(tsd, 0x19); - break; - case 6: // Level 10 Blessing - sc_start(bl,SC_BLESSING,100,10,time); - break; - case 7: // Level 10 Increase AGI - sc_start(bl,SC_INCREASEAGI,100,10,time); - break; - case 8: // Enchant weapon with Holy element - sc_start(bl,SC_ASPERSIO,100,1,time); - if (tsd) clif_gospel_info(tsd, 0x1c); - break; - case 9: // Enchant armor with Holy element - sc_start(bl,SC_BENEDICTIO,100,1,time); - if (tsd) clif_gospel_info(tsd, 0x1d); - break; - case 10: // DEF +25% - sc_start(bl,SC_INCDEFRATE,100,25,time); - if (tsd) clif_gospel_info(tsd, 0x1e); - break; - case 11: // ATK +100% - sc_start(bl,SC_INCATKRATE,100,100,time); - if (tsd) clif_gospel_info(tsd, 0x1f); - break; - case 12: // HIT/Flee +50 - sc_start(bl,SC_INCHIT,100,50,time); - sc_start(bl,SC_INCFLEE,100,50,time); - if (tsd) clif_gospel_info(tsd, 0x20); - break; - } - } - else if (battle_check_target(&src->bl,bl,BCT_ENEMY)>0) - { // Offensive Effect - int i = rnd()%9; // Negative buff count - int time = skill_get_time2(sg->skill_id, sg->skill_lv); - switch (i) - { - case 0: // Deal 1~9999 damage - skill_attack(BF_MISC,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); - break; - case 1: // Curse - sc_start(bl,SC_CURSE,100,1,time); - break; - case 2: // Blind - sc_start(bl,SC_BLIND,100,1,time); - break; - case 3: // Poison - sc_start(bl,SC_POISON,100,1,time); - break; - case 4: // Level 10 Provoke - sc_start(bl,SC_PROVOKE,100,10,time); - break; - case 5: // DEF -100% - sc_start(bl,SC_INCDEFRATE,100,-100,time); - break; - case 6: // ATK -100% - sc_start(bl,SC_INCATKRATE,100,-100,time); - break; - case 7: // Flee -100% - sc_start(bl,SC_INCFLEERATE,100,-100,time); - break; - case 8: // Speed/ASPD -25% - sc_start4(bl,SC_GOSPEL,100,1,0,0,BCT_ENEMY,time); - break; - } - } - break; - - case UNT_BASILICA: - { - int i = battle_check_target(&src->bl, bl, BCT_ENEMY); - if( i > 0 && !(status_get_mode(bl)&MD_BOSS) ) - { // knock-back any enemy except Boss - skill_blown(&src->bl, bl, 2, unit_getdir(bl), 0); - clif_fixpos(bl); - } - - if( sg->src_id != bl->id && i <= 0 ) - sc_start4(bl, type, 100, 0, 0, 0, src->bl.id, sg->interval + 100); - } - break; - - case UNT_GRAVITATION: - case UNT_EARTHSTRAIN: - case UNT_FIREWALK: - case UNT_ELECTRICWALK: - case UNT_PSYCHIC_WAVE: - skill_attack(skill_get_type(sg->skill_id),ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); - break; - - case UNT_GROUNDDRIFT_WIND: - case UNT_GROUNDDRIFT_DARK: - case UNT_GROUNDDRIFT_POISON: - case UNT_GROUNDDRIFT_WATER: - case UNT_GROUNDDRIFT_FIRE: - map_foreachinrange(skill_trap_splash,&src->bl, - skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, - &src->bl,tick); - sg->unit_id = UNT_USED_TRAPS; - //clif_changetraplook(&src->bl, UNT_FIREPILLAR_ACTIVE); - sg->limit=DIFF_TICK(tick,sg->tick)+1500; - break; - /** - * 3rd stuff - **/ - case UNT_POISONSMOKE: - if( battle_check_target(ss,bl,BCT_ENEMY) > 0 && !(tsc && tsc->data[sg->val2]) && rnd()%100 < 20 ) - sc_start(bl,sg->val2,100,sg->val3,skill_get_time2(GC_POISONINGWEAPON, 1)); - break; - - case UNT_EPICLESIS: - if( bl->type == BL_PC && !battle_check_undead(tstatus->race, tstatus->def_ele) && tstatus->race != RC_DEMON ) - { - if( ++sg->val2 % 3 == 0 ) { - int hp, sp; - switch( sg->skill_lv ) - { - case 1: case 2: hp = 3; sp = 2; break; - case 3: case 4: hp = 4; sp = 3; break; - case 5: default: hp = 5; sp = 4; break; - } - hp = tstatus->max_hp * hp / 100; - sp = tstatus->max_sp * sp / 100; - status_heal(bl, hp, sp, 2); - sc_start(bl, type, 100, sg->skill_lv, (sg->interval * 3) + 100); - } - // Reveal hidden players every 5 seconds. - if( sg->val2 % 5 == 0 ) { - // TODO: check if other hidden status can be removed. - status_change_end(bl,SC_HIDING,INVALID_TIMER); - status_change_end(bl,SC_CLOAKING,INVALID_TIMER); - } - } - /* Enable this if kRO fix the current skill. Currently no damage on undead and demon monster. [Jobbie] - else if( battle_check_target(ss, bl, BCT_ENEMY) > 0 && battle_check_undead(tstatus->race, tstatus->def_ele) ) - skill_castend_damage_id(&src->bl, bl, sg->skill_id, sg->skill_lv, 0, 0);*/ - break; - - case UNT_STEALTHFIELD: - if( bl->id == sg->src_id ) - break; // Dont work on Self (video shows that) - case UNT_NEUTRALBARRIER: - sc_start(bl,type,100,sg->skill_lv,sg->interval + 100); - break; - - case UNT_DIMENSIONDOOR: - if( tsd && !map[bl->m].flag.noteleport ) - pc_randomwarp(tsd,3); - else if( bl->type == BL_MOB && battle_config.mob_warp&8 ) - unit_warp(bl,-1,-1,-1,3); - break; - - case UNT_REVERBERATION: - clif_changetraplook(&src->bl,UNT_USED_TRAPS); - map_foreachinrange(skill_trap_splash,&src->bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, &src->bl,tick); - sg->limit = DIFF_TICK(tick,sg->tick)+1000; - sg->unit_id = UNT_USED_TRAPS; - break; - - case UNT_SEVERE_RAINSTORM: - if( battle_check_target(&src->bl, bl, BCT_ENEMY) ) - skill_attack(BF_WEAPON,ss,&src->bl,bl,WM_SEVERE_RAINSTORM_MELEE,sg->skill_lv,tick,0); - break; - case UNT_NETHERWORLD: - if( !(status_get_mode(bl)&MD_BOSS) && ss != bl && battle_check_target(&src->bl, bl, BCT_PARTY) ) { - if( !(tsc && tsc->data[type]) ){ - sc_start(bl, type, 100, sg->skill_lv, skill_get_time2(sg->skill_id,sg->skill_lv)); - sg->limit = DIFF_TICK(tick,sg->tick); - sg->unit_id = UNT_USED_TRAPS; - } - } - break; - case UNT_THORNS_TRAP: - if( tsc ) { - if( !sg->val2 ) { - int sec = skill_get_time2(sg->skill_id, sg->skill_lv); - if( sc_start(bl, type, 100, sg->skill_lv, sec) ) { - const struct TimerData* td = tsc->data[type]?get_timer(tsc->data[type]->timer):NULL; - if( td ) - sec = DIFF_TICK(td->tick, tick); - ///map_moveblock(bl, src->bl.x, src->bl.y, tick); // in official server it doesn't behave like this. [malufett] - clif_fixpos(bl); - sg->val2 = bl->id; - } else - sec = 3000; // Couldn't trap it? - sg->limit = DIFF_TICK(tick, sg->tick) + sec; - } else if( tsc->data[SC_THORNSTRAP] && bl->id == sg->val2 ) - skill_attack(skill_get_type(GN_THORNS_TRAP), ss, ss, bl, sg->skill_id, sg->skill_lv, tick, SD_LEVEL|SD_ANIMATION); - } - break; - - case UNT_DEMONIC_FIRE: { - TBL_PC* sd = BL_CAST(BL_PC, ss); - switch( sg->val2 ) { - case 1: - case 2: - default: - sc_start(bl, SC_BURNING, 4 + 4 * sg->skill_lv, sg->skill_lv, - skill_get_time2(sg->skill_id, sg->skill_lv)); - skill_attack(skill_get_type(sg->skill_id), ss, &src->bl, bl, - sg->skill_id, sg->skill_lv + 10 * sg->val2, tick, 0); - break; - case 3: - skill_attack(skill_get_type(CR_ACIDDEMONSTRATION), ss, &src->bl, bl, - CR_ACIDDEMONSTRATION, sd ? pc_checkskill(sd, CR_ACIDDEMONSTRATION) : sg->skill_lv, tick, 0); - break; - - } - } - break; - - case UNT_FIRE_EXPANSION_SMOKE_POWDER: - sc_start(bl, status_skill2sc(GN_FIRE_EXPANSION_SMOKE_POWDER), 100, sg->skill_lv, 1000); - break; - - case UNT_FIRE_EXPANSION_TEAR_GAS: - sc_start(bl, status_skill2sc(GN_FIRE_EXPANSION_TEAR_GAS), 100, sg->skill_lv, 1000); - break; - - case UNT_HELLS_PLANT: - if( battle_check_target(&src->bl,bl,BCT_ENEMY) > 0 ) - skill_attack(skill_get_type(GN_HELLS_PLANT_ATK), ss, &src->bl, bl, GN_HELLS_PLANT_ATK, sg->skill_lv, tick, 0); - if( ss != bl) //The caster is the only one who can step on the Plants, without destroying them - sg->limit = DIFF_TICK(tick, sg->tick) + 100; - break; - - case UNT_CLOUD_KILL: - if(tsc && !tsc->data[type]) - status_change_start(bl,type,10000,sg->skill_lv,sg->group_id,0,0,skill_get_time2(sg->skill_id,sg->skill_lv),8); - skill_attack(skill_get_type(sg->skill_id),ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); - break; - - case UNT_WARMER: - if( bl->type == BL_PC && !battle_check_undead(tstatus->race, tstatus->def_ele) && tstatus->race != RC_DEMON ) { - int hp = 125 * sg->skill_lv; // Officially is 125 * skill_lv. - struct status_change *ssc = status_get_sc(ss); - if( ssc && ssc->data[SC_HEATER_OPTION] ) - hp += hp * ssc->data[SC_HEATER_OPTION]->val3 / 100; - if( tstatus->hp != tstatus->max_hp ) - clif_skill_nodamage(&src->bl, bl, AL_HEAL, hp, 0); - if( tsc && tsc->data[SC_AKAITSUKI] && hp ) - hp = ~hp + 1; - status_heal(bl, hp, 0, 0); - sc_start(bl, SC_WARMER, 100, sg->skill_lv, skill_get_time2(sg->skill_id,sg->skill_lv)); - } - break; - - case UNT_FIRE_INSIGNIA: - case UNT_WATER_INSIGNIA: - case UNT_WIND_INSIGNIA: - case UNT_EARTH_INSIGNIA: - case UNT_ZEPHYR: - sc_start(bl,type, 100, sg->skill_lv, sg->interval); - if (sg->unit_id != UNT_ZEPHYR && !battle_check_undead(tstatus->race, tstatus->def_ele)) { - int hp = tstatus->max_hp / 100; //+1% each 5s - if ((sg->val3) % 5) { //each 5s - if (tstatus->def_ele == skill_get_ele(sg->skill_id,sg->skill_lv)){ - status_heal(bl, hp, 0, 2); - } else if((sg->unit_id == UNT_FIRE_INSIGNIA && tstatus->def_ele == ELE_EARTH) - ||(sg->unit_id == UNT_WATER_INSIGNIA && tstatus->def_ele == ELE_FIRE) - ||(sg->unit_id == UNT_WIND_INSIGNIA && tstatus->def_ele == ELE_WATER) - ||(sg->unit_id == UNT_EARTH_INSIGNIA && tstatus->def_ele == ELE_WIND) - ){ - status_heal(bl, -hp, 0, 0); - } - } - sg->val3++; //timer - if (sg->val3 > 5) sg->val3 = 0; - } - break; - - case UNT_VACUUM_EXTREME: - {// TODO: official behavior in gvg area. [malufett] - int sec = sg->limit - DIFF_TICK(tick, sg->tick); - int range = skill_get_unit_range(sg->skill_id, sg->skill_lv); - - if( tsc && !tsc->data[type] && - distance_xy(src->bl.x, src->bl.y, bl->x, bl->y) <= range)// don't consider outer bounderies - sc_start(bl, type, 100, sg->skill_lv, sec); - - if( unit_is_walking(bl) && // wait until target stop walking - ( tsc && tsc->data[type] && tsc->data[type]->val4 >= tsc->data[type]->val3-range )) - break; - - if( tsc && ( !tsc->data[type] || (tsc->data[type] && tsc->data[type]->val4 < 1 ) ) ) - break; - - if( unit_is_walking(bl) && - distance_xy(src->bl.x, src->bl.y, bl->x, bl->y) > range )// going outside of boundaries? then force it to stop - unit_stop_walking(bl,1); - - if( !unit_is_walking(bl) && - distance_xy(src->bl.x, src->bl.y, bl->x, bl->y) <= range && // only snap if the target is inside the range or - src->bl.x != bl->x && src->bl.y != bl->y){// diagonal position parallel to VE's center - unit_movepos(bl, src->bl.x, src->bl.y, 0, 0); - clif_fixpos(bl); - } - } - break; - - case UNT_FIRE_MANTLE: - if( battle_check_target(&src->bl, bl, BCT_ENEMY) ) - skill_attack(BF_MAGIC,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); - break; - - case UNT_ZENKAI_WATER: - case UNT_ZENKAI_LAND: - case UNT_ZENKAI_FIRE: - case UNT_ZENKAI_WIND: - if( battle_check_target(&src->bl,bl,BCT_ENEMY) > 0 ){ - switch( sg->unit_id ){ - case UNT_ZENKAI_WATER: - sc_start(bl, SC_CRYSTALIZE, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); - sc_start(bl, SC_FREEZE, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); - sc_start(bl, SC_FREEZING, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); - break; - case UNT_ZENKAI_LAND: - sc_start(bl, SC_STONE, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); - sc_start(bl, SC_POISON, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); - break; - case UNT_ZENKAI_FIRE: - sc_start(bl, SC_BURNING, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); - break; - case UNT_ZENKAI_WIND: - sc_start(bl, SC_SILENCE, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); - sc_start(bl, SC_SLEEP, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); - sc_start(bl, SC_DEEPSLEEP, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); - break; - } - }else - sc_start2(bl,type,100,sg->val1,sg->val2,skill_get_time2(sg->skill_id, sg->skill_lv)); - break; - - case UNT_MAKIBISHI: - skill_attack(BF_MISC, ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); - sg->limit = DIFF_TICK(tick, sg->tick); - sg->unit_id = UNT_USED_TRAPS; - break; - - case UNT_LAVA_SLIDE: - skill_attack(BF_WEAPON, ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); - if(++sg->val1 > 4) //after 5 stop hit and destroy me - sg->limit = DIFF_TICK(tick, sg->tick); - break; - case UNT_POISON_MIST: - skill_attack(BF_MAGIC, ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); - status_change_start(bl, SC_BLIND, rnd() % 100 > sg->skill_lv * 10, sg->skill_lv, sg->skill_id, 0, 0, skill_get_time2(sg->skill_id, sg->skill_lv), 2|8); - break; - } - - if (bl->type == BL_MOB && ss != bl) - mobskill_event((TBL_MOB*)bl, ss, tick, MSC_SKILLUSED|(skill_id<<16)); - - return skill_id; -} -/*========================================== - * Triggered when a char steps out of a skill cell - *------------------------------------------*/ -int skill_unit_onout (struct skill_unit *src, struct block_list *bl, unsigned int tick) -{ - struct skill_unit_group *sg; - struct status_change *sc; - struct status_change_entry *sce; - enum sc_type type; - - nullpo_ret(src); - nullpo_ret(bl); - nullpo_ret(sg=src->group); - sc = status_get_sc(bl); - type = status_skill2sc(sg->skill_id); - sce = (sc && type != -1)?sc->data[type]:NULL; - - if( bl->prev==NULL || - (status_isdead(bl) && sg->unit_id != UNT_ANKLESNARE && sg->unit_id != UNT_SPIDERWEB) ) //Need to delete the trap if the source died. - return 0; - - switch(sg->unit_id){ - case UNT_SAFETYWALL: - case UNT_PNEUMA: - case UNT_EPICLESIS://Arch Bishop - case UNT_NEUTRALBARRIER: - case UNT_STEALTHFIELD: - if (sce) - status_change_end(bl, type, INVALID_TIMER); - break; - - case UNT_BASILICA: - if( sce && sce->val4 == src->bl.id ) - status_change_end(bl, type, INVALID_TIMER); - break; - case UNT_HERMODE: //Clear Hermode if the owner moved. - if (sce && sce->val3 == BCT_SELF && sce->val4 == sg->group_id) - status_change_end(bl, type, INVALID_TIMER); - break; - - case UNT_SPIDERWEB: - { - struct block_list *target = map_id2bl(sg->val2); - if (target && target==bl) - { - if (sce && sce->val3 == sg->group_id) - status_change_end(bl, type, INVALID_TIMER); - sg->limit = DIFF_TICK(tick,sg->tick)+1000; - } - break; - } - } - return sg->skill_id; -} - -/*========================================== - * Triggered when a char steps out of a skill group (entirely) [Skotlex] - *------------------------------------------*/ -static int skill_unit_onleft (uint16 skill_id, struct block_list *bl, unsigned int tick) -{ - struct status_change *sc; - struct status_change_entry *sce; - enum sc_type type; - - sc = status_get_sc(bl); - if (sc && !sc->count) - sc = NULL; - - type = status_skill2sc(skill_id); - sce = (sc && type != -1)?sc->data[type]:NULL; - - switch (skill_id) - { - case WZ_QUAGMIRE: - if (bl->type==BL_MOB) - break; - if (sce) - status_change_end(bl, type, INVALID_TIMER); - 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: - if(sc && sc->data[SC_DANCING] && (sc->data[SC_DANCING]->val1&0xFFFF) == skill_id) - { //Check if you just stepped out of your ensemble skill to cancel dancing. [Skotlex] - //We don't check for SC_LONGING because someone could always have knocked you back and out of the song/dance. - //FIXME: This code is not perfect, it doesn't checks for the real ensemble's owner, - //it only checks if you are doing the same ensemble. So if there's two chars doing an ensemble - //which overlaps, by stepping outside of the other parther's ensemble will cause you to cancel - //your own. Let's pray that scenario is pretty unlikely and noone will complain too much about it. - status_change_end(bl, SC_DANCING, INVALID_TIMER); - } - case MH_STEINWAND: - case MG_SAFETYWALL: - case AL_PNEUMA: - case SA_VOLCANO: - case SA_DELUGE: - case SA_VIOLENTGALE: - case CG_HERMODE: - case HW_GRAVITATION: - case NJ_SUITON: - case SC_MAELSTROM: - case EL_WATER_BARRIER: - case EL_ZEPHYR: - case EL_POWER_OF_GAIA: - case SO_FIRE_INSIGNIA: - case SO_WATER_INSIGNIA: - case SO_WIND_INSIGNIA: - case SO_EARTH_INSIGNIA: - if (sce) - status_change_end(bl, type, INVALID_TIMER); - break; - case SC_BLOODYLUST: - if (sce) { - status_change_end(bl, type, INVALID_TIMER); - status_set_sp(bl, 0, 0); //set sp to 0 when quitting zone - } - break; - - case BA_POEMBRAGI: - case BA_WHISTLE: - case BA_ASSASSINCROSS: - case BA_APPLEIDUN: - case DC_HUMMING: - case DC_DONTFORGETME: - case DC_FORTUNEKISS: - case DC_SERVICEFORYOU: - if (sce) - { - delete_timer(sce->timer, status_change_timer); - //NOTE: It'd be nice if we could get the skill_lv for a more accurate extra time, but alas... - //not possible on our current implementation. - sce->val4 = 1; //Store the fact that this is a "reduced" duration effect. - sce->timer = add_timer(tick+skill_get_time2(skill_id,1), status_change_timer, bl->id, type); - } - break; - case PF_FOGWALL: - if (sce) - { - status_change_end(bl, type, INVALID_TIMER); - if ((sce=sc->data[SC_BLIND])) - { - if (bl->type == BL_PC) //Players get blind ended inmediately, others have it still for 30 secs. [Skotlex] - status_change_end(bl, SC_BLIND, INVALID_TIMER); - else { - delete_timer(sce->timer, status_change_timer); - sce->timer = add_timer(30000+tick, status_change_timer, bl->id, SC_BLIND); - } - } - } - break; - case GD_LEADERSHIP: - case GD_GLORYWOUNDS: - case GD_SOULCOLD: - case GD_HAWKEYES: - if( !(sce && sce->val4) ) - status_change_end(bl, type, INVALID_TIMER); - break; - } - - return skill_id; -} - -/*========================================== - * Invoked when a unit cell has been placed/removed/deleted. - * flag values: - * flag&1: Invoke onplace function (otherwise invoke onout) - * flag&4: Invoke a onleft call (the unit might be scheduled for deletion) - *------------------------------------------*/ -static int skill_unit_effect (struct block_list* bl, va_list ap) -{ - struct skill_unit* unit = va_arg(ap,struct skill_unit*); - struct skill_unit_group* group = unit->group; - unsigned int tick = va_arg(ap,unsigned int); - unsigned int flag = va_arg(ap,unsigned int); - uint16 skill_id; - bool dissonance; - - if( (!unit->alive && !(flag&4)) || bl->prev == NULL ) - return 0; - - nullpo_ret(group); - - dissonance = skill_dance_switch(unit, 0); - - //Necessary in case the group is deleted after calling on_place/on_out [Skotlex] - skill_id = group->skill_id; - //Target-type check. - if( !(group->bl_flag&bl->type && battle_check_target(&unit->bl,bl,group->target_flag)>0) && (flag&4) ) { - if( group->state.song_dance&0x1 || (group->src_id == bl->id && group->state.song_dance&0x2) ) - skill_unit_onleft(skill_id, bl, tick);//Ensemble check to terminate it. - } else { - if( flag&1 ) - skill_unit_onplace(unit,bl,tick); - else - skill_unit_onout(unit,bl,tick); - - if( flag&4 ) - skill_unit_onleft(skill_id, bl, tick); - } - - if( dissonance ) skill_dance_switch(unit, 1); - - 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_ret(src); - nullpo_ret(sg=src->group); - - switch( sg->unit_id ) { - case UNT_BLASTMINE: - case UNT_SKIDTRAP: - case UNT_LANDMINE: - case UNT_SHOCKWAVE: - case UNT_SANDMAN: - case UNT_FLASHER: - case UNT_CLAYMORETRAP: - case UNT_FREEZINGTRAP: - case UNT_TALKIEBOX: - case UNT_ANKLESNARE: - case UNT_ICEWALL: - case UNT_REVERBERATION: - case UNT_WALLOFTHORN: - src->val1-=damage; - break; - default: - damage = 0; - break; - } - return damage; -} - -/*========================================== - * - *------------------------------------------*/ -static int skill_check_condition_char_sub (struct block_list *bl, va_list ap) -{ - int *c, skill_id; - struct block_list *src; - struct map_session_data *sd; - struct map_session_data *tsd; - int *p_sd; //Contains the list of characters found. - - nullpo_ret(bl); - nullpo_ret(tsd=(struct map_session_data*)bl); - nullpo_ret(src=va_arg(ap,struct block_list *)); - nullpo_ret(sd=(struct map_session_data*)src); - - c=va_arg(ap,int *); - p_sd = va_arg(ap, int *); - skill_id = va_arg(ap,int); - - if ( ((skill_id != PR_BENEDICTIO && *c >=1) || *c >=2) && !(skill_get_inf2(skill_id)&INF2_CHORUS_SKILL) ) - return 0; //Partner found for ensembles, or the two companions for Benedictio. [Skotlex] - - if (bl == src) - return 0; - - if(pc_isdead(tsd)) - return 0; - - if (tsd->sc.data[SC_SILENCE] || ( tsd->sc.opt1 && tsd->sc.opt1 != OPT1_BURNING )) - return 0; - - if( skill_get_inf2(skill_id)&INF2_CHORUS_SKILL ) { - if( tsd->status.party_id == sd->status.party_id && (tsd->class_&MAPID_THIRDMASK) == MAPID_MINSTRELWANDERER ) - p_sd[(*c)++] = tsd->bl.id; - return 1; - } else { - - switch(skill_id) { - case PR_BENEDICTIO: { - uint8 dir = map_calc_dir(&sd->bl,tsd->bl.x,tsd->bl.y); - dir = (unit_getdir(&sd->bl) + dir)%8; //This adjusts dir to account for the direction the sd is facing. - if ((tsd->class_&MAPID_BASEMASK) == MAPID_ACOLYTE && (dir == 2 || dir == 6) //Must be standing to the left/right of Priest. - && sd->status.sp >= 10) - p_sd[(*c)++]=tsd->bl.id; - return 1; - } - case AB_ADORAMUS: - // Adoramus does not consume Blue Gemstone when there is at least 1 Priest class next to the caster - if( (tsd->class_&MAPID_UPPERMASK) == MAPID_PRIEST ) - p_sd[(*c)++] = tsd->bl.id; - return 1; - case WL_COMET: - // Comet does not consume Red Gemstones when there is at least 1 Warlock class next to the caster - if( ( sd->class_&MAPID_THIRDMASK ) == MAPID_WARLOCK ) - p_sd[(*c)++] = tsd->bl.id; - return 1; - case LG_RAYOFGENESIS: - if( tsd->status.party_id == sd->status.party_id && (tsd->class_&MAPID_THIRDMASK) == MAPID_ROYAL_GUARD && - tsd->sc.data[SC_BANDING] ) - p_sd[(*c)++] = tsd->bl.id; - return 1; - default: //Warning: Assuming Ensemble Dance/Songs for code speed. [Skotlex] - { - uint16 skill_lv; - if(pc_issit(tsd) || !unit_can_move(&tsd->bl)) - return 0; - if (sd->status.sex != tsd->status.sex && - (tsd->class_&MAPID_UPPERMASK) == MAPID_BARDDANCER && - (skill_lv = pc_checkskill(tsd, skill_id)) > 0 && - (tsd->weapontype1==W_MUSICAL || tsd->weapontype1==W_WHIP) && - sd->status.party_id && tsd->status.party_id && - sd->status.party_id == tsd->status.party_id && - !tsd->sc.data[SC_DANCING]) - { - p_sd[(*c)++]=tsd->bl.id; - return skill_lv; - } else { - return 0; - } - } - break; - } - - } - return 0; -} - -/*========================================== - * Checks and stores partners for ensemble skills [Skotlex] - *------------------------------------------*/ -int skill_check_pc_partner (struct map_session_data *sd, uint16 skill_id, short* skill_lv, int range, int cast_flag) -{ - static int c=0; - static int p_sd[2] = { 0, 0 }; - int i; - bool is_chorus = ( skill_get_inf2(skill_id)&INF2_CHORUS_SKILL ); - - if (!battle_config.player_skill_partner_check || pc_has_permission(sd, PC_PERM_SKILL_UNCONDITIONAL)) - return is_chorus ? MAX_PARTY : 99; //As if there were infinite partners. - - if (cast_flag) { //Execute the skill on the partners. - struct map_session_data* tsd; - switch (skill_id) { - case PR_BENEDICTIO: - for (i = 0; i < c; i++) { - if ((tsd = map_id2sd(p_sd[i])) != NULL) - status_charge(&tsd->bl, 0, 10); - } - return c; - case AB_ADORAMUS: - if( c > 0 && (tsd = map_id2sd(p_sd[0])) != NULL ) { - i = 2 * (*skill_lv); - status_charge(&tsd->bl, 0, i); - } - break; - case WM_GREAT_ECHO: - for( i = 0; i < c; i++ ) { - if( (tsd = map_id2sd(p_sd[i])) != NULL ) - status_zap(&tsd->bl,0,skill_get_sp(skill_id,*skill_lv)/c); - } - break; - default: //Warning: Assuming Ensemble skills here (for speed) - if( is_chorus ) - break;//Chorus skills are not to be parsed as ensambles - if (c > 0 && sd->sc.data[SC_DANCING] && (tsd = map_id2sd(p_sd[0])) != NULL) { - sd->sc.data[SC_DANCING]->val4 = tsd->bl.id; - sc_start4(&tsd->bl,SC_DANCING,100,skill_id,sd->sc.data[SC_DANCING]->val2,*skill_lv,sd->bl.id,skill_get_time(skill_id,*skill_lv)+1000); - clif_skill_nodamage(&tsd->bl, &sd->bl, skill_id, *skill_lv, 1); - tsd->skill_id_dance = skill_id; - tsd->skill_lv_dance = *skill_lv; - } - return c; - } - } - - //Else: new search for partners. - c = 0; - memset (p_sd, 0, sizeof(p_sd)); - if( is_chorus ) - i = party_foreachsamemap(skill_check_condition_char_sub,sd,AREA_SIZE,&sd->bl, &c, &p_sd, skill_id, *skill_lv); - else - i = map_foreachinrange(skill_check_condition_char_sub, &sd->bl, range, BL_PC, &sd->bl, &c, &p_sd, skill_id); - - if ( skill_id != PR_BENEDICTIO && skill_id != AB_ADORAMUS && skill_id != WL_COMET ) //Apply the average lv to encore skills. - *skill_lv = (i+(*skill_lv))/(c+1); //I know c should be one, but this shows how it could be used for the average of n partners. - return c; -} - -/*========================================== - * - *------------------------------------------*/ -static int skill_check_condition_mob_master_sub (struct block_list *bl, va_list ap) -{ - int *c,src_id,mob_class,skill; - struct mob_data *md; - - md=(struct mob_data*)bl; - src_id=va_arg(ap,int); - mob_class=va_arg(ap,int); - skill=va_arg(ap,int); - c=va_arg(ap,int *); - - if( md->master_id != src_id || md->special_state.ai != (unsigned)(skill == AM_SPHEREMINE?2:skill == KO_ZANZOU?4:3) ) - return 0; //Non alchemist summoned mobs have nothing to do here. - - if(md->class_==mob_class) - (*c)++; - - return 1; -} - -/*========================================== - * Determines if a given skill should be made to consume ammo - * when used by the player. [Skotlex] - *------------------------------------------*/ -int skill_isammotype (struct map_session_data *sd, int skill) -{ - return ( - battle_config.arrow_decrement==2 && - (sd->status.weapon == W_BOW || (sd->status.weapon >= W_REVOLVER && sd->status.weapon <= W_GRENADE)) && - skill != HT_PHANTASMIC && - skill_get_type(skill) == BF_WEAPON && - !(skill_get_nk(skill)&NK_NO_DAMAGE) && - !skill_get_spiritball(skill,1) //Assume spirit spheres are used as ammo instead. - ); -} - -int skill_check_condition_castbegin(struct map_session_data* sd, uint16 skill_id, uint16 skill_lv) { - struct status_data *status; - struct status_change *sc; - struct skill_condition require; - int i; - - nullpo_ret(sd); - - if (sd->chatID) return 0; - - if( pc_has_permission(sd, PC_PERM_SKILL_UNCONDITIONAL) && sd->skillitem != skill_id ) - { //GMs don't override the skillItem check, otherwise they can use items without them being consumed! [Skotlex] - sd->state.arrow_atk = skill_get_ammotype(skill_id)?1:0; //Need to do arrow state check. - sd->spiritball_old = sd->spiritball; //Need to do Spiritball check. - return 1; - } - - switch( sd->menuskill_id ) { - case AM_PHARMACY: - switch( skill_id ) { - case AM_PHARMACY: - case AC_MAKINGARROW: - case BS_REPAIRWEAPON: - case AM_TWILIGHT1: - case AM_TWILIGHT2: - case AM_TWILIGHT3: - return 0; - } - break; - case GN_MIX_COOKING: - case GN_MAKEBOMB: - case GN_S_PHARMACY: - case GN_CHANGEMATERIAL: - if( sd->menuskill_id != skill_id ) - return 0; - break; - } - status = &sd->battle_status; - sc = &sd->sc; - if( !sc->count ) - sc = NULL; - - if( sd->skillitem == skill_id ) - { - if( sd->state.abra_flag ) // Hocus-Pocus was used. [Inkfish] - sd->state.abra_flag = 0; - else - { // When a target was selected, consume items that were skipped in pc_use_item [Skotlex] - if( (i = sd->itemindex) == -1 || - sd->status.inventory[i].nameid != sd->itemid || - sd->inventory_data[i] == NULL || - !sd->inventory_data[i]->flag.delay_consume || - sd->status.inventory[i].amount < 1 - ) - { //Something went wrong, item exploit? - sd->itemid = sd->itemindex = -1; - return 0; - } - //Consume - sd->itemid = sd->itemindex = -1; - if( skill_id == WZ_EARTHSPIKE && sc && sc->data[SC_EARTHSCROLL] && rnd()%100 > sc->data[SC_EARTHSCROLL]->val2 ) // [marquis007] - ; //Do not consume item. - else if( sd->status.inventory[i].expire_time == 0 ) - pc_delitem(sd,i,1,0,0,LOG_TYPE_CONSUME); // Rental usable items are not consumed until expiration - } - return 1; - } - - if( pc_is90overweight(sd) ) - { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_WEIGHTOVER,0); - return 0; - } - - if( sc && ( sc->data[SC__SHADOWFORM] || sc->data[SC__IGNORANCE] ) ) - return 0; - - switch( skill_id ) { // Turn off check. - case BS_MAXIMIZE: case NV_TRICKDEAD: case TF_HIDING: case AS_CLOAKING: case CR_AUTOGUARD: - case ML_AUTOGUARD: case CR_DEFENDER: case ML_DEFENDER: case ST_CHASEWALK: case PA_GOSPEL: - case CR_SHRINK: case TK_RUN: case GS_GATLINGFEVER: case TK_READYCOUNTER: case TK_READYDOWN: - case TK_READYSTORM: case TK_READYTURN: case SG_FUSION: case RA_WUGDASH: case KO_YAMIKUMO: - if( sc && sc->data[status_skill2sc(skill_id)] ) - return 1; - } - - // Check the skills that can be used while mounted on a warg - if( pc_isridingwug(sd) ) { - switch( skill_id ) { - 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_SPRINGTRAP: case RA_DETONATOR: case RA_CLUSTERBOMB: - case HT_TALKIEBOX: case RA_FIRINGTRAP: case RA_ICEBOUNDTRAP: - case RA_WUGDASH: case RA_WUGRIDER: case RA_WUGSTRIKE: - break; - default: // in official there is no message. - return 0; - } - - } - if( pc_ismadogear(sd) ) { - switch( skill_id ) { //None Mado skills are unusable when Mado is equipped. [Jobbie] - case BS_REPAIRWEAPON: case WS_MELTDOWN: - case BS_HAMMERFALL: case WS_CARTBOOST: - case BS_ADRENALINE: case WS_WEAPONREFINE: - case BS_WEAPONPERFECT: case WS_CARTTERMINATION: - case BS_OVERTHRUST: case WS_OVERTHRUSTMAX: - case BS_MAXIMIZE: case NC_AXEBOOMERANG: - case BS_ADRENALINE2: case NC_POWERSWING: - case BS_UNFAIRLYTRICK: case NC_AXETORNADO: - case BS_GREED: - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - default: //Only Mechanic exlcusive skill can be used. - break; - } - } - if( skill_lv < 1 || skill_lv > MAX_SKILL_LEVEL ) - return 0; - - require = skill_get_requirement(sd,skill_id,skill_lv); - - //Can only update state when weapon/arrow info is checked. - sd->state.arrow_atk = require.ammo?1:0; - - // perform skill-specific checks (and actions) - switch( skill_id ) { - case SO_SPELLFIST: - if(sd->skill_id_old != MG_FIREBOLT && sd->skill_id_old != MG_COLDBOLT && sd->skill_id_old != MG_LIGHTNINGBOLT){ - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - case SA_CASTCANCEL: - if(sd->ud.skilltimer == INVALID_TIMER) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case AL_WARP: - if(!battle_config.duel_allow_teleport && sd->duel_group) { // duel restriction [LuzZza] - char output[128]; sprintf(output, msg_txt(365), skill_get_name(AL_WARP)); - clif_displaymessage(sd->fd, output); //"Duel: Can't use %s in duel." - return 0; - } - break; - case MO_CALLSPIRITS: - if(sc && sc->data[SC_RAISINGDRAGON]) - skill_lv += sc->data[SC_RAISINGDRAGON]->val1; - if(sd->spiritball >= skill_lv) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case MO_FINGEROFFENSIVE: - case GS_FLING: - case SR_RAMPAGEBLASTER: - case SR_RIDEINLIGHTNING: - if( sd->spiritball > 0 && sd->spiritball < require.spiritball ) - sd->spiritball_old = require.spiritball = sd->spiritball; - else - sd->spiritball_old = require.spiritball; - break; - case MO_CHAINCOMBO: - if(!sc) - return 0; - if(sc->data[SC_BLADESTOP]) - break; - if(sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == MO_TRIPLEATTACK) - break; - return 0; - case MO_COMBOFINISH: - if(!(sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == MO_CHAINCOMBO)) - return 0; - break; - case CH_TIGERFIST: - if(!(sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == MO_COMBOFINISH)) - return 0; - break; - case CH_CHAINCRUSH: - if(!(sc && sc->data[SC_COMBO])) - return 0; - if(sc->data[SC_COMBO]->val1 != MO_COMBOFINISH && sc->data[SC_COMBO]->val1 != CH_TIGERFIST) - return 0; - break; - case MO_EXTREMITYFIST: - // if(sc && sc->data[SC_EXTREMITYFIST]) //To disable Asura during the 5 min skill block uncomment this... - // return 0; - if( sc && (sc->data[SC_BLADESTOP] || sc->data[SC_CURSEDCIRCLE_ATKER]) ) - break; - if( sc && sc->data[SC_COMBO] ) - { - switch(sc->data[SC_COMBO]->val1) { - case MO_COMBOFINISH: - case CH_TIGERFIST: - case CH_CHAINCRUSH: - break; - default: - return 0; - } - } - else if( !unit_can_move(&sd->bl) ) - { //Placed here as ST_MOVE_ENABLE should not apply if rooted or on a combo. [Skotlex] - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - - case TK_MISSION: - if( (sd->class_&MAPID_UPPERMASK) != MAPID_TAEKWON ) - {// Cannot be used by Non-Taekwon classes - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - - case TK_READYCOUNTER: - case TK_READYDOWN: - case TK_READYSTORM: - case TK_READYTURN: - case TK_JUMPKICK: - if( (sd->class_&MAPID_UPPERMASK) == MAPID_SOUL_LINKER ) - {// Soul Linkers cannot use this skill - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - - case TK_TURNKICK: - case TK_STORMKICK: - case TK_DOWNKICK: - case TK_COUNTER: - if ((sd->class_&MAPID_UPPERMASK) == MAPID_SOUL_LINKER) - return 0; //Anti-Soul Linker check in case you job-changed with Stances active. - if(!(sc && sc->data[SC_COMBO]) || sc->data[SC_COMBO]->val1 == TK_JUMPKICK) - return 0; //Combo needs to be ready - - if (sc->data[SC_COMBO]->val3) { //Kick chain - //Do not repeat a kick. - if (sc->data[SC_COMBO]->val3 != skill_id) - break; - status_change_end(&sd->bl, SC_COMBO, INVALID_TIMER); - return 0; - } - if(sc->data[SC_COMBO]->val1 != skill_id && !( sd && sd->status.base_level >= 90 && pc_famerank(sd->status.char_id, MAPID_TAEKWON) )) { //Cancel combo wait. - unit_cancel_combo(&sd->bl); - return 0; - } - break; //Combo ready. - case BD_ADAPTATION: - { - int time; - if(!(sc && sc->data[SC_DANCING])) - { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - time = 1000*(sc->data[SC_DANCING]->val3>>16); - if (skill_get_time( - (sc->data[SC_DANCING]->val1&0xFFFF), //Dance Skill ID - (sc->data[SC_DANCING]->val1>>16)) //Dance Skill LV - - time < skill_get_time2(skill_id,skill_lv)) - { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - } - break; - - case PR_BENEDICTIO: - if (skill_check_pc_partner(sd, skill_id, &skill_lv, 1, 0) < 2) - { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - - case SL_SMA: - if(!(sc && sc->data[SC_SMA])) - return 0; - break; - - case HT_POWER: - if(!(sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == skill_id)) - return 0; - break; - - case CG_HERMODE: - if(!npc_check_areanpc(1,sd->bl.m,sd->bl.x,sd->bl.y,skill_get_splash(skill_id, skill_lv))) - { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case CG_MOONLIT: //Check there's no wall in the range+1 area around the caster. [Skotlex] - { - int i,x,y,range = skill_get_splash(skill_id, skill_lv)+1; - int size = range*2+1; - for (i=0;i<size*size;i++) { - x = sd->bl.x+(i%size-range); - y = sd->bl.y+(i/size-range); - if (map_getcell(sd->bl.m,x,y,CELL_CHKWALL)) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - } - } - break; - case PR_REDEMPTIO: - { - int exp; - if( ((exp = pc_nextbaseexp(sd)) > 0 && get_percentage(sd->status.base_exp, exp) < 1) || - ((exp = pc_nextjobexp(sd)) > 0 && get_percentage(sd->status.job_exp, exp) < 1)) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); //Not enough exp. - return 0; - } - break; - } - case AM_TWILIGHT2: - case AM_TWILIGHT3: - if (!party_skill_check(sd, sd->status.party_id, skill_id, skill_lv)) - { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case SG_SUN_WARM: - case SG_MOON_WARM: - case SG_STAR_WARM: - if (sc && sc->data[SC_MIRACLE]) - break; - i = skill_id-SG_SUN_WARM; - if (sd->bl.m == sd->feel_map[i].m) - break; - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - break; - case SG_SUN_COMFORT: - case SG_MOON_COMFORT: - case SG_STAR_COMFORT: - if (sc && sc->data[SC_MIRACLE]) - break; - i = skill_id-SG_SUN_COMFORT; - if (sd->bl.m == sd->feel_map[i].m && - (battle_config.allow_skill_without_day || sg_info[i].day_func())) - break; - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - case SG_FUSION: - if (sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_STAR) - break; - //Auron insists we should implement SP consumption when you are not Soul Linked. [Skotlex] - //Only invoke on skill begin cast (instant cast skill). [Kevin] - if( require.sp > 0 ) - { - if (status->sp < (unsigned int)require.sp) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_SP_INSUFFICIENT,0); - else - status_zap(&sd->bl, 0, require.sp); - } - return 0; - case GD_BATTLEORDER: - case GD_REGENERATION: - case GD_RESTORE: - if (!map_flag_gvg2(sd->bl.m)) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - case GD_EMERGENCYCALL: - // other checks were already done in skillnotok() - if (!sd->status.guild_id || !sd->state.gmaster_flag) - return 0; - break; - - case GS_GLITTERING: - if(sd->spiritball >= 10) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - - case NJ_ISSEN: -#ifdef RENEWAL - if (status->hp < (status->hp/100)) { -#else - if (status->hp < 2) { -#endif - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - case NJ_BUNSINJYUTSU: - if (!(sc && sc->data[SC_NEN])) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - - case NJ_ZENYNAGE: - case KO_MUCHANAGE: - if(sd->status.zeny < require.zeny) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_MONEY,0); - return 0; - } - break; - case PF_HPCONVERSION: - if (status->sp == status->max_sp) - return 0; //Unusable when at full SP. - break; - case AM_CALLHOMUN: //Can't summon if a hom is already out - if (sd->status.hom_id && sd->hd && !sd->hd->homunculus.vaporize) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case AM_REST: //Can't vapo homun if you don't have an active homunc or it's hp is < 80% - if (!merc_is_hom_active(sd->hd) || sd->hd->battle_status.hp < (sd->hd->battle_status.max_hp*80/100)) - { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - /** - * Arch Bishop - **/ - case AB_ANCILLA: - { - int count = 0; - for( i = 0; i < MAX_INVENTORY; i ++ ) - if( sd->status.inventory[i].nameid == ITEMID_ANCILLA ) - count += sd->status.inventory[i].amount; - if( count >= 3 ) { - clif_skill_fail(sd, skill_id, USESKILL_FAIL_ANCILLA_NUMOVER, 0); - return 0; - } - } - break; - /** - * Keeping as a note: - * Bug Report #17 provides a link to a sep-2011 changelog that shows this requirement was removed - **/ - //case AB_LAUDAAGNUS: - //case AB_LAUDARAMUS: - // if( !sd->status.party_id ) { - // clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - // return 0; - // } - // break; - - case AB_ADORAMUS: - /** - * Warlock - **/ - case WL_COMET: - if( skill_check_pc_partner(sd,skill_id,&skill_lv,1,0) <= 0 && ((i = pc_search_inventory(sd,require.itemid[0])) < 0 || sd->status.inventory[i].amount < require.amount[0]) ) - { - //clif_skill_fail(sd,skill_id,USESKILL_FAIL_NEED_ITEM,require.amount[0],require.itemid[0]); - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case WL_SUMMONFB: - case WL_SUMMONBL: - case WL_SUMMONWB: - case WL_SUMMONSTONE: - if( sc ) - { - ARR_FIND(SC_SPHERE_1,SC_SPHERE_5+1,i,!sc->data[i]); - if( i == SC_SPHERE_5+1 ) - { // No more free slots - clif_skill_fail(sd,skill_id,USESKILL_FAIL_SUMMON,0); - return 0; - } - } - break; - /** - * Guilotine Cross - **/ - case GC_HALLUCINATIONWALK: - if( sc && (sc->data[SC_HALLUCINATIONWALK] || sc->data[SC_HALLUCINATIONWALK_POSTDELAY]) ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case GC_COUNTERSLASH: - case GC_WEAPONCRUSH: - if( !(sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == GC_WEAPONBLOCKING) ) { - clif_skill_fail(sd, skill_id, USESKILL_FAIL_GC_WEAPONBLOCKING, 0); - return 0; - } - break; - /** - * Ranger - **/ - case RA_WUGMASTERY: - if( pc_isfalcon(sd) || pc_isridingwug(sd) || sd->sc.data[SC__GROOMY]) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case RA_WUGSTRIKE: - if( !pc_iswug(sd) && !pc_isridingwug(sd) ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case RA_WUGRIDER: - if( pc_isfalcon(sd) || ( !pc_isridingwug(sd) && !pc_iswug(sd) ) ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case RA_WUGDASH: - if(!pc_isridingwug(sd)) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - /** - * Royal Guard - **/ - case LG_BANDING: - if( sc && sc->data[SC_INSPIRATION] ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case LG_PRESTIGE: - if( sc && (sc->data[SC_BANDING] || sc->data[SC_INSPIRATION]) ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case LG_RAGEBURST: - if( sd->spiritball == 0 ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_SKILLINTERVAL,0); - return 0; - } - sd->spiritball_old = require.spiritball = sd->spiritball; - break; - case LG_RAYOFGENESIS: - if( sc && sc->data[SC_INSPIRATION] ) - return 1; // Don't check for partner. - if( !(sc && sc->data[SC_BANDING]) ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL,0); - return 0; - } else if( skill_check_pc_partner(sd,skill_id,&skill_lv,skill_get_range(skill_id,skill_lv),0) < 1 ) - return 0; // Just fails, no msg here. - break; - case LG_HESPERUSLIT: - if( !sc || !sc->data[SC_BANDING] ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case SR_FALLENEMPIRE: - if( !(sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == SR_DRAGONCOMBO) ) - return 0; - break; - - case SR_CRESCENTELBOW: - if( sc && sc->data[SC_CRESCENTELBOW] ) { - clif_skill_fail(sd, skill_id, USESKILL_FAIL_DUPLICATE, 0); - return 0; - } - break; - case SR_CURSEDCIRCLE: - if (map_flag_gvg(sd->bl.m)) { - if (map_foreachinrange(mob_count_sub, &sd->bl, skill_get_splash(skill_id, skill_lv), BL_MOB, - MOBID_EMPERIUM, MOBID_GUARIDAN_STONE1, MOBID_GUARIDAN_STONE2)) { - char output[128]; - sprintf(output, "You're too close to a stone or emperium to do this skill"); - clif_colormes(sd, COLOR_RED, output); - return 0; - } - } - if( sd->spiritball > 0 ) - sd->spiritball_old = require.spiritball = sd->spiritball; - else { - clif_skill_fail(sd,skill_id,0,0); - return 0; - } - break; - case SR_GATEOFHELL: - if( sd->spiritball > 0 ) - sd->spiritball_old = require.spiritball; - break; - case SC_MANHOLE: - case SC_DIMENSIONDOOR: - if( sc && sc->data[SC_MAGNETICFIELD] ) { - clif_skill_fail(sd,skill_id,0,0); - return 0; - } - break; - case WM_GREAT_ECHO: { - int count; - count = skill_check_pc_partner(sd, skill_id, &skill_lv, skill_get_splash(skill_id,skill_lv), 0); - if( count < 1 ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_NEED_HELPER,0); - return 0; - } else - require.sp -= require.sp * 20 * count / 100; // -20% each W/M in the party. - } - break; - case SO_FIREWALK: - case SO_ELECTRICWALK: // Can't be casted until you've walked all cells. - if( sc && sc->data[SC_PROPERTYWALK] && - sc->data[SC_PROPERTYWALK]->val3 < skill_get_maxcount(sc->data[SC_PROPERTYWALK]->val1,sc->data[SC_PROPERTYWALK]->val2) ) { - clif_skill_fail(sd,skill_id,0x0,0); - return 0; - } - break; - case SO_EL_CONTROL: - if( !sd->status.ele_id || !sd->ed ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case RETURN_TO_ELDICASTES: - if( pc_ismadogear(sd) ) { //Cannot be used if Mado is equipped. - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case LG_REFLECTDAMAGE: - case CR_REFLECTSHIELD: - if( sc && sc->data[SC_KYOMU] && rand()%100 < 30){ - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case KO_KAHU_ENTEN: - case KO_HYOUHU_HUBUKI: - case KO_KAZEHU_SEIRAN: - case KO_DOHU_KOUKAI: - { - int ttype = skill_get_ele(skill_id, skill_lv); - ARR_FIND(1, 5, i, sd->talisman[i] > 0 && i != ttype); - if( (i < 5 && i != ttype) || sd->talisman[ttype] >= 10 ){ - clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); - return 0; - } - } - break; - case KO_KAIHOU: - case KO_ZENKAI: - ARR_FIND(1, 6, i, sd->talisman[i] > 0); - if( i > 4 ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - } - - switch(require.state) { - case ST_HIDING: - if(!(sc && sc->option&OPTION_HIDE)) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case ST_CLOAKING: - if(!pc_iscloaking(sd)) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case ST_HIDDEN: - if(!pc_ishiding(sd)) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case ST_RIDING: - if(!pc_isriding(sd)) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case ST_FALCON: - if(!pc_isfalcon(sd)) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case ST_CARTBOOST: - if(!(sc && sc->data[SC_CARTBOOST])) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - case ST_CART: - if(!pc_iscarton(sd)) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case ST_SHIELD: - if(sd->status.shield <= 0) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case ST_SIGHT: - if(!(sc && sc->data[SC_SIGHT])) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case ST_EXPLOSIONSPIRITS: - if(!(sc && sc->data[SC_EXPLOSIONSPIRITS])) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case ST_RECOV_WEIGHT_RATE: - if(battle_config.natural_heal_weight_rate <= 100 && sd->weight*100/sd->max_weight >= (unsigned int)battle_config.natural_heal_weight_rate) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case ST_MOVE_ENABLE: - if (sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == skill_id) - sd->ud.canmove_tick = gettick(); //When using a combo, cancel the can't move delay to enable the skill. [Skotlex] - - if (!unit_can_move(&sd->bl)) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - case ST_WATER: - if (sc && (sc->data[SC_DELUGE] || sc->data[SC_SUITON])) - break; - if (map_getcell(sd->bl.m,sd->bl.x,sd->bl.y,CELL_CHKWATER)) - break; - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - /** - * Rune Knight - **/ - case ST_RIDINGDRAGON: - if( !pc_isridingdragon(sd) ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - /** - * Wug - **/ - case ST_WUG: - if( !pc_iswug(sd) ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - /** - * Riding Wug - **/ - case ST_RIDINGWUG: - if( !pc_isridingwug(sd) ){ - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - /** - * Mechanic - **/ - case ST_MADO: - if( !pc_ismadogear(sd) ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - break; - /** - * Sorcerer - **/ - case ST_ELEMENTALSPIRIT: - if(!sd->ed) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_EL_SUMMON,0); - return 0; - } - break; - case ST_POISONINGWEAPON: - if (!(sc && sc->data[SC_POISONINGWEAPON])) { - clif_skill_fail(sd, skill_id, USESKILL_FAIL_GC_POISONINGWEAPON, 0); - return 0; - } - break; - case ST_ROLLINGCUTTER: - if (!(sc && sc->data[SC_ROLLINGCUTTER])) { - clif_skill_fail(sd, skill_id, USESKILL_FAIL_CONDITION, 0); - return 0; - } - break; - case ST_MH_FIGHTING: - if (!(sc && sc->data[SC_STYLE_CHANGE] && sc->data[SC_STYLE_CHANGE]->val2 == MH_MD_FIGHTING)){ - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - case ST_MH_GRAPPLING: - if (!(sc && sc->data[SC_STYLE_CHANGE] && sc->data[SC_STYLE_CHANGE]->val2 == MH_MD_GRAPPLING)){ - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - } - - if(require.mhp > 0 && get_percentage(status->hp, status->max_hp) > require.mhp) { - //mhp is the max-hp-requirement, that is, - //you must have this % or less of HP to cast it. - clif_skill_fail(sd,skill_id,USESKILL_FAIL_HP_INSUFFICIENT,0); - return 0; - } - - if( require.weapon && !pc_check_weapontype(sd,require.weapon) ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_THIS_WEAPON,0); - return 0; - } - - if( require.sp > 0 && status->sp < (unsigned int)require.sp) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_SP_INSUFFICIENT,0); - return 0; - } - - if( require.zeny > 0 && sd->status.zeny < require.zeny ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_MONEY,0); - return 0; - } - - if( require.spiritball > 0 && sd->spiritball < require.spiritball) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_SPIRITS,require.spiritball); - return 0; - } - - return 1; -} - -int skill_check_condition_castend(struct map_session_data* sd, uint16 skill_id, uint16 skill_lv) -{ - struct skill_condition require; - struct status_data *status; - int i; - int index[MAX_SKILL_ITEM_REQUIRE]; - - nullpo_ret(sd); - - if( sd->chatID ) - return 0; - - if( pc_has_permission(sd, PC_PERM_SKILL_UNCONDITIONAL) && sd->skillitem != skill_id ) { - //GMs don't override the skillItem check, otherwise they can use items without them being consumed! [Skotlex] - sd->state.arrow_atk = skill_get_ammotype(skill_id)?1:0; //Need to do arrow state check. - sd->spiritball_old = sd->spiritball; //Need to do Spiritball check. - return 1; - } - - switch( sd->menuskill_id ) { // Cast start or cast end?? - case AM_PHARMACY: - switch( skill_id ) { - case AM_PHARMACY: - case AC_MAKINGARROW: - case BS_REPAIRWEAPON: - case AM_TWILIGHT1: - case AM_TWILIGHT2: - case AM_TWILIGHT3: - return 0; - } - break; - case GN_MIX_COOKING: - case GN_MAKEBOMB: - case GN_S_PHARMACY: - case GN_CHANGEMATERIAL: - if( sd->menuskill_id != skill_id ) - return 0; - break; - } - - if( sd->skillitem == skill_id ) // Casting finished (Item skill or Hocus-Pocus) - return 1; - - if( pc_is90overweight(sd) ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_WEIGHTOVER,0); - return 0; - } - - // perform skill-specific checks (and actions) - switch( skill_id ) { - case PR_BENEDICTIO: - skill_check_pc_partner(sd, skill_id, &skill_lv, 1, 1); - break; - case AM_CANNIBALIZE: - case AM_SPHEREMINE: { - int c=0; - int summons[5] = { 1589, 1579, 1575, 1555, 1590 }; - //int summons[5] = { 1020, 1068, 1118, 1500, 1368 }; - int maxcount = (skill_id==AM_CANNIBALIZE)? 6-skill_lv : skill_get_maxcount(skill_id,skill_lv); - int mob_class = (skill_id==AM_CANNIBALIZE)? summons[skill_lv-1] :1142; - if(battle_config.land_skill_limit && maxcount>0 && (battle_config.land_skill_limit&BL_PC)) { - i = map_foreachinmap(skill_check_condition_mob_master_sub ,sd->bl.m, BL_MOB, sd->bl.id, mob_class, skill_id, &c); - if(c >= maxcount || - (skill_id==AM_CANNIBALIZE && c != i && battle_config.summon_flora&2)) - { //Fails when: exceed max limit. There are other plant types already out. - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - } - break; - } - case NC_SILVERSNIPER: - case NC_MAGICDECOY: { - int c = 0, j; - int maxcount = skill_get_maxcount(skill_id,skill_lv); - int mob_class = 2042; - if( skill_id == NC_MAGICDECOY ) - mob_class = 2043; - - if( battle_config.land_skill_limit && maxcount > 0 && ( battle_config.land_skill_limit&BL_PC ) ) { - if( skill_id == NC_MAGICDECOY ) { - for( j = mob_class; j <= 2046; j++ ) - map_foreachinmap(skill_check_condition_mob_master_sub, sd->bl.m, BL_MOB, sd->bl.id, j, skill_id, &c); - } else - map_foreachinmap(skill_check_condition_mob_master_sub, sd->bl.m, BL_MOB, sd->bl.id, mob_class, skill_id, &c); - if( c >= maxcount ) { - clif_skill_fail(sd , skill_id, USESKILL_FAIL_LEVEL, 0); - return 0; - } - } - } - break; - case KO_ZANZOU: { - int c = 0; - i = map_foreachinmap(skill_check_condition_mob_master_sub, sd->bl.m, BL_MOB, sd->bl.id, 2308, skill_id, &c); - if( c >= skill_get_maxcount(skill_id,skill_lv) || c != i) - { - clif_skill_fail(sd , skill_id, USESKILL_FAIL_LEVEL, 0); - return 0; - } - } - break; - } - - status = &sd->battle_status; - - require = skill_get_requirement(sd,skill_id,skill_lv); - - if( require.hp > 0 && status->hp <= (unsigned int)require.hp) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_HP_INSUFFICIENT,0); - return 0; - } - - if( require.weapon && !pc_check_weapontype(sd,require.weapon) ) { - clif_skill_fail(sd,skill_id,USESKILL_FAIL_THIS_WEAPON,0); - return 0; - } - - if( require.ammo ) { //Skill requires stuff equipped in the arrow slot. - if((i=sd->equip_index[EQI_AMMO]) < 0 || !sd->inventory_data[i] ) { - clif_arrow_fail(sd,0); - return 0; - } else if( sd->status.inventory[i].amount < require.ammo_qty ) { - char e_msg[100]; - sprintf(e_msg,"Skill Failed. [%s] requires %dx %s.", - skill_get_desc(skill_id), - require.ammo_qty, - itemdb_jname(sd->status.inventory[i].nameid)); - clif_colormes(sd,COLOR_RED,e_msg); - return 0; - } - if (!(require.ammo&1<<sd->inventory_data[i]->look)) { //Ammo type check. Send the "wrong weapon type" message - //which is the closest we have to wrong ammo type. [Skotlex] - clif_arrow_fail(sd,0); //Haplo suggested we just send the equip-arrows message instead. [Skotlex] - //clif_skill_fail(sd,skill_id,USESKILL_FAIL_THIS_WEAPON,0); - return 0; - } - } - - for( i = 0; i < MAX_SKILL_ITEM_REQUIRE; ++i ) { - if( !require.itemid[i] ) - continue; - index[i] = pc_search_inventory(sd,require.itemid[i]); - if( index[i] < 0 || sd->status.inventory[index[i]].amount < require.amount[i] ) { - if( require.itemid[i] == ITEMID_RED_GEMSTONE ) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_REDJAMSTONE,0);// red gemstone required - else if( require.itemid[i] == ITEMID_BLUE_GEMSTONE ) - clif_skill_fail(sd,skill_id,USESKILL_FAIL_BLUEJAMSTONE,0);// blue gemstone required - else - clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); - return 0; - } - } - - return 1; -} - -// type&2: consume items (after skill was used) -// type&1: consume the others (before skill was used) -int skill_consume_requirement( struct map_session_data *sd, uint16 skill_id, uint16 skill_lv, short type) -{ - struct skill_condition req; - - nullpo_ret(sd); - - req = skill_get_requirement(sd,skill_id,skill_lv); - - if( type&1 ) - { - if( skill_id == CG_TAROTCARD || sd->state.autocast ) - req.sp = 0; // TarotCard will consume sp in skill_cast_nodamage_id [Inkfish] - if(req.hp || req.sp) - status_zap(&sd->bl, req.hp, req.sp); - - if(req.spiritball > 0) - pc_delspiritball(sd,req.spiritball,0); - - if(req.zeny > 0) - { - if( skill_id == NJ_ZENYNAGE ) - req.zeny = 0; //Zeny is reduced on skill_attack. - if( sd->status.zeny < req.zeny ) - req.zeny = sd->status.zeny; - pc_payzeny(sd,req.zeny,LOG_TYPE_CONSUME,NULL); - } - } - - if( type&2 ) - { - struct status_change *sc = &sd->sc; - int n,i; - - if( !sc->count ) - sc = NULL; - - for( i = 0; i < MAX_SKILL_ITEM_REQUIRE; ++i ) - { - if( !req.itemid[i] ) - continue; - - if( itemid_isgemstone(req.itemid[i]) && skill_id != HW_GANBANTEIN && sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_WIZARD ) - continue; //Gemstones are checked, but not substracted from inventory. - - switch( skill_id ){ - case SA_SEISMICWEAPON: - if( sc && sc->data[SC_UPHEAVAL_OPTION] && rnd()%100 < 50 ) - continue; - break; - case SA_FLAMELAUNCHER: - case SA_VOLCANO: - if( sc && sc->data[SC_TROPIC_OPTION] && rnd()%100 < 50 ) - continue; - break; - case SA_FROSTWEAPON: - case SA_DELUGE: - if( sc && sc->data[SC_CHILLY_AIR_OPTION] && rnd()%100 < 50 ) - continue; - break; - case SA_LIGHTNINGLOADER: - case SA_VIOLENTGALE: - if( sc && sc->data[SC_WILD_STORM_OPTION] && rnd()%100 < 50 ) - continue; - break; - } - - if( (n = pc_search_inventory(sd,req.itemid[i])) >= 0 ) - pc_delitem(sd,n,req.amount[i],0,1,LOG_TYPE_CONSUME); - } - } - - return 1; -} - -struct skill_condition skill_get_requirement(struct map_session_data* sd, uint16 skill_id, uint16 skill_lv) -{ - struct skill_condition req; - struct status_data *status; - struct status_change *sc; - int i,hp_rate,sp_rate, sp_skill_rate_bonus = 100; - uint16 idx; - - memset(&req,0,sizeof(req)); - - if( !sd ) - return req; - - if( sd->skillitem == skill_id ) - return req; // Item skills and Hocus-Pocus don't have requirements.[Inkfish] - - sc = &sd->sc; - if( !sc->count ) - sc = NULL; - - switch( skill_id ) - { // Turn off check. - case BS_MAXIMIZE: case NV_TRICKDEAD: case TF_HIDING: case AS_CLOAKING: case CR_AUTOGUARD: - case ML_AUTOGUARD: case CR_DEFENDER: case ML_DEFENDER: case ST_CHASEWALK: case PA_GOSPEL: - case CR_SHRINK: case TK_RUN: case GS_GATLINGFEVER: case TK_READYCOUNTER: case TK_READYDOWN: - case TK_READYSTORM: case TK_READYTURN: case SG_FUSION: case KO_YAMIKUMO: - if( sc && sc->data[status_skill2sc(skill_id)] ) - return req; - } - - idx = skill_get_index(skill_id); - if( idx == 0 ) // invalid skill id - return req; - if( skill_lv < 1 || skill_lv > MAX_SKILL_LEVEL ) - return req; - - status = &sd->battle_status; - - req.hp = skill_db[idx].hp[skill_lv-1]; - hp_rate = skill_db[idx].hp_rate[skill_lv-1]; - if(hp_rate > 0) - req.hp += (status->hp * hp_rate)/100; - else - req.hp += (status->max_hp * (-hp_rate))/100; - - req.sp = skill_db[idx].sp[skill_lv-1]; - if((sd->skill_id_old == BD_ENCORE) && skill_id == sd->skill_id_dance) - req.sp /= 2; - sp_rate = skill_db[idx].sp_rate[skill_lv-1]; - if(sp_rate > 0) - req.sp += (status->sp * sp_rate)/100; - else - req.sp += (status->max_sp * (-sp_rate))/100; - if( sd->dsprate != 100 ) - req.sp = req.sp * sd->dsprate / 100; - - ARR_FIND(0, ARRAYLENGTH(sd->skillusesprate), i, sd->skillusesprate[i].id == skill_id); - if( i < ARRAYLENGTH(sd->skillusesprate) ) - sp_skill_rate_bonus += sd->skillusesprate[i].val; - ARR_FIND(0, ARRAYLENGTH(sd->skillusesp), i, sd->skillusesp[i].id == skill_id); - if( i < ARRAYLENGTH(sd->skillusesp) ) - req.sp -= sd->skillusesp[i].val; - - req.sp = cap_value(req.sp * sp_skill_rate_bonus / 100, 0, SHRT_MAX); - - if( sc ) { - if( sc->data[SC__LAZINESS] ) - req.sp += req.sp + sc->data[SC__LAZINESS]->val1 * 10; - if (sc->data[SC_UNLIMITEDHUMMINGVOICE]) - req.sp += req.sp * sc->data[SC_UNLIMITEDHUMMINGVOICE]->val2 / 100; - if( sc->data[SC_RECOGNIZEDSPELL] ) - req.sp += req.sp / 4; - } - - req.zeny = skill_db[idx].zeny[skill_lv-1]; - - if( sc && sc->data[SC__UNLUCKY] ) - req.zeny += sc->data[SC__UNLUCKY]->val1 * 500; - - req.spiritball = skill_db[idx].spiritball[skill_lv-1]; - - req.state = skill_db[idx].state; - - req.mhp = skill_db[idx].mhp[skill_lv-1]; - - req.weapon = skill_db[idx].weapon; - - req.ammo_qty = skill_db[idx].ammo_qty[skill_lv-1]; - if (req.ammo_qty) - req.ammo = skill_db[idx].ammo; - - if (!req.ammo && skill_id && skill_isammotype(sd, skill_id)) - { //Assume this skill is using the weapon, therefore it requires arrows. - req.ammo = 0xFFFFFFFF; //Enable use on all ammo types. - req.ammo_qty = 1; - } - - for( i = 0; i < MAX_SKILL_ITEM_REQUIRE; i++ ) { - if( (skill_id == AM_POTIONPITCHER || skill_id == CR_SLIMPITCHER || skill_id == CR_CULTIVATION) && i != skill_lv%11 - 1 ) - continue; - - switch( skill_id ) { - case AM_CALLHOMUN: - if (sd->status.hom_id) //Don't delete items when hom is already out. - continue; - break; - case NC_SHAPESHIFT: - if( i < 4 ) - continue; - break; - case WZ_FIREPILLAR: // celest - if (skill_lv <= 5) // no gems required at level 1-5 - continue; - break; - case AB_ADORAMUS: - if( itemid_isgemstone(skill_db[idx].itemid[i]) && skill_check_pc_partner(sd,skill_id,&skill_lv, 1, 2) ) - continue; - break; - case WL_COMET: - if( itemid_isgemstone(skill_db[idx].itemid[i]) && skill_check_pc_partner(sd,skill_id,&skill_lv, 1, 0) ) - continue; - break; - case GN_FIRE_EXPANSION: - if( i < 5 ) - continue; - break; - case SO_SUMMON_AGNI: - case SO_SUMMON_AQUA: - case SO_SUMMON_VENTUS: - case SO_SUMMON_TERA: - case SO_WATER_INSIGNIA: - case SO_FIRE_INSIGNIA: - case SO_WIND_INSIGNIA: - case SO_EARTH_INSIGNIA: - if( i < 3 ) - continue; - break; - } - - req.itemid[i] = skill_db[idx].itemid[i]; - req.amount[i] = skill_db[idx].amount[i]; - - if( itemid_isgemstone(req.itemid[i]) && skill_id != HW_GANBANTEIN ) - { - if( sd->special_state.no_gemstone ) - { //Make it substract 1 gem rather than skipping the cost. - if( --req.amount[i] < 1 ) - req.itemid[i] = 0; - } - if(sc && sc->data[SC_INTOABYSS]) - { - if( skill_id != SA_ABRACADABRA ) - req.itemid[i] = req.amount[i] = 0; - else if( --req.amount[i] < 1 ) - req.amount[i] = 1; // Hocus Pocus allways use at least 1 gem - } - } - if( skill_id >= HT_SKIDTRAP && skill_id <= HT_TALKIEBOX && pc_checkskill(sd, RA_RESEARCHTRAP) > 0){ - int16 itIndex; - if( (itIndex = pc_search_inventory(sd,req.itemid[i])) < 0 || ( itIndex >= 0 && sd->status.inventory[itIndex].amount < req.amount[i] ) ){ - req.itemid[i] = ITEMID_TRAP_ALLOY; - req.amount[i] = 1; - } - break; - } - } - - /* requirements are level-dependent */ - switch( skill_id ) { - case NC_SHAPESHIFT: - case GN_FIRE_EXPANSION: - case SO_SUMMON_AGNI: - case SO_SUMMON_AQUA: - case SO_SUMMON_VENTUS: - case SO_SUMMON_TERA: - case SO_WATER_INSIGNIA: - case SO_FIRE_INSIGNIA: - case SO_WIND_INSIGNIA: - case SO_EARTH_INSIGNIA: - req.itemid[skill_lv-1] = skill_db[idx].itemid[skill_lv-1]; - req.amount[skill_lv-1] = skill_db[idx].amount[skill_lv-1]; - break; - } - - // Check for cost reductions due to skills & SCs - switch(skill_id) { - case MC_MAMMONITE: - if(pc_checkskill(sd,BS_UNFAIRLYTRICK)>0) - req.zeny -= req.zeny*10/100; - break; - case AL_HOLYLIGHT: - if(sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_PRIEST) - req.sp *= 5; - break; - case SL_SMA: - case SL_STUN: - case SL_STIN: - { - int kaina_lv = pc_checkskill(sd,SL_KAINA); - - if(kaina_lv==0 || sd->status.base_level<70) - break; - if(sd->status.base_level>=90) - req.sp -= req.sp*7*kaina_lv/100; - else if(sd->status.base_level>=80) - req.sp -= req.sp*5*kaina_lv/100; - else if(sd->status.base_level>=70) - req.sp -= req.sp*3*kaina_lv/100; - } - break; - case MO_TRIPLEATTACK: - case MO_CHAINCOMBO: - case MO_COMBOFINISH: - case CH_TIGERFIST: - case CH_CHAINCRUSH: - if(sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_MONK) - req.sp -= req.sp*25/100; //FIXME: Need real data. this is a custom value. - break; - case MO_BODYRELOCATION: - if( sc && sc->data[SC_EXPLOSIONSPIRITS] ) - req.spiritball = 0; - break; - case MO_EXTREMITYFIST: - if( sc ) - { - if( sc->data[SC_BLADESTOP] ) - req.spiritball--; - else if( sc->data[SC_COMBO] ) - { - switch( sc->data[SC_COMBO]->val1 ) - { - case MO_COMBOFINISH: - req.spiritball = 4; - break; - case CH_TIGERFIST: - req.spiritball = 3; - break; - case CH_CHAINCRUSH: //It should consume whatever is left as long as it's at least 1. - req.spiritball = sd->spiritball?sd->spiritball:1; - break; - } - }else if( sc->data[SC_RAISINGDRAGON] && sd->spiritball > 5) - req.spiritball = sd->spiritball; // must consume all regardless of the amount required - } - break; - case SR_RAMPAGEBLASTER: - req.spiritball = sd->spiritball?sd->spiritball:15; - break; - case SR_GATEOFHELL: - if( sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == SR_FALLENEMPIRE ) - req.sp -= req.sp * 10 / 100; - break; - case SO_SUMMON_AGNI: - case SO_SUMMON_AQUA: - case SO_SUMMON_VENTUS: - case SO_SUMMON_TERA: - req.sp -= req.sp * (5 + 5 * pc_checkskill(sd,SO_EL_SYMPATHY)) / 100; - break; - case SO_PSYCHIC_WAVE: - if( sc && sc->data[SC_BLAST_OPTION] ) - req.sp += req.sp * 150 / 100; - break; - } - - return req; -} - -/*========================================== - * Does cast-time reductions based on dex, item bonuses and config setting - *------------------------------------------*/ -int skill_castfix (struct block_list *bl, uint16 skill_id, uint16 skill_lv) { - int time = skill_get_cast(skill_id, skill_lv); - - nullpo_ret(bl); -#ifndef RENEWAL_CAST - { - struct map_session_data *sd; - - sd = BL_CAST(BL_PC, bl); - - // calculate base cast time (reduced by dex) - if( !(skill_get_castnodex(skill_id, skill_lv)&1) ) { - int scale = battle_config.castrate_dex_scale - status_get_dex(bl); - if( scale > 0 ) // not instant cast - time = time * scale / battle_config.castrate_dex_scale; - else - return 0; // instant cast - } - - // calculate cast time reduced by item/card bonuses - if( !(skill_get_castnodex(skill_id, skill_lv)&4) && sd ) - { - int i; - if( sd->castrate != 100 ) - time = time * sd->castrate / 100; - for( i = 0; i < ARRAYLENGTH(sd->skillcast) && sd->skillcast[i].id; i++ ) - { - if( sd->skillcast[i].id == skill_id ) - { - time+= time * sd->skillcast[i].val / 100; - break; - } - } - } - - } -#endif - // config cast time multiplier - if (battle_config.cast_rate != 100) - time = time * battle_config.cast_rate / 100; - // return final cast time - time = max(time, 0); - -// ShowInfo("Castime castfix = %d\n",time); - return time; -} - -/*========================================== - * Does cast-time reductions based on sc data. - *------------------------------------------*/ -int skill_castfix_sc (struct block_list *bl, int time) -{ - struct status_change *sc = status_get_sc(bl); - - if( time < 0 ) - return 0; - - if (sc && sc->count) { - if (sc->data[SC_SLOWCAST]) - time += time * sc->data[SC_SLOWCAST]->val2 / 100; - if (sc->data[SC_PARALYSIS]) - time += sc->data[SC_PARALYSIS]->val3; - if (sc->data[SC_SUFFRAGIUM]) { - time -= time * sc->data[SC_SUFFRAGIUM]->val2 / 100; - status_change_end(bl, SC_SUFFRAGIUM, INVALID_TIMER); - } - if (sc->data[SC_MEMORIZE]) { - time>>=1; - if ((--sc->data[SC_MEMORIZE]->val2) <= 0) - status_change_end(bl, SC_MEMORIZE, INVALID_TIMER); - } - if (sc->data[SC_POEMBRAGI]) - time -= time * sc->data[SC_POEMBRAGI]->val2 / 100; - if (sc->data[SC_IZAYOI]) - time -= time * 50 / 100; - } - time = max(time, 0); - -// ShowInfo("Castime castfix_sc = %d\n",time); - return time; -} -#ifdef RENEWAL_CAST -int skill_vfcastfix (struct block_list *bl, double time, uint16 skill_id, uint16 skill_lv) -{ - struct status_change *sc = status_get_sc(bl); - struct map_session_data *sd = BL_CAST(BL_PC,bl); - int fixed = skill_get_fixed_cast(skill_id, skill_lv), fixcast_r = 0, varcast_r = 0, i = 0; - - if( time < 0 ) - return 0; - - if( fixed == 0 ){ - fixed = (int)time * 20 / 100; // fixed time - time = time * 80 / 100; // variable time - }else if( fixed < 0 ) // no fixed cast time - fixed = 0; - - if(sd && !(skill_get_castnodex(skill_id, skill_lv)&4) ){ // Increases/Decreases fixed/variable cast time of a skill by item/card bonuses. - if( sd->bonus.varcastrate < 0 ) - VARCAST_REDUCTION(sd->bonus.varcastrate); - for (i = 0; i < ARRAYLENGTH(sd->skillfixcast) && sd->skillfixcast[i].id; i++) - if (sd->skillfixcast[i].id == skill_id){ // bonus2 bSkillFixedCast - fixed += sd->skillfixcast[i].val; - break; - } - for( i = 0; i < ARRAYLENGTH(sd->skillvarcast) && sd->skillvarcast[i].id; i++ ) - if( sd->skillvarcast[i].id == skill_id ){ // bonus2 bSkillVariableCast - time += sd->skillvarcast[i].val; - break; - } - for( i = 0; i < ARRAYLENGTH(sd->skillcast) && sd->skillcast[i].id; i++ ) - if( sd->skillcast[i].id == skill_id ){ // bonus2 bVariableCastrate - if( (i=sd->skillcast[i].val) < 0) - VARCAST_REDUCTION(i); - break; - } - } - - if (sc && sc->count && !(skill_get_castnodex(skill_id, skill_lv)&2) ) { - // All variable cast additive bonuses must come first - if (sc->data[SC_SLOWCAST]) - VARCAST_REDUCTION(-sc->data[SC_SLOWCAST]->val2); - - // Variable cast reduction bonuses - if (sc->data[SC_SUFFRAGIUM]) { - VARCAST_REDUCTION(sc->data[SC_SUFFRAGIUM]->val2); - status_change_end(bl, SC_SUFFRAGIUM, INVALID_TIMER); - } - if (sc->data[SC_MEMORIZE]) { - VARCAST_REDUCTION(50); - if ((--sc->data[SC_MEMORIZE]->val2) <= 0) - status_change_end(bl, SC_MEMORIZE, INVALID_TIMER); - } - if (sc->data[SC_POEMBRAGI]) - VARCAST_REDUCTION(sc->data[SC_POEMBRAGI]->val2); - if (sc->data[SC_IZAYOI]) - VARCAST_REDUCTION(50); - if (sc->data[SC_WATER_INSIGNIA] && sc->data[SC_WATER_INSIGNIA]->val1 == 3 && (skill_get_ele(skill_id, skill_lv) == ELE_WATER)) - VARCAST_REDUCTION(30); //Reduces 30% Variable Cast Time of Water spells. - // Fixed cast reduction bonuses - if( sc->data[SC__LAZINESS] ) - fixcast_r = max(fixcast_r, sc->data[SC__LAZINESS]->val2); - if( sc->data[SC_SECRAMENT] ) - fixcast_r = max(fixcast_r, sc->data[SC_SECRAMENT]->val2); - if( sd && ( skill_lv = pc_checkskill(sd, WL_RADIUS) ) && skill_id >= WL_WHITEIMPRISON && skill_id <= WL_FREEZE_SP ) - fixcast_r = max(fixcast_r, 5 + skill_lv * 5); - // Fixed cast non percentage bonuses - if( sc->data[SC_MANDRAGORA] && (skill_id >= SM_BASH && skill_id <= RETURN_TO_ELDICASTES) ) - fixed += sc->data[SC_MANDRAGORA]->val1 * 1000 / 2; - if (sc->data[SC_IZAYOI] && (skill_id >= NJ_TOBIDOUGU && skill_id <= NJ_ISSEN)) - fixed = 0; - if( sc->data[SC_GUST_OPTION] || sc->data[SC_BLAST_OPTION] || sc->data[SC_WILD_STORM_OPTION] ) - fixed -= 1000; - } - - if( sd && !(skill_get_castnodex(skill_id, skill_lv)&4) ){ - VARCAST_REDUCTION( max(sd->bonus.varcastrate, 0) + max(i, 0) ); - fixcast_r = max(fixcast_r, sd->bonus.fixcastrate) + min(sd->bonus.fixcastrate,0); - } - - if( varcast_r < 0 ) // now compute overall factors - time = time * (1 - (float)varcast_r / 100); - if( !(skill_get_castnodex(skill_id, skill_lv)&1) )// reduction from status point - time = (1 - sqrt( ((float)(status_get_dex(bl)*2 + status_get_int(bl)) / battle_config.vcast_stat_scale) )) * time; - // underflow checking/capping - time = max(time, 0) + (1 - (float)min(fixcast_r, 100) / 100) * max(fixed,0); - - return (int)time; -} -#endif - -/*========================================== - * Does delay reductions based on dex/agi, sc data, item bonuses, ... - *------------------------------------------*/ -int skill_delayfix (struct block_list *bl, uint16 skill_id, uint16 skill_lv) -{ - int delaynodex = skill_get_delaynodex(skill_id, skill_lv); - int time = skill_get_delay(skill_id, skill_lv); - struct map_session_data *sd; - struct status_change *sc = status_get_sc(bl); - - nullpo_ret(bl); - sd = BL_CAST(BL_PC, bl); - - if (skill_id == SA_ABRACADABRA || skill_id == WM_RANDOMIZESPELL) - return 0; //Will use picked skill's delay. - - if (bl->type&battle_config.no_skill_delay) - return battle_config.min_skill_delay_limit; - - if (time < 0) - time = -time + status_get_amotion(bl); // If set to <0, add to attack motion. - - // Delay reductions - switch (skill_id) { //Monk combo skills have their delay reduced by agi/dex. - case MO_TRIPLEATTACK: - case MO_CHAINCOMBO: - case MO_COMBOFINISH: - case CH_TIGERFIST: - case CH_CHAINCRUSH: - case SR_DRAGONCOMBO: - case SR_FALLENEMPIRE: - time -= 4*status_get_agi(bl) - 2*status_get_dex(bl); - break; - case HP_BASILICA: - if( sc && !sc->data[SC_BASILICA] ) - time = 0; // There is no Delay on Basilica creation, only on cancel - break; - default: - if (battle_config.delay_dependon_dex && !(delaynodex&1)) - { // if skill delay is allowed to be reduced by dex - int scale = battle_config.castrate_dex_scale - status_get_dex(bl); - if (scale > 0) - time = time * scale / battle_config.castrate_dex_scale; - else //To be capped later to minimum. - time = 0; - } - if (battle_config.delay_dependon_agi && !(delaynodex&1)) - { // if skill delay is allowed to be reduced by agi - int scale = battle_config.castrate_dex_scale - status_get_agi(bl); - if (scale > 0) - time = time * scale / battle_config.castrate_dex_scale; - else //To be capped later to minimum. - time = 0; - } - } - - if ( sc && sc->data[SC_SPIRIT] ) - { - switch (skill_id) { - case CR_SHIELDBOOMERANG: - if (sc->data[SC_SPIRIT]->val2 == SL_CRUSADER) - time /= 2; - break; - case AS_SONICBLOW: - if (!map_flag_gvg(bl->m) && !map[bl->m].flag.battleground && sc->data[SC_SPIRIT]->val2 == SL_ASSASIN) - time /= 2; - break; - } - } - - if (!(delaynodex&2)) - { - if (sc && sc->count) { - if (sc->data[SC_POEMBRAGI]) - time -= time * sc->data[SC_POEMBRAGI]->val3 / 100; - if (sc->data[SC_WIND_INSIGNIA] && sc->data[SC_WIND_INSIGNIA]->val1 == 3 && (skill_get_ele(skill_id, skill_lv) == ELE_WIND)) - time /= 2; // After Delay of Wind element spells reduced by 50%. - } - - } - - if( !(delaynodex&4) && sd && sd->delayrate != 100 ) - time = time * sd->delayrate / 100; - - if (battle_config.delay_rate != 100) - time = time * battle_config.delay_rate / 100; - - //min delay - time = max(time, status_get_amotion(bl)); // Delay can never be below amotion [Playtester] - time = max(time, battle_config.min_skill_delay_limit); - -// ShowInfo("Delay delayfix = %d\n",time); - return time; -} - -/*========================================= - * - *-----------------------------------------*/ -struct square { - int val1[5]; - int val2[5]; -}; - -static void skill_brandishspear_first (struct square *tc, uint8 dir, int16 x, int16 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; - } - -} - -static void skill_brandishspear_dir (struct square* tc, uint8 dir, int are) -{ - int c; - nullpo_retv(tc); - - for( c = 0; c < 5; c++ ) - { - switch( dir ) - { - case 0: tc->val2[c]+=are; break; - case 1: tc->val1[c]-=are; tc->val2[c]+=are; break; - case 2: tc->val1[c]-=are; break; - case 3: tc->val1[c]-=are; tc->val2[c]-=are; break; - case 4: tc->val2[c]-=are; break; - case 5: tc->val1[c]+=are; tc->val2[c]-=are; break; - case 6: tc->val1[c]+=are; break; - case 7: tc->val1[c]+=are; tc->val2[c]+=are; break; - } - } -} - -void skill_brandishspear(struct block_list* src, struct block_list* bl, uint16 skill_id, uint16 skill_lv, unsigned int tick, int flag) -{ - int c,n=4; - uint8 dir = map_calc_dir(src,bl->x,bl->y); - struct square tc; - int x=bl->x,y=bl->y; - skill_brandishspear_first(&tc,dir,x,y); - skill_brandishspear_dir(&tc,dir,4); - skill_area_temp[1] = bl->id; - - if(skill_lv > 9){ - for(c=1;c<4;c++){ - map_foreachincell(skill_area_sub, - bl->m,tc.val1[c],tc.val2[c],BL_CHAR, - src,skill_id,skill_lv,tick, flag|BCT_ENEMY|n, - skill_castend_damage_id); - } - } - if(skill_lv > 6){ - skill_brandishspear_dir(&tc,dir,-1); - n--; - }else{ - skill_brandishspear_dir(&tc,dir,-2); - n-=2; - } - - if(skill_lv > 3){ - for(c=0;c<5;c++){ - map_foreachincell(skill_area_sub, - bl->m,tc.val1[c],tc.val2[c],BL_CHAR, - src,skill_id,skill_lv,tick, flag|BCT_ENEMY|n, - skill_castend_damage_id); - if(skill_lv > 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_foreachincell(skill_area_sub, - bl->m,tc.val1[c%5],tc.val2[c%5],BL_CHAR, - src,skill_id,skill_lv,tick, flag|BCT_ENEMY|1, - skill_castend_damage_id); - } -} - -/*========================================== - * Weapon Repair [Celest/DracoRPG] - *------------------------------------------*/ -void skill_repairweapon (struct map_session_data *sd, int idx) { - int material; - int materials[4] = { 1002, 998, 999, 756 }; - struct item *item; - struct map_session_data *target_sd; - - nullpo_retv(sd); - - if ( !( target_sd = map_id2sd(sd->menuskill_val) ) ) //Failed.... - return; - - if( idx == 0xFFFF ) // No item selected ('Cancel' clicked) - return; - if( idx < 0 || idx >= MAX_INVENTORY ) - return; //Invalid index?? - - item = &target_sd->status.inventory[idx]; - if( item->nameid <= 0 || item->attribute == 0 ) - return; //Again invalid item.... - - if( sd != target_sd && !battle_check_range(&sd->bl,&target_sd->bl, skill_get_range2(&sd->bl, sd->menuskill_id,sd->menuskill_val2) ) ){ - clif_item_repaireffect(sd,idx,1); - return; - } - - if ( target_sd->inventory_data[idx]->type == IT_WEAPON ) - material = materials [ target_sd->inventory_data[idx]->wlv - 1 ]; // Lv1/2/3/4 weapons consume 1 Iron Ore/Iron/Steel/Rough Oridecon - else - material = materials [2]; // Armors consume 1 Steel - if ( pc_search_inventory(sd,material) < 0 ) { - clif_skill_fail(sd,sd->menuskill_id,USESKILL_FAIL_LEVEL,0); - return; - } - - clif_skill_nodamage(&sd->bl,&target_sd->bl,sd->menuskill_id,1,1); - - item->attribute = 0;/* clear broken state */ - - clif_equiplist(target_sd); - - pc_delitem(sd,pc_search_inventory(sd,material),1,0,0,LOG_TYPE_CONSUME); - - clif_item_repaireffect(sd,idx,0); - - if( sd != target_sd ) - clif_item_repaireffect(target_sd,idx,0); -} - -/*========================================== - * Item Appraisal - *------------------------------------------*/ -void skill_identify (struct map_session_data *sd, int idx) -{ - int flag=1; - - nullpo_retv(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); -} - -/*========================================== - * Weapon Refine [Celest] - *------------------------------------------*/ -void skill_weaponrefine (struct map_session_data *sd, int idx) -{ - nullpo_retv(sd); - - if (idx >= 0 && idx < MAX_INVENTORY) - { - int i = 0, ep = 0, per; - int material[5] = { 0, 1010, 1011, 984, 984 }; - struct item *item; - struct item_data *ditem = sd->inventory_data[idx]; - item = &sd->status.inventory[idx]; - - if(item->nameid > 0 && ditem->type == IT_WEAPON) - { - if( item->refine >= sd->menuskill_val - || item->refine >= 10 // if it's no longer refineable - || ditem->flag.no_refine // if the item isn't refinable - || (i = pc_search_inventory(sd, material [ditem->wlv])) < 0 ) - { - clif_skill_fail(sd,sd->menuskill_id,USESKILL_FAIL_LEVEL,0); - return; - } - - per = status_get_refine_chance(ditem->wlv, (int)item->refine); - per += (((signed int)sd->status.job_level)-50)/2; //Updated per the new kro descriptions. [Skotlex] - - pc_delitem(sd, i, 1, 0, 0, LOG_TYPE_OTHER); - if (per > rnd() % 100) { - log_pick_pc(sd, LOG_TYPE_OTHER, -1, item); - item->refine++; - log_pick_pc(sd, LOG_TYPE_OTHER, 1, item); - if(item->equip) { - ep = item->equip; - pc_unequipitem(sd,idx,3); - } - clif_refine(sd->fd,0,idx,item->refine); - clif_delitem(sd,idx,1,3); - clif_additem(sd,idx,1,0); - if (ep) - pc_equipitem(sd,idx,ep); - clif_misceffect(&sd->bl,3); - if(item->refine == 10 && - item->card[0] == CARD0_FORGE && - (int)MakeDWord(item->card[2],item->card[3]) == sd->status.char_id) - { // Fame point system [DracoRPG] - switch(ditem->wlv){ - case 1: - pc_addfame(sd,1); // Success to refine to +10 a lv1 weapon you forged = +1 fame point - break; - case 2: - pc_addfame(sd,25); // Success to refine to +10 a lv2 weapon you forged = +25 fame point - break; - case 3: - pc_addfame(sd,1000); // Success to refine to +10 a lv3 weapon you forged = +1000 fame point - break; - } - } - } else { - item->refine = 0; - if(item->equip) - pc_unequipitem(sd,idx,3); - clif_refine(sd->fd,1,idx,item->refine); - pc_delitem(sd,idx,1,0,2, LOG_TYPE_OTHER); - clif_misceffect(&sd->bl,2); - clif_emotion(&sd->bl, E_OMG); - } - } - } -} - -/*========================================== - * - *------------------------------------------*/ -int skill_autospell (struct map_session_data *sd, uint16 skill_id) -{ - uint16 skill_lv; - int maxlv=1,lv; - - nullpo_ret(sd); - - skill_lv = sd->menuskill_val; - lv=pc_checkskill(sd,skill_id); - - if(!skill_lv || !lv) return 0; // Player must learn the skill before doing auto-spell [Lance] - - if(skill_id==MG_NAPALMBEAT) maxlv=3; - else if(skill_id==MG_COLDBOLT || skill_id==MG_FIREBOLT || skill_id==MG_LIGHTNINGBOLT){ - if (sd->sc.data[SC_SPIRIT] && sd->sc.data[SC_SPIRIT]->val2 == SL_SAGE) - maxlv =10; //Soul Linker bonus. [Skotlex] - else if(skill_lv==2) maxlv=1; - else if(skill_lv==3) maxlv=2; - else if(skill_lv>=4) maxlv=3; - } - else if(skill_id==MG_SOULSTRIKE){ - if(skill_lv==5) maxlv=1; - else if(skill_lv==6) maxlv=2; - else if(skill_lv>=7) maxlv=3; - } - else if(skill_id==MG_FIREBALL){ - if(skill_lv==8) maxlv=1; - else if(skill_lv>=9) maxlv=2; - } - else if(skill_id==MG_FROSTDIVER) maxlv=1; - else return 0; - - if(maxlv > lv) - maxlv = lv; - - sc_start4(&sd->bl,SC_AUTOSPELL,100,skill_lv,skill_id,maxlv,0, - skill_get_time(SA_AUTOSPELL,skill_lv)); - return 0; -} - -/*========================================== - * Sitting skills functions. - *------------------------------------------*/ -static int skill_sit_count (struct block_list *bl, va_list ap) -{ - struct map_session_data *sd; - int type =va_arg(ap,int); - sd=(struct map_session_data*)bl; - - if(!pc_issit(sd)) - return 0; - - if(type&1 && pc_checkskill(sd,RG_GANGSTER) > 0) - return 1; - - if(type&2 && (pc_checkskill(sd,TK_HPTIME) > 0 || pc_checkskill(sd,TK_SPTIME) > 0)) - return 1; - - return 0; -} - -static int skill_sit_in (struct block_list *bl, va_list ap) -{ - struct map_session_data *sd; - int type =va_arg(ap,int); - - sd=(struct map_session_data*)bl; - - if(!pc_issit(sd)) - return 0; - - if(type&1 && pc_checkskill(sd,RG_GANGSTER) > 0) - sd->state.gangsterparadise=1; - - if(type&2 && (pc_checkskill(sd,TK_HPTIME) > 0 || pc_checkskill(sd,TK_SPTIME) > 0 )) - { - sd->state.rest=1; - status_calc_regen(bl, &sd->battle_status, &sd->regen); - status_calc_regen_rate(bl, &sd->regen, &sd->sc); - } - - return 0; -} - -static int skill_sit_out (struct block_list *bl, va_list ap) -{ - struct map_session_data *sd; - int type =va_arg(ap,int); - sd=(struct map_session_data*)bl; - if(sd->state.gangsterparadise && type&1) - sd->state.gangsterparadise=0; - if(sd->state.rest && type&2) { - sd->state.rest=0; - status_calc_regen(bl, &sd->battle_status, &sd->regen); - status_calc_regen_rate(bl, &sd->regen, &sd->sc); - } - return 0; -} - -int skill_sit (struct map_session_data *sd, int type) -{ - int flag = 0; - int range = 0, lv; - nullpo_ret(sd); - - - if((lv = pc_checkskill(sd,RG_GANGSTER)) > 0) { - flag|=1; - range = skill_get_splash(RG_GANGSTER, lv); - } - if((lv = pc_checkskill(sd,TK_HPTIME)) > 0) { - flag|=2; - range = skill_get_splash(TK_HPTIME, lv); - } - else if ((lv = pc_checkskill(sd,TK_SPTIME)) > 0) { - flag|=2; - range = skill_get_splash(TK_SPTIME, lv); - } - - if( type ) { - clif_status_load(&sd->bl,SI_SIT,1); - } else { - clif_status_load(&sd->bl,SI_SIT,0); - } - - if (!flag) return 0; - - if(type) { - if (map_foreachinrange(skill_sit_count,&sd->bl, range, BL_PC, flag) > 1) - map_foreachinrange(skill_sit_in,&sd->bl, range, BL_PC, flag); - } else { - if (map_foreachinrange(skill_sit_count,&sd->bl, range, BL_PC, flag) < 2) - map_foreachinrange(skill_sit_out,&sd->bl, range, BL_PC, flag); - } - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -int skill_frostjoke_scream (struct block_list *bl, va_list ap) -{ - struct block_list *src; - uint16 skill_id,skill_lv; - unsigned int tick; - - nullpo_ret(bl); - nullpo_ret(src=va_arg(ap,struct block_list*)); - - skill_id=va_arg(ap,int); - skill_lv=va_arg(ap,int); - if(!skill_lv) return 0; - tick=va_arg(ap,unsigned int); - - if (src == bl || status_isdead(bl)) - return 0; - if (bl->type == BL_PC) { - struct map_session_data *sd = (struct map_session_data *)bl; - if ( sd && sd->sc.option&(OPTION_INVISIBLE|OPTION_MADOGEAR) ) - return 0;//Frost Joke / Scream cannot target invisible or MADO Gear characters [Ind] - } - //It has been reported that Scream/Joke works the same regardless of woe-setting. [Skotlex] - if(battle_check_target(src,bl,BCT_ENEMY) > 0) - skill_additional_effect(src,bl,skill_id,skill_lv,BF_MISC,ATK_DEF,tick); - else if(battle_check_target(src,bl,BCT_PARTY) > 0 && rnd()%100 < 10) - skill_additional_effect(src,bl,skill_id,skill_lv,BF_MISC,ATK_DEF,tick); - - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -static void skill_unitsetmapcell (struct skill_unit *src, uint16 skill_id, uint16 skill_lv, cell_t cell, bool flag) -{ - int range = skill_get_unit_range(skill_id,skill_lv); - int x,y; - - for( y = src->bl.y - range; y <= src->bl.y + range; ++y ) - for( x = src->bl.x - range; x <= src->bl.x + range; ++x ) - map_setcell(src->bl.m, x, y, cell, flag); -} - -/*========================================== - * - *------------------------------------------*/ -int skill_attack_area (struct block_list *bl, va_list ap) -{ - struct block_list *src,*dsrc; - int atk_type,skill_id,skill_lv,flag,type; - unsigned int tick; - - if(status_isdead(bl)) - return 0; - - atk_type = va_arg(ap,int); - src=va_arg(ap,struct block_list*); - dsrc=va_arg(ap,struct block_list*); - skill_id=va_arg(ap,int); - skill_lv=va_arg(ap,int); - tick=va_arg(ap,unsigned int); - flag=va_arg(ap,int); - type=va_arg(ap,int); - - - if (skill_area_temp[1] == bl->id) //This is the target of the skill, do a full attack and skip target checks. - return skill_attack(atk_type,src,dsrc,bl,skill_id,skill_lv,tick,flag); - - if(battle_check_target(dsrc,bl,type) <= 0 || - !status_check_skilluse(NULL, bl, skill_id, 2)) - return 0; - - - switch (skill_id) { - case WZ_FROSTNOVA: //Skills that don't require the animation to be removed - case NPC_ACIDBREATH: - case NPC_DARKNESSBREATH: - case NPC_FIREBREATH: - case NPC_ICEBREATH: - case NPC_THUNDERBREATH: - return skill_attack(atk_type,src,dsrc,bl,skill_id,skill_lv,tick,flag); - default: - //Area-splash, disable skill animation. - return skill_attack(atk_type,src,dsrc,bl,skill_id,skill_lv,tick,flag|SD_ANIMATION); - } -} -/*========================================== - * - *------------------------------------------*/ -int skill_clear_group (struct block_list *bl, int flag) -{ - struct unit_data *ud = unit_bl2ud(bl); - struct skill_unit_group *group[MAX_SKILLUNITGROUP]; - int i, count=0; - - nullpo_ret(bl); - if (!ud) return 0; - - //All groups to be deleted are first stored on an array since the array elements shift around when you delete them. [Skotlex] - for (i=0;i<MAX_SKILLUNITGROUP && ud->skillunit[i];i++) - { - switch (ud->skillunit[i]->skill_id) { - case SA_DELUGE: - case SA_VOLCANO: - case SA_VIOLENTGALE: - case SA_LANDPROTECTOR: - case NJ_SUITON: - case NJ_KAENSIN: - if (flag&1) - group[count++]= ud->skillunit[i]; - break; - case SO_WARMER: - if( flag&8 ) - group[count++]= ud->skillunit[i]; - break; - case SC_BLOODYLUST: - if (flag & 32) - group[count++] = ud->skillunit[i]; - break; - default: - if (flag&2 && skill_get_inf2(ud->skillunit[i]->skill_id)&INF2_TRAP) - group[count++]= ud->skillunit[i]; - break; - } - - } - for (i=0;i<count;i++) - skill_delunitgroup(group[i]); - return count; -} - -/*========================================== - * Returns the first element field found [Skotlex] - *------------------------------------------*/ -struct skill_unit_group *skill_locate_element_field(struct block_list *bl) -{ - struct unit_data *ud = unit_bl2ud(bl); - int i; - nullpo_ret(bl); - if (!ud) return NULL; - - for (i=0;i<MAX_SKILLUNITGROUP && ud->skillunit[i];i++) { - switch (ud->skillunit[i]->skill_id) { - case SA_DELUGE: - case SA_VOLCANO: - case SA_VIOLENTGALE: - case SA_LANDPROTECTOR: - case NJ_SUITON: - case SO_WARMER: - case SC_BLOODYLUST: - return ud->skillunit[i]; - } - } - return NULL; -} - -// for graffiti cleaner [Valaris] -int skill_graffitiremover (struct block_list *bl, va_list ap) -{ - struct skill_unit *unit=NULL; - - nullpo_ret(bl); - nullpo_ret(ap); - - if(bl->type!=BL_SKILL || (unit=(struct skill_unit *)bl) == NULL) - return 0; - - if((unit->group) && (unit->group->unit_id == UNT_GRAFFITI)) - skill_delunit(unit); - - return 0; -} - -int skill_greed (struct block_list *bl, va_list ap) -{ - struct block_list *src; - struct map_session_data *sd=NULL; - struct flooritem_data *fitem=NULL; - - nullpo_ret(bl); - nullpo_ret(src = va_arg(ap, struct block_list *)); - - if(src->type == BL_PC && (sd=(struct map_session_data *)src) && bl->type==BL_ITEM && (fitem=(struct flooritem_data *)bl)) - pc_takeitem(sd, fitem); - - return 0; -} -//For Ranger's Detonator [Jobbie/3CeAM] -int skill_detonator(struct block_list *bl, va_list ap) -{ - struct skill_unit *unit=NULL; - struct block_list *src; - int unit_id; - - nullpo_ret(bl); - nullpo_ret(ap); - src = va_arg(ap,struct block_list *); - - if( bl->type != BL_SKILL || (unit = (struct skill_unit *)bl) == NULL || !unit->group ) - return 0; - if( unit->group->src_id != src->id ) - return 0; - - unit_id = unit->group->unit_id; - switch( unit_id ) - { //List of Hunter and Ranger Traps that can be detonate. - case UNT_BLASTMINE: - case UNT_SANDMAN: - case UNT_CLAYMORETRAP: - case UNT_TALKIEBOX: - case UNT_CLUSTERBOMB: - case UNT_FIRINGTRAP: - case UNT_ICEBOUNDTRAP: - if( unit_id == UNT_TALKIEBOX ) - { - clif_talkiebox(bl,unit->group->valstr); - unit->group->val2 = -1; - } - else - map_foreachinrange(skill_trap_splash,bl,skill_get_splash(unit->group->skill_id,unit->group->skill_lv),unit->group->bl_flag,bl,unit->group->tick); - - clif_changetraplook(bl,unit_id == UNT_FIRINGTRAP ? UNT_DUMMYSKILL : UNT_USED_TRAPS); - unit->group->unit_id = UNT_USED_TRAPS; - unit->group->limit = DIFF_TICK(gettick(),unit->group->tick) + - (unit_id == UNT_TALKIEBOX ? 5000 : (unit_id == UNT_CLUSTERBOMB || unit_id == UNT_ICEBOUNDTRAP? 2500 : 1500) ); - break; - } - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -static int skill_cell_overlap(struct block_list *bl, va_list ap) -{ - uint16 skill_id; - int *alive; - struct skill_unit *unit; - - skill_id = va_arg(ap,int); - alive = va_arg(ap,int *); - unit = (struct skill_unit *)bl; - - if (unit == NULL || unit->group == NULL || (*alive) == 0) - return 0; - - switch (skill_id) { - case SA_LANDPROTECTOR: - if( unit->group->skill_id == SA_LANDPROTECTOR ) {//Check for offensive Land Protector to delete both. [Skotlex] - (*alive) = 0; - skill_delunit(unit); - return 1; - } - if( !(skill_get_inf2(unit->group->skill_id)&(INF2_SONG_DANCE|INF2_TRAP)) ) { //It deletes everything except songs/dances and traps - skill_delunit(unit); - return 1; - } - break; - case HW_GANBANTEIN: - case LG_EARTHDRIVE: - if( !(unit->group->state.song_dance&0x1) ) {// Don't touch song/dance. - skill_delunit(unit); - return 1; - } - break; - case SA_VOLCANO: - case SA_DELUGE: - case SA_VIOLENTGALE: -// The official implementation makes them fail to appear when casted on top of ANYTHING -// but I wonder if they didn't actually meant to fail when casted on top of each other? -// hence, I leave the alternate implementation here, commented. [Skotlex] - if (unit->range <= 0) - { - (*alive) = 0; - return 1; - } -/* - switch (unit->group->skill_id) - { //These cannot override each other. - case SA_VOLCANO: - case SA_DELUGE: - case SA_VIOLENTGALE: - (*alive) = 0; - return 1; - } -*/ - break; - case PF_FOGWALL: - switch(unit->group->skill_id) { - case SA_VOLCANO: //Can't be placed on top of these - case SA_VIOLENTGALE: - (*alive) = 0; - return 1; - case SA_DELUGE: - case NJ_SUITON: - //Cheap 'hack' to notify the calling function that duration should be doubled [Skotlex] - (*alive) = 2; - break; - } - break; - case HP_BASILICA: - if (unit->group->skill_id == HP_BASILICA) - { //Basilica can't be placed on top of itself to avoid map-cell stacking problems. [Skotlex] - (*alive) = 0; - return 1; - } - break; - case GN_CRAZYWEED_ATK: - switch(unit->group->unit_id){ //TODO: look for other ground skills that are affected. - case UNT_WALLOFTHORN: - case UNT_THORNS_TRAP: - case UNT_BLOODYLUST: - case UNT_CHAOSPANIC: - case UNT_MAELSTROM: - case UNT_FIREPILLAR_ACTIVE: - case UNT_LANDPROTECTOR: - case UNT_VOLCANO: - case UNT_DELUGE: - case UNT_VIOLENTGALE: - case UNT_SAFETYWALL: - case UNT_PNEUMA: - skill_delunit(unit); - return 1; - } - break; - } - - if (unit->group->skill_id == SA_LANDPROTECTOR && !(skill_get_inf2(skill_id)&(INF2_SONG_DANCE|INF2_TRAP))) { //It deletes everything except songs/dances/traps - (*alive) = 0; - return 1; - } - - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -int skill_chastle_mob_changetarget(struct block_list *bl,va_list ap) -{ - struct mob_data* md; - struct unit_data*ud = unit_bl2ud(bl); - struct block_list *from_bl; - struct block_list *to_bl; - md = (struct mob_data*)bl; - from_bl = va_arg(ap,struct block_list *); - to_bl = va_arg(ap,struct block_list *); - - if(ud && ud->target == from_bl->id) - ud->target = to_bl->id; - - if(md->bl.type == BL_MOB && md->target_id == from_bl->id) - md->target_id = to_bl->id; - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -static int skill_trap_splash (struct block_list *bl, va_list ap) -{ - struct block_list *src; - int tick; - struct skill_unit *unit; - struct skill_unit_group *sg; - struct block_list *ss; - src = va_arg(ap,struct block_list *); - unit = (struct skill_unit *)src; - tick = va_arg(ap,int); - - if( !unit->alive || bl->prev == NULL ) - return 0; - - nullpo_ret(sg = unit->group); - nullpo_ret(ss = map_id2bl(sg->src_id)); - - if(battle_check_target(src,bl,sg->target_flag) <= 0) - return 0; - - switch(sg->unit_id){ - case UNT_SHOCKWAVE: - case UNT_SANDMAN: - case UNT_FLASHER: - skill_additional_effect(ss,bl,sg->skill_id,sg->skill_lv,BF_MISC,ATK_DEF,tick); - break; - case UNT_GROUNDDRIFT_WIND: - if(skill_attack(BF_WEAPON,ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1)) - sc_start(bl,SC_STUN,5,sg->skill_lv,skill_get_time2(sg->skill_id, sg->skill_lv)); - break; - case UNT_GROUNDDRIFT_DARK: - if(skill_attack(BF_WEAPON,ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1)) - sc_start(bl,SC_BLIND,5,sg->skill_lv,skill_get_time2(sg->skill_id, sg->skill_lv)); - break; - case UNT_GROUNDDRIFT_POISON: - if(skill_attack(BF_WEAPON,ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1)) - sc_start(bl,SC_POISON,5,sg->skill_lv,skill_get_time2(sg->skill_id, sg->skill_lv)); - break; - case UNT_GROUNDDRIFT_WATER: - if(skill_attack(BF_WEAPON,ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1)) - sc_start(bl,SC_FREEZE,5,sg->skill_lv,skill_get_time2(sg->skill_id, sg->skill_lv)); - break; - case UNT_GROUNDDRIFT_FIRE: - if(skill_attack(BF_WEAPON,ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1)) - skill_blown(src,bl,skill_get_blewcount(sg->skill_id,sg->skill_lv),-1,0); - break; - case UNT_ELECTRICSHOCKER: - clif_skill_damage(src,bl,tick,0,0,-30000,1,sg->skill_id,sg->skill_lv,5); - break; - case UNT_FIRINGTRAP: - case UNT_ICEBOUNDTRAP: - case UNT_CLUSTERBOMB: - if( ss != bl ) - skill_attack(BF_MISC,ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1|SD_LEVEL); - break; - case UNT_MAGENTATRAP: - case UNT_COBALTTRAP: - case UNT_MAIZETRAP: - case UNT_VERDURETRAP: - if( bl->type != BL_PC && !is_boss(bl) ) - sc_start2(bl,SC_ELEMENTALCHANGE,100,sg->skill_lv,skill_get_ele(sg->skill_id,sg->skill_lv),skill_get_time2(sg->skill_id,sg->skill_lv)); - break; - case UNT_REVERBERATION: - skill_addtimerskill(ss,tick+50,bl->id,0,0,WM_REVERBERATION_MELEE,sg->skill_lv,BF_WEAPON,0); // for proper skill delay animation when use with Dominion Impulse - skill_addtimerskill(ss,tick+250,bl->id,0,0,WM_REVERBERATION_MAGIC,sg->skill_lv,BF_MAGIC,0); - break; - default: - skill_attack(skill_get_type(sg->skill_id),ss,src,bl,sg->skill_id,sg->skill_lv,tick,0); - break; - } - return 1; -} - -/*========================================== - * - *------------------------------------------*/ -int skill_enchant_elemental_end (struct block_list *bl, int type) -{ - struct status_change *sc; - const enum sc_type scs[] = { SC_ENCPOISON, SC_ASPERSIO, SC_FIREWEAPON, SC_WATERWEAPON, SC_WINDWEAPON, SC_EARTHWEAPON, SC_SHADOWWEAPON, SC_GHOSTWEAPON, SC_ENCHANTARMS, SC_EXEEDBREAK }; - int i; - nullpo_ret(bl); - nullpo_ret(sc= status_get_sc(bl)); - - if (!sc->count) return 0; - - for (i = 0; i < ARRAYLENGTH(scs); i++) - if (type != scs[i] && sc->data[scs[i]]) - status_change_end(bl, scs[i], INVALID_TIMER); - - return 0; -} - -bool skill_check_cloaking(struct block_list *bl, struct status_change_entry *sce) -{ - static int dx[] = { 0, 1, 0, -1, -1, 1, 1, -1}; - static int dy[] = {-1, 0, 1, 0, -1, -1, 1, 1}; - bool wall = true; - - if( (bl->type == BL_PC && battle_config.pc_cloak_check_type&1) - || (bl->type != BL_PC && battle_config.monster_cloak_check_type&1) ) - { //Check for walls. - int i; - ARR_FIND( 0, 8, i, map_getcell(bl->m, bl->x+dx[i], bl->y+dy[i], CELL_CHKNOPASS) != 0 ); - if( i == 8 ) - wall = false; - } - - if( sce ) - { - if( !wall ) - { - if( sce->val1 < 3 ) //End cloaking. - status_change_end(bl, SC_CLOAKING, INVALID_TIMER); - else - if( sce->val4&1 ) - { //Remove wall bonus - sce->val4&=~1; - status_calc_bl(bl,SCB_SPEED); - } - } - else - { - if( !(sce->val4&1) ) - { //Add wall speed bonus - sce->val4|=1; - status_calc_bl(bl,SCB_SPEED); - } - } - } - - return wall; -} -bool skill_check_camouflage(struct block_list *bl, struct status_change_entry *sce) -{ - static int dx[] = { 0, 1, 0, -1, -1, 1, 1, -1}; - static int dy[] = {-1, 0, 1, 0, -1, -1, 1, 1}; - bool wall = true; - - if( bl->type == BL_PC ) - { //Check for walls. - int i; - ARR_FIND( 0, 8, i, map_getcell(bl->m, bl->x+dx[i], bl->y+dy[i], CELL_CHKNOPASS) != 0 ); - if( i == 8 ) - wall = false; - } - - if( sce ) - { - if( !wall ) - { - if( sce->val1 < 3 ) //End camouflage. - status_change_end(bl, SC_CAMOUFLAGE, INVALID_TIMER); - else - if( sce->val3&1 ) - { //Remove wall bonus - sce->val3&=~1; - status_calc_bl(bl,SCB_SPEED); - } - } - } - - return wall; -} - -/*========================================== - * - *------------------------------------------*/ -struct skill_unit *skill_initunit (struct skill_unit_group *group, int idx, int x, int y, int val1, int val2) -{ - struct skill_unit *unit; - - nullpo_retr(NULL, group); - nullpo_retr(NULL, group->unit); // crash-protection against poor coding - nullpo_retr(NULL, unit=&group->unit[idx]); - - if(!unit->alive) - group->alive_count++; - - unit->bl.id=map_get_new_object_id(); - unit->bl.type=BL_SKILL; - unit->bl.m=group->map; - unit->bl.x=x; - unit->bl.y=y; - unit->group=group; - unit->alive=1; - unit->val1=val1; - unit->val2=val2; - - idb_put(skillunit_db, unit->bl.id, unit); - map_addiddb(&unit->bl); - map_addblock(&unit->bl); - - // perform oninit actions - switch (group->skill_id) { - case WZ_ICEWALL: - map_setgatcell(unit->bl.m,unit->bl.x,unit->bl.y,5); - clif_changemapcell(0,unit->bl.m,unit->bl.x,unit->bl.y,5,AREA); - skill_unitsetmapcell(unit,WZ_ICEWALL,group->skill_lv,CELL_ICEWALL,true); - map[unit->bl.m].icewall_num++; - break; - case SA_LANDPROTECTOR: - skill_unitsetmapcell(unit,SA_LANDPROTECTOR,group->skill_lv,CELL_LANDPROTECTOR,true); - break; - case HP_BASILICA: - skill_unitsetmapcell(unit,HP_BASILICA,group->skill_lv,CELL_BASILICA,true); - break; - case SC_MAELSTROM: - skill_unitsetmapcell(unit,SC_MAELSTROM,group->skill_lv,CELL_MAELSTROM,true); - break; - default: - if (group->state.song_dance&0x1) //Check for dissonance. - skill_dance_overlap(unit, 1); - break; - } - - clif_skill_setunit(unit); - - return unit; -} - -/*========================================== - * - *------------------------------------------*/ -int skill_delunit (struct skill_unit* unit) -{ - struct skill_unit_group *group; - - nullpo_ret(unit); - if( !unit->alive ) - return 0; - unit->alive=0; - - nullpo_ret(group=unit->group); - - if( group->state.song_dance&0x1 ) //Cancel dissonance effect. - skill_dance_overlap(unit, 0); - - // invoke onout event - if( !unit->range ) - map_foreachincell(skill_unit_effect,unit->bl.m,unit->bl.x,unit->bl.y,group->bl_flag,&unit->bl,gettick(),4); - - // perform ondelete actions - switch (group->skill_id) { - case HT_ANKLESNARE: { - struct block_list* target = map_id2bl(group->val2); - if( target ) - status_change_end(target, SC_ANKLE, INVALID_TIMER); - } - break; - case WZ_ICEWALL: - map_setgatcell(unit->bl.m,unit->bl.x,unit->bl.y,unit->val2); - clif_changemapcell(0,unit->bl.m,unit->bl.x,unit->bl.y,unit->val2,ALL_SAMEMAP); // hack to avoid clientside cell bug - skill_unitsetmapcell(unit,WZ_ICEWALL,group->skill_lv,CELL_ICEWALL,false); - map[unit->bl.m].icewall_num--; - break; - case SA_LANDPROTECTOR: - skill_unitsetmapcell(unit,SA_LANDPROTECTOR,group->skill_lv,CELL_LANDPROTECTOR,false); - break; - case HP_BASILICA: - skill_unitsetmapcell(unit,HP_BASILICA,group->skill_lv,CELL_BASILICA,false); - break; - case RA_ELECTRICSHOCKER: { - struct block_list* target = map_id2bl(group->val2); - if( target ) - status_change_end(target, SC_ELECTRICSHOCKER, INVALID_TIMER); - } - break; - case SC_MAELSTROM: - skill_unitsetmapcell(unit,SC_MAELSTROM,group->skill_lv,CELL_MAELSTROM,false); - break; - case SC_MANHOLE: // Note : Removing the unit don't remove the status (official info) - if( group->val2 ) { // Someone Traped - struct status_change *tsc = status_get_sc( map_id2bl(group->val2)); - if( tsc && tsc->data[SC__MANHOLE] ) - tsc->data[SC__MANHOLE]->val4 = 0; // Remove the Unit ID - } - break; - } - - clif_skill_delunit(unit); - - unit->group=NULL; - map_delblock(&unit->bl); // don't free yet - map_deliddb(&unit->bl); - idb_remove(skillunit_db, unit->bl.id); - if(--group->alive_count==0) - skill_delunitgroup(group); - - return 0; -} -/*========================================== - * - *------------------------------------------*/ -static DBMap* group_db = NULL;// int group_id -> struct skill_unit_group* - -/// Returns the target skill_unit_group or NULL if not found. -struct skill_unit_group* skill_id2group(int group_id) -{ - return (struct skill_unit_group*)idb_get(group_db, group_id); -} - - -static int skill_unit_group_newid = MAX_SKILL_DB; - -/// Returns a new group_id that isn't being used in group_db. -/// Fatal error if nothing is available. -static int skill_get_new_group_id(void) -{ - if( skill_unit_group_newid >= MAX_SKILL_DB && skill_id2group(skill_unit_group_newid) == NULL ) - return skill_unit_group_newid++;// available - {// find next id - int base_id = skill_unit_group_newid; - while( base_id != ++skill_unit_group_newid ) - { - if( skill_unit_group_newid < MAX_SKILL_DB ) - skill_unit_group_newid = MAX_SKILL_DB; - if( skill_id2group(skill_unit_group_newid) == NULL ) - return skill_unit_group_newid++;// available - } - // full loop, nothing available - ShowFatalError("skill_get_new_group_id: All ids are taken. Exiting..."); - exit(1); - } -} - -struct skill_unit_group* skill_initunitgroup (struct block_list* src, int count, uint16 skill_id, uint16 skill_lv, int unit_id, int limit, int interval) -{ - struct unit_data* ud = unit_bl2ud( src ); - struct skill_unit_group* group; - int i; - - if(!(skill_id && skill_lv)) return 0; - - nullpo_retr(NULL, src); - nullpo_retr(NULL, ud); - - // find a free spot to store the new unit group - ARR_FIND( 0, MAX_SKILLUNITGROUP, i, ud->skillunit[i] == NULL ); - if(i == MAX_SKILLUNITGROUP) - { - // array is full, make room by discarding oldest group - int j=0; - unsigned maxdiff=0,x,tick=gettick(); - for(i=0;i<MAX_SKILLUNITGROUP && ud->skillunit[i];i++) - if((x=DIFF_TICK(tick,ud->skillunit[i]->tick))>maxdiff){ - maxdiff=x; - j=i; - } - skill_delunitgroup(ud->skillunit[j]); - //Since elements must have shifted, we use the last slot. - i = MAX_SKILLUNITGROUP-1; - } - - group = ers_alloc(skill_unit_ers, struct skill_unit_group); - group->src_id = src->id; - group->party_id = status_get_party_id(src); - group->guild_id = status_get_guild_id(src); - group->bg_id = bg_team_get_id(src); - group->group_id = skill_get_new_group_id(); - group->unit = (struct skill_unit *)aCalloc(count,sizeof(struct skill_unit)); - group->unit_count = count; - group->alive_count = 0; - group->val1 = 0; - group->val2 = 0; - group->val3 = 0; - group->skill_id = skill_id; - group->skill_lv = skill_lv; - group->unit_id = unit_id; - group->map = src->m; - group->limit = limit; - group->interval = interval; - group->tick = gettick(); - group->valstr = NULL; - - ud->skillunit[i] = group; - - if (skill_id == PR_SANCTUARY) //Sanctuary starts healing +1500ms after casted. [Skotlex] - group->tick += 1500; - - idb_put(group_db, group->group_id, group); - return group; -} - -/*========================================== - * - *------------------------------------------*/ -int skill_delunitgroup_(struct skill_unit_group *group, const char* file, int line, const char* func) -{ - struct block_list* src; - struct unit_data *ud; - int i,j; - - if( group == NULL ) - { - ShowDebug("skill_delunitgroup: group is NULL (source=%s:%d, %s)! Please report this! (#3504)\n", file, line, func); - return 0; - } - - src=map_id2bl(group->src_id); - ud = unit_bl2ud(src); - if(!src || !ud) { - ShowError("skill_delunitgroup: Group's source not found! (src_id: %d skill_id: %d)\n", group->src_id, group->skill_id); - return 0; - } - - if( !status_isdead(src) && ((TBL_PC*)src)->state.warping && !((TBL_PC*)src)->state.changemap ) { - switch( group->skill_id ) { - 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: - skill_usave_add(((TBL_PC*)src), group->skill_id, group->skill_lv); - break; - } - } - - if (skill_get_unit_flag(group->skill_id)&(UF_DANCE|UF_SONG|UF_ENSEMBLE)) - { - struct status_change* sc = status_get_sc(src); - if (sc && sc->data[SC_DANCING]) - { - sc->data[SC_DANCING]->val2 = 0 ; //This prevents status_change_end attempting to redelete the group. [Skotlex] - status_change_end(src, SC_DANCING, INVALID_TIMER); - } - } - - // end Gospel's status change on 'src' - // (needs to be done when the group is deleted by other means than skill deactivation) - if (group->unit_id == UNT_GOSPEL) { - struct status_change *sc = status_get_sc(src); - if(sc && sc->data[SC_GOSPEL]) { - sc->data[SC_GOSPEL]->val3 = 0; //Remove reference to this group. [Skotlex] - status_change_end(src, SC_GOSPEL, INVALID_TIMER); - } - } - - switch( group->skill_id ) { - case SG_SUN_WARM: - case SG_MOON_WARM: - case SG_STAR_WARM: - { - struct status_change *sc = NULL; - if( (sc = status_get_sc(src)) != NULL && sc->data[SC_WARM] ) { - sc->data[SC_WARM]->val4 = 0; - status_change_end(src, SC_WARM, INVALID_TIMER); - } - } - break; - case NC_NEUTRALBARRIER: - { - struct status_change *sc = NULL; - if( (sc = status_get_sc(src)) != NULL && sc->data[SC_NEUTRALBARRIER_MASTER] ) { - sc->data[SC_NEUTRALBARRIER_MASTER]->val2 = 0; - status_change_end(src,SC_NEUTRALBARRIER_MASTER,INVALID_TIMER); - } - } - break; - case NC_STEALTHFIELD: - { - struct status_change *sc = NULL; - if( (sc = status_get_sc(src)) != NULL && sc->data[SC_STEALTHFIELD_MASTER] ) { - sc->data[SC_STEALTHFIELD_MASTER]->val2 = 0; - status_change_end(src,SC_STEALTHFIELD_MASTER,INVALID_TIMER); - } - } - break; - case LG_BANDING: - { - struct status_change *sc = NULL; - if( (sc = status_get_sc(src)) && sc->data[SC_BANDING] ) { - sc->data[SC_BANDING]->val4 = 0; - status_change_end(src,SC_BANDING,INVALID_TIMER); - } - } - break; - } - - if (src->type==BL_PC && group->state.ammo_consume) - battle_consume_ammo((TBL_PC*)src, group->skill_id, group->skill_lv); - - group->alive_count=0; - - // remove all unit cells - if(group->unit != NULL) - for( i = 0; i < group->unit_count; i++ ) - skill_delunit(&group->unit[i]); - - // clear Talkie-box string - if( group->valstr != NULL ) - { - aFree(group->valstr); - group->valstr = NULL; - } - - idb_remove(group_db, group->group_id); - map_freeblock(&group->unit->bl); // schedules deallocation of whole array (HACK) - group->unit=NULL; - group->group_id=0; - group->unit_count=0; - - // locate this group, swap with the last entry and delete it - ARR_FIND( 0, MAX_SKILLUNITGROUP, i, ud->skillunit[i] == group ); - ARR_FIND( i, MAX_SKILLUNITGROUP, j, ud->skillunit[j] == NULL ); j--; - if( i < MAX_SKILLUNITGROUP ) - { - ud->skillunit[i] = ud->skillunit[j]; - ud->skillunit[j] = NULL; - ers_free(skill_unit_ers, group); - } - else - ShowError("skill_delunitgroup: Group not found! (src_id: %d skill_id: %d)\n", group->src_id, group->skill_id); - - return 1; -} - -/*========================================== - * - *------------------------------------------*/ -int skill_clear_unitgroup (struct block_list *src) -{ - struct unit_data *ud = unit_bl2ud(src); - - nullpo_ret(ud); - - while (ud->skillunit[0]) - skill_delunitgroup(ud->skillunit[0]); - - return 1; -} - -/*========================================== - * - *------------------------------------------*/ -struct skill_unit_group_tickset *skill_unitgrouptickset_search (struct block_list *bl, struct skill_unit_group *group, int tick) -{ - int i,j=-1,k,s,id; - struct unit_data *ud; - struct skill_unit_group_tickset *set; - - nullpo_ret(bl); - if (group->interval==-1) - return NULL; - - ud = unit_bl2ud(bl); - if (!ud) return NULL; - - set = ud->skillunittick; - - if (skill_get_unit_flag(group->skill_id)&UF_NOOVERLAP) - id = s = group->skill_id; - else - id = s = group->group_id; - - for (i=0; i<MAX_SKILLUNITGROUPTICKSET; i++) { - k = (i+s) % MAX_SKILLUNITGROUPTICKSET; - if (set[k].id == id) - return &set[k]; - else if (j==-1 && (DIFF_TICK(tick,set[k].tick)>0 || set[k].id==0)) - j=k; - } - - if (j == -1) { - ShowWarning ("skill_unitgrouptickset_search: tickset is full\n"); - j = id % MAX_SKILLUNITGROUPTICKSET; - } - - set[j].id = id; - set[j].tick = tick; - return &set[j]; -} - -/*========================================== - * - *------------------------------------------*/ -int skill_unit_timer_sub_onplace (struct block_list* bl, va_list ap) -{ - struct skill_unit* unit = va_arg(ap,struct skill_unit *); - struct skill_unit_group* group = unit->group; - unsigned int tick = va_arg(ap,unsigned int); - - if( !unit->alive || bl->prev == NULL ) - return 0; - - nullpo_ret(group); - - if( !(skill_get_inf2(group->skill_id)&(INF2_SONG_DANCE|INF2_TRAP|INF2_NOLP)) && map_getcell(bl->m, bl->x, bl->y, CELL_CHKLANDPROTECTOR) ) - return 0; //AoE skills are ineffective. [Skotlex] - - if( battle_check_target(&unit->bl,bl,group->target_flag) <= 0 ) - return 0; - - skill_unit_onplace_timer(unit,bl,tick); - - return 1; -} - -/** - * @see DBApply - */ -static int skill_unit_timer_sub(DBKey key, DBData *data, va_list ap) -{ - struct skill_unit* unit = db_data2ptr(data); - struct skill_unit_group* group = unit->group; - unsigned int tick = va_arg(ap,unsigned int); - bool dissonance; - struct block_list* bl = &unit->bl; - - if( !unit->alive ) - return 0; - - nullpo_ret(group); - - // check for expiration - if( !group->state.guildaura && (DIFF_TICK(tick,group->tick) >= group->limit || DIFF_TICK(tick,group->tick) >= unit->limit) ) - {// skill unit expired (inlined from skill_unit_onlimit()) - switch( group->unit_id ) - { - case UNT_BLASTMINE: -#ifdef RENEWAL - case UNT_CLAYMORETRAP: -#endif - case UNT_GROUNDDRIFT_WIND: - case UNT_GROUNDDRIFT_DARK: - case UNT_GROUNDDRIFT_POISON: - case UNT_GROUNDDRIFT_WATER: - case UNT_GROUNDDRIFT_FIRE: - group->unit_id = UNT_USED_TRAPS; - //clif_changetraplook(bl, UNT_FIREPILLAR_ACTIVE); - group->limit=DIFF_TICK(tick+1500,group->tick); - unit->limit=DIFF_TICK(tick+1500,group->tick); - break; - - case UNT_ANKLESNARE: - case UNT_ELECTRICSHOCKER: - if( group->val2 > 0 ) { - // Used Trap don't returns back to item - skill_delunit(unit); - break; - } - case UNT_SKIDTRAP: - case UNT_LANDMINE: - case UNT_SHOCKWAVE: - case UNT_SANDMAN: - case UNT_FLASHER: - case UNT_FREEZINGTRAP: -#ifndef RENEWAL - case UNT_CLAYMORETRAP: -#endif - case UNT_TALKIEBOX: - case UNT_CLUSTERBOMB: - case UNT_MAGENTATRAP: - case UNT_COBALTTRAP: - case UNT_MAIZETRAP: - case UNT_VERDURETRAP: - case UNT_FIRINGTRAP: - case UNT_ICEBOUNDTRAP: - - { - struct block_list* src; - if( unit->val1 > 0 && (src = map_id2bl(group->src_id)) != NULL && src->type == BL_PC ) - { // revert unit back into a trap - struct item item_tmp; - memset(&item_tmp,0,sizeof(item_tmp)); - item_tmp.nameid = group->item_id?group->item_id:ITEMID_TRAP; - item_tmp.identify = 1; - map_addflooritem(&item_tmp,1,bl->m,bl->x,bl->y,0,0,0,0); - } - skill_delunit(unit); - } - break; - - case UNT_WARP_ACTIVE: - // warp portal opens (morph to a UNT_WARP_WAITING cell) - group->unit_id = skill_get_unit_id(group->skill_id, 1); // UNT_WARP_WAITING - clif_changelook(&unit->bl, LOOK_BASE, group->unit_id); - // restart timers - group->limit = skill_get_time(group->skill_id,group->skill_lv); - unit->limit = skill_get_time(group->skill_id,group->skill_lv); - // apply effect to all units standing on it - map_foreachincell(skill_unit_effect,unit->bl.m,unit->bl.x,unit->bl.y,group->bl_flag,&unit->bl,gettick(),1); - break; - - case UNT_CALLFAMILY: - { - struct map_session_data *sd = NULL; - if(group->val1) { - sd = map_charid2sd(group->val1); - group->val1 = 0; - if (sd && !map[sd->bl.m].flag.nowarp) - pc_setpos(sd,map_id2index(unit->bl.m),unit->bl.x,unit->bl.y,CLR_TELEPORT); - } - if(group->val2) { - sd = map_charid2sd(group->val2); - group->val2 = 0; - if (sd && !map[sd->bl.m].flag.nowarp) - pc_setpos(sd,map_id2index(unit->bl.m),unit->bl.x,unit->bl.y,CLR_TELEPORT); - } - skill_delunit(unit); - } - break; - - case UNT_REVERBERATION: - if( unit->val1 <= 0 ) { // If it was deactivated. - skill_delunit(unit); - break; - } - clif_changetraplook(bl,UNT_USED_TRAPS); - map_foreachinrange(skill_trap_splash, bl, skill_get_splash(group->skill_id, group->skill_lv), group->bl_flag, bl, tick); - group->limit = DIFF_TICK(tick,group->tick)+1000; - unit->limit = DIFF_TICK(tick,group->tick)+1000; - group->unit_id = UNT_USED_TRAPS; - break; - - case UNT_FEINTBOMB: { - struct block_list *src = map_id2bl(group->src_id); - if( src ) - map_foreachinrange(skill_area_sub, &group->unit->bl, unit->range, splash_target(src), src, SC_FEINTBOMB, group->skill_lv, tick, BCT_ENEMY|SD_ANIMATION|1, skill_castend_damage_id); - skill_delunit(unit); - break; - } - - case UNT_BANDING: - { - struct block_list *src = map_id2bl(group->src_id); - struct status_change *sc; - if( !src || (sc = status_get_sc(src)) == NULL || !sc->data[SC_BANDING] ) - { - skill_delunit(unit); - break; - } - // This unit isn't removed while SC_BANDING is active. - group->limit = DIFF_TICK(tick+group->interval,group->tick); - unit->limit = DIFF_TICK(tick+group->interval,group->tick); - } - break; - - default: - skill_delunit(unit); - } - } - else - {// skill unit is still active - switch( group->unit_id ) - { - case UNT_ICEWALL: - // icewall loses 50 hp every second - unit->val1 -= SKILLUNITTIMER_INTERVAL/20; // trap's hp - if( unit->val1 <= 0 && unit->limit + group->tick > tick + 700 ) - unit->limit = DIFF_TICK(tick+700,group->tick); - break; - case UNT_BLASTMINE: - case UNT_SKIDTRAP: - case UNT_LANDMINE: - case UNT_SHOCKWAVE: - case UNT_SANDMAN: - case UNT_FLASHER: - case UNT_CLAYMORETRAP: - case UNT_FREEZINGTRAP: - case UNT_TALKIEBOX: - case UNT_ANKLESNARE: - if( unit->val1 <= 0 ) { - if( group->unit_id == UNT_ANKLESNARE && group->val2 > 0 ) - skill_delunit(unit); - else { - clif_changetraplook(bl, group->unit_id==UNT_LANDMINE?UNT_FIREPILLAR_ACTIVE:UNT_USED_TRAPS); - group->limit = DIFF_TICK(tick, group->tick) + 1500; - group->unit_id = UNT_USED_TRAPS; - } - } - break; - case UNT_REVERBERATION: - if( unit->val1 <= 0 ){ - clif_changetraplook(bl,UNT_USED_TRAPS); - map_foreachinrange(skill_trap_splash, bl, skill_get_splash(group->skill_id, group->skill_lv), group->bl_flag, bl, tick); - group->limit = DIFF_TICK(tick,group->tick)+1000; - unit->limit = DIFF_TICK(tick,group->tick)+1000; - group->unit_id = UNT_USED_TRAPS; - } - break; - case UNT_WALLOFTHORN: - if( unit->val1 <= 0 ) { - group->unit_id = UNT_USED_TRAPS; - group->limit = DIFF_TICK(tick, group->tick) + 1500; - } - break; - } - } - - //Don't continue if unit or even group is expired and has been deleted. - if( !group || !unit->alive ) - return 0; - - dissonance = skill_dance_switch(unit, 0); - - if( unit->range >= 0 && group->interval != -1 ) - { - if( battle_config.skill_wall_check ) - map_foreachinshootrange(skill_unit_timer_sub_onplace, bl, unit->range, group->bl_flag, bl,tick); - else - map_foreachinrange(skill_unit_timer_sub_onplace, bl, unit->range, group->bl_flag, bl,tick); - - if(unit->range == -1) //Unit disabled, but it should not be deleted yet. - group->unit_id = UNT_USED_TRAPS; - - if( group->unit_id == UNT_TATAMIGAESHI ) - { - unit->range = -1; //Disable processed cell. - if (--group->val1 <= 0) // number of live cells - { //All tiles were processed, disable skill. - group->target_flag=BCT_NOONE; - group->bl_flag= BL_NUL; - } - } - } - - if( dissonance ) skill_dance_switch(unit, 1); - - return 0; -} -/*========================================== - * Executes on all skill units every SKILLUNITTIMER_INTERVAL miliseconds. - *------------------------------------------*/ -int skill_unit_timer(int tid, unsigned int tick, int id, intptr_t data) -{ - map_freeblock_lock(); - - skillunit_db->foreach(skillunit_db, skill_unit_timer_sub, tick); - - map_freeblock_unlock(); - - return 0; -} - -static int skill_unit_temp[20]; // temporary storage for tracking skill unit skill ids as players move in/out of them -/*========================================== - * - *------------------------------------------*/ -int skill_unit_move_sub (struct block_list* bl, va_list ap) -{ - struct skill_unit* unit = (struct skill_unit *)bl; - struct skill_unit_group* group = unit->group; - - struct block_list* target = va_arg(ap,struct block_list*); - unsigned int tick = va_arg(ap,unsigned int); - int flag = va_arg(ap,int); - - bool dissonance; - uint16 skill_id; - int i; - - nullpo_ret(group); - - if( !unit->alive || target->prev == NULL ) - return 0; - - if( flag&1 && ( unit->group->skill_id == PF_SPIDERWEB || unit->group->skill_id == GN_THORNS_TRAP ) ) - return 0; // Fiberlock is never supposed to trigger on skill_unit_move. [Inkfish] - - dissonance = skill_dance_switch(unit, 0); - - //Necessary in case the group is deleted after calling on_place/on_out [Skotlex] - skill_id = unit->group->skill_id; - - if( unit->group->interval != -1 && !(skill_get_unit_flag(skill_id)&UF_DUALMODE) && skill_id != BD_LULLABY ) //Lullaby is the exception, bugreport:411 - { //Non-dualmode unit skills with a timer don't trigger when walking, so just return - if( dissonance ) skill_dance_switch(unit, 1); - return 0; - } - - //Target-type check. - if( !(group->bl_flag&target->type && battle_check_target(&unit->bl,target,group->target_flag) > 0) ) - { - if( group->src_id == target->id && group->state.song_dance&0x2 ) - { //Ensemble check to see if they went out/in of the area [Skotlex] - if( flag&1 ) - { - if( flag&2 ) - { //Clear this skill id. - ARR_FIND( 0, ARRAYLENGTH(skill_unit_temp), i, skill_unit_temp[i] == skill_id ); - if( i < ARRAYLENGTH(skill_unit_temp) ) - skill_unit_temp[i] = 0; - } - } - else - { - if( flag&2 ) - { //Store this skill id. - ARR_FIND( 0, ARRAYLENGTH(skill_unit_temp), i, skill_unit_temp[i] == 0 ); - if( i < ARRAYLENGTH(skill_unit_temp) ) - skill_unit_temp[i] = skill_id; - else - ShowError("skill_unit_move_sub: Reached limit of unit objects per cell!\n"); - } - - } - - if( flag&4 ) - skill_unit_onleft(skill_id,target,tick); - } - - if( dissonance ) skill_dance_switch(unit, 1); - - return 0; - } - else - { - if( flag&1 ) - { - int result = skill_unit_onplace(unit,target,tick); - if( flag&2 && result ) - { //Clear skill ids we have stored in onout. - ARR_FIND( 0, ARRAYLENGTH(skill_unit_temp), i, skill_unit_temp[i] == result ); - if( i < ARRAYLENGTH(skill_unit_temp) ) - skill_unit_temp[i] = 0; - } - } - else - { - int result = skill_unit_onout(unit,target,tick); - if( flag&2 && result ) - { //Store this unit id. - ARR_FIND( 0, ARRAYLENGTH(skill_unit_temp), i, skill_unit_temp[i] == 0 ); - if( i < ARRAYLENGTH(skill_unit_temp) ) - skill_unit_temp[i] = skill_id; - else - ShowError("skill_unit_move_sub: Reached limit of unit objects per cell!\n"); - } - } - - //TODO: Normally, this is dangerous since the unit and group could be freed - //inside the onout/onplace functions. Currently it is safe because we know song/dance - //cells do not get deleted within them. [Skotlex] - if( dissonance ) skill_dance_switch(unit, 1); - - if( flag&4 ) - skill_unit_onleft(skill_id,target,tick); - - return 1; - } -} - -/*========================================== - * Invoked when a char has moved and unit cells must be invoked (onplace, onout, onleft) - * Flag values: - * flag&1: invoke skill_unit_onplace (otherwise invoke skill_unit_onout) - * flag&2: this function is being invoked twice as a bl moves, store in memory the affected - * units to figure out when they have left a group. - * flag&4: Force a onleft event (triggered when the bl is killed, for example) - *------------------------------------------*/ -int skill_unit_move (struct block_list *bl, unsigned int tick, int flag) -{ - nullpo_ret(bl); - - if( bl->prev == NULL ) - return 0; - - if( flag&2 && !(flag&1) ) - { //Onout, clear data - memset(skill_unit_temp, 0, sizeof(skill_unit_temp)); - } - - map_foreachincell(skill_unit_move_sub,bl->m,bl->x,bl->y,BL_SKILL,bl,tick,flag); - - if( flag&2 && flag&1 ) - { //Onplace, check any skill units you have left. - int i; - for( i = 0; i < ARRAYLENGTH(skill_unit_temp); i++ ) - if( skill_unit_temp[i] ) - skill_unit_onleft(skill_unit_temp[i], bl, tick); - } - - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -int skill_unit_move_unit_group (struct skill_unit_group *group, int16 m, int16 dx, int16 dy) -{ - int i,j; - unsigned int tick = gettick(); - int *m_flag; - struct skill_unit *unit1; - struct skill_unit *unit2; - - if (group == NULL) - return 0; - if (group->unit_count<=0) - return 0; - if (group->unit==NULL) - return 0; - - if (skill_get_unit_flag(group->skill_id)&UF_ENSEMBLE) - return 0; //Ensembles may not be moved around. - - if( group->unit_id == UNT_ICEWALL || group->unit_id == UNT_WALLOFTHORN ) - return 0; //Icewalls and Wall of Thorns don't get knocked back - - m_flag = (int *) aCalloc(group->unit_count, sizeof(int)); - // m_flag - // 0: Neither of the following (skill_unit_onplace & skill_unit_onout are needed) - // 1: Unit will move to a slot that had another unit of the same group (skill_unit_onplace not needed) - // 2: Another unit from same group will end up positioned on this unit (skill_unit_onout not needed) - // 3: Both 1+2. - for(i=0;i<group->unit_count;i++){ - unit1=&group->unit[i]; - if (!unit1->alive || unit1->bl.m!=m) - continue; - for(j=0;j<group->unit_count;j++){ - unit2=&group->unit[j]; - if (!unit2->alive) - continue; - if (unit1->bl.x+dx==unit2->bl.x && unit1->bl.y+dy==unit2->bl.y){ - m_flag[i] |= 0x1; - } - if (unit1->bl.x-dx==unit2->bl.x && unit1->bl.y-dy==unit2->bl.y){ - m_flag[i] |= 0x2; - } - } - } - j = 0; - for (i=0;i<group->unit_count;i++) { - unit1=&group->unit[i]; - if (!unit1->alive) - continue; - if (!(m_flag[i]&0x2)) { - if (group->state.song_dance&0x1) //Cancel dissonance effect. - skill_dance_overlap(unit1, 0); - map_foreachincell(skill_unit_effect,unit1->bl.m,unit1->bl.x,unit1->bl.y,group->bl_flag,&unit1->bl,tick,4); - } - //Move Cell using "smart" criteria (avoid useless moving around) - switch(m_flag[i]) - { - case 0: - //Cell moves independently, safely move it. - map_moveblock(&unit1->bl, unit1->bl.x+dx, unit1->bl.y+dy, tick); - break; - case 1: - //Cell moves unto another cell, look for a replacement cell that won't collide - //and has no cell moving into it (flag == 2) - for(;j<group->unit_count;j++) - { - if(m_flag[j]!=2 || !group->unit[j].alive) - continue; - //Move to where this cell would had moved. - unit2 = &group->unit[j]; - map_moveblock(&unit1->bl, unit2->bl.x+dx, unit2->bl.y+dy, tick); - j++; //Skip this cell as we have used it. - break; - } - break; - case 2: - case 3: - break; //Don't move the cell as a cell will end on this tile anyway. - } - if (!(m_flag[i]&0x2)) { //We only moved the cell in 0-1 - if (group->state.song_dance&0x1) //Check for dissonance effect. - skill_dance_overlap(unit1, 1); - clif_skill_setunit(unit1); - map_foreachincell(skill_unit_effect,unit1->bl.m,unit1->bl.x,unit1->bl.y,group->bl_flag,&unit1->bl,tick,1); - } - } - aFree(m_flag); - return 0; -} - -/*========================================== - * - *------------------------------------------*/ -int skill_can_produce_mix (struct map_session_data *sd, int nameid, int trigger, int qty) -{ - int i,j; - - nullpo_ret(sd); - - if(nameid<=0) - return 0; - - for(i=0;i<MAX_SKILL_PRODUCE_DB;i++){ - if(skill_produce_db[i].nameid == nameid ){ - if((j=skill_produce_db[i].req_skill)>0 && - pc_checkskill(sd,j) < skill_produce_db[i].req_skill_lv) - continue; // must iterate again to check other skills that produce it. [malufett] - if( j > 0 && sd->menuskill_id > 0 && sd->menuskill_id != j ) - continue; // special case - break; - } - } - - if( i >= MAX_SKILL_PRODUCE_DB ) - return 0; - - if( pc_checkadditem(sd, nameid, qty) == ADDITEM_OVERAMOUNT ) - {// cannot carry the produced stuff - return 0; - } - - if(trigger>=0){ - if(trigger>20) { // Non-weapon, non-food item (itemlv must match) - if(skill_produce_db[i].itemlv!=trigger) - return 0; - } else if(trigger>10) { // Food (any item level between 10 and 20 will do) - if(skill_produce_db[i].itemlv<=10 || skill_produce_db[i].itemlv>20) - return 0; - } else { // Weapon (itemlv must be higher or equal) - if(skill_produce_db[i].itemlv>trigger) - return 0; - } - } - - for(j=0;j<MAX_PRODUCE_RESOURCE;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<qty*skill_produce_db[i].mat_amount[j]) - return 0; - } - } - return i+1; -} - -/*========================================== - * - *------------------------------------------*/ -int skill_produce_mix (struct map_session_data *sd, uint16 skill_id, int nameid, int slot1, int slot2, int slot3, int qty) -{ - int slot[3]; - int i,sc,ele,idx,equip,wlv,make_per = 0,flag = 0,skill_lv = 0; - int num = -1; // exclude the recipe - struct status_data *status; - struct item_data* data; - - nullpo_ret(sd); - status = status_get_status_data(&sd->bl); - - if( sd->skill_id_old == skill_id ) - skill_lv = sd->skill_lv_old; - - if( !(idx=skill_can_produce_mix(sd,nameid,-1, qty)) ) - return 0; - idx--; - - if (qty < 1) - qty = 1; - - if (!skill_id) //A skill can be specified for some override cases. - skill_id = skill_produce_db[idx].req_skill; - - if( skill_id == GC_RESEARCHNEWPOISON ) - skill_id = GC_CREATENEWPOISON; - - slot[0]=slot1; - slot[1]=slot2; - slot[2]=slot3; - - for(i=0,sc=0,ele=0;i<3;i++){ //Note that qty should always be one if you are using these! - int j; - if( slot[i]<=0 ) - continue; - j = pc_search_inventory(sd,slot[i]); - if(j < 0) - continue; - if(slot[i]==1000){ /* Star Crumb */ - pc_delitem(sd,j,1,1,0,LOG_TYPE_PRODUCE); - sc++; - } - if(slot[i]>=994 && slot[i]<=997 && ele==0){ /* Flame Heart . . . Great Nature */ - static const int ele_table[4]={3,1,4,2}; - pc_delitem(sd,j,1,1,0,LOG_TYPE_PRODUCE); - ele=ele_table[slot[i]-994]; - } - } - - if( skill_id == RK_RUNEMASTERY ) { - int temp_qty, skill_lv = pc_checkskill(sd,skill_id); - data = itemdb_search(nameid); - - if( skill_lv == 10 ) temp_qty = 1 + rnd()%3; - else if( skill_lv > 5 ) temp_qty = 1 + rnd()%2; - else temp_qty = 1; - - if (data->stack.inventory) { - for( i = 0; i < MAX_INVENTORY; i++ ) { - if( sd->status.inventory[i].nameid == nameid ) { - if( sd->status.inventory[i].amount >= data->stack.amount ) { - clif_msgtable(sd->fd,0x61b); - return 0; - } else { - /** - * the amount fits, say we got temp_qty 4 and 19 runes, we trim temp_qty to 1. - **/ - if( temp_qty + sd->status.inventory[i].amount >= data->stack.amount ) - temp_qty = data->stack.amount - sd->status.inventory[i].amount; - } - break; - } - } - } - qty = temp_qty; - } - - for(i=0;i<MAX_PRODUCE_RESOURCE;i++){ - int j,id,x; - if( (id=skill_produce_db[idx].mat_id[i]) <= 0 ) - continue; - num++; - x=( skill_id == RK_RUNEMASTERY ? 1 : qty)*skill_produce_db[idx].mat_amount[i]; - do{ - 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,0,LOG_TYPE_PRODUCE); - } else - ShowError("skill_produce_mix: material item error\n"); - - x-=y; - }while( j>=0 && x>0 ); - } - - if( (equip = (itemdb_isequip(nameid) && skill_id != GN_CHANGEMATERIAL && skill_id != GN_MAKEBOMB )) ) - wlv = itemdb_wlv(nameid); - if(!equip) { - switch(skill_id){ - case BS_IRON: - case BS_STEEL: - case BS_ENCHANTEDSTONE: - // Ores & Metals Refining - skill bonuses are straight from kRO website [DracoRPG] - i = pc_checkskill(sd,skill_id); - make_per = sd->status.job_level*20 + status->dex*10 + status->luk*10; //Base chance - switch(nameid){ - case 998: // Iron - make_per += 4000+i*500; // Temper Iron bonus: +26/+32/+38/+44/+50 - break; - case 999: // Steel - make_per += 3000+i*500; // Temper Steel bonus: +35/+40/+45/+50/+55 - break; - case 1000: //Star Crumb - make_per = 100000; // Star Crumbs are 100% success crafting rate? (made 1000% so it succeeds even after penalties) [Skotlex] - break; - default: // Enchanted Stones - make_per += 1000+i*500; // Enchantedstone Craft bonus: +15/+20/+25/+30/+35 - break; - } - break; - case ASC_CDP: - make_per = (2000 + 40*status->dex + 20*status->luk); - break; - case AL_HOLYWATER: - /** - * Arch Bishop - **/ - case AB_ANCILLA: - make_per = 100000; //100% success - break; - case AM_PHARMACY: // Potion Preparation - reviewed with the help of various Ragnainfo sources [DracoRPG] - case AM_TWILIGHT1: - case AM_TWILIGHT2: - case AM_TWILIGHT3: - make_per = pc_checkskill(sd,AM_LEARNINGPOTION)*50 - + pc_checkskill(sd,AM_PHARMACY)*300 + sd->status.job_level*20 - + (status->int_/2)*10 + status->dex*10+status->luk*10; - if(merc_is_hom_active(sd->hd)) {//Player got a homun - int skill; - if((skill=merc_hom_checkskill(sd->hd,HVAN_INSTRUCT)) > 0) //His homun is a vanil with instruction change - make_per += skill*100; //+1% bonus per level - } - switch(nameid){ - case 501: // Red Potion - case 503: // Yellow Potion - case 504: // White Potion - make_per += (1+rnd()%100)*10 + 2000; - break; - case 970: // Alcohol - make_per += (1+rnd()%100)*10 + 1000; - break; - case 7135: // Bottle Grenade - case 7136: // Acid Bottle - case 7137: // Plant Bottle - case 7138: // Marine Sphere Bottle - make_per += (1+rnd()%100)*10; - break; - case 546: // Condensed Yellow Potion - make_per -= (1+rnd()%50)*10; - break; - case 547: // Condensed White Potion - case 7139: // Glistening Coat - make_per -= (1+rnd()%100)*10; - break; - //Common items, recieve no bonus or penalty, listed just because they are commonly produced - case 505: // Blue Potion - case 545: // Condensed Red Potion - case 605: // Anodyne - case 606: // Aloevera - default: - break; - } - if(battle_config.pp_rate != 100) - make_per = make_per * battle_config.pp_rate / 100; - break; - case SA_CREATECON: // Elemental Converter Creation - make_per = 100000; // should be 100% success rate - break; - /** - * Rune Knight - **/ - case RK_RUNEMASTERY: - { - int A = 100 * (51 + 2 * pc_checkskill(sd, skill_id)); - int B = 100 * status->dex / 30 + 10 * (status->luk + sd->status.job_level); - int C = 100 * cap_value(sd->itemid,0,100); //itemid depend on makerune() - int D = 0; - switch (nameid) { //rune rank it_diff 9 craftable rune - case ITEMID_BERKANA: - D = -2000; - break; //Rank S - case ITEMID_NAUTHIZ: - case ITEMID_URUZ: - D = -1500; - break; //Rank A - case ITEMID_ISA: - case ITEMID_WYRD: - D = -1000; - break; //Rank B - case ITEMID_RAIDO: - case ITEMID_THURISAZ: - case ITEMID_HAGALAZ: - case ITEMID_OTHILA: - D = -500; - break; //Rank C - default: D = -1500; - break; //not specified =-15% - } - make_per = A + B + C + D; - break; - } - /** - * Guilotine Cross - **/ - case GC_CREATENEWPOISON: - make_per = 3000 + 500 * pc_checkskill(sd,GC_RESEARCHNEWPOISON); - qty = 1+rnd()%pc_checkskill(sd,GC_RESEARCHNEWPOISON); - break; - case GN_CHANGEMATERIAL: - for(i=0; i<MAX_SKILL_PRODUCE_DB; i++) - if( skill_changematerial_db[i].itemid == nameid ){ - make_per = skill_changematerial_db[i].rate * 10; - break; - } - break; - case GN_S_PHARMACY: - { - int difficulty = 0; - - difficulty = (620 - 20 * skill_lv);// (620 - 20 * Skill Level) - - make_per = status->int_ + status->dex/2 + status->luk + sd->status.job_level + (30+rnd()%120) + // (Caster?s INT) + (Caster?s DEX / 2) + (Caster?s LUK) + (Caster?s Job Level) + Random number between (30 ~ 150) + - (sd->status.base_level-100) + pc_checkskill(sd, AM_LEARNINGPOTION) + pc_checkskill(sd, CR_FULLPROTECTION)*(4+rnd()%6); // (Caster?s Base Level - 100) + (Potion Research x 5) + (Full Chemical Protection Skill Level) x (Random number between 4 ~ 10) - - switch(nameid){// difficulty factor - case 12422: case 12425: - case 12428: - difficulty += 10; - break; - case 6212: case 12426: - difficulty += 15; - break; - case 13264: case 12423: - case 12427: case 12436: - difficulty += 20; - break; - case 6210: case 6211: - case 12437: - difficulty += 30; - break; - case 12424: case 12475: - difficulty += 40; - break; - } - - if( make_per >= 400 && make_per > difficulty) - qty = 10; - else if( make_per >= 300 && make_per > difficulty) - qty = 7; - else if( make_per >= 100 && make_per > difficulty) - qty = 6; - else if( make_per >= 1 && make_per > difficulty) - qty = 5; - else - qty = 4; - make_per = 10000; - } - break; - case GN_MAKEBOMB: - case GN_MIX_COOKING: - { - int difficulty = 30 + rnd()%120; // Random number between (30 ~ 150) - - make_per = sd->status.job_level / 4 + status->luk / 2 + status->dex / 3; // (Caster?s Job Level / 4) + (Caster?s LUK / 2) + (Caster?s DEX / 3) - qty = ~(5 + rnd()%5) + 1; - - switch(nameid){// difficulty factor - case 13260: - difficulty += 5; - break; - case 13261: case 13262: - difficulty += 10; - break; - case 12429: case 12430: case 12431: - case 12432: case 12433: case 12434: - case 13263: - difficulty += 15; - break; - case 13264: - difficulty += 20; - break; - } - - if( make_per >= 30 && make_per > difficulty) - qty = 10 + rnd()%2; - else if( make_per >= 10 && make_per > difficulty) - qty = 10; - else if( make_per == 10 && make_per > difficulty) - qty = 8; - else if( (make_per >= 50 || make_per < 30) && make_per < difficulty) - ;// Food/Bomb creation fails. - else if( make_per >= 30 && make_per < difficulty) - qty = 5; - - if( qty < 0 || (skill_lv == 1 && make_per < difficulty)){ - qty = ~qty + 1; - make_per = 0; - }else - make_per = 10000; - qty = (skill_lv > 1 ? qty : 1); - } - break; - default: - if (sd->menuskill_id == AM_PHARMACY && - sd->menuskill_val > 10 && sd->menuskill_val <= 20) - { //Assume Cooking Dish - if (sd->menuskill_val >= 15) //Legendary Cooking Set. - make_per = 10000; //100% Success - else - make_per = 1200 * (sd->menuskill_val - 10) - + 20 * (sd->status.base_level + 1) - + 20 * (status->dex + 1) - + 100 * (rnd()%(30+5*(sd->cook_mastery/400) - (6+sd->cook_mastery/80)) + (6+sd->cook_mastery/80)) - - 400 * (skill_produce_db[idx].itemlv - 11 + 1) - - 10 * (100 - status->luk + 1) - - 500 * (num - 1) - - 100 * (rnd()%4 + 1); - break; - } - make_per = 5000; - break; - } - } else { // Weapon Forging - skill bonuses are straight from kRO website, other things from a jRO calculator [DracoRPG] - make_per = 5000 + sd->status.job_level*20 + status->dex*10 + status->luk*10; // Base - make_per += pc_checkskill(sd,skill_id)*500; // Smithing skills bonus: +5/+10/+15 - make_per += pc_checkskill(sd,BS_WEAPONRESEARCH)*100 +((wlv >= 3)? pc_checkskill(sd,BS_ORIDEOCON)*100:0); // Weaponry Research bonus: +1/+2/+3/+4/+5/+6/+7/+8/+9/+10, Oridecon Research bonus (custom): +1/+2/+3/+4/+5 - make_per -= (ele?2000:0) + sc*1500 + (wlv>1?wlv*1000:0); // Element Stone: -20%, Star Crumb: -15% each, Weapon level malus: -0/-20/-30 - if(pc_search_inventory(sd,989) > 0) make_per+= 1000; // Emperium Anvil: +10 - else if(pc_search_inventory(sd,988) > 0) make_per+= 500; // Golden Anvil: +5 - else if(pc_search_inventory(sd,987) > 0) make_per+= 300; // Oridecon Anvil: +3 - else if(pc_search_inventory(sd,986) > 0) make_per+= 0; // Anvil: +0? - if(battle_config.wp_rate != 100) - make_per = make_per * battle_config.wp_rate / 100; - } - - if (sd->class_&JOBL_BABY) //if it's a Baby Class - make_per = (make_per * 50) / 100; //Baby penalty is 50% (bugreport:4847) - - if(make_per < 1) make_per = 1; - - - if(rnd()%10000 < make_per || qty > 1){ //Success, or crafting multiple items. - 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]=CARD0_FORGE; - tmp_item.card[1]=((sc*5)<<8)+ele; - tmp_item.card[2]=GetWord(sd->status.char_id,0); // CharId - tmp_item.card[3]=GetWord(sd->status.char_id,1); - } else { - //Flag is only used on the end, so it can be used here. [Skotlex] - switch (skill_id) { - case BS_DAGGER: - case BS_SWORD: - case BS_TWOHANDSWORD: - case BS_AXE: - case BS_MACE: - case BS_KNUCKLE: - case BS_SPEAR: - flag = battle_config.produce_item_name_input&0x1; - break; - case AM_PHARMACY: - case AM_TWILIGHT1: - case AM_TWILIGHT2: - case AM_TWILIGHT3: - flag = battle_config.produce_item_name_input&0x2; - break; - case AL_HOLYWATER: - /** - * Arch Bishop - **/ - case AB_ANCILLA: - flag = battle_config.produce_item_name_input&0x8; - break; - case ASC_CDP: - flag = battle_config.produce_item_name_input&0x10; - break; - default: - flag = battle_config.produce_item_name_input&0x80; - break; - } - if (flag) { - tmp_item.card[0]=CARD0_CREATE; - tmp_item.card[1]=0; - tmp_item.card[2]=GetWord(sd->status.char_id,0); // CharId - tmp_item.card[3]=GetWord(sd->status.char_id,1); - } - } - -// if(log_config.produce > 0) -// log_produce(sd,nameid,slot1,slot2,slot3,1); -//TODO update PICKLOG - - if(equip){ - clif_produceeffect(sd,0,nameid); - clif_misceffect(&sd->bl,3); - if(itemdb_wlv(nameid) >= 3 && ((ele? 1 : 0) + sc) >= 3) // Fame point system [DracoRPG] - pc_addfame(sd,10); // Success to forge a lv3 weapon with 3 additional ingredients = +10 fame point - } else { - int fame = 0; - tmp_item.amount = 0; - - for (i=0; i< qty; i++) { //Apply quantity modifiers. - if( (skill_id == GN_MIX_COOKING || skill_id == GN_MAKEBOMB || skill_id == GN_S_PHARMACY) && make_per > 1){ - tmp_item.amount = qty; - break; - } - if (rnd()%10000 < make_per || qty == 1) { //Success - tmp_item.amount++; - if(nameid < 545 || nameid > 547) - continue; - if( skill_id != AM_PHARMACY && - skill_id != AM_TWILIGHT1 && - skill_id != AM_TWILIGHT2 && - skill_id != AM_TWILIGHT3 ) - continue; - //Add fame as needed. - switch(++sd->potion_success_counter) { - case 3: - fame+=1; // Success to prepare 3 Condensed Potions in a row - break; - case 5: - fame+=3; // Success to prepare 5 Condensed Potions in a row - break; - case 7: - fame+=10; // Success to prepare 7 Condensed Potions in a row - break; - case 10: - fame+=50; // Success to prepare 10 Condensed Potions in a row - sd->potion_success_counter = 0; - break; - } - } else //Failure - sd->potion_success_counter = 0; - } - - if (fame) - pc_addfame(sd,fame); - //Visual effects and the like. - switch (skill_id) { - case AM_PHARMACY: - case AM_TWILIGHT1: - case AM_TWILIGHT2: - case AM_TWILIGHT3: - case ASC_CDP: - clif_produceeffect(sd,2,nameid); - clif_misceffect(&sd->bl,5); - break; - case BS_IRON: - case BS_STEEL: - case BS_ENCHANTEDSTONE: - clif_produceeffect(sd,0,nameid); - clif_misceffect(&sd->bl,3); - break; - case RK_RUNEMASTERY: - case GC_CREATENEWPOISON: - clif_produceeffect(sd,2,nameid); - clif_misceffect(&sd->bl,5); - break; - default: //Those that don't require a skill? - if( skill_produce_db[idx].itemlv > 10 && skill_produce_db[idx].itemlv <= 20) - { //Cooking items. - clif_specialeffect(&sd->bl, 608, AREA); - if( sd->cook_mastery < 1999 ) - pc_setglobalreg(sd, "COOK_MASTERY",sd->cook_mastery + ( 1 << ( (skill_produce_db[idx].itemlv - 11) / 2 ) ) * 5); - } - break; - } - } - if ( skill_id == GN_CHANGEMATERIAL && tmp_item.amount) { //Success - int j, k = 0; - for(i=0; i<MAX_SKILL_PRODUCE_DB; i++) - if( skill_changematerial_db[i].itemid == nameid ){ - for(j=0; j<5; j++){ - if( rnd()%1000 < skill_changematerial_db[i].qty_rate[j] ){ - tmp_item.amount = qty * skill_changematerial_db[i].qty[j]; - if((flag = pc_additem(sd,&tmp_item,tmp_item.amount,LOG_TYPE_PRODUCE))) { - clif_additem(sd,0,0,flag); - map_addflooritem(&tmp_item,tmp_item.amount,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); - } - k++; - } - } - break; - } - if( k ){ - clif_msg_skill(sd,skill_id,0x627); - return 1; - } - } else if (tmp_item.amount) { //Success - if((flag = pc_additem(sd,&tmp_item,tmp_item.amount,LOG_TYPE_PRODUCE))) { - clif_additem(sd,0,0,flag); - map_addflooritem(&tmp_item,tmp_item.amount,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); - } - if( skill_id == GN_MIX_COOKING || skill_id == GN_MAKEBOMB || skill_id == GN_S_PHARMACY ) - clif_msg_skill(sd,skill_id,0x627); - return 1; - } - } - //Failure -// if(log_config.produce) -// log_produce(sd,nameid,slot1,slot2,slot3,0); -//TODO update PICKLOG - - if(equip){ - clif_produceeffect(sd,1,nameid); - clif_misceffect(&sd->bl,2); - } else { - switch (skill_id) { - case ASC_CDP: //25% Damage yourself, and display same effect as failed potion. - status_percent_damage(NULL, &sd->bl, -25, 0, true); - case AM_PHARMACY: - case AM_TWILIGHT1: - case AM_TWILIGHT2: - case AM_TWILIGHT3: - clif_produceeffect(sd,3,nameid); - clif_misceffect(&sd->bl,6); - sd->potion_success_counter = 0; // Fame point system [DracoRPG] - break; - case BS_IRON: - case BS_STEEL: - case BS_ENCHANTEDSTONE: - clif_produceeffect(sd,1,nameid); - clif_misceffect(&sd->bl,2); - break; - case RK_RUNEMASTERY: - case GC_CREATENEWPOISON: - clif_produceeffect(sd,3,nameid); - clif_misceffect(&sd->bl,6); - break; - case GN_MIX_COOKING: { - struct item tmp_item; - const int compensation[5] = {13265, 13266, 13267, 12435, 13268}; - int rate = rnd()%500; - memset(&tmp_item,0,sizeof(tmp_item)); - if( rate < 50) i = 4; - else if( rate < 100) i = 2+rnd()%1; - else if( rate < 250 ) i = 1; - else if( rate < 500 ) i = 0; - tmp_item.nameid = compensation[i]; - tmp_item.amount = qty; - tmp_item.identify = 1; - if( pc_additem(sd,&tmp_item,tmp_item.amount,LOG_TYPE_PRODUCE) ) { - clif_additem(sd,0,0,flag); - map_addflooritem(&tmp_item,tmp_item.amount,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); - } - clif_msg_skill(sd,skill_id,0x628); - } - break; - case GN_MAKEBOMB: - case GN_S_PHARMACY: - case GN_CHANGEMATERIAL: - clif_msg_skill(sd,skill_id,0x628); - break; - default: - if( skill_produce_db[idx].itemlv > 10 && skill_produce_db[idx].itemlv <= 20 ) - { //Cooking items. - clif_specialeffect(&sd->bl, 609, AREA); - if( sd->cook_mastery > 0 ) - pc_setglobalreg(sd, "COOK_MASTERY", sd->cook_mastery - ( 1 << ((skill_produce_db[idx].itemlv - 11) / 2) ) - ( ( ( 1 << ((skill_produce_db[idx].itemlv - 11) / 2) ) >> 1 ) * 3 )); - } - } - } - return 0; -} - -int skill_arrow_create (struct map_session_data *sd, int nameid) -{ - int i,j,flag,index=-1; - struct item tmp_item; - - nullpo_ret(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,0,LOG_TYPE_PRODUCE); - for(i=0;i<MAX_ARROW_RESOURCE;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.produce_item_name_input&0x4) { - tmp_item.card[0]=CARD0_CREATE; - tmp_item.card[1]=0; - tmp_item.card[2]=GetWord(sd->status.char_id,0); // CharId - tmp_item.card[3]=GetWord(sd->status.char_id,1); - } - if(tmp_item.nameid <= 0 || tmp_item.amount <= 0) - continue; - if((flag = pc_additem(sd,&tmp_item,tmp_item.amount,LOG_TYPE_PRODUCE))) { - clif_additem(sd,0,0,flag); - map_addflooritem(&tmp_item,tmp_item.amount,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); - } - } - - return 0; -} -int skill_poisoningweapon( struct map_session_data *sd, int nameid) { - sc_type type; - int chance, i; - nullpo_ret(sd); - if( nameid <= 0 || (i = pc_search_inventory(sd,nameid)) < 0 || pc_delitem(sd,i,1,0,0,LOG_TYPE_CONSUME) ) { - clif_skill_fail(sd,GC_POISONINGWEAPON,USESKILL_FAIL_LEVEL,0); - return 0; - } - switch( nameid ) - { // t_lv used to take duration from skill_get_time2 - case PO_PARALYSE: type = SC_PARALYSE; break; - case PO_PYREXIA: type = SC_PYREXIA; break; - case PO_DEATHHURT: type = SC_DEATHHURT; break; - case PO_LEECHESEND: type = SC_LEECHESEND; break; - case PO_VENOMBLEED: type = SC_VENOMBLEED; break; - case PO_TOXIN: type = SC_TOXIN; break; - case PO_MAGICMUSHROOM: type = SC_MAGICMUSHROOM; break; - case PO_OBLIVIONCURSE: type = SC_OBLIVIONCURSE; break; - default: - clif_skill_fail(sd,GC_POISONINGWEAPON,USESKILL_FAIL_LEVEL,0); - return 0; - } - - chance = 2 + 2 * sd->menuskill_val; // 2 + 2 * skill_lv - sc_start4(&sd->bl, SC_POISONINGWEAPON, 100, pc_checkskill(sd, GC_RESEARCHNEWPOISON), //in Aegis it store the level of GC_RESEARCHNEWPOISON in val1 - type, chance, 0, skill_get_time(GC_POISONINGWEAPON, sd->menuskill_val)); - - return 0; -} - -static void skill_toggle_magicpower(struct block_list *bl, uint16 skill_id) -{ - struct status_change *sc = status_get_sc(bl); - - // non-offensive and non-magic skills do not affect the status - if (skill_get_nk(skill_id)&NK_NO_DAMAGE || !(skill_get_type(skill_id)&BF_MAGIC)) - return; - - if (sc && sc->count && sc->data[SC_MAGICPOWER]) - { - if (sc->data[SC_MAGICPOWER]->val4) - { - status_change_end(bl, SC_MAGICPOWER, INVALID_TIMER); - } - else - { - sc->data[SC_MAGICPOWER]->val4 = 1; - status_calc_bl(bl, status_sc2scb_flag(SC_MAGICPOWER)); -#ifndef RENEWAL - if(bl->type == BL_PC){// update current display. - clif_updatestatus(((TBL_PC *)bl),SP_MATK1); - clif_updatestatus(((TBL_PC *)bl),SP_MATK2); - } -#endif - } - } -} - - -int skill_magicdecoy(struct map_session_data *sd, int nameid) { - int x, y, i, class_, skill; - struct mob_data *md; - nullpo_ret(sd); - skill = sd->menuskill_val; - - if( nameid <= 0 || !itemdb_is_element(nameid) || (i = pc_search_inventory(sd,nameid)) < 0 || !skill || pc_delitem(sd,i,1,0,0,LOG_TYPE_CONSUME) ) - { - clif_skill_fail(sd,NC_MAGICDECOY,USESKILL_FAIL_LEVEL,0); - return 0; - } - - // Spawn Position - pc_delitem(sd,i,1,0,0,LOG_TYPE_CONSUME); - x = sd->sc.comet_x; - y = sd->sc.comet_y; - sd->sc.comet_x = sd->sc.comet_y = 0; - sd->menuskill_val = 0; - - class_ = (nameid == 990 || nameid == 991) ? 2043 + nameid - 990 : (nameid == 992) ? 2046 : 2045; - - - md = mob_once_spawn_sub(&sd->bl, sd->bl.m, x, y, sd->status.name, class_, "", SZ_SMALL, AI_NONE); - if( md ) { - md->master_id = sd->bl.id; - md->special_state.ai = AI_FLORA; - if( md->deletetimer != INVALID_TIMER ) - delete_timer(md->deletetimer, mob_timer_delete); - md->deletetimer = add_timer (gettick() + skill_get_time(NC_MAGICDECOY,skill), mob_timer_delete, md->bl.id, 0); - mob_spawn(md); - md->status.matk_min = md->status.matk_max = 250 + (50 * skill); - } - - return 0; -} - -// Warlock Spellbooks. [LimitLine/3CeAM] -int skill_spellbook (struct map_session_data *sd, int nameid) { - int i, max_preserve, skill_id, point; - struct status_change *sc; - - nullpo_ret(sd); - - sc = status_get_sc(&sd->bl); - status_change_end(&sd->bl, SC_STOP, INVALID_TIMER); - - for(i=SC_SPELLBOOK1; i <= SC_MAXSPELLBOOK; i++) if( sc && !sc->data[i] ) break; - if( i > SC_MAXSPELLBOOK ) - { - clif_skill_fail(sd, WL_READING_SB, USESKILL_FAIL_SPELLBOOK_READING, 0); - return 0; - } - - ARR_FIND(0,MAX_SKILL_SPELLBOOK_DB,i,skill_spellbook_db[i].nameid == nameid); // Search for information of this item - if( i == MAX_SKILL_SPELLBOOK_DB ) return 0; - - if( !pc_checkskill(sd, (skill_id = skill_spellbook_db[i].skill_id)) ) - { // User don't know the skill - sc_start(&sd->bl, SC_SLEEP, 100, 1, skill_get_time(WL_READING_SB, pc_checkskill(sd,WL_READING_SB))); - clif_skill_fail(sd, WL_READING_SB, USESKILL_FAIL_SPELLBOOK_DIFFICULT_SLEEP, 0); - return 0; - } - - max_preserve = 4 * pc_checkskill(sd, WL_FREEZE_SP) + status_get_int(&sd->bl) / 10 + sd->status.base_level / 10; - point = skill_spellbook_db[i].point; - - if( sc && sc->data[SC_READING_SB] ){ - if( (sc->data[SC_READING_SB]->val2 + point) > max_preserve ) - { - clif_skill_fail(sd, WL_READING_SB, USESKILL_FAIL_SPELLBOOK_PRESERVATION_POINT, 0); - return 0; - } - for(i = SC_MAXSPELLBOOK; i >= SC_SPELLBOOK1; i--){ // This is how official saves spellbook. [malufett] - if( !sc->data[i] ){ - sc->data[SC_READING_SB]->val2 += point; // increase points - sc_start4(&sd->bl, (sc_type)i, 100, skill_id, pc_checkskill(sd,skill_id), point, 0, INVALID_TIMER); - break; - } - } - }else{ - sc_start2(&sd->bl, SC_READING_SB, 100, 0, point, INVALID_TIMER); - sc_start4(&sd->bl, SC_MAXSPELLBOOK, 100, skill_id, pc_checkskill(sd,skill_id), point, 0, INVALID_TIMER); - } - - return 1; -} -int skill_select_menu(struct map_session_data *sd,uint16 skill_id) { - int id, lv, prob, aslvl = 0; - nullpo_ret(sd); - - if (sd->sc.data[SC_STOP]) { - aslvl = sd->sc.data[SC_STOP]->val1; - status_change_end(&sd->bl,SC_STOP,INVALID_TIMER); - } - - if( skill_id >= GS_GLITTERING || skill_get_type(skill_id) != BF_MAGIC || - (id = sd->status.skill[skill_id].id) == 0 || sd->status.skill[skill_id].flag != SKILL_FLAG_PLAGIARIZED ) { - clif_skill_fail(sd,SC_AUTOSHADOWSPELL,0,0); - return 0; - } - - lv = (aslvl + 1) / 2; // The level the skill will be autocasted - lv = min(lv,sd->status.skill[skill_id].lv); - prob = (aslvl == 10) ? 15 : (32 - 2 * aslvl); // Probability at level 10 was increased to 15. - sc_start4(&sd->bl,SC__AUTOSHADOWSPELL,100,id,lv,prob,0,skill_get_time(SC_AUTOSHADOWSPELL,aslvl)); - return 0; -} -int skill_elementalanalysis(struct map_session_data* sd, int n, uint16 skill_lv, unsigned short* item_list) { - int i; - - nullpo_ret(sd); - nullpo_ret(item_list); - - if( n <= 0 ) - return 1; - - for( i = 0; i < n; i++ ) { - int nameid, add_amount, del_amount, idx, product, flag; - struct item tmp_item; - - idx = item_list[i*2+0]-2; - del_amount = item_list[i*2+1]; - - if( skill_lv == 2 ) - del_amount -= (del_amount % 10); - add_amount = (skill_lv == 1) ? del_amount * (5 + rnd()%5) : del_amount / 10 ; - - if( (nameid = sd->status.inventory[idx].nameid) <= 0 || del_amount > sd->status.inventory[idx].amount ) { - clif_skill_fail(sd,SO_EL_ANALYSIS,USESKILL_FAIL_LEVEL,0); - return 1; - } - - switch( nameid ) { - // Level 1 - case 994: product = 990; break; // Flame Heart -> Red Blood. - case 995: product = 991; break; // Mystic Frozen -> Crystal Blue. - case 996: product = 992; break; // Rough Wind -> Wind of Verdure. - case 997: product = 993; break; // Great Nature -> Green Live. - // Level 2 - case 990: product = 994; break; // Red Blood -> Flame Heart. - case 991: product = 995; break; // Crystal Blue -> Mystic Frozen. - case 992: product = 996; break; // Wind of Verdure -> Rough Wind. - case 993: product = 997; break; // Green Live -> Great Nature. - default: - clif_skill_fail(sd,SO_EL_ANALYSIS,USESKILL_FAIL_LEVEL,0); - return 1; - } - - if( pc_delitem(sd,idx,del_amount,0,1,LOG_TYPE_CONSUME) ) { - clif_skill_fail(sd,SO_EL_ANALYSIS,USESKILL_FAIL_LEVEL,0); - return 1; - } - - if( skill_lv == 2 && rnd()%100 < 25 ) { // At level 2 have a fail chance. You loose your items if it fails. - clif_skill_fail(sd,SO_EL_ANALYSIS,USESKILL_FAIL_LEVEL,0); - return 1; - } - - - memset(&tmp_item,0,sizeof(tmp_item)); - tmp_item.nameid = product; - tmp_item.amount = add_amount; - tmp_item.identify = 1; - - if( tmp_item.amount ) { - if( (flag = pc_additem(sd,&tmp_item,tmp_item.amount,LOG_TYPE_CONSUME)) ) { - clif_additem(sd,0,0,flag); - map_addflooritem(&tmp_item,tmp_item.amount,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); - } - } - - } - - return 0; -} - -int skill_changematerial(struct map_session_data *sd, int n, unsigned short *item_list) { - int i, j, k, c, p = 0, nameid, amount; - - nullpo_ret(sd); - nullpo_ret(item_list); - - // Search for objects that can be created. - for( i = 0; i < MAX_SKILL_PRODUCE_DB; i++ ) { - if( skill_produce_db[i].itemlv == 26 ) { - p = 0; - do { - c = 0; - // Verification of overlap between the objects required and the list submitted. - for( j = 0; j < MAX_PRODUCE_RESOURCE; j++ ) { - if( skill_produce_db[i].mat_id[j] > 0 ) { - for( k = 0; k < n; k++ ) { - int idx = item_list[k*2+0]-2; - nameid = sd->status.inventory[idx].nameid; - amount = item_list[k*2+1]; - if( nameid > 0 && sd->status.inventory[idx].identify == 0 ){ - clif_msg_skill(sd,GN_CHANGEMATERIAL,0x62D); - return 0; - } - if( nameid == skill_produce_db[i].mat_id[j] && (amount-p*skill_produce_db[i].mat_amount[j]) >= skill_produce_db[i].mat_amount[j] - && (amount-p*skill_produce_db[i].mat_amount[j])%skill_produce_db[i].mat_amount[j] == 0 ) // must be in exact amount - c++; // match - } - } - else - break; // No more items required - } - p++; - } while(n == j && c == n); - p--; - if ( p > 0 ) { - skill_produce_mix(sd,GN_CHANGEMATERIAL,skill_produce_db[i].nameid,0,0,0,p); - return 1; - } - } - } - - if( p == 0) - clif_msg_skill(sd,GN_CHANGEMATERIAL,0x623); - - return 0; -} -/** - * for Royal Guard's LG_TRAMPLE - **/ -static int skill_destroy_trap( struct block_list *bl, va_list ap ) { - struct skill_unit *su = (struct skill_unit *)bl; - struct skill_unit_group *sg; - unsigned int tick; - - nullpo_ret(su); - tick = va_arg(ap, unsigned int); - - if (su->alive && (sg = su->group) && skill_get_inf2(sg->skill_id)&INF2_TRAP) { - switch( sg->unit_id ) { - case UNT_LANDMINE: - case UNT_CLAYMORETRAP: - case UNT_BLASTMINE: - case UNT_SHOCKWAVE: - case UNT_SANDMAN: - case UNT_FLASHER: - case UNT_FREEZINGTRAP: - case UNT_CLUSTERBOMB: - case UNT_FIRINGTRAP: - case UNT_ICEBOUNDTRAP: - map_foreachinrange(skill_trap_splash,&su->bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, &su->bl,tick); - break; - } - // Traps aren't recovered. - skill_delunit(su); - } - return 0; -} -/*========================================== - * - *------------------------------------------*/ -int skill_blockpc_end(int tid, unsigned int tick, int id, intptr_t data) -{ - struct map_session_data *sd = map_id2sd(id); - struct skill_cd * cd = NULL; - - if (data <= 0 || data >= MAX_SKILL) - return 0; - if (!sd) return 0; - if (sd->blockskill[data] != (0x1|(tid&0xFE))) return 0; - - if( ( cd = idb_get(skillcd_db,sd->status.char_id) ) ) { - int i,cursor; - ARR_FIND( 0, cd->cursor+1, cursor, cd->skidx[cursor] == data ); - cd->duration[cursor] = 0; - cd->skidx[cursor] = 0; - cd->nameid[cursor] = 0; - // compact the cool down list - for( i = 0, cursor = 0; i < cd->cursor; i++ ) { - if( cd->duration[i] == 0 ) - continue; - if( cursor != i ) { - cd->duration[cursor] = cd->duration[i]; - cd->skidx[cursor] = cd->skidx[i]; - cd->nameid[cursor] = cd->nameid[i]; - } - cursor++; - } - if( cursor == 0 ) - idb_remove(skillcd_db,sd->status.char_id); - else - cd->cursor = cursor; - } - - sd->blockskill[data] = 0; - return 1; -} - -/** - * flags a singular skill as being blocked from persistent usage. - * @param sd the player the skill delay affects - * @param skill_id the skill which should be delayed - * @param tick the length of time the delay should last - * @param load whether this assignment is being loaded upon player login - * @return 0 if successful, -1 otherwise - */ -int skill_blockpc_start_(struct map_session_data *sd, uint16 skill_id, int tick, bool load) -{ - int oskill_id = skill_id; - struct skill_cd* cd = NULL; - uint16 idx = skill_get_index(skill_id); - - nullpo_retr (-1, sd); - - if (idx == 0) - return -1; - - if (tick < 1) { - sd->blockskill[idx] = 0; - return -1; - } - - if( battle_config.display_status_timers ) - clif_skill_cooldown(sd, idx, tick); - - if( !load ) - {// not being loaded initially so ensure the skill delay is recorded - if( !(cd = idb_get(skillcd_db,sd->status.char_id)) ) - {// create a new skill cooldown object for map storage - CREATE( cd, struct skill_cd, 1 ); - idb_put( skillcd_db, sd->status.char_id, cd ); - } - - // record the skill duration in the database map - cd->duration[cd->cursor] = tick; - cd->skidx[cd->cursor] = idx; - cd->nameid[cd->cursor] = oskill_id; - cd->cursor++; - } - - sd->blockskill[idx] = 0x1|(0xFE&add_timer(gettick()+tick,skill_blockpc_end,sd->bl.id,idx)); - return 0; -} - -int skill_blockhomun_end(int tid, unsigned int tick, int id, intptr_t data) //[orn] -{ - struct homun_data *hd = (TBL_HOM*) map_id2bl(id); - if (data <= 0 || data >= MAX_SKILL) - return 0; - if (hd) hd->blockskill[data] = 0; - - return 1; -} - -int skill_blockhomun_start(struct homun_data *hd, uint16 skill_id, int tick) //[orn] -{ - uint16 idx = skill_get_index(skill_id); - nullpo_retr (-1, hd); - - - if (idx == 0) - return -1; - - if (tick < 1) { - hd->blockskill[idx] = 0; - return -1; - } - hd->blockskill[idx] = 1; - return add_timer(gettick() + tick, skill_blockhomun_end, hd->bl.id, idx); -} - -int skill_blockmerc_end(int tid, unsigned int tick, int id, intptr_t data) //[orn] -{ - struct mercenary_data *md = (TBL_MER*)map_id2bl(id); - if( data <= 0 || data >= MAX_SKILL ) - return 0; - if( md ) md->blockskill[data] = 0; - - return 1; -} - -int skill_blockmerc_start(struct mercenary_data *md, uint16 skill_id, int tick) -{ - uint16 idx = skill_get_index(skill_id); - nullpo_retr (-1, md); - - if (idx == 0) - return -1; - if( tick < 1 ) - { - md->blockskill[idx] = 0; - return -1; - } - md->blockskill[idx] = 1; - return add_timer(gettick() + tick, skill_blockmerc_end, md->bl.id, idx); -} -/** - * Adds a new skill unit entry for this player to recast after map load - **/ -void skill_usave_add(struct map_session_data * sd, uint16 skill_id, uint16 skill_lv) { - struct skill_usave * sus = NULL; - - if( idb_exists(skillusave_db,sd->status.char_id) ) { - idb_remove(skillusave_db,sd->status.char_id); - } - - CREATE( sus, struct skill_usave, 1 ); - idb_put( skillusave_db, sd->status.char_id, sus ); - - sus->skill_id = skill_id; - sus->skill_lv = skill_lv; - - return; -} -void skill_usave_trigger(struct map_session_data *sd) { - struct skill_usave * sus = NULL; - - if( ! (sus = idb_get(skillusave_db,sd->status.char_id)) ) { - return; - } - - skill_unitsetting(&sd->bl,sus->skill_id,sus->skill_lv,sd->bl.x,sd->bl.y,0); - - idb_remove(skillusave_db,sd->status.char_id); - - return; -} -/* - * - */ -int skill_split_str (char *str, char **val, int num) -{ - int i; - - for( i = 0; i < num && str; i++ ) - { - val[i] = str; - str = strchr(str,','); - if( str ) - *str++=0; - } - - return i; -} -/* - * - */ -int skill_split_atoi (char *str, int *val) -{ - int i, j, diff, step = 1; - - for (i=0; i<MAX_SKILL_LEVEL; i++) { - if (!str) break; - val[i] = atoi(str); - str = strchr(str,':'); - if (str) - *str++=0; - } - if(i==0) //No data found. - return 0; - if(i==1) - { //Single value, have the whole range have the same value. - for (; i < MAX_SKILL_LEVEL; i++) - val[i] = val[i-1]; - return i; - } - //Check for linear change with increasing steps until we reach half of the data acquired. - for (step = 1; step <= i/2; step++) - { - diff = val[i-1] - val[i-step-1]; - for(j = i-1; j >= step; j--) - if ((val[j]-val[j-step]) != diff) - break; - - if (j>=step) //No match, try next step. - continue; - - for(; i < MAX_SKILL_LEVEL; i++) - { //Apply linear increase - val[i] = val[i-step]+diff; - if (val[i] < 1 && val[i-1] >=0) //Check if we have switched from + to -, cap the decrease to 0 in said cases. - { val[i] = 1; diff = 0; step = 1; } - } - return i; - } - //Okay.. we can't figure this one out, just fill out the stuff with the previous value. - for (;i<MAX_SKILL_LEVEL; i++) - val[i] = val[i-1]; - return i; -} - -/* - * - */ -void skill_init_unit_layout (void) -{ - int i,j,size,pos = 0; - - memset(skill_unit_layout,0,sizeof(skill_unit_layout)); - - // standard square layouts go first - for (i=0; i<=MAX_SQUARE_LAYOUT; i++) { - size = i*2+1; - skill_unit_layout[i].count = size*size; - for (j=0; j<size*size; j++) { - skill_unit_layout[i].dx[j] = (j%size-i); - skill_unit_layout[i].dy[j] = (j/size-i); - } - } - - // afterwards add special ones - pos = i; - for (i=0;i<MAX_SKILL_DB;i++) { - if (!skill_db[i].unit_id[0] || skill_db[i].unit_layout_type[0] != -1) - continue; - if( i >= HM_SKILLRANGEMIN && i <= EL_SKILLRANGEMAX ) { - int skill = i; - - if( i >= EL_SKILLRANGEMIN && i <= EL_SKILLRANGEMAX ) { - skill -= EL_SKILLRANGEMIN; - skill += EL_SKILLBASE; - } - if( skill == EL_FIRE_MANTLE ) { - static const int dx[] = {-1, 0, 1, 1, 1, 0,-1,-1}; - static const int dy[] = { 1, 1, 1, 0,-1,-1,-1, 0}; - skill_unit_layout[pos].count = 8; - memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); - memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); - } - } else { - switch (i) { - case MG_FIREWALL: - case WZ_ICEWALL: - case WL_EARTHSTRAIN://Warlock - // these will be handled later - break; - case PR_SANCTUARY: - case NPC_EVILLAND: { - 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}; - skill_unit_layout[pos].count = 21; - memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); - memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); - } - 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}; - skill_unit_layout[pos].count = 33; - memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); - memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); - } - break; - case MH_POISON_MIST: - case AS_VENOMDUST: { - static const int dx[] = {-1, 0, 0, 0, 1}; - static const int dy[] = { 0,-1, 0, 1, 0}; - skill_unit_layout[pos].count = 5; - memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); - memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); - } - break; - case CR_GRANDCROSS: - case NPC_GRANDDARKNESS: { - 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}; - skill_unit_layout[pos].count = 29; - memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); - memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); - } - break; - case PF_FOGWALL: { - static const int dx[] = { - -2,-1, 0, 1, 2,-2,-1, 0, 1, 2,-2,-1, 0, 1, 2}; - static const int dy[] = { - -1,-1,-1,-1,-1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1}; - skill_unit_layout[pos].count = 15; - memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); - memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); - } - break; - case PA_GOSPEL: { - 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}; - skill_unit_layout[pos].count = 33; - memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); - memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); - } - break; - case NJ_KAENSIN: { - static const int dx[] = {-2,-1, 0, 1, 2,-2,-1, 0, 1, 2,-2,-1, 1, 2,-2,-1, 0, 1, 2,-2,-1, 0, 1, 2}; - static const int dy[] = { 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0,-1,-1,-1,-1,-1,-2,-2,-2,-2,-2}; - skill_unit_layout[pos].count = 24; - memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); - memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); - } - break; - case NJ_TATAMIGAESHI: { - //Level 1 (count 4, cross of 3x3) - static const int dx1[] = {-1, 1, 0, 0}; - static const int dy1[] = { 0, 0,-1, 1}; - //Level 2-3 (count 8, cross of 5x5) - static const int dx2[] = {-2,-1, 1, 2, 0, 0, 0, 0}; - static const int dy2[] = { 0, 0, 0, 0,-2,-1, 1, 2}; - //Level 4-5 (count 12, cross of 7x7 - static const int dx3[] = {-3,-2,-1, 1, 2, 3, 0, 0, 0, 0, 0, 0}; - static const int dy3[] = { 0, 0, 0, 0, 0, 0,-3,-2,-1, 1, 2, 3}; - //lv1 - j = 0; - skill_unit_layout[pos].count = 4; - memcpy(skill_unit_layout[pos].dx,dx1,sizeof(dx1)); - memcpy(skill_unit_layout[pos].dy,dy1,sizeof(dy1)); - skill_db[i].unit_layout_type[j] = pos; - //lv2/3 - j++; - pos++; - skill_unit_layout[pos].count = 8; - memcpy(skill_unit_layout[pos].dx,dx2,sizeof(dx2)); - memcpy(skill_unit_layout[pos].dy,dy2,sizeof(dy2)); - skill_db[i].unit_layout_type[j] = pos; - skill_db[i].unit_layout_type[++j] = pos; - //lv4/5 - j++; - pos++; - skill_unit_layout[pos].count = 12; - memcpy(skill_unit_layout[pos].dx,dx3,sizeof(dx3)); - memcpy(skill_unit_layout[pos].dy,dy3,sizeof(dy3)); - skill_db[i].unit_layout_type[j] = pos; - skill_db[i].unit_layout_type[++j] = pos; - //Fill in the rest using lv 5. - for (;j<MAX_SKILL_LEVEL;j++) - skill_db[i].unit_layout_type[j] = pos; - //Skip, this way the check below will fail and continue to the next skill. - pos++; - } - break; - case GN_WALLOFTHORN: { - static const int dx[] = {-1,-2,-2,-2,-2,-2,-1, 0, 1, 2, 2, 2, 2, 2, 1, 0}; - static const int dy[] = { 2, 2, 1, 0,-1,-2,-2,-2,-2,-2,-1, 0, 1, 2, 2, 2}; - skill_unit_layout[pos].count = 16; - memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); - memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); - } - break; - default: - ShowError("unknown unit layout at skill %d\n",i); - break; - } - } - if (!skill_unit_layout[pos].count) - continue; - for (j=0;j<MAX_SKILL_LEVEL;j++) - skill_db[i].unit_layout_type[j] = pos; - pos++; - } - - // firewall and icewall have 8 layouts (direction-dependent) - firewall_unit_pos = pos; - for (i=0;i<8;i++) { - if (i&1) { - skill_unit_layout[pos].count = 5; - if (i&0x2) { - int dx[] = {-1,-1, 0, 0, 1}; - int dy[] = { 1, 0, 0,-1,-1}; - memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); - memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); - } else { - int dx[] = { 1, 1 ,0, 0,-1}; - int dy[] = { 1, 0, 0,-1,-1}; - memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); - memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); - } - } else { - skill_unit_layout[pos].count = 3; - if (i%4==0) { - int dx[] = {-1, 0, 1}; - int dy[] = { 0, 0, 0}; - memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); - memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); - } else { - int dx[] = { 0, 0, 0}; - int dy[] = {-1, 0, 1}; - memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); - memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); - } - } - pos++; - } - icewall_unit_pos = pos; - for (i=0;i<8;i++) { - skill_unit_layout[pos].count = 5; - if (i&1) { - if (i&0x2) { - int dx[] = {-2,-1, 0, 1, 2}; - int dy[] = { 2, 1, 0,-1,-2}; - memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); - memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); - } else { - int dx[] = { 2, 1 ,0,-1,-2}; - int dy[] = { 2, 1, 0,-1,-2}; - memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); - memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); - } - } else { - if (i%4==0) { - int dx[] = {-2,-1, 0, 1, 2}; - int dy[] = { 0, 0, 0, 0, 0}; - memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); - memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); - } else { - int dx[] = { 0, 0, 0, 0, 0}; - int dy[] = {-2,-1, 0, 1, 2}; - memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); - memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); - } - } - pos++; - } - earthstrain_unit_pos = pos; - for( i = 0; i < 8; i++ ) - { // For each Direction - skill_unit_layout[pos].count = 15; - switch( i ) - { - case 0: case 1: case 3: case 4: case 5: case 7: - { - int dx[] = {-7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7}; - int dy[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); - memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); - } - break; - case 2: - case 6: - { - int dx[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - int dy[] = {-7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7}; - memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); - memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); - } - break; - } - pos++; - } - -} - -int skill_block_check(struct block_list *bl, sc_type type , uint16 skill_id) { - int inf = 0; - struct status_change *sc = status_get_sc(bl); - - if( !sc || !bl || !skill_id ) - return 0; // Can do it - - switch(type){ - case SC_STASIS: - inf = skill_get_inf2(skill_id); - if( inf == INF2_SONG_DANCE || /*skill_get_inf2(skill_id) == INF2_CHORUS_SKILL ||*/ inf == INF2_SPIRIT_SKILL ) - return 1; // Can't do it. - switch( skill_id ) - { - case NV_FIRSTAID: case TF_HIDING: case AS_CLOAKING: case WZ_SIGHTRASHER: - case RG_STRIPWEAPON: case RG_STRIPSHIELD: case RG_STRIPARMOR: case WZ_METEOR: - case RG_STRIPHELM: case SC_STRIPACCESSARY: case ST_FULLSTRIP: case WZ_SIGHTBLASTER: - case ST_CHASEWALK: case SC_ENERVATION: case SC_GROOMY: case WZ_ICEWALL: - case SC_IGNORANCE: case SC_LAZINESS: case SC_UNLUCKY: case WZ_STORMGUST: - case SC_WEAKNESS: case AL_RUWACH: case AL_PNEUMA: case WZ_JUPITEL: - case AL_HEAL: case AL_BLESSING: case AL_INCAGI: case WZ_VERMILION: - case AL_TELEPORT: case AL_WARP: case AL_HOLYWATER: case WZ_EARTHSPIKE: - case AL_HOLYLIGHT: case PR_IMPOSITIO: case PR_ASPERSIO: case WZ_HEAVENDRIVE: - case PR_SANCTUARY: case PR_STRECOVERY: case PR_MAGNIFICAT: case WZ_QUAGMIRE: - case ALL_RESURRECTION: case PR_LEXDIVINA: case PR_LEXAETERNA: case HW_GRAVITATION: - case PR_MAGNUS: case PR_TURNUNDEAD: case MG_SRECOVERY: case HW_MAGICPOWER: - case MG_SIGHT: case MG_NAPALMBEAT: case MG_SAFETYWALL: case HW_GANBANTEIN: - case MG_SOULSTRIKE: case MG_COLDBOLT: case MG_FROSTDIVER: case WL_DRAINLIFE: - case MG_STONECURSE: case MG_FIREBALL: case MG_FIREWALL: case WL_SOULEXPANSION: - case MG_FIREBOLT: case MG_LIGHTNINGBOLT: case MG_THUNDERSTORM: case MG_ENERGYCOAT: - case WL_WHITEIMPRISON: case WL_SUMMONFB: case WL_SUMMONBL: case WL_SUMMONWB: - case WL_SUMMONSTONE: case WL_SIENNAEXECRATE: case WL_RELEASE: case WL_EARTHSTRAIN: - case WL_RECOGNIZEDSPELL: case WL_READING_SB: case SA_MAGICROD: case SA_SPELLBREAKER: - case SA_DISPELL: case SA_FLAMELAUNCHER: case SA_FROSTWEAPON: case SA_LIGHTNINGLOADER: - case SA_SEISMICWEAPON: case SA_VOLCANO: case SA_DELUGE: case SA_VIOLENTGALE: - case SA_LANDPROTECTOR: case PF_HPCONVERSION: case PF_SOULCHANGE: case PF_SPIDERWEB: - case PF_FOGWALL: case TK_RUN: case TK_HIGHJUMP: case TK_SEVENWIND: - case SL_KAAHI: case SL_KAUPE: case SL_KAITE: - - // Skills that need to be confirmed. - case SO_FIREWALK: case SO_ELECTRICWALK: case SO_SPELLFIST: case SO_EARTHGRAVE: - case SO_DIAMONDDUST: case SO_POISON_BUSTER: case SO_PSYCHIC_WAVE: case SO_CLOUD_KILL: - case SO_STRIKING: case SO_WARMER: case SO_VACUUM_EXTREME: case SO_VARETYR_SPEAR: - case SO_ARRULLO: - return 1; // Can't do it. - } - break; - case SC_KAGEHUMI: - switch(skill_id){ - case TF_HIDING: case AS_CLOAKING: case GC_CLOAKINGEXCEED: case SC_SHADOWFORM: - case MI_HARMONIZE: case CG_MARIONETTE: case AL_TELEPORT: case TF_BACKSLIDING: - case RA_CAMOUFLAGE: case ST_CHASEWALK: case GD_EMERGENCYCALL: - return 1; // needs more info - } - break; - } - - return 0; -} - -int skill_get_elemental_type( uint16 skill_id , uint16 skill_lv ) { - int type = 0; - - switch( skill_id ) { - case SO_SUMMON_AGNI: type = 2114; break; - case SO_SUMMON_AQUA: type = 2117; break; - case SO_SUMMON_VENTUS: type = 2120; break; - case SO_SUMMON_TERA: type = 2123; break; - } - - type += skill_lv - 1; - - return type; -} - -/** - * reload stored skill cooldowns when a player logs in. - * @param sd the affected player structure - */ -void skill_cooldown_load(struct map_session_data * sd) -{ - int i; - struct skill_cd* cd = NULL; - - // always check to make sure the session properly exists - nullpo_retv(sd); - - if( !(cd = idb_get(skillcd_db, sd->status.char_id)) ) - {// no skill cooldown is associated with this character - return; - } - - // process each individual cooldown associated with the character - for( i = 0; i < cd->cursor; i++ ) - { - // block the skill from usage but ensure it is not recorded (load = true) - skill_blockpc_start_( sd, cd->nameid[i], cd->duration[i], true ); - } -} - -/*========================================== - * sub-function of DB reading. - * skill_db.txt - *------------------------------------------*/ - -static bool skill_parse_row_skilldb(char* split[], int columns, int current) -{// id,range,hit,inf,element,nk,splash,max,list_num,castcancel,cast_defence_rate,inf2,maxcount,skill_type,blow_count,name,description - uint16 skill_id = atoi(split[0]); - uint16 idx; - if( (skill_id >= GD_SKILLRANGEMIN && skill_id <= GD_SKILLRANGEMAX) - || (skill_id >= HM_SKILLRANGEMIN && skill_id <= HM_SKILLRANGEMAX) - || (skill_id >= MC_SKILLRANGEMIN && skill_id <= MC_SKILLRANGEMAX) - || (skill_id >= EL_SKILLRANGEMIN && skill_id <= EL_SKILLRANGEMAX) ) { - ShowWarning("skill_parse_row_skilldb: Skill id %d is forbidden (interferes with guild/homun/mercenary skill mapping)!\n", skill_id); - return false; - } - - idx = skill_get_index(skill_id); - if( !idx ) // invalid skill id - return false; - - skill_split_atoi(split[1],skill_db[idx].range); - skill_db[idx].hit = atoi(split[2]); - skill_db[idx].inf = atoi(split[3]); - skill_split_atoi(split[4],skill_db[idx].element); - skill_db[idx].nk = (int)strtol(split[5], NULL, 0); - skill_split_atoi(split[6],skill_db[idx].splash); - skill_db[idx].max = atoi(split[7]); - skill_split_atoi(split[8],skill_db[idx].num); - - if( strcmpi(split[9],"yes") == 0 ) - skill_db[idx].castcancel = 1; - else - skill_db[idx].castcancel = 0; - skill_db[idx].cast_def_rate = atoi(split[10]); - skill_db[idx].inf2 = (int)strtol(split[11], NULL, 0); - skill_split_atoi(split[12],skill_db[idx].maxcount); - if( strcmpi(split[13],"weapon") == 0 ) - skill_db[idx].skill_type = BF_WEAPON; - else if( strcmpi(split[13],"magic") == 0 ) - skill_db[idx].skill_type = BF_MAGIC; - else if( strcmpi(split[13],"misc") == 0 ) - skill_db[idx].skill_type = BF_MISC; - else - skill_db[idx].skill_type = 0; - skill_split_atoi(split[14],skill_db[idx].blewcount); - safestrncpy(skill_db[idx].name, trim(split[15]), sizeof(skill_db[idx].name)); - safestrncpy(skill_db[idx].desc, trim(split[16]), sizeof(skill_db[idx].desc)); - strdb_iput(skilldb_name2id, skill_db[idx].name, skill_id); - - return true; -} - -static bool skill_parse_row_requiredb(char* split[], int columns, int current) -{// skill_id,HPCost,MaxHPTrigger,SPCost,HPRateCost,SPRateCost,ZenyCost,RequiredWeapons,RequiredAmmoTypes,RequiredAmmoAmount,RequiredState,SpiritSphereCost,RequiredItemID1,RequiredItemAmount1,RequiredItemID2,RequiredItemAmount2,RequiredItemID3,RequiredItemAmount3,RequiredItemID4,RequiredItemAmount4,RequiredItemID5,RequiredItemAmount5,RequiredItemID6,RequiredItemAmount6,RequiredItemID7,RequiredItemAmount7,RequiredItemID8,RequiredItemAmount8,RequiredItemID9,RequiredItemAmount9,RequiredItemID10,RequiredItemAmount10 - char* p; - int j; - - uint16 skill_id = atoi(split[0]); - uint16 idx = skill_get_index(skill_id); - if( !idx ) // invalid skill id - return false; - - skill_split_atoi(split[1],skill_db[idx].hp); - skill_split_atoi(split[2],skill_db[idx].mhp); - skill_split_atoi(split[3],skill_db[idx].sp); - skill_split_atoi(split[4],skill_db[idx].hp_rate); - skill_split_atoi(split[5],skill_db[idx].sp_rate); - skill_split_atoi(split[6],skill_db[idx].zeny); - - //Wich weapon type are required, see doc/item_db for types - p = split[7]; - for( j = 0; j < 32; j++ ) - { - int l = atoi(p); - if( l == 99 ) // Any weapon - { - skill_db[idx].weapon = 0; - break; - } - else - skill_db[idx].weapon |= 1<<l; - p = strchr(p,':'); - if(!p) - break; - p++; - } - - //FIXME: document this - p = split[8]; - for( j = 0; j < 32; j++ ) - { - int l = atoi(p); - if( l == 99 ) // Any ammo type - { - skill_db[idx].ammo = 0xFFFFFFFF; - break; - } - else if( l ) // 0 stands for no requirement - skill_db[idx].ammo |= 1<<l; - p = strchr(p,':'); - if( !p ) - break; - p++; - } - skill_split_atoi(split[9],skill_db[idx].ammo_qty); - - if( strcmpi(split[10],"hiding")==0 ) skill_db[idx].state = ST_HIDING; - else if( strcmpi(split[10],"cloaking")==0 ) skill_db[idx].state = ST_CLOAKING; - else if( strcmpi(split[10],"hidden")==0 ) skill_db[idx].state = ST_HIDDEN; - else if( strcmpi(split[10],"riding")==0 ) skill_db[idx].state = ST_RIDING; - else if( strcmpi(split[10],"falcon")==0 ) skill_db[idx].state = ST_FALCON; - else if( strcmpi(split[10],"cart")==0 ) skill_db[idx].state = ST_CART; - else if( strcmpi(split[10],"shield")==0 ) skill_db[idx].state = ST_SHIELD; - else if( strcmpi(split[10],"sight")==0 ) skill_db[idx].state = ST_SIGHT; - else if( strcmpi(split[10],"explosionspirits")==0 ) skill_db[idx].state = ST_EXPLOSIONSPIRITS; - else if( strcmpi(split[10],"cartboost")==0 ) skill_db[idx].state = ST_CARTBOOST; - else if( strcmpi(split[10],"recover_weight_rate")==0 ) skill_db[idx].state = ST_RECOV_WEIGHT_RATE; - else if( strcmpi(split[10],"move_enable")==0 ) skill_db[idx].state = ST_MOVE_ENABLE; - else if( strcmpi(split[10],"water")==0 ) skill_db[idx].state = ST_WATER; - /** - * New States - **/ - else if( strcmpi(split[10],"dragon")==0 ) skill_db[idx].state = ST_RIDINGDRAGON; - else if( strcmpi(split[10],"warg")==0 ) skill_db[idx].state = ST_WUG; - else if( strcmpi(split[10],"ridingwarg")==0 ) skill_db[idx].state = ST_RIDINGWUG; - else if( strcmpi(split[10],"mado")==0 ) skill_db[idx].state = ST_MADO; - else if( strcmpi(split[10],"elementalspirit")==0 ) skill_db[idx].state = ST_ELEMENTALSPIRIT; - else if (strcmpi(split[10], "poisonweapon") == 0) skill_db[idx].state = ST_POISONINGWEAPON; - else if (strcmpi(split[10], "rollingcutter") == 0) skill_db[idx].state = ST_ROLLINGCUTTER; - else if (strcmpi(split[10], "mh_fighting") == 0) skill_db[idx].state = ST_MH_FIGHTING; - else if (strcmpi(split[10], "mh_grappling") == 0) skill_db[idx].state = ST_MH_GRAPPLING; - - /** - * Unknown or no state - **/ - else skill_db[idx].state = ST_NONE; - - skill_split_atoi(split[11],skill_db[idx].spiritball); - for( j = 0; j < MAX_SKILL_ITEM_REQUIRE; j++ ) { - skill_db[idx].itemid[j] = atoi(split[12+ 2*j]); - skill_db[idx].amount[j] = atoi(split[13+ 2*j]); - } - - return true; -} - -static bool skill_parse_row_castdb(char* split[], int columns, int current) -{// skill_id,CastingTime,AfterCastActDelay,AfterCastWalkDelay,Duration1,Duration2 - uint16 skill_id = atoi(split[0]); - uint16 idx = skill_get_index(skill_id); - if( !idx ) // invalid skill id - return false; - - skill_split_atoi(split[1],skill_db[idx].cast); - skill_split_atoi(split[2],skill_db[idx].delay); - skill_split_atoi(split[3],skill_db[idx].walkdelay); - skill_split_atoi(split[4],skill_db[idx].upkeep_time); - skill_split_atoi(split[5],skill_db[idx].upkeep_time2); - skill_split_atoi(split[6],skill_db[idx].cooldown); -#ifdef RENEWAL_CAST - skill_split_atoi(split[7],skill_db[idx].fixed_cast); -#endif - return true; -} - -static bool skill_parse_row_castnodexdb(char* split[], int columns, int current) -{// Skill id,Cast,Delay (optional) - uint16 skill_id = atoi(split[0]); - uint16 idx = skill_get_index(skill_id); - if( !idx ) // invalid skill id - return false; - - skill_split_atoi(split[1],skill_db[idx].castnodex); - if( split[2] ) // optional column - skill_split_atoi(split[2],skill_db[idx].delaynodex); - - return true; -} - -static bool skill_parse_row_nocastdb(char* split[], int columns, int current) -{// skill_id,Flag - uint16 skill_id = atoi(split[0]); - uint16 idx = skill_get_index(skill_id); - if( !idx ) // invalid skill id - return false; - - skill_db[idx].nocast |= atoi(split[1]); - - return true; -} - -static bool skill_parse_row_unitdb(char* split[], int columns, int current) -{// ID,unit ID,unit ID 2,layout,range,interval,target,flag - uint16 skill_id = atoi(split[0]); - uint16 idx = skill_get_index(skill_id); - if( !idx ) // invalid skill id - return false; - - skill_db[idx].unit_id[0] = strtol(split[1],NULL,16); - skill_db[idx].unit_id[1] = strtol(split[2],NULL,16); - skill_split_atoi(split[3],skill_db[idx].unit_layout_type); - skill_split_atoi(split[4],skill_db[idx].unit_range); - skill_db[idx].unit_interval = atoi(split[5]); - - if( strcmpi(split[6],"noenemy")==0 ) skill_db[idx].unit_target = BCT_NOENEMY; - else if( strcmpi(split[6],"friend")==0 ) skill_db[idx].unit_target = BCT_NOENEMY; - else if( strcmpi(split[6],"party")==0 ) skill_db[idx].unit_target = BCT_PARTY; - else if( strcmpi(split[6],"ally")==0 ) skill_db[idx].unit_target = BCT_PARTY|BCT_GUILD; - else if( strcmpi(split[6],"guild")==0 ) skill_db[idx].unit_target = BCT_GUILD; - else if( strcmpi(split[6],"all")==0 ) skill_db[idx].unit_target = BCT_ALL; - else if( strcmpi(split[6],"enemy")==0 ) skill_db[idx].unit_target = BCT_ENEMY; - else if( strcmpi(split[6],"self")==0 ) skill_db[idx].unit_target = BCT_SELF; - else if( strcmpi(split[6],"noone")==0 ) skill_db[idx].unit_target = BCT_NOONE; - else skill_db[idx].unit_target = strtol(split[6],NULL,16); - - skill_db[idx].unit_flag = strtol(split[7],NULL,16); - - if (skill_db[idx].unit_flag&UF_DEFNOTENEMY && battle_config.defnotenemy) - skill_db[idx].unit_target = BCT_NOENEMY; - - //By default, target just characters. - skill_db[idx].unit_target |= BL_CHAR; - if (skill_db[idx].unit_flag&UF_NOPC) - skill_db[idx].unit_target &= ~BL_PC; - if (skill_db[idx].unit_flag&UF_NOMOB) - skill_db[idx].unit_target &= ~BL_MOB; - if (skill_db[idx].unit_flag&UF_SKILL) - skill_db[idx].unit_target |= BL_SKILL; - - return true; -} - -static bool skill_parse_row_producedb(char* split[], int columns, int current) -{// ProduceItemID,ItemLV,RequireSkill,Requireskill_lv,MaterialID1,MaterialAmount1,...... - int x,y; - - int i = atoi(split[0]); - if( !i ) - return false; - - skill_produce_db[current].nameid = i; - skill_produce_db[current].itemlv = atoi(split[1]); - skill_produce_db[current].req_skill = atoi(split[2]); - skill_produce_db[current].req_skill_lv = atoi(split[3]); - - for( x = 4, y = 0; x+1 < columns && split[x] && split[x+1] && y < MAX_PRODUCE_RESOURCE; x += 2, y++ ) - { - skill_produce_db[current].mat_id[y] = atoi(split[x]); - skill_produce_db[current].mat_amount[y] = atoi(split[x+1]); - } - - return true; -} - -static bool skill_parse_row_createarrowdb(char* split[], int columns, int current) -{// SourceID,MakeID1,MakeAmount1,...,MakeID5,MakeAmount5 - int x,y; - - int i = atoi(split[0]); - if( !i ) - return false; - - skill_arrow_db[current].nameid = i; - - for( x = 1, y = 0; x+1 < columns && split[x] && split[x+1] && y < MAX_ARROW_RESOURCE; x += 2, y++ ) - { - skill_arrow_db[current].cre_id[y] = atoi(split[x]); - skill_arrow_db[current].cre_amount[y] = atoi(split[x+1]); - } - - return true; -} -static bool skill_parse_row_spellbookdb(char* split[], int columns, int current) -{// skill_id,PreservePoints - - uint16 skill_id = atoi(split[0]); - int points = atoi(split[1]); - int nameid = atoi(split[2]); - - if( !skill_get_index(skill_id) || !skill_get_max(skill_id) ) - ShowError("spellbook_db: Invalid skill ID %d\n", skill_id); - if ( !skill_get_inf(skill_id) ) - ShowError("spellbook_db: Passive skills cannot be memorized (%d/%s)\n", skill_id, skill_get_name(skill_id)); - if( points < 1 ) - ShowError("spellbook_db: PreservePoints have to be 1 or above! (%d/%s)\n", skill_id, skill_get_name(skill_id)); - else - { - skill_spellbook_db[current].skill_id = skill_id; - skill_spellbook_db[current].point = points; - skill_spellbook_db[current].nameid = nameid; - - return true; - } - - return false; -} -static bool skill_parse_row_improvisedb(char* split[], int columns, int current) -{// SkillID,Rate - uint16 skill_id = atoi(split[0]); - short j = atoi(split[1]); - - if( !skill_get_index(skill_id) || !skill_get_max(skill_id) ) { - ShowError("skill_improvise_db: Invalid skill ID %d\n", skill_id); - return false; - } - if ( !skill_get_inf(skill_id) ) { - ShowError("skill_improvise_db: Passive skills cannot be casted (%d/%s)\n", skill_id, skill_get_name(skill_id)); - return false; - } - if( j < 1 ) { - ShowError("skill_improvise_db: Chances have to be 1 or above! (%d/%s)\n", skill_id, skill_get_name(skill_id)); - return false; - } - if( current >= MAX_SKILL_IMPROVISE_DB ) { - ShowError("skill_improvise_db: Maximum amount of entries reached (%d), increase MAX_SKILL_IMPROVISE_DB\n",MAX_SKILL_IMPROVISE_DB); - } - skill_improvise_db[current].skill_id = skill_id; - skill_improvise_db[current].per = j; // Still need confirm it. - - return true; -} -static bool skill_parse_row_magicmushroomdb(char* split[], int column, int current) -{// SkillID - uint16 skill_id = atoi(split[0]); - - if( !skill_get_index(skill_id) || !skill_get_max(skill_id) ) - { - ShowError("magicmushroom_db: Invalid skill ID %d\n", skill_id); - return false; - } - if ( !skill_get_inf(skill_id) ) - { - ShowError("magicmushroom_db: Passive skills cannot be casted (%d/%s)\n", skill_id, skill_get_name(skill_id)); - return false; - } - - skill_magicmushroom_db[current].skill_id = skill_id; - - return true; -} - -static bool skill_parse_row_reproducedb(char* split[], int column, int current) { - uint16 skill_id = atoi(split[0]); - uint16 idx = skill_get_index(skill_id); - if( !idx ) - return false; - - skill_reproduce_db[idx] = true; - - return true; -} - - -static bool skill_parse_row_abradb(char* split[], int columns, int current) -{// skill_id,DummyName,RequiredHocusPocusLevel,Rate - uint16 skill_id = atoi(split[0]); - if( !skill_get_index(skill_id) || !skill_get_max(skill_id) ) - { - ShowError("abra_db: Invalid skill ID %d\n", skill_id); - return false; - } - if ( !skill_get_inf(skill_id) ) - { - ShowError("abra_db: Passive skills cannot be casted (%d/%s)\n", skill_id, skill_get_name(skill_id)); - return false; - } - - skill_abra_db[current].skill_id = skill_id; - skill_abra_db[current].req_lv = atoi(split[2]); - skill_abra_db[current].per = atoi(split[3]); - - return true; -} - -static bool skill_parse_row_changematerialdb(char* split[], int columns, int current) -{// ProductID,BaseRate,MakeAmount1,MakeAmountRate1...,MakeAmount5,MakeAmountRate5 - uint16 skill_id = atoi(split[0]); - short j = atoi(split[1]); - int x,y; - - for(x=0; x<MAX_SKILL_PRODUCE_DB; x++){ - if( skill_produce_db[x].nameid == skill_id ) - if( skill_produce_db[x].req_skill == GN_CHANGEMATERIAL ) - break; - } - - if( x >= MAX_SKILL_PRODUCE_DB ){ - ShowError("changematerial_db: Not supported item ID(%d) for Change Material. \n", skill_id); - return false; - } - - if( current >= MAX_SKILL_PRODUCE_DB ) { - ShowError("skill_changematerial_db: Maximum amount of entries reached (%d), increase MAX_SKILL_PRODUCE_DB\n",MAX_SKILL_PRODUCE_DB); - } - - skill_changematerial_db[current].itemid = skill_id; - skill_changematerial_db[current].rate = j; - - for( x = 2, y = 0; x+1 < columns && split[x] && split[x+1] && y < 5; x += 2, y++ ) - { - skill_changematerial_db[current].qty[y] = atoi(split[x]); - skill_changematerial_db[current].qty_rate[y] = atoi(split[x+1]); - } - - return true; -} - -/*=============================== - * DB reading. - * skill_db.txt - * skill_require_db.txt - * skill_cast_db.txt - * skill_castnodex_db.txt - * skill_nocast_db.txt - * skill_unit_db.txt - * produce_db.txt - * create_arrow_db.txt - * abra_db.txt - *------------------------------*/ -static void skill_readdb(void) -{ - // init skill db structures - db_clear(skilldb_name2id); - memset(skill_db,0,sizeof(skill_db)); - memset(skill_produce_db,0,sizeof(skill_produce_db)); - memset(skill_arrow_db,0,sizeof(skill_arrow_db)); - memset(skill_abra_db,0,sizeof(skill_abra_db)); - memset(skill_spellbook_db,0,sizeof(skill_spellbook_db)); - memset(skill_magicmushroom_db,0,sizeof(skill_magicmushroom_db)); - memset(skill_reproduce_db,0,sizeof(skill_reproduce_db)); - memset(skill_changematerial_db,0,sizeof(skill_changematerial_db)); - - // load skill databases - safestrncpy(skill_db[0].name, "UNKNOWN_SKILL", sizeof(skill_db[0].name)); - safestrncpy(skill_db[0].desc, "Unknown Skill", sizeof(skill_db[0].desc)); - - sv_readdb(db_path, DBPATH"skill_db.txt" , ',', 17, 17, MAX_SKILL_DB, skill_parse_row_skilldb); - sv_readdb(db_path, DBPATH"skill_require_db.txt" , ',', 32, 32, MAX_SKILL_DB, skill_parse_row_requiredb); -#ifdef RENEWAL_CAST - sv_readdb(db_path, "re/skill_cast_db.txt" , ',', 8, 8, MAX_SKILL_DB, skill_parse_row_castdb); -#else - sv_readdb(db_path, "pre-re/skill_cast_db.txt" , ',', 7, 7, MAX_SKILL_DB, skill_parse_row_castdb); -#endif - sv_readdb(db_path, DBPATH"skill_castnodex_db.txt", ',', 2, 3, MAX_SKILL_DB, skill_parse_row_castnodexdb); - sv_readdb(db_path, DBPATH"skill_unit_db.txt" , ',', 8, 8, MAX_SKILL_DB, skill_parse_row_unitdb); - - sv_readdb(db_path, DBPATH"skill_nocast_db.txt" , ',', 2, 2, MAX_SKILL_DB, skill_parse_row_nocastdb); - - skill_init_unit_layout(); - sv_readdb(db_path, "produce_db.txt" , ',', 4, 4+2*MAX_PRODUCE_RESOURCE, MAX_SKILL_PRODUCE_DB, skill_parse_row_producedb); - sv_readdb(db_path, "create_arrow_db.txt" , ',', 1+2, 1+2*MAX_ARROW_RESOURCE, MAX_SKILL_ARROW_DB, skill_parse_row_createarrowdb); - sv_readdb(db_path, "abra_db.txt" , ',', 4, 4, MAX_SKILL_ABRA_DB, skill_parse_row_abradb); - //Warlock - sv_readdb(db_path, "spellbook_db.txt" , ',', 3, 3, MAX_SKILL_SPELLBOOK_DB, skill_parse_row_spellbookdb); - //Guillotine Cross - sv_readdb(db_path, "magicmushroom_db.txt" , ',', 1, 1, MAX_SKILL_MAGICMUSHROOM_DB, skill_parse_row_magicmushroomdb); - sv_readdb(db_path, "skill_reproduce_db.txt", ',', 1, 1, MAX_SKILL_DB, skill_parse_row_reproducedb); - sv_readdb(db_path, "skill_improvise_db.txt" , ',', 2, 2, MAX_SKILL_IMPROVISE_DB, skill_parse_row_improvisedb); - sv_readdb(db_path, "skill_changematerial_db.txt" , ',', 4, 4+2*5, MAX_SKILL_PRODUCE_DB, skill_parse_row_changematerialdb); - -} - -void skill_reload (void) { - struct s_mapiterator *iter; - struct map_session_data *sd; - skill_readdb(); - /* lets update all players skill tree : so that if any skill modes were changed they're properly updated */ - iter = mapit_getallusers(); - for( sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC*)mapit_next(iter) ) - clif_skillinfoblock(sd); - mapit_free(iter); - -} - -/*========================================== - * - *------------------------------------------*/ -int do_init_skill (void) -{ - skilldb_name2id = strdb_alloc(DB_OPT_DUP_KEY|DB_OPT_RELEASE_DATA, 0); - skill_readdb(); - - group_db = idb_alloc(DB_OPT_BASE); - skillunit_db = idb_alloc(DB_OPT_BASE); - skillcd_db = idb_alloc(DB_OPT_RELEASE_DATA); - skillusave_db = idb_alloc(DB_OPT_RELEASE_DATA); - skill_unit_ers = ers_new(sizeof(struct skill_unit_group),"skill.c::skill_unit_ers",ERS_OPT_NONE); - skill_timer_ers = ers_new(sizeof(struct skill_timerskill),"skill.c::skill_timer_ers",ERS_OPT_NONE); - - 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_blockpc_end, "skill_blockpc_end"); - - add_timer_interval(gettick()+SKILLUNITTIMER_INTERVAL,skill_unit_timer,0,0,SKILLUNITTIMER_INTERVAL); - - return 0; -} - -int do_final_skill(void) -{ - db_destroy(skilldb_name2id); - db_destroy(group_db); - db_destroy(skillunit_db); - db_destroy(skillcd_db); - db_destroy(skillusave_db); - ers_destroy(skill_unit_ers); - ers_destroy(skill_timer_ers); - return 0; -} +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/timer.h" +#include "../common/nullpo.h" +#include "../common/malloc.h" +#include "../common/random.h" +#include "../common/showmsg.h" +#include "../common/strlib.h" +#include "../common/utils.h" +#include "../common/ers.h" + +#include "map.h" +#include "path.h" +#include "clif.h" +#include "pc.h" +#include "status.h" +#include "skill.h" +#include "pet.h" +#include "homunculus.h" +#include "mercenary.h" +#include "elemental.h" +#include "mob.h" +#include "npc.h" +#include "battle.h" +#include "battleground.h" +#include "party.h" +#include "itemdb.h" +#include "script.h" +#include "intif.h" +#include "log.h" +#include "chrif.h" +#include "guild.h" +#include "date.h" +#include "unit.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <math.h> + + +#define SKILLUNITTIMER_INTERVAL 100 + +// ranges reserved for mapping skill ids to skilldb offsets +#define HM_SKILLRANGEMIN 700 +#define HM_SKILLRANGEMAX HM_SKILLRANGEMIN + MAX_HOMUNSKILL +#define MC_SKILLRANGEMIN HM_SKILLRANGEMAX + 1 +#define MC_SKILLRANGEMAX MC_SKILLRANGEMIN + MAX_MERCSKILL +#define EL_SKILLRANGEMIN MC_SKILLRANGEMAX + 1 +#define EL_SKILLRANGEMAX EL_SKILLRANGEMIN + MAX_ELEMENTALSKILL +#define GD_SKILLRANGEMIN EL_SKILLRANGEMAX + 1 +#define GD_SKILLRANGEMAX GD_SKILLRANGEMIN + MAX_GUILDSKILL + +#if GD_SKILLRANGEMAX > 999 + #error GD_SKILLRANGEMAX is greater than 999 +#endif +static struct eri *skill_unit_ers = NULL; //For handling skill_unit's [Skotlex] +static struct eri *skill_timer_ers = NULL; //For handling skill_timerskills [Skotlex] + +DBMap* skillunit_db = NULL; // int id -> struct skill_unit* + +DBMap* skilldb_name2id = NULL; + +/** + * Skill Cool Down Delay Saving + * Struct skill_cd is not a member of struct map_session_data + * to keep cooldowns in memory between player log-ins. + * All cooldowns are reset when server is restarted. + **/ +DBMap* skillcd_db = NULL; // char_id -> struct skill_cd +struct skill_cd { + int duration[MAX_SKILL_TREE];//milliseconds + short skidx[MAX_SKILL_TREE];//the skill index entries belong to + short nameid[MAX_SKILL_TREE];//skill id + unsigned char cursor; +}; + +/** + * Skill Unit Persistency during endack routes (mostly for songs see bugreport:4574) + **/ +DBMap* skillusave_db = NULL; // char_id -> struct skill_usave +struct skill_usave { + uint16 skill_id, skill_lv; +}; + +struct s_skill_db skill_db[MAX_SKILL_DB]; +struct s_skill_produce_db skill_produce_db[MAX_SKILL_PRODUCE_DB]; +struct s_skill_arrow_db skill_arrow_db[MAX_SKILL_ARROW_DB]; +struct s_skill_abra_db skill_abra_db[MAX_SKILL_ABRA_DB]; +struct s_skill_improvise_db { + uint16 skill_id; + short per;//1-10000 +}; +struct s_skill_improvise_db skill_improvise_db[MAX_SKILL_IMPROVISE_DB]; +bool skill_reproduce_db[MAX_SKILL_DB]; +struct s_skill_changematerial_db { + int itemid; + short rate; + int qty[5]; + short qty_rate[5]; +}; +struct s_skill_changematerial_db skill_changematerial_db[MAX_SKILL_PRODUCE_DB]; + +//Warlock +struct s_skill_spellbook_db { + int nameid; + uint16 skill_id; + int point; +}; + +struct s_skill_spellbook_db skill_spellbook_db[MAX_SKILL_SPELLBOOK_DB]; +//Guillotine Cross +struct s_skill_magicmushroom_db skill_magicmushroom_db[MAX_SKILL_MAGICMUSHROOM_DB]; + +struct s_skill_unit_layout skill_unit_layout[MAX_SKILL_UNIT_LAYOUT]; +int firewall_unit_pos; +int icewall_unit_pos; +int earthstrain_unit_pos; +//early declaration +int skill_block_check(struct block_list *bl, enum sc_type type, uint16 skill_id); +static int skill_check_unit_range (struct block_list *bl, int x, int y, uint16 skill_id, uint16 skill_lv); +static int skill_check_unit_range2 (struct block_list *bl, int x, int y, uint16 skill_id, uint16 skill_lv); +static int skill_destroy_trap( struct block_list *bl, va_list ap ); +//Since only mob-casted splash skills can hit ice-walls +static inline int splash_target(struct block_list* bl) +{ +#ifndef RENEWAL + return ( bl->type == BL_MOB ) ? BL_SKILL|BL_CHAR : BL_CHAR; +#else // Some skills can now hit ground skills(traps, ice wall & etc.) + return BL_SKILL|BL_CHAR; +#endif +} + +/// Returns the id of the skill, or 0 if not found. +int skill_name2id(const char* name) +{ + if( name == NULL ) + return 0; + + return strdb_iget(skilldb_name2id, name); +} + +/// Maps skill ids to skill db offsets. +/// Returns the skill's array index, or 0 (Unknown Skill). +int skill_get_index( uint16 skill_id ) +{ + // avoid ranges reserved for mapping guild/homun/mercenary skills + if( (skill_id >= GD_SKILLRANGEMIN && skill_id <= GD_SKILLRANGEMAX) + || (skill_id >= HM_SKILLRANGEMIN && skill_id <= HM_SKILLRANGEMAX) + || (skill_id >= MC_SKILLRANGEMIN && skill_id <= MC_SKILLRANGEMAX) + || (skill_id >= EL_SKILLRANGEMIN && skill_id <= EL_SKILLRANGEMAX) ) + return 0; + + // map skill id to skill db index + if( skill_id >= GD_SKILLBASE ) + skill_id = GD_SKILLRANGEMIN + skill_id - GD_SKILLBASE; + else if( skill_id >= EL_SKILLBASE ) + skill_id = EL_SKILLRANGEMIN + skill_id - EL_SKILLBASE; + else if( skill_id >= MC_SKILLBASE ) + skill_id = MC_SKILLRANGEMIN + skill_id - MC_SKILLBASE; + else if( skill_id >= HM_SKILLBASE ) + skill_id = HM_SKILLRANGEMIN + skill_id - HM_SKILLBASE; + + // validate result + if( !skill_id || skill_id >= MAX_SKILL_DB ) + return 0; + + return skill_id; +} + +const char* skill_get_name( uint16 skill_id ) +{ + return skill_db[skill_get_index(skill_id)].name; +} + +const char* skill_get_desc( uint16 skill_id ) +{ + return skill_db[skill_get_index(skill_id)].desc; +} + +// out of bounds error checking [celest] +static void skill_chk(int16* skill_id, uint16 skill_lv) +{ + *skill_id = skill_get_index(*skill_id); // checks/adjusts id + if( skill_lv > MAX_SKILL_LEVEL ) *skill_id = 0; +} + +#define skill_get(var,id,lv) { skill_chk(&id,lv); if(!id) return 0; return var; } + +// Skill DB +int skill_get_hit( uint16 skill_id ) { skill_get (skill_db[skill_id].hit, skill_id, 1); } +int skill_get_inf( uint16 skill_id ) { skill_get (skill_db[skill_id].inf, skill_id, 1); } +int skill_get_ele( uint16 skill_id , uint16 skill_lv ) { skill_get (skill_db[skill_id].element[skill_lv-1], skill_id, skill_lv); } +int skill_get_nk( uint16 skill_id ) { skill_get (skill_db[skill_id].nk, skill_id, 1); } +int skill_get_max( uint16 skill_id ) { skill_get (skill_db[skill_id].max, skill_id, 1); } +int skill_get_range( uint16 skill_id , uint16 skill_lv ) { skill_get (skill_db[skill_id].range[skill_lv-1], skill_id, skill_lv); } +int skill_get_splash( uint16 skill_id , uint16 skill_lv ) { skill_get ( (skill_db[skill_id].splash[skill_lv-1]>=0?skill_db[skill_id].splash[skill_lv-1]:AREA_SIZE), skill_id, skill_lv); } +int skill_get_hp( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].hp[skill_lv-1], skill_id, skill_lv); } +int skill_get_sp( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].sp[skill_lv-1], skill_id, skill_lv); } +int skill_get_hp_rate(uint16 skill_id, uint16 skill_lv ) { skill_get (skill_db[skill_id].hp_rate[skill_lv-1], skill_id, skill_lv); } +int skill_get_sp_rate(uint16 skill_id, uint16 skill_lv ) { skill_get (skill_db[skill_id].sp_rate[skill_lv-1], skill_id, skill_lv); } +int skill_get_state(uint16 skill_id) { skill_get (skill_db[skill_id].state, skill_id, 1); } +int skill_get_spiritball(uint16 skill_id, uint16 skill_lv) { skill_get (skill_db[skill_id].spiritball[skill_lv-1], skill_id, skill_lv); } +int skill_get_itemid(uint16 skill_id, int idx) { skill_get (skill_db[skill_id].itemid[idx], skill_id, 1); } +int skill_get_itemqty(uint16 skill_id, int idx) { skill_get (skill_db[skill_id].amount[idx], skill_id, 1); } +int skill_get_zeny( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].zeny[skill_lv-1], skill_id, skill_lv); } +int skill_get_num( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].num[skill_lv-1], skill_id, skill_lv); } +int skill_get_cast( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].cast[skill_lv-1], skill_id, skill_lv); } +int skill_get_delay( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].delay[skill_lv-1], skill_id, skill_lv); } +int skill_get_walkdelay( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].walkdelay[skill_lv-1], skill_id, skill_lv); } +int skill_get_time( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].upkeep_time[skill_lv-1], skill_id, skill_lv); } +int skill_get_time2( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].upkeep_time2[skill_lv-1], skill_id, skill_lv); } +int skill_get_castdef( uint16 skill_id ) { skill_get (skill_db[skill_id].cast_def_rate, skill_id, 1); } +int skill_get_weapontype( uint16 skill_id ) { skill_get (skill_db[skill_id].weapon, skill_id, 1); } +int skill_get_ammotype( uint16 skill_id ) { skill_get (skill_db[skill_id].ammo, skill_id, 1); } +int skill_get_ammo_qty( uint16 skill_id, uint16 skill_lv ) { skill_get (skill_db[skill_id].ammo_qty[skill_lv-1], skill_id, skill_lv); } +int skill_get_inf2( uint16 skill_id ) { skill_get (skill_db[skill_id].inf2, skill_id, 1); } +int skill_get_castcancel( uint16 skill_id ) { skill_get (skill_db[skill_id].castcancel, skill_id, 1); } +int skill_get_maxcount( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].maxcount[skill_lv-1], skill_id, skill_lv); } +int skill_get_blewcount( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].blewcount[skill_lv-1], skill_id, skill_lv); } +int skill_get_mhp( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].mhp[skill_lv-1], skill_id, skill_lv); } +int skill_get_castnodex( uint16 skill_id ,uint16 skill_lv ) { skill_get (skill_db[skill_id].castnodex[skill_lv-1], skill_id, skill_lv); } +int skill_get_delaynodex( uint16 skill_id ,uint16 skill_lv ){ skill_get (skill_db[skill_id].delaynodex[skill_lv-1], skill_id, skill_lv); } +int skill_get_nocast ( uint16 skill_id ) { skill_get (skill_db[skill_id].nocast, skill_id, 1); } +int skill_get_type( uint16 skill_id ) { skill_get (skill_db[skill_id].skill_type, skill_id, 1); } +int skill_get_unit_id ( uint16 skill_id, int flag ){ skill_get (skill_db[skill_id].unit_id[flag], skill_id, 1); } +int skill_get_unit_interval( uint16 skill_id ) { skill_get (skill_db[skill_id].unit_interval, skill_id, 1); } +int skill_get_unit_range( uint16 skill_id, uint16 skill_lv ){ skill_get (skill_db[skill_id].unit_range[skill_lv-1], skill_id, skill_lv); } +int skill_get_unit_target( uint16 skill_id ) { skill_get (skill_db[skill_id].unit_target&BCT_ALL, skill_id, 1); } +int skill_get_unit_bl_target( uint16 skill_id ) { skill_get (skill_db[skill_id].unit_target&BL_ALL, skill_id, 1); } +int skill_get_unit_flag( uint16 skill_id ) { skill_get (skill_db[skill_id].unit_flag, skill_id, 1); } +int skill_get_unit_layout_type( uint16 skill_id ,uint16 skill_lv ){ skill_get (skill_db[skill_id].unit_layout_type[skill_lv-1], skill_id, skill_lv); } +int skill_get_cooldown( uint16 skill_id, uint16 skill_lv ) { skill_get (skill_db[skill_id].cooldown[skill_lv-1], skill_id, skill_lv); } +#ifdef RENEWAL_CAST +int skill_get_fixed_cast( uint16 skill_id ,uint16 skill_lv ){ skill_get (skill_db[skill_id].fixed_cast[skill_lv-1], skill_id, skill_lv); } +#endif +int skill_tree_get_max(uint16 skill_id, int b_class) +{ + int i; + b_class = pc_class2idx(b_class); + + ARR_FIND( 0, MAX_SKILL_TREE, i, skill_tree[b_class][i].id == 0 || skill_tree[b_class][i].id == skill_id ); + if( i < MAX_SKILL_TREE && skill_tree[b_class][i].id == skill_id ) + return skill_tree[b_class][i].max; + else + return skill_get_max(skill_id); +} + +int skill_frostjoke_scream(struct block_list *bl,va_list ap); +int skill_attack_area(struct block_list *bl,va_list ap); +struct skill_unit_group *skill_locate_element_field(struct block_list *bl); // [Skotlex] +int skill_graffitiremover(struct block_list *bl, va_list ap); // [Valaris] +int skill_greed(struct block_list *bl, va_list ap); +static void skill_toggle_magicpower(struct block_list *bl, uint16 skill_id); +static int skill_cell_overlap(struct block_list *bl, va_list ap); +static int skill_trap_splash(struct block_list *bl, va_list ap); +struct skill_unit_group_tickset *skill_unitgrouptickset_search(struct block_list *bl,struct skill_unit_group *sg,int tick); +static int skill_unit_onplace(struct skill_unit *src,struct block_list *bl,unsigned int tick); +static int skill_unit_onleft(uint16 skill_id, struct block_list *bl,unsigned int tick); +static int skill_unit_effect(struct block_list *bl,va_list ap); + +int enchant_eff[5] = { 10, 14, 17, 19, 20 }; +int deluge_eff[5] = { 5, 9, 12, 14, 15 }; + +int skill_get_casttype (uint16 skill_id) +{ + int inf = skill_get_inf(skill_id); + if (inf&(INF_GROUND_SKILL)) + return CAST_GROUND; + if (inf&INF_SUPPORT_SKILL) + return CAST_NODAMAGE; + if (inf&INF_SELF_SKILL) { + if(skill_get_inf2(skill_id)&INF2_NO_TARGET_SELF) + return CAST_DAMAGE; //Combo skill. + return CAST_NODAMAGE; + } + if (skill_get_nk(skill_id)&NK_NO_DAMAGE) + return CAST_NODAMAGE; + return CAST_DAMAGE; +} + +//Returns actual skill range taking into account attack range and AC_OWL [Skotlex] +int skill_get_range2 (struct block_list *bl, uint16 skill_id, uint16 skill_lv) +{ + int range; + if( bl->type == BL_MOB && battle_config.mob_ai&0x400 ) + return 9; //Mobs have a range of 9 regardless of skill used. + + range = skill_get_range(skill_id, skill_lv); + + if( range < 0 ) + { + if( battle_config.use_weapon_skill_range&bl->type ) + return status_get_range(bl); + range *=-1; + } + + //TODO: Find a way better than hardcoding the list of skills affected by AC_VULTURE + switch( skill_id ) + { + case AC_SHOWER: case MA_SHOWER: + case AC_DOUBLE: case MA_DOUBLE: + case HT_BLITZBEAT: + case AC_CHARGEARROW: + case MA_CHARGEARROW: + case SN_FALCONASSAULT: + case HT_POWER: + /** + * Ranger + **/ + case RA_ARROWSTORM: + case RA_AIMEDBOLT: + case RA_WUGBITE: + if( bl->type == BL_PC ) + range += pc_checkskill((TBL_PC*)bl, AC_VULTURE); + else + range += 10; //Assume level 10? + break; + // added to allow GS skills to be effected by the range of Snake Eyes [Reddozen] + case GS_RAPIDSHOWER: + case GS_PIERCINGSHOT: + case GS_FULLBUSTER: + case GS_SPREADATTACK: + case GS_GROUNDDRIFT: + if (bl->type == BL_PC) + range += pc_checkskill((TBL_PC*)bl, GS_SNAKEEYE); + else + range += 10; //Assume level 10? + break; + case NJ_KIRIKAGE: + if (bl->type == BL_PC) + range = skill_get_range(NJ_SHADOWJUMP,pc_checkskill((TBL_PC*)bl,NJ_SHADOWJUMP)); + break; + /** + * Warlock + **/ + case WL_WHITEIMPRISON: + case WL_SOULEXPANSION: + case WL_FROSTMISTY: + case WL_MARSHOFABYSS: + case WL_SIENNAEXECRATE: + case WL_DRAINLIFE: + case WL_CRIMSONROCK: + case WL_HELLINFERNO: + case WL_COMET: + case WL_CHAINLIGHTNING: + case WL_TETRAVORTEX: + case WL_RELEASE: + if( bl->type == BL_PC ) + range += pc_checkskill((TBL_PC*)bl, WL_RADIUS); + break; + /** + * Ranger Bonus + **/ + case HT_LANDMINE: + case HT_FREEZINGTRAP: + case HT_BLASTMINE: + case HT_CLAYMORETRAP: + case RA_CLUSTERBOMB: + case RA_FIRINGTRAP: + case RA_ICEBOUNDTRAP: + if( bl->type == BL_PC ) + range += (1 + pc_checkskill((TBL_PC*)bl, RA_RESEARCHTRAP))/2; + } + + if( !range && bl->type != BL_PC ) + return 9; // Enable non players to use self skills on others. [Skotlex] + return range; +} + +int skill_calc_heal(struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv, bool heal) { + int skill, hp; + struct map_session_data *sd = BL_CAST(BL_PC, src); + struct map_session_data *tsd = BL_CAST(BL_PC, target); + struct status_change* sc; + + switch( skill_id ) { + case BA_APPLEIDUN: + #ifdef RENEWAL + hp = 100+5*skill_lv+5*(status_get_vit(src)/10); // HP recovery + #else + hp = 30+5*skill_lv+5*(status_get_vit(src)/10); // HP recovery + #endif + if( sd ) + hp += 5*pc_checkskill(sd,BA_MUSICALLESSON); + break; + case PR_SANCTUARY: + hp = (skill_lv>6)?777:skill_lv*100; + break; + case NPC_EVILLAND: + hp = (skill_lv>6)?666:skill_lv*100; + break; + default: + if (skill_lv >= battle_config.max_heal_lv) + return battle_config.max_heal; + #ifdef RENEWAL + /** + * Renewal Heal Formula + * Formula: ( [(Base Level + INT) / 5] × 30 ) × (Heal Level / 10) × (Modifiers) + MATK + **/ + hp = (status_get_lv(src) + status_get_int(src)) / 5 * 30 * skill_lv / 10; + #else + hp = ( status_get_lv(src) + status_get_int(src) ) / 8 * (4 + ( skill_id == AB_HIGHNESSHEAL ? ( sd ? pc_checkskill(sd,AL_HEAL) : 10 ) : skill_lv ) * 8); + #endif + if( sd && ((skill = pc_checkskill(sd, HP_MEDITATIO)) > 0) ) + hp += hp * skill * 2 / 100; + else if( src->type == BL_HOM && (skill = merc_hom_checkskill(((TBL_HOM*)src), HLIF_BRAIN)) > 0 ) + hp += hp * skill * 2 / 100; + break; + } + + if( ( (target && target->type == BL_MER) || !heal ) && skill_id != NPC_EVILLAND ) + hp >>= 1; + + if( sd && (skill = pc_skillheal_bonus(sd, skill_id)) ) + hp += hp*skill/100; + + if( tsd && (skill = pc_skillheal2_bonus(tsd, skill_id)) ) + hp += hp*skill/100; + + sc = status_get_sc(target); + if( sc && sc->count ) { + if( sc->data[SC_CRITICALWOUND] && heal ) // Critical Wound has no effect on offensive heal. [Inkfish] + hp -= hp * sc->data[SC_CRITICALWOUND]->val2/100; + if( sc->data[SC_DEATHHURT] && heal ) + hp -= hp * 20/100; + if( sc->data[SC_INCHEALRATE] && skill_id != NPC_EVILLAND && skill_id != BA_APPLEIDUN ) + hp += hp * sc->data[SC_INCHEALRATE]->val1/100; // Only affects Heal, Sanctuary and PotionPitcher.(like bHealPower) [Inkfish] + if( sc->data[SC_WATER_INSIGNIA] && sc->data[SC_WATER_INSIGNIA]->val1 == 2) + hp += hp / 10; + } + +#ifdef RENEWAL + // MATK part of the RE heal formula [malufett] + // Note: in this part matk bonuses from items or skills are not applied + switch( skill_id ) { + case BA_APPLEIDUN: case PR_SANCTUARY: + case NPC_EVILLAND: break; + default: + { + struct status_data *status = status_get_status_data(src); + int min, max, wMatk, variance; + + min = max = status_base_matk(status, status_get_lv(src)); + if( status->rhw.matk > 0 ){ + wMatk = status->rhw.matk; + variance = wMatk * status->rhw.wlv / 10; + min += wMatk - variance; + max += wMatk + variance; + } + + if( sc && sc->data[SC_RECOGNIZEDSPELL] ) + min = max; + + if( sd && sd->right_weapon.overrefine > 0 ){ + min++; + max += sd->right_weapon.overrefine - 1; + } + + if(max > min) + hp += min+rnd()%(max-min); + else + hp += min; + } + } +#endif + return hp; +} + +// Making plagiarize check its own function [Aru] +int can_copy (struct map_session_data *sd, uint16 skill_id, struct block_list* bl) +{ + // Never copy NPC/Wedding Skills + if (skill_get_inf2(skill_id)&(INF2_NPC_SKILL|INF2_WEDDING_SKILL)) + return 0; + + // High-class skills + if((skill_id >= LK_AURABLADE && skill_id <= ASC_CDP) || (skill_id >= ST_PRESERVE && skill_id <= CR_CULTIVATION)) + { + if(battle_config.copyskill_restrict == 2) + return 0; + else if(battle_config.copyskill_restrict) + return (sd->status.class_ == JOB_STALKER); + } + + //Added so plagarize can't copy agi/bless if you're undead since it damages you + if ((skill_id == AL_INCAGI || skill_id == AL_BLESSING || + skill_id == CASH_BLESSING || skill_id == CASH_INCAGI || + skill_id == MER_INCAGI || skill_id == MER_BLESSING)) + return 0; + + // Couldn't preserve 3rd Class skills except only when using Reproduce skill. [Jobbie] + if( !(sd->sc.data[SC__REPRODUCE]) && (skill_id >= RK_ENCHANTBLADE && skill_id <= SR_RIDEINLIGHTNING) ) + return 0; + // Reproduce will only copy skills according on the list. [Jobbie] + else if( sd->sc.data[SC__REPRODUCE] && !skill_reproduce_db[skill_id] ) + return 0; + + return 1; +} + +// [MouseJstr] - skill ok to cast? and when? +int skillnotok (uint16 skill_id, struct map_session_data *sd) +{ + int16 idx,m; + nullpo_retr (1, sd); + m = sd->bl.m; + idx = skill_get_index(skill_id); + + if (idx == 0) + return 1; // invalid skill id + + if (pc_has_permission(sd, PC_PERM_SKILL_UNCONDITIONAL)) + return 0; // can do any damn thing they want + + if( skill_id == AL_TELEPORT && sd->skillitem == skill_id && sd->skillitemlv > 2 ) + return 0; // Teleport lv 3 bypasses this check.[Inkfish] + + // Epoque: + // This code will compare the player's attack motion value which is influenced by ASPD before + // allowing a skill to be cast. This is to prevent no-delay ACT files from spamming skills such as + // AC_DOUBLE which do not have a skill delay and are not regarded in terms of attack motion. + if( !sd->state.autocast && sd->skillitem != skill_id && sd->canskill_tick && + DIFF_TICK(gettick(), sd->canskill_tick) < (sd->battle_status.amotion * (battle_config.skill_amotion_leniency) / 100) ) + {// attempted to cast a skill before the attack motion has finished + return 1; + } + + if (sd->blockskill[idx] > 0){ + clif_skill_fail(sd, skill_id, USESKILL_FAIL_SKILLINTERVAL, 0); + return 1; + } + /** + * It has been confirmed on a official server (thanks to Yommy) that item-cast skills bypass all the restrictions above + * Also, without this check, an exploit where an item casting + healing (or any other kind buff) isn't deleted after used on a restricted map + **/ + if( sd->skillitem == skill_id ) + return 0; + // Check skill restrictions [Celest] + if( (!map_flag_vs(m) && skill_get_nocast (skill_id) & 1) || + (map[m].flag.pvp && skill_get_nocast (skill_id) & 2) || + (map_flag_gvg(m) && skill_get_nocast (skill_id) & 4) || + (map[m].flag.battleground && skill_get_nocast (skill_id) & 8) || + (map[m].flag.restricted && map[m].zone && skill_get_nocast (skill_id) & (8*map[m].zone)) ){ + clif_msg(sd, 0x536); // This skill cannot be used within this area + return 1; + } + + if( sd->sc.option&OPTION_MOUNTING ) + return 1;//You can't use skills while in the new mounts (The client doesn't let you, this is to make cheat-safe) + + switch (skill_id) { + case AL_WARP: + case RETURN_TO_ELDICASTES: + case ALL_GUARDIAN_RECALL: + if(map[m].flag.nowarp) { + clif_skill_teleportmessage(sd,0); + return 1; + } + return 0; + case AL_TELEPORT: + case SC_FATALMENACE: + case SC_DIMENSIONDOOR: + if(map[m].flag.noteleport) { + clif_skill_teleportmessage(sd,0); + return 1; + } + return 0; // gonna be checked in 'skill_castend_nodamage_id' + case WE_CALLPARTNER: + case WE_CALLPARENT: + case WE_CALLBABY: + if (map[m].flag.nomemo) { + clif_skill_teleportmessage(sd,1); + return 1; + } + break; + case MC_VENDING: + case MC_IDENTIFY: + case ALL_BUYING_STORE: + return 0; // always allowed + case WZ_ICEWALL: + // noicewall flag [Valaris] + if (map[m].flag.noicewall) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 1; + } + break; + case GC_DARKILLUSION: + if( map_flag_gvg(m) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 1; + } + break; + case GD_EMERGENCYCALL: + if ( + !(battle_config.emergency_call&((agit_flag || agit2_flag)?2:1)) || + !(battle_config.emergency_call&(map[m].flag.gvg || map[m].flag.gvg_castle?8:4)) || + (battle_config.emergency_call&16 && map[m].flag.nowarpto && !map[m].flag.gvg_castle) + ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 1; + } + break; + case BS_GREED: + case WS_CARTBOOST: + case BS_HAMMERFALL: + case BS_ADRENALINE: + case MC_CARTREVOLUTION: + case MC_MAMMONITE: + case WS_MELTDOWN: + case MG_SIGHT: + case TF_HIDING: + /** + * These skills cannot be used while in mado gear (credits to Xantara) + **/ + if( pc_ismadogear(sd) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 1; + } + break; + + case WM_SIRCLEOFNATURE: + case WM_SOUND_OF_DESTRUCTION: + case SC_MANHOLE: + case WM_LULLABY_DEEPSLEEP: + case WM_SATURDAY_NIGHT_FEVER: + if( !map_flag_vs(m) ) { + clif_skill_teleportmessage(sd,2); // This skill uses this msg instead of skill fails. + return 1; + } + break; + + } + return (map[m].flag.noskill); +} + +int skillnotok_hom(uint16 skill_id, struct homun_data *hd) +{ + uint16 idx = skill_get_index(skill_id); + nullpo_retr(1,hd); + + if (idx == 0) + return 1; // invalid skill id + + if (hd->blockskill[idx] > 0) + return 1; + switch(skill_id){ + case MH_LIGHT_OF_REGENE: + if(hd->homunculus.intimacy <= 750) //if not cordial + return 1; + break; + case MH_OVERED_BOOST: + if(hd->homunculus.hunger <= 1) //if we starving + return 1; + case MH_GOLDENE_FERSE: //can be used with angriff + if(hd->sc.data[SC_ANGRIFFS_MODUS]) + return 1; + case MH_ANGRIFFS_MODUS: + if(hd->sc.data[SC_GOLDENE_FERSE]) + return 1; + break; + } + + //Use master's criteria. + return skillnotok(skill_id, hd->master); +} + +int skillnotok_mercenary(uint16 skill_id, struct mercenary_data *md) +{ + uint16 idx = skill_get_index(skill_id); + nullpo_retr(1,md); + + if( idx == 0 ) + return 1; // Invalid Skill ID + if( md->blockskill[idx] > 0 ) + return 1; + + return skillnotok(skill_id, md->master); +} + +struct s_skill_unit_layout* skill_get_unit_layout (uint16 skill_id, uint16 skill_lv, struct block_list* src, int x, int y) +{ + int pos = skill_get_unit_layout_type(skill_id,skill_lv); + uint8 dir; + + if (pos < -1 || pos >= MAX_SKILL_UNIT_LAYOUT) { + ShowError("skill_get_unit_layout: unsupported layout type %d for skill %d (level %d)\n", pos, skill_id, skill_lv); + pos = cap_value(pos, 0, MAX_SQUARE_LAYOUT); // cap to nearest square layout + } + + if (pos != -1) // simple single-definition layout + return &skill_unit_layout[pos]; + + dir = (src->x == x && src->y == y) ? 6 : map_calc_dir(src,x,y); // 6 - default aegis direction + + if (skill_id == MG_FIREWALL) + return &skill_unit_layout [firewall_unit_pos + dir]; + else if (skill_id == WZ_ICEWALL) + return &skill_unit_layout [icewall_unit_pos + dir]; + else if( skill_id == WL_EARTHSTRAIN ) //Warlock + return &skill_unit_layout [earthstrain_unit_pos + dir]; + + ShowError("skill_get_unit_layout: unknown unit layout for skill %d (level %d)\n", skill_id, skill_lv); + return &skill_unit_layout[0]; // default 1x1 layout +} + +/*========================================== + * + *------------------------------------------*/ +int skill_additional_effect (struct block_list* src, struct block_list *bl, uint16 skill_id, uint16 skill_lv, int attack_type, int dmg_lv, unsigned int tick) +{ + struct map_session_data *sd, *dstsd; + struct mob_data *md, *dstmd; + struct status_data *sstatus, *tstatus; + struct status_change *sc, *tsc; + + enum sc_type status; + int skill; + int rate; + + nullpo_ret(src); + nullpo_ret(bl); + + if(skill_id > 0 && !skill_lv) return 0; // don't forget auto attacks! - celest + + if( dmg_lv < ATK_BLOCK ) // Don't apply effect if miss. + return 0; + + sd = BL_CAST(BL_PC, src); + md = BL_CAST(BL_MOB, src); + dstsd = BL_CAST(BL_PC, bl); + dstmd = BL_CAST(BL_MOB, bl); + + sc = status_get_sc(src); + tsc = status_get_sc(bl); + sstatus = status_get_status_data(src); + tstatus = status_get_status_data(bl); + if (!tsc) //skill additional effect is about adding effects to the target... + //So if the target can't be inflicted with statuses, this is pointless. + return 0; + + if( sd ) + { // These statuses would be applied anyway even if the damage was blocked by some skills. [Inkfish] + if( skill_id != WS_CARTTERMINATION && skill_id != AM_DEMONSTRATION && skill_id != CR_REFLECTSHIELD && skill_id != MS_REFLECTSHIELD && skill_id != ASC_BREAKER ) + { // Trigger status effects + enum sc_type type; + int i; + for( i = 0; i < ARRAYLENGTH(sd->addeff) && sd->addeff[i].flag; i++ ) + { + rate = sd->addeff[i].rate; + if( attack_type&BF_LONG ) // Any ranged physical attack takes status arrows into account (Grimtooth...) [DracoRPG] + rate += sd->addeff[i].arrow_rate; + if( !rate ) continue; + + if( (sd->addeff[i].flag&(ATF_WEAPON|ATF_MAGIC|ATF_MISC)) != (ATF_WEAPON|ATF_MAGIC|ATF_MISC) ) + { // Trigger has attack type consideration. + if( (sd->addeff[i].flag&ATF_WEAPON && attack_type&BF_WEAPON) || + (sd->addeff[i].flag&ATF_MAGIC && attack_type&BF_MAGIC) || + (sd->addeff[i].flag&ATF_MISC && attack_type&BF_MISC) ) ; + else + continue; + } + + if( (sd->addeff[i].flag&(ATF_LONG|ATF_SHORT)) != (ATF_LONG|ATF_SHORT) ) + { // Trigger has range consideration. + if((sd->addeff[i].flag&ATF_LONG && !(attack_type&BF_LONG)) || + (sd->addeff[i].flag&ATF_SHORT && !(attack_type&BF_SHORT))) + continue; //Range Failed. + } + + type = sd->addeff[i].id; + skill = skill_get_time2(status_sc2skill(type),7); + + if (sd->addeff[i].flag&ATF_TARGET) + status_change_start(bl,type,rate,7,0,0,0,skill,0); + + if (sd->addeff[i].flag&ATF_SELF) + status_change_start(src,type,rate,7,0,0,0,skill,0); + } + } + + if( skill_id ) + { // Trigger status effects on skills + enum sc_type type; + int i; + for( i = 0; i < ARRAYLENGTH(sd->addeff3) && sd->addeff3[i].skill; i++ ) + { + if( skill_id != sd->addeff3[i].skill || !sd->addeff3[i].rate ) + continue; + type = sd->addeff3[i].id; + skill = skill_get_time2(status_sc2skill(type),7); + + if( sd->addeff3[i].target&ATF_TARGET ) + status_change_start(bl,type,sd->addeff3[i].rate,7,0,0,0,skill,0); + if( sd->addeff3[i].target&ATF_SELF ) + status_change_start(src,type,sd->addeff3[i].rate,7,0,0,0,skill,0); + } + } + } + + if( dmg_lv < ATK_DEF ) // no damage, return; + return 0; + + switch(skill_id) + { + case 0: // Normal attacks (no skill used) + { + if( attack_type&BF_SKILL ) + break; // If a normal attack is a skill, it's splash damage. [Inkfish] + if(sd) { + // Automatic trigger of Blitz Beat + if (pc_isfalcon(sd) && sd->status.weapon == W_BOW && (skill=pc_checkskill(sd,HT_BLITZBEAT))>0 && + rnd()%1000 <= sstatus->luk*10/3+1 ) { + rate=(sd->status.job_level+9)/10; + skill_castend_damage_id(src,bl,HT_BLITZBEAT,(skill<rate)?skill:rate,tick,SD_LEVEL); + } + // Automatic trigger of Warg Strike [Jobbie] + if( pc_iswug(sd) && (sd->status.weapon == W_BOW || sd->status.weapon == W_FIST) && (skill=pc_checkskill(sd,RA_WUGSTRIKE)) > 0 && rnd()%1000 <= sstatus->luk*10/3+1 ) + skill_castend_damage_id(src,bl,RA_WUGSTRIKE,skill,tick,0); + // Gank + if(dstmd && sd->status.weapon != W_BOW && + (skill=pc_checkskill(sd,RG_SNATCHER)) > 0 && + (skill*15 + 55) + pc_checkskill(sd,TF_STEAL)*10 > rnd()%1000) { + if(pc_steal_item(sd,bl,pc_checkskill(sd,TF_STEAL))) + clif_skill_nodamage(src,bl,TF_STEAL,skill,1); + else + clif_skill_fail(sd,RG_SNATCHER,USESKILL_FAIL_LEVEL,0); + } + // Chance to trigger Taekwon kicks [Dralnu] + if(sc && !sc->data[SC_COMBO]) { + if(sc->data[SC_READYSTORM] && + sc_start(src,SC_COMBO, 15, TK_STORMKICK, + (2000 - 4*sstatus->agi - 2*sstatus->dex))) + ; //Stance triggered + else if(sc->data[SC_READYDOWN] && + sc_start(src,SC_COMBO, 15, TK_DOWNKICK, + (2000 - 4*sstatus->agi - 2*sstatus->dex))) + ; //Stance triggered + else if(sc->data[SC_READYTURN] && + sc_start(src,SC_COMBO, 15, TK_TURNKICK, + (2000 - 4*sstatus->agi - 2*sstatus->dex))) + ; //Stance triggered + else if (sc->data[SC_READYCOUNTER]) { //additional chance from SG_FRIEND [Komurka] + rate = 20; + if (sc->data[SC_SKILLRATE_UP] && sc->data[SC_SKILLRATE_UP]->val1 == TK_COUNTER) { + rate += rate*sc->data[SC_SKILLRATE_UP]->val2/100; + status_change_end(src, SC_SKILLRATE_UP, INVALID_TIMER); + } + sc_start2(src, SC_COMBO, rate, TK_COUNTER, bl->id, + (2000 - 4*sstatus->agi - 2*sstatus->dex)); + } + } + if(sc && sc->data[SC_PYROCLASTIC] && (rnd() % 1000 <= sstatus->luk * 10 / 3 + 1) ) + skill_castend_pos2(src, bl->x, bl->y, BS_HAMMERFALL,sc->data[SC_PYROCLASTIC]->val1, tick, 0); + } + + if (sc) { + struct status_change_entry *sce; + // Enchant Poison gives a chance to poison attacked enemies + if((sce=sc->data[SC_ENCPOISON])) //Don't use sc_start since chance comes in 1/10000 rate. + status_change_start(bl,SC_POISON,sce->val2, sce->val1,src->id,0,0, + skill_get_time2(AS_ENCHANTPOISON,sce->val1),0); + // Enchant Deadly Poison gives a chance to deadly poison attacked enemies + if((sce=sc->data[SC_EDP])) + sc_start4(bl,SC_DPOISON,sce->val2, sce->val1,src->id,0,0, + skill_get_time2(ASC_EDP,sce->val1)); + } + } + break; + + case SM_BASH: + if( sd && skill_lv > 5 && pc_checkskill(sd,SM_FATALBLOW)>0 ){ + //TODO: How much % per base level it actually is? + sc_start(bl,SC_STUN,(5*(skill_lv-5)+(int)sd->status.base_level/10), + skill_lv,skill_get_time2(SM_FATALBLOW,skill_lv)); + } + break; + + case MER_CRASH: + sc_start(bl,SC_STUN,(6*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case AS_VENOMKNIFE: + if (sd) //Poison chance must be that of Envenom. [Skotlex] + skill_lv = pc_checkskill(sd, TF_POISON); + case TF_POISON: + case AS_SPLASHER: + if(!sc_start2(bl,SC_POISON,(4*skill_lv+10),skill_lv,src->id,skill_get_time2(skill_id,skill_lv)) + && sd && skill_id==TF_POISON + ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + + case AS_SONICBLOW: + sc_start(bl,SC_STUN,(2*skill_lv+10),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case WZ_FIREPILLAR: + unit_set_walkdelay(bl, tick, skill_get_time2(skill_id, skill_lv), 1); + break; + + case MG_FROSTDIVER: +#ifndef RENEWAL + case WZ_FROSTNOVA: +#endif + sc_start(bl,SC_FREEZE,skill_lv*3+35,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + +#ifdef RENEWAL + case WZ_FROSTNOVA: + sc_start(bl,SC_FREEZE,skill_lv*5+33,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; +#endif + + case WZ_STORMGUST: + /** + * Storm Gust counter was dropped in renewal + **/ + #ifdef RENEWAL + sc_start(bl,SC_FREEZE,65-(5*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); + #else + //Tharis pointed out that this is normal freeze chance with a base of 300% + if(tsc->sg_counter >= 3 && + sc_start(bl,SC_FREEZE,300,skill_lv,skill_get_time2(skill_id,skill_lv))) + tsc->sg_counter = 0; + /** + * being it only resets on success it'd keep stacking and eventually overflowing on mvps, so we reset at a high value + **/ + else if( tsc->sg_counter > 250 ) + tsc->sg_counter = 0; + #endif + break; + + case WZ_METEOR: + sc_start(bl,SC_STUN,3*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case WZ_VERMILION: + sc_start(bl,SC_BLIND,4*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case HT_FREEZINGTRAP: + case MA_FREEZINGTRAP: + sc_start(bl,SC_FREEZE,(3*skill_lv+35),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case HT_FLASHER: + sc_start(bl,SC_BLIND,(10*skill_lv+30),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case HT_LANDMINE: + case MA_LANDMINE: + sc_start(bl,SC_STUN,(5*skill_lv+30),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case HT_SHOCKWAVE: + status_percent_damage(src, bl, 0, 15*skill_lv+5, false); + break; + + case HT_SANDMAN: + case MA_SANDMAN: + sc_start(bl,SC_SLEEP,(10*skill_lv+40),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case TF_SPRINKLESAND: + sc_start(bl,SC_BLIND,20,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case TF_THROWSTONE: + sc_start(bl,SC_STUN,3,skill_lv,skill_get_time(skill_id,skill_lv)); + sc_start(bl,SC_BLIND,3,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case NPC_DARKCROSS: + case CR_HOLYCROSS: + sc_start(bl,SC_BLIND,3*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case CR_GRANDCROSS: + case NPC_GRANDDARKNESS: + //Chance to cause blind status vs demon and undead element, but not against players + if(!dstsd && (battle_check_undead(tstatus->race,tstatus->def_ele) || tstatus->race == RC_DEMON)) + sc_start(bl,SC_BLIND,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + attack_type |= BF_WEAPON; + break; + + case AM_ACIDTERROR: + sc_start(bl,SC_BLEEDING,(skill_lv*3),skill_lv,skill_get_time2(skill_id,skill_lv)); + if (skill_break_equip(bl, EQP_ARMOR, 100*skill_get_time(skill_id,skill_lv), BCT_ENEMY)) + clif_emotion(bl,E_OMG); + break; + + case AM_DEMONSTRATION: + skill_break_equip(bl, EQP_WEAPON, 100*skill_lv, BCT_ENEMY); + break; + + case CR_SHIELDCHARGE: + sc_start(bl,SC_STUN,(15+skill_lv*5),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case PA_PRESSURE: + status_percent_damage(src, bl, 0, 15+5*skill_lv, false); + break; + + case RG_RAID: + sc_start(bl,SC_STUN,(10+3*skill_lv),skill_lv,skill_get_time(skill_id,skill_lv)); + sc_start(bl,SC_BLIND,(10+3*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); + +#ifdef RENEWAL + sc_start(bl,SC_RAID,100,7,5000); + break; + + case RG_BACKSTAP: + sc_start(bl,SC_STUN,(5+2*skill_lv),skill_lv,skill_get_time(skill_id,skill_lv)); +#endif + break; + + case BA_FROSTJOKER: + sc_start(bl,SC_FREEZE,(15+5*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case DC_SCREAM: + sc_start(bl,SC_STUN,(25+5*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case BD_LULLABY: + sc_start(bl,SC_SLEEP,15,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case DC_UGLYDANCE: + rate = 5+5*skill_lv; + if(sd && (skill=pc_checkskill(sd,DC_DANCINGLESSON))) + rate += 5+skill; + status_zap(bl, 0, rate); + break; + case SL_STUN: + if (tstatus->size==SZ_MEDIUM) //Only stuns mid-sized mobs. + sc_start(bl,SC_STUN,(30+10*skill_lv),skill_lv,skill_get_time(skill_id,skill_lv)); + break; + + case NPC_PETRIFYATTACK: + sc_start4(bl,status_skill2sc(skill_id),50+10*skill_lv, + skill_lv,0,0,skill_get_time(skill_id,skill_lv), + skill_get_time2(skill_id,skill_lv)); + break; + case NPC_CURSEATTACK: + case NPC_SLEEPATTACK: + case NPC_BLINDATTACK: + case NPC_POISON: + case NPC_SILENCEATTACK: + case NPC_STUNATTACK: + case NPC_HELLPOWER: + sc_start(bl,status_skill2sc(skill_id),50+10*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case NPC_ACIDBREATH: + case NPC_ICEBREATH: + sc_start(bl,status_skill2sc(skill_id),70,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case NPC_BLEEDING: + sc_start(bl,SC_BLEEDING,(20*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case NPC_MENTALBREAKER: + { //Based on observations by Tharis, Mental Breaker should do SP damage + //equal to Matk*skLevel. + rate = sstatus->matk_min; + if (rate < sstatus->matk_max) + rate += rnd()%(sstatus->matk_max - sstatus->matk_min); + rate*=skill_lv; + status_zap(bl, 0, rate); + break; + } + // Equipment breaking monster skills [Celest] + case NPC_WEAPONBRAKER: + skill_break_equip(bl, EQP_WEAPON, 150*skill_lv, BCT_ENEMY); + break; + case NPC_ARMORBRAKE: + skill_break_equip(bl, EQP_ARMOR, 150*skill_lv, BCT_ENEMY); + break; + case NPC_HELMBRAKE: + skill_break_equip(bl, EQP_HELM, 150*skill_lv, BCT_ENEMY); + break; + case NPC_SHIELDBRAKE: + skill_break_equip(bl, EQP_SHIELD, 150*skill_lv, BCT_ENEMY); + break; + + case CH_TIGERFIST: + sc_start(bl,SC_STOP,(10+skill_lv*10),0,skill_get_time2(skill_id,skill_lv)); + break; + + case LK_SPIRALPIERCE: + case ML_SPIRALPIERCE: + sc_start(bl,SC_STOP,(15+skill_lv*5),0,skill_get_time2(skill_id,skill_lv)); + break; + + case ST_REJECTSWORD: + sc_start(bl,SC_AUTOCOUNTER,(skill_lv*15),skill_lv,skill_get_time(skill_id,skill_lv)); + break; + + case PF_FOGWALL: + if (src != bl && !tsc->data[SC_DELUGE]) + sc_start(bl,SC_BLIND,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case LK_HEADCRUSH: //Headcrush has chance of causing Bleeding status, except on demon and undead element + if (!(battle_check_undead(tstatus->race, tstatus->def_ele) || tstatus->race == RC_DEMON)) + sc_start(bl, SC_BLEEDING,50, skill_lv, skill_get_time2(skill_id,skill_lv)); + break; + + case LK_JOINTBEAT: + status = status_skill2sc(skill_id); + if (tsc->jb_flag) { + sc_start2(bl,status,(5*skill_lv+5),skill_lv,tsc->jb_flag&BREAK_FLAGS,skill_get_time2(skill_id,skill_lv)); + tsc->jb_flag = 0; + } + break; + case ASC_METEORASSAULT: + //Any enemies hit by this skill will receive Stun, Darkness, or external bleeding status ailment with a 5%+5*skill_lv% chance. + switch(rnd()%3) { + case 0: + sc_start(bl,SC_BLIND,(5+skill_lv*5),skill_lv,skill_get_time2(skill_id,1)); + break; + case 1: + sc_start(bl,SC_STUN,(5+skill_lv*5),skill_lv,skill_get_time2(skill_id,2)); + break; + default: + sc_start(bl,SC_BLEEDING,(5+skill_lv*5),skill_lv,skill_get_time2(skill_id,3)); + } + break; + + case HW_NAPALMVULCAN: + sc_start(bl,SC_CURSE,5*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case WS_CARTTERMINATION: // Cart termination + sc_start(bl,SC_STUN,5*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case CR_ACIDDEMONSTRATION: + skill_break_equip(bl, EQP_WEAPON|EQP_ARMOR, 100*skill_lv, BCT_ENEMY); + break; + + case TK_DOWNKICK: + sc_start(bl,SC_STUN,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case TK_JUMPKICK: + if( dstsd && dstsd->class_ != MAPID_SOUL_LINKER && !tsc->data[SC_PRESERVE] ) + {// debuff the following statuses + status_change_end(bl, SC_SPIRIT, INVALID_TIMER); + status_change_end(bl, SC_ADRENALINE2, INVALID_TIMER); + status_change_end(bl, SC_KAITE, INVALID_TIMER); + status_change_end(bl, SC_KAAHI, INVALID_TIMER); + status_change_end(bl, SC_ONEHAND, INVALID_TIMER); + status_change_end(bl, SC_ASPDPOTION2, INVALID_TIMER); + } + break; + case TK_TURNKICK: + case MO_BALKYOUNG: //Note: attack_type is passed as BF_WEAPON for the actual target, BF_MISC for the splash-affected mobs. + if(attack_type&BF_MISC) //70% base stun chance... + sc_start(bl,SC_STUN,70,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case GS_BULLSEYE: //0.1% coma rate. + if(tstatus->race == RC_BRUTE || tstatus->race == RC_DEMIHUMAN) + status_change_start(bl,SC_COMA,10,skill_lv,0,src->id,0,0,0); + break; + case GS_PIERCINGSHOT: + sc_start(bl,SC_BLEEDING,(skill_lv*3),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case NJ_HYOUSYOURAKU: + sc_start(bl,SC_FREEZE,(10+10*skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case GS_FLING: + sc_start(bl,SC_FLING,100, sd?sd->spiritball_old:5,skill_get_time(skill_id,skill_lv)); + break; + case GS_DISARM: + rate = 3*skill_lv; + if (sstatus->dex > tstatus->dex) + rate += (sstatus->dex - tstatus->dex)/5; //TODO: Made up formula + skill_strip_equip(bl, EQP_WEAPON, rate, skill_lv, skill_get_time(skill_id,skill_lv)); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + case NPC_EVILLAND: + sc_start(bl,SC_BLIND,5*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case NPC_HELLJUDGEMENT: + sc_start(bl,SC_CURSE,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case NPC_CRITICALWOUND: + sc_start(bl,SC_CRITICALWOUND,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case RK_HUNDREDSPEAR: + if( !sd || pc_checkskill(sd,KN_SPEARBOOMERANG) == 0 ) + break; // Spear Boomerang auto cast chance only works if you have mastered Spear Boomerang. + rate = 10 + 3 * skill_lv; + if( rnd()%100 < rate ) + skill_castend_damage_id(src,bl,KN_SPEARBOOMERANG,1,tick,0); + break; + case RK_WINDCUTTER: + sc_start(bl,SC_FEAR,3+2*skill_lv,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + case RK_DRAGONBREATH: + sc_start4(bl,SC_BURNING,5+5*skill_lv,skill_lv,1000,src->id,0,skill_get_time(skill_id,skill_lv)); + break; + case AB_ADORAMUS: + if( tsc && !tsc->data[SC_DECREASEAGI] ) //Prevent duplicate agi-down effect. + sc_start(bl, SC_ADORAMUS, 100, skill_lv, skill_get_time(skill_id, skill_lv)); + break; + case WL_CRIMSONROCK: + sc_start(bl, SC_STUN, 40, skill_lv, skill_get_time(skill_id, skill_lv)); + break; + case WL_COMET: + sc_start4(bl,SC_BURNING,100,skill_lv,1000,src->id,0,skill_get_time(skill_id,skill_lv)); + break; + case WL_EARTHSTRAIN: + { + int rate = 0, i; + const int pos[5] = { EQP_WEAPON, EQP_HELM, EQP_SHIELD, EQP_ARMOR, EQP_ACC }; + rate = 6 * skill_lv + sstatus->dex / 10 + (sd? sd->status.job_level / 4 : 0) - tstatus->dex /5;// The tstatus->dex / 5 part is unofficial, but players gotta have some kind of way to have resistance. [Rytech] + //rate -= rate * tstatus->dex / 200; // Disabled until official resistance is found. + + for( i = 0; i < skill_lv; i++ ) + skill_strip_equip(bl,pos[i],rate,skill_lv,skill_get_time2(skill_id,skill_lv)); + } + break; + case WL_JACKFROST: + sc_start(bl,SC_FREEZE,100,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + case RA_WUGBITE: + sc_start(bl, SC_BITE, (sd ? pc_checkskill(sd,RA_TOOTHOFWUG)*2 : 0), skill_lv, (skill_get_time(skill_id,skill_lv) + (sd ? pc_checkskill(sd,RA_TOOTHOFWUG)*500 : 0)) ); + break; + case RA_SENSITIVEKEEN: + if( rnd()%100 < 8 * skill_lv ) + skill_castend_damage_id(src, bl, RA_WUGBITE, sd ? pc_checkskill(sd, RA_WUGBITE):skill_lv, tick, SD_ANIMATION); + break; + case RA_FIRINGTRAP: + case RA_ICEBOUNDTRAP: + sc_start(bl, (skill_id == RA_FIRINGTRAP) ? SC_BURNING:SC_FREEZING, 40 + 10 * skill_lv, skill_lv, skill_get_time2(skill_id, skill_lv)); + break; + case NC_PILEBUNKER: + if( rnd()%100 < 5 + 15*skill_lv ) + { //Deactivatable Statuses: Kyrie Eleison, Auto Guard, Steel Body, Assumptio, and Millennium Shield + status_change_end(bl, SC_KYRIE, INVALID_TIMER); + status_change_end(bl, SC_AUTOGUARD, INVALID_TIMER); + status_change_end(bl, SC_STEELBODY, INVALID_TIMER); + status_change_end(bl, SC_ASSUMPTIO, INVALID_TIMER); + status_change_end(bl, SC_MILLENNIUMSHIELD, INVALID_TIMER); + } + break; + case NC_FLAMELAUNCHER: + sc_start4(bl, SC_BURNING, 50 + 10 * skill_lv, skill_lv, 1000, src->id, 0, skill_get_time2(skill_id, skill_lv)); + break; + case NC_COLDSLOWER: + sc_start(bl, SC_FREEZE, 10 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); + sc_start(bl, SC_FREEZING, 20 + 10 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); + break; + case NC_POWERSWING: + sc_start(bl, SC_STUN, 5*skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); + if( rnd()%100 < 5*skill_lv ) + skill_castend_damage_id(src, bl, NC_AXEBOOMERANG, pc_checkskill(sd, NC_AXEBOOMERANG), tick, 1); + break; + case GC_WEAPONCRUSH: + skill_castend_nodamage_id(src,bl,skill_id,skill_lv,tick,BCT_ENEMY); + break; + case LG_SHIELDPRESS: + sc_start(bl, SC_STUN, 30 + 8 * skill_lv, skill_lv, skill_get_time(skill_id,skill_lv)); + break; + case LG_PINPOINTATTACK: + rate = 30 + (((5 * (sd?pc_checkskill(sd,LG_PINPOINTATTACK):skill_lv)) + (sstatus->agi + status_get_lv(src))) / 10); + switch( skill_lv ) { + case 1: + sc_start(bl,SC_BLEEDING,rate,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + case 2: + if( dstsd && dstsd->spiritball && rnd()%100 < rate ) + pc_delspiritball(dstsd, dstsd->spiritball, 0); + break; + default: + skill_break_equip(bl,(skill_lv == 3) ? EQP_SHIELD : (skill_lv == 4) ? EQP_ARMOR : EQP_WEAPON,rate * 100,BCT_ENEMY); + break; + } + break; + case LG_MOONSLASHER: + rate = 32 + 8 * skill_lv; + if( rnd()%100 < rate && dstsd ) // Uses skill_addtimerskill to avoid damage and setsit packet overlaping. Officially clif_setsit is received about 500 ms after damage packet. + skill_addtimerskill(src,tick+500,bl->id,0,0,skill_id,skill_lv,BF_WEAPON,0); + else if( dstmd && !is_boss(bl) ) + sc_start(bl,SC_STOP,100,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + case LG_RAYOFGENESIS: // 50% chance to cause Blind on Undead and Demon monsters. + if ( battle_check_undead(tstatus->race, tstatus->def_ele) || tstatus->race == RC_DEMON ) + sc_start(bl, SC_BLIND,50, skill_lv, skill_get_time(skill_id,skill_lv)); + break; + case LG_EARTHDRIVE: + skill_break_equip(src, EQP_SHIELD, 500, BCT_SELF); + sc_start(bl, SC_EARTHDRIVE, 100, skill_lv, skill_get_time(skill_id, skill_lv)); + break; + case SR_DRAGONCOMBO: + sc_start(bl, SC_STUN, 1 + skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); + break; + case SR_FALLENEMPIRE: + sc_start(bl, SC_STOP, 100, skill_lv, skill_get_time(skill_id, skill_lv)); + break; + case SR_WINDMILL: + if( dstsd ) + skill_addtimerskill(src,tick+status_get_amotion(src),bl->id,0,0,skill_id,skill_lv,BF_WEAPON,0); + else if( dstmd && !is_boss(bl) ) + sc_start(bl, SC_STUN, 100, skill_lv, 1000 + 1000 * (rnd() %3)); + break; + case SR_GENTLETOUCH_QUIET: // [(Skill Level x 5) + (Caster?s DEX + Caster?s Base Level) / 10] + sc_start(bl, SC_SILENCE, 5 * skill_lv + (sstatus->dex + status_get_lv(src)) / 10, skill_lv, skill_get_time(skill_id, skill_lv)); + break; + case SR_EARTHSHAKER: + sc_start(bl,SC_STUN, 25 + 5 * skill_lv,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + case SR_HOWLINGOFLION: + sc_start(bl, SC_FEAR, 5 + 5 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); + break; + case WM_SOUND_OF_DESTRUCTION: + if( rnd()%100 < 5 + 5 * skill_lv ) { // Temporarly Check Until We Get the Official Formula + status_change_end(bl, SC_DANCING, INVALID_TIMER); + status_change_end(bl, SC_RICHMANKIM, INVALID_TIMER); + status_change_end(bl, SC_ETERNALCHAOS, INVALID_TIMER); + status_change_end(bl, SC_DRUMBATTLE, INVALID_TIMER); + status_change_end(bl, SC_NIBELUNGEN, INVALID_TIMER); + status_change_end(bl, SC_INTOABYSS, INVALID_TIMER); + status_change_end(bl, SC_SIEGFRIED, INVALID_TIMER); + status_change_end(bl, SC_WHISTLE, INVALID_TIMER); + status_change_end(bl, SC_ASSNCROS, INVALID_TIMER); + status_change_end(bl, SC_POEMBRAGI, INVALID_TIMER); + status_change_end(bl, SC_APPLEIDUN, INVALID_TIMER); + status_change_end(bl, SC_HUMMING, INVALID_TIMER); + status_change_end(bl, SC_FORTUNE, INVALID_TIMER); + status_change_end(bl, SC_SERVICE4U, INVALID_TIMER); + status_change_end(bl, SC_LONGING, INVALID_TIMER); + status_change_end(bl, SC_SWINGDANCE, INVALID_TIMER); + status_change_end(bl, SC_SYMPHONYOFLOVER, INVALID_TIMER); + status_change_end(bl, SC_MOONLITSERENADE, INVALID_TIMER); + status_change_end(bl, SC_RUSHWINDMILL, INVALID_TIMER); + status_change_end(bl, SC_ECHOSONG, INVALID_TIMER); + status_change_end(bl, SC_HARMONIZE, INVALID_TIMER); + status_change_end(bl, SC_WINKCHARM, INVALID_TIMER); + status_change_end(bl, SC_SONGOFMANA, INVALID_TIMER); + status_change_end(bl, SC_DANCEWITHWUG, INVALID_TIMER); + status_change_end(bl, SC_LERADSDEW, INVALID_TIMER); + status_change_end(bl, SC_MELODYOFSINK, INVALID_TIMER); + status_change_end(bl, SC_BEYONDOFWARCRY, INVALID_TIMER); + status_change_end(bl, SC_UNLIMITEDHUMMINGVOICE, INVALID_TIMER); + } + break; + case SO_EARTHGRAVE: + sc_start(bl, SC_BLEEDING, 5 * skill_lv, skill_lv, skill_get_time2(skill_id, skill_lv)); // Need official rate. [LimitLine] + break; + case SO_DIAMONDDUST: + rate = 5 + 5 * skill_lv; + if( sc && sc->data[SC_COOLER_OPTION] ) + rate += rate * sc->data[SC_COOLER_OPTION]->val2 / 100; + sc_start(bl, SC_CRYSTALIZE, rate, skill_lv, skill_get_time2(skill_id, skill_lv)); + break; + case SO_VARETYR_SPEAR: + sc_start(bl, SC_STUN, 5 + 5 * skill_lv, skill_lv, skill_get_time2(skill_id, skill_lv)); + break; + case GN_SLINGITEM_RANGEMELEEATK: + if( sd ) { + switch( sd->itemid ) { // Starting SCs here instead of do it in skill_additional_effect to simplify the code. + case 13261: + sc_start(bl, SC_STUN, 100, skill_lv, skill_get_time2(GN_SLINGITEM, skill_lv)); + sc_start(bl, SC_BLEEDING, 100, skill_lv, skill_get_time2(GN_SLINGITEM, skill_lv)); + break; + case 13262: + sc_start(bl, SC_MELON_BOMB, 100, skill_lv, skill_get_time(GN_SLINGITEM, skill_lv)); // Reduces ASPD and moviment speed + break; + case 13264: + sc_start(bl, SC_BANANA_BOMB, 100, skill_lv, skill_get_time(GN_SLINGITEM, skill_lv)); // Reduces LUK ??Needed confirm it, may be it's bugged in kRORE? + sc_start(bl, SC_BANANA_BOMB_SITDOWN, 75, skill_lv, skill_get_time(GN_SLINGITEM_RANGEMELEEATK,skill_lv)); // Sitdown for 3 seconds. + break; + } + sd->itemid = -1; + } + break; + case GN_HELLS_PLANT_ATK: + sc_start(bl, SC_STUN, 5 + 5 * skill_lv, skill_lv, skill_get_time2(skill_id, skill_lv)); + sc_start(bl, SC_BLEEDING, 20 + 10 * skill_lv, skill_lv, skill_get_time2(skill_id, skill_lv)); + break; + case EL_WIND_SLASH: // Non confirmed rate. + sc_start(bl, SC_BLEEDING, 25, skill_lv, skill_get_time(skill_id,skill_lv)); + break; + case EL_STONE_HAMMER: + rate = 10 * skill_lv; + sc_start(bl, SC_STUN, rate, skill_lv, skill_get_time(skill_id,skill_lv)); + break; + case EL_ROCK_CRUSHER: + case EL_ROCK_CRUSHER_ATK: + sc_start(bl,status_skill2sc(skill_id),50,skill_lv,skill_get_time(EL_ROCK_CRUSHER,skill_lv)); + break; + case EL_TYPOON_MIS: + sc_start(bl,SC_SILENCE,10*skill_lv,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + case KO_JYUMONJIKIRI: // needs more info + sc_start(bl,SC_JYUMONJIKIRI,25,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + case KO_MAKIBISHI: + sc_start(bl, SC_STUN, 100, skill_lv, skill_get_time2(skill_id,skill_lv)); + break; + case MH_LAVA_SLIDE: + if (tsc && !tsc->data[SC_BURNING]) sc_start4(bl, SC_BURNING, 10 * skill_lv, skill_lv, 1000, src->id, 0, skill_get_time(skill_id, skill_lv)); + break; + case MH_STAHL_HORN: + sc_start(bl, SC_STUN, (20 + 4 * (skill_lv-1)), skill_lv, skill_get_time(skill_id, skill_lv)); + break; + case MH_NEEDLE_OF_PARALYZE: + sc_start(bl, SC_PARALYSIS, 40 + (5*skill_lv), skill_lv, skill_get_time(skill_id, skill_lv)); + break; + } + + if (md && battle_config.summons_trigger_autospells && md->master_id && md->special_state.ai) + { //Pass heritage to Master for status causing effects. [Skotlex] + sd = map_id2sd(md->master_id); + src = sd?&sd->bl:src; + } + + if( attack_type&BF_WEAPON ) + { // Coma, Breaking Equipment + if( sd && sd->special_state.bonus_coma ) + { + rate = sd->weapon_coma_ele[tstatus->def_ele]; + rate += sd->weapon_coma_race[tstatus->race]; + rate += sd->weapon_coma_race[tstatus->mode&MD_BOSS?RC_BOSS:RC_NONBOSS]; + if (rate) + status_change_start(bl, SC_COMA, rate, 0, 0, src->id, 0, 0, 0); + } + if( sd && battle_config.equip_self_break_rate ) + { // Self weapon breaking + rate = battle_config.equip_natural_break_rate; + if( sc ) + { + if(sc->data[SC_OVERTHRUST]) + rate += 10; + if(sc->data[SC_MAXOVERTHRUST]) + rate += 10; + } + if( rate ) + skill_break_equip(src, EQP_WEAPON, rate, BCT_SELF); + } + if( battle_config.equip_skill_break_rate && skill_id != WS_CARTTERMINATION && skill_id != ITM_TOMAHAWK ) + { // Cart Termination/Tomahawk won't trigger breaking data. Why? No idea, go ask Gravity. + // Target weapon breaking + rate = 0; + if( sd ) + rate += sd->bonus.break_weapon_rate; + if( sc && sc->data[SC_MELTDOWN] ) + rate += sc->data[SC_MELTDOWN]->val2; + if( rate ) + skill_break_equip(bl, EQP_WEAPON, rate, BCT_ENEMY); + + // Target armor breaking + rate = 0; + if( sd ) + rate += sd->bonus.break_armor_rate; + if( sc && sc->data[SC_MELTDOWN] ) + rate += sc->data[SC_MELTDOWN]->val3; + if( rate ) + skill_break_equip(bl, EQP_ARMOR, rate, BCT_ENEMY); + } + } + + if( sd && sd->ed && sc && !status_isdead(bl) && !skill_id ){ + struct unit_data *ud = unit_bl2ud(src); + + if( sc->data[SC_WILD_STORM_OPTION] ) + skill = sc->data[SC_WILD_STORM_OPTION]->val2; + else if( sc->data[SC_UPHEAVAL_OPTION] ) + skill = sc->data[SC_UPHEAVAL_OPTION]->val2; + else if( sc->data[SC_TROPIC_OPTION] ) + skill = sc->data[SC_TROPIC_OPTION]->val3; + else if( sc->data[SC_CHILLY_AIR_OPTION] ) + skill = sc->data[SC_CHILLY_AIR_OPTION]->val3; + else + skill = 0; + + if ( rnd()%100 < 25 && skill ){ + skill_castend_damage_id(src, bl, skill, 5, tick, 0); + + if (ud) { + rate = skill_delayfix(src, skill, skill_lv); + if (DIFF_TICK(ud->canact_tick, tick + rate) < 0){ + ud->canact_tick = tick+rate; + if ( battle_config.display_status_timers ) + clif_status_change(src, SI_ACTIONDELAY, 1, rate, 0, 0, 0); + } + } + } + } + + // Autospell when attacking + if( sd && !status_isdead(bl) && sd->autospell[0].id ) + { + struct block_list *tbl; + struct unit_data *ud; + int i, skill_lv, type, notok; + + for (i = 0; i < ARRAYLENGTH(sd->autospell) && sd->autospell[i].id; i++) { + + if(!(sd->autospell[i].flag&attack_type&BF_WEAPONMASK && + sd->autospell[i].flag&attack_type&BF_RANGEMASK && + sd->autospell[i].flag&attack_type&BF_SKILLMASK)) + continue; // one or more trigger conditions were not fulfilled + + skill = (sd->autospell[i].id > 0) ? sd->autospell[i].id : -sd->autospell[i].id; + + sd->state.autocast = 1; + notok = skillnotok(skill, sd); + sd->state.autocast = 0; + + if ( notok ) + continue; + + skill_lv = sd->autospell[i].lv?sd->autospell[i].lv:1; + if (skill_lv < 0) skill_lv = 1+rnd()%(-skill_lv); + + rate = (!sd->state.arrow_atk) ? sd->autospell[i].rate : sd->autospell[i].rate / 2; + + if (rnd()%1000 >= rate) + continue; + + tbl = (sd->autospell[i].id < 0) ? src : bl; + + if( (type = skill_get_casttype(skill)) == CAST_GROUND ) { + int maxcount = 0; + if( !(BL_PC&battle_config.skill_reiteration) && + skill_get_unit_flag(skill)&UF_NOREITERATION && + skill_check_unit_range(src,tbl->x,tbl->y,skill,skill_lv) + ) { + continue; + } + if( BL_PC&battle_config.skill_nofootset && + skill_get_unit_flag(skill)&UF_NOFOOTSET && + skill_check_unit_range2(src,tbl->x,tbl->y,skill,skill_lv) + ) { + continue; + } + if( BL_PC&battle_config.land_skill_limit && + (maxcount = skill_get_maxcount(skill, skill_lv)) > 0 + ) { + int v; + for(v=0;v<MAX_SKILLUNITGROUP && sd->ud.skillunit[v] && maxcount;v++) { + if(sd->ud.skillunit[v]->skill_id == skill) + maxcount--; + } + if( maxcount == 0 ) { + continue; + } + } + } + if( battle_config.autospell_check_range && + !battle_check_range(src, tbl, skill_get_range2(src, skill,skill_lv) + (skill == RG_CLOSECONFINE?0:1)) ) + continue; + + if (skill == AS_SONICBLOW) + pc_stop_attack(sd); //Special case, Sonic Blow autospell should stop the player attacking. + if (skill == PF_SPIDERWEB) //Special case, due to its nature of coding. + type = CAST_GROUND; + + sd->state.autocast = 1; + skill_consume_requirement(sd,skill,skill_lv,1); + skill_toggle_magicpower(src, skill); + switch (type) { + case CAST_GROUND: + skill_castend_pos2(src, tbl->x, tbl->y, skill, skill_lv, tick, 0); + break; + case CAST_NODAMAGE: + skill_castend_nodamage_id(src, tbl, skill, skill_lv, tick, 0); + break; + case CAST_DAMAGE: + skill_castend_damage_id(src, tbl, skill, skill_lv, tick, 0); + break; + } + sd->state.autocast = 0; + //Set canact delay. [Skotlex] + ud = unit_bl2ud(src); + if (ud) { + rate = skill_delayfix(src, skill, skill_lv); + if (DIFF_TICK(ud->canact_tick, tick + rate) < 0){ + ud->canact_tick = tick+rate; + if ( battle_config.display_status_timers && sd ) + clif_status_change(src, SI_ACTIONDELAY, 1, rate, 0, 0, 0); + } + } + } + } + + //Autobonus when attacking + if( sd && sd->autobonus[0].rate ) + { + int i; + for( i = 0; i < ARRAYLENGTH(sd->autobonus); i++ ) + { + if( rnd()%1000 >= sd->autobonus[i].rate ) + continue; + if( sd->autobonus[i].active != INVALID_TIMER ) + continue; + if(!(sd->autobonus[i].atk_type&attack_type&BF_WEAPONMASK && + sd->autobonus[i].atk_type&attack_type&BF_RANGEMASK && + sd->autobonus[i].atk_type&attack_type&BF_SKILLMASK)) + continue; // one or more trigger conditions were not fulfilled + pc_exeautobonus(sd,&sd->autobonus[i]); + } + } + + //Polymorph + if(sd && sd->bonus.classchange && attack_type&BF_WEAPON && + dstmd && !(tstatus->mode&MD_BOSS) && + (rnd()%10000 < sd->bonus.classchange)) + { + struct mob_db *mob; + int class_; + skill = 0; + do { + do { + class_ = rnd() % MAX_MOB_DB; + } while (!mobdb_checkid(class_)); + + rate = rnd() % 1000000; + mob = mob_db(class_); + } while ( + (mob->status.mode&(MD_BOSS|MD_PLANT) || mob->summonper[0] <= rate) && + (skill++) < 2000); + if (skill < 2000) + mob_class_change(dstmd,class_); + } + + return 0; +} + +int skill_onskillusage(struct map_session_data *sd, struct block_list *bl, uint16 skill_id, unsigned int tick) { + int skill, skill_lv, i, type, notok; + struct block_list *tbl; + + if( sd == NULL || !skill_id ) + return 0; + + for( i = 0; i < ARRAYLENGTH(sd->autospell3) && sd->autospell3[i].flag; i++ ) { + if( sd->autospell3[i].flag != skill_id ) + continue; + + if( sd->autospell3[i].lock ) + continue; // autospell already being executed + + skill = (sd->autospell3[i].id > 0) ? sd->autospell3[i].id : -sd->autospell3[i].id; + + sd->state.autocast = 1; + notok = skillnotok(skill, sd); + sd->state.autocast = 0; + + if ( notok ) + continue; + + skill_lv = sd->autospell3[i].lv ? sd->autospell3[i].lv : 1; + if( skill_lv < 0 ) skill_lv = 1 + rnd()%(-skill_lv); + + if( sd->autospell3[i].id >= 0 && bl == NULL ) + continue; // No target + if( rnd()%1000 >= sd->autospell3[i].rate ) + continue; + + tbl = (sd->autospell3[i].id < 0) ? &sd->bl : bl; + + if( (type = skill_get_casttype(skill)) == CAST_GROUND ) { + int maxcount = 0; + if( !(BL_PC&battle_config.skill_reiteration) && + skill_get_unit_flag(skill)&UF_NOREITERATION && + skill_check_unit_range(&sd->bl,tbl->x,tbl->y,skill,skill_lv) + ) { + continue; + } + if( BL_PC&battle_config.skill_nofootset && + skill_get_unit_flag(skill)&UF_NOFOOTSET && + skill_check_unit_range2(&sd->bl,tbl->x,tbl->y,skill,skill_lv) + ) { + continue; + } + if( BL_PC&battle_config.land_skill_limit && + (maxcount = skill_get_maxcount(skill, skill_lv)) > 0 + ) { + int v; + for(v=0;v<MAX_SKILLUNITGROUP && sd->ud.skillunit[v] && maxcount;v++) { + if(sd->ud.skillunit[v]->skill_id == skill) + maxcount--; + } + if( maxcount == 0 ) { + continue; + } + } + } + if( battle_config.autospell_check_range && + !battle_check_range(&sd->bl, tbl, skill_get_range2(&sd->bl, skill,skill_lv) + (skill == RG_CLOSECONFINE?0:1)) ) + continue; + + sd->state.autocast = 1; + sd->autospell3[i].lock = true; + skill_consume_requirement(sd,skill,skill_lv,1); + switch( type ) + { + case CAST_GROUND: skill_castend_pos2(&sd->bl, tbl->x, tbl->y, skill, skill_lv, tick, 0); break; + case CAST_NODAMAGE: skill_castend_nodamage_id(&sd->bl, tbl, skill, skill_lv, tick, 0); break; + case CAST_DAMAGE: skill_castend_damage_id(&sd->bl, tbl, skill, skill_lv, tick, 0); break; + } + sd->autospell3[i].lock = false; + sd->state.autocast = 0; + } + + if( sd && sd->autobonus3[0].rate ) + { + for( i = 0; i < ARRAYLENGTH(sd->autobonus3); i++ ) + { + if( rnd()%1000 >= sd->autobonus3[i].rate ) + continue; + if( sd->autobonus3[i].active != INVALID_TIMER ) + continue; + if( sd->autobonus3[i].atk_type != skill_id ) + continue; + pc_exeautobonus(sd,&sd->autobonus3[i]); + } + } + + return 1; +} + +/* Splitted off from skill_additional_effect, which is never called when the + * attack skill kills the enemy. Place in this function counter status effects + * when using skills (eg: Asura's sp regen penalty, or counter-status effects + * from cards) that will take effect on the source, not the target. [Skotlex] + * Note: Currently this function only applies to Extremity Fist and BF_WEAPON + * type of skills, so not every instance of skill_additional_effect needs a call + * to this one. + */ +int skill_counter_additional_effect (struct block_list* src, struct block_list *bl, uint16 skill_id, uint16 skill_lv, int attack_type, unsigned int tick) +{ + int rate; + struct map_session_data *sd=NULL; + struct map_session_data *dstsd=NULL; + + nullpo_ret(src); + nullpo_ret(bl); + + if(skill_id > 0 && !skill_lv) return 0; // don't forget auto attacks! - celest + + sd = BL_CAST(BL_PC, src); + dstsd = BL_CAST(BL_PC, bl); + + if(dstsd && attack_type&BF_WEAPON) + { //Counter effects. + enum sc_type type; + int i, time; + for(i=0; i < ARRAYLENGTH(dstsd->addeff2) && dstsd->addeff2[i].flag; i++) + { + rate = dstsd->addeff2[i].rate; + if (attack_type&BF_LONG) + rate+=dstsd->addeff2[i].arrow_rate; + if (!rate) continue; + + if ((dstsd->addeff2[i].flag&(ATF_LONG|ATF_SHORT)) != (ATF_LONG|ATF_SHORT)) + { //Trigger has range consideration. + if((dstsd->addeff2[i].flag&ATF_LONG && !(attack_type&BF_LONG)) || + (dstsd->addeff2[i].flag&ATF_SHORT && !(attack_type&BF_SHORT))) + continue; //Range Failed. + } + type = dstsd->addeff2[i].id; + time = skill_get_time2(status_sc2skill(type),7); + + if (dstsd->addeff2[i].flag&ATF_TARGET) + status_change_start(src,type,rate,7,0,0,0,time,0); + + if (dstsd->addeff2[i].flag&ATF_SELF && !status_isdead(bl)) + status_change_start(bl,type,rate,7,0,0,0,time,0); + } + } + + switch(skill_id){ + case MO_EXTREMITYFIST: + sc_start(src,SC_EXTREMITYFIST,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case GS_FULLBUSTER: + sc_start(src,SC_BLIND,2*skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case HFLI_SBR44: //[orn] + case HVAN_EXPLOSION: + if(src->type == BL_HOM){ + TBL_HOM *hd = (TBL_HOM*)src; + hd->homunculus.intimacy = 200; + if (hd->master) + clif_send_homdata(hd->master,SP_INTIMATE,hd->homunculus.intimacy/100); + } + break; + case CR_GRANDCROSS: + case NPC_GRANDDARKNESS: + attack_type |= BF_WEAPON; + break; + } + + if(sd && (sd->class_&MAPID_UPPERMASK) == MAPID_STAR_GLADIATOR && + rnd()%10000 < battle_config.sg_miracle_skill_ratio) //SG_MIRACLE [Komurka] + sc_start(src,SC_MIRACLE,100,1,battle_config.sg_miracle_skill_duration); + + if(sd && skill_id && attack_type&BF_MAGIC && status_isdead(bl) && + !(skill_get_inf(skill_id)&(INF_GROUND_SKILL|INF_SELF_SKILL)) && + (rate=pc_checkskill(sd,HW_SOULDRAIN))>0 + ){ //Soul Drain should only work on targetted spells [Skotlex] + if (pc_issit(sd)) pc_setstand(sd); //Character stuck in attacking animation while 'sitting' fix. [Skotlex] + clif_skill_nodamage(src,bl,HW_SOULDRAIN,rate,1); + status_heal(src, 0, status_get_lv(bl)*(95+15*rate)/100, 2); + } + + if( sd && status_isdead(bl) ) { + int sp = 0, hp = 0; + if( attack_type&BF_WEAPON ) { + sp += sd->bonus.sp_gain_value; + sp += sd->sp_gain_race[status_get_race(bl)]; + sp += sd->sp_gain_race[is_boss(bl)?RC_BOSS:RC_NONBOSS]; + hp += sd->bonus.hp_gain_value; + } + if( attack_type&BF_MAGIC ) { + sp += sd->bonus.magic_sp_gain_value; + hp += sd->bonus.magic_hp_gain_value; + if( skill_id == WZ_WATERBALL ) {//(bugreport:5303) + struct status_change *sc = NULL; + if( ( sc = status_get_sc(src) ) ) { + if(sc->data[SC_SPIRIT] && + sc->data[SC_SPIRIT]->val2 == SL_WIZARD && + sc->data[SC_SPIRIT]->val3 == WZ_WATERBALL) + sc->data[SC_SPIRIT]->val3 = 0; //Clear bounced spell check. + } + } + } + if( hp || sp ) { // updated to force healing to allow healing through berserk + status_heal(src, hp, sp, battle_config.show_hp_sp_gain ? 3 : 1); + } + } + + // Trigger counter-spells to retaliate against damage causing skills. + if(dstsd && !status_isdead(bl) && dstsd->autospell2[0].id && + !(skill_id && skill_get_nk(skill_id)&NK_NO_DAMAGE)) + { + struct block_list *tbl; + struct unit_data *ud; + int i, skill_id, skill_lv, rate, type, notok; + + for (i = 0; i < ARRAYLENGTH(dstsd->autospell2) && dstsd->autospell2[i].id; i++) { + + if(!(dstsd->autospell2[i].flag&attack_type&BF_WEAPONMASK && + dstsd->autospell2[i].flag&attack_type&BF_RANGEMASK && + dstsd->autospell2[i].flag&attack_type&BF_SKILLMASK)) + continue; // one or more trigger conditions were not fulfilled + + skill_id = (dstsd->autospell2[i].id > 0) ? dstsd->autospell2[i].id : -dstsd->autospell2[i].id; + skill_lv = dstsd->autospell2[i].lv?dstsd->autospell2[i].lv:1; + if (skill_lv < 0) skill_lv = 1+rnd()%(-skill_lv); + + rate = dstsd->autospell2[i].rate; + if (attack_type&BF_LONG) + rate>>=1; + + dstsd->state.autocast = 1; + notok = skillnotok(skill_id, dstsd); + dstsd->state.autocast = 0; + + if ( notok ) + continue; + + if (rnd()%1000 >= rate) + continue; + + tbl = (dstsd->autospell2[i].id < 0) ? bl : src; + + if( (type = skill_get_casttype(skill_id)) == CAST_GROUND ) { + int maxcount = 0; + if( !(BL_PC&battle_config.skill_reiteration) && + skill_get_unit_flag(skill_id)&UF_NOREITERATION && + skill_check_unit_range(bl,tbl->x,tbl->y,skill_id,skill_lv) + ) { + continue; + } + if( BL_PC&battle_config.skill_nofootset && + skill_get_unit_flag(skill_id)&UF_NOFOOTSET && + skill_check_unit_range2(bl,tbl->x,tbl->y,skill_id,skill_lv) + ) { + continue; + } + if( BL_PC&battle_config.land_skill_limit && + (maxcount = skill_get_maxcount(skill_id, skill_lv)) > 0 + ) { + int v; + for(v=0;v<MAX_SKILLUNITGROUP && dstsd->ud.skillunit[v] && maxcount;v++) { + if(dstsd->ud.skillunit[v]->skill_id == skill_id) + maxcount--; + } + if( maxcount == 0 ) { + continue; + } + } + } + + if( !battle_check_range(src, tbl, skill_get_range2(src, skill_id,skill_lv) + (skill_id == RG_CLOSECONFINE?0:1)) && battle_config.autospell_check_range ) + continue; + + dstsd->state.autocast = 1; + skill_consume_requirement(dstsd,skill_id,skill_lv,1); + switch (type) { + case CAST_GROUND: + skill_castend_pos2(bl, tbl->x, tbl->y, skill_id, skill_lv, tick, 0); + break; + case CAST_NODAMAGE: + skill_castend_nodamage_id(bl, tbl, skill_id, skill_lv, tick, 0); + break; + case CAST_DAMAGE: + skill_castend_damage_id(bl, tbl, skill_id, skill_lv, tick, 0); + break; + } + dstsd->state.autocast = 0; + //Set canact delay. [Skotlex] + ud = unit_bl2ud(bl); + if (ud) { + rate = skill_delayfix(bl, skill_id, skill_lv); + if (DIFF_TICK(ud->canact_tick, tick + rate) < 0){ + ud->canact_tick = tick+rate; + if ( battle_config.display_status_timers && dstsd ) + clif_status_change(bl, SI_ACTIONDELAY, 1, rate, 0, 0, 0); + } + } + } + } + + //Autobonus when attacked + if( dstsd && !status_isdead(bl) && dstsd->autobonus2[0].rate && !(skill_id && skill_get_nk(skill_id)&NK_NO_DAMAGE) ) + { + int i; + for( i = 0; i < ARRAYLENGTH(dstsd->autobonus2); i++ ) + { + if( rnd()%1000 >= dstsd->autobonus2[i].rate ) + continue; + if( dstsd->autobonus2[i].active != INVALID_TIMER ) + continue; + if(!(dstsd->autobonus2[i].atk_type&attack_type&BF_WEAPONMASK && + dstsd->autobonus2[i].atk_type&attack_type&BF_RANGEMASK && + dstsd->autobonus2[i].atk_type&attack_type&BF_SKILLMASK)) + continue; // one or more trigger conditions were not fulfilled + pc_exeautobonus(dstsd,&dstsd->autobonus2[i]); + } + } + + return 0; +} +/*========================================================================= + Breaks equipment. On-non players causes the corresponding strip effect. + - rate goes from 0 to 10000 (100.00%) + - flag is a BCT_ flag to indicate which type of adjustment should be used + (BCT_ENEMY/BCT_PARTY/BCT_SELF) are the valid values. +--------------------------------------------------------------------------*/ +int skill_break_equip (struct block_list *bl, unsigned short where, int rate, int flag) +{ + const int where_list[4] = {EQP_WEAPON, EQP_ARMOR, EQP_SHIELD, EQP_HELM}; + const enum sc_type scatk[4] = {SC_STRIPWEAPON, SC_STRIPARMOR, SC_STRIPSHIELD, SC_STRIPHELM}; + const enum sc_type scdef[4] = {SC_CP_WEAPON, SC_CP_ARMOR, SC_CP_SHIELD, SC_CP_HELM}; + struct status_change *sc = status_get_sc(bl); + int i,j; + TBL_PC *sd; + sd = BL_CAST(BL_PC, bl); + if (sc && !sc->count) + sc = NULL; + + if (sd) { + if (sd->bonus.unbreakable_equip) + where &= ~sd->bonus.unbreakable_equip; + if (sd->bonus.unbreakable) + rate -= rate*sd->bonus.unbreakable/100; + if (where&EQP_WEAPON) { + switch (sd->status.weapon) { + case W_FIST: //Bare fists should not break :P + case W_1HAXE: + case W_2HAXE: + case W_MACE: // Axes and Maces can't be broken [DracoRPG] + case W_2HMACE: + case W_STAFF: + case W_2HSTAFF: + case W_BOOK: //Rods and Books can't be broken [Skotlex] + case W_HUUMA: + where &= ~EQP_WEAPON; + } + } + } + if (flag&BCT_ENEMY) { + if (battle_config.equip_skill_break_rate != 100) + rate = rate*battle_config.equip_skill_break_rate/100; + } else if (flag&(BCT_PARTY|BCT_SELF)) { + if (battle_config.equip_self_break_rate != 100) + rate = rate*battle_config.equip_self_break_rate/100; + } + + for (i = 0; i < 4; i++) { + if (where&where_list[i]) { + if (sc && sc->count && sc->data[scdef[i]]) + where&=~where_list[i]; + else if (rnd()%10000 >= rate) + where&=~where_list[i]; + else if (!sd && !(status_get_mode(bl)&MD_BOSS)) //Cause Strip effect. + sc_start(bl,scatk[i],100,0,skill_get_time(status_sc2skill(scatk[i]),1)); + } + } + if (!where) //Nothing to break. + return 0; + if (sd) { + for (i = 0; i < EQI_MAX; i++) { + j = sd->equip_index[i]; + if (j < 0 || sd->status.inventory[j].attribute == 1 || !sd->inventory_data[j]) + continue; + + switch(i) { + case EQI_HEAD_TOP: //Upper Head + flag = (where&EQP_HELM); + break; + case EQI_ARMOR: //Body + flag = (where&EQP_ARMOR); + break; + case EQI_HAND_R: //Left/Right hands + case EQI_HAND_L: + flag = ( + (where&EQP_WEAPON && sd->inventory_data[j]->type == IT_WEAPON) || + (where&EQP_SHIELD && sd->inventory_data[j]->type == IT_ARMOR)); + break; + case EQI_SHOES: + flag = (where&EQP_SHOES); + break; + case EQI_GARMENT: + flag = (where&EQP_GARMENT); + break; + default: + continue; + } + if (flag) { + sd->status.inventory[j].attribute = 1; + pc_unequipitem(sd, j, 3); + } + } + clif_equiplist(sd); + } + + return where; //Return list of pieces broken. +} + +int skill_strip_equip(struct block_list *bl, unsigned short where, int rate, int lv, int time) +{ + struct status_change *sc; + const int pos[5] = {EQP_WEAPON, EQP_SHIELD, EQP_ARMOR, EQP_HELM, EQP_ACC}; + const enum sc_type sc_atk[5] = {SC_STRIPWEAPON, SC_STRIPSHIELD, SC_STRIPARMOR, SC_STRIPHELM, SC__STRIPACCESSORY}; + const enum sc_type sc_def[5] = {SC_CP_WEAPON, SC_CP_SHIELD, SC_CP_ARMOR, SC_CP_HELM, 0}; + int i; + + if (rnd()%100 >= rate) + return 0; + + sc = status_get_sc(bl); + if (!sc || sc->option&OPTION_MADOGEAR ) //Mado Gear cannot be divested [Ind] + return 0; + + for (i = 0; i < ARRAYLENGTH(pos); i++) { + if (where&pos[i] && sc->data[sc_def[i]]) + where&=~pos[i]; + } + if (!where) return 0; + + for (i = 0; i < ARRAYLENGTH(pos); i++) { + if (where&pos[i] && !sc_start(bl, sc_atk[i], 100, lv, time)) + where&=~pos[i]; + } + return where?1:0; +} +//Early declaration +static int skill_area_temp[8]; +/*========================================================================= + Used to knock back players, monsters, traps, etc + - 'count' is the number of squares to knock back + - 'direction' indicates the way OPPOSITE to the knockback direction (or -1 for default behavior) + - if 'flag&0x1', position update packets must not be sent. + - if 'flag&0x2', skill blown ignores players' special_state.no_knockback + -------------------------------------------------------------------------*/ +int skill_blown(struct block_list* src, struct block_list* target, int count, int8 dir, int flag) +{ + int dx = 0, dy = 0; + struct skill_unit* su = NULL; + + nullpo_ret(src); + + if (src != target && (map_flag_gvg(target->m) || map[target->m].flag.battleground)) + return 0; //No knocking back in WoE + if (count == 0) + return 0; //Actual knockback distance is 0. + + switch (target->type) { + case BL_MOB: { + struct mob_data* md = BL_CAST(BL_MOB, target); + if( md->class_ == MOBID_EMPERIUM ) + return 0; + if(src != target && is_boss(target)) //Bosses can't be knocked-back + return 0; + } + break; + case BL_PC: { + struct map_session_data *sd = BL_CAST(BL_PC, target); + if( sd->sc.data[SC_BASILICA] && sd->sc.data[SC_BASILICA]->val4 == sd->bl.id && !is_boss(src)) + return 0; // Basilica caster can't be knocked-back by normal monsters. + if( !(flag&0x2) && src != target && sd->special_state.no_knockback ) + return 0; + } + break; + case BL_SKILL: + su = (struct skill_unit *)target; + if( su && su->group && su->group->unit_id == UNT_ANKLESNARE ) + return 0; // ankle snare cannot be knocked back + break; + } + + if (dir == -1) // <optimized>: do the computation here instead of outside + dir = map_calc_dir(target, src->x, src->y); // direction from src to target, reversed + + if (dir >= 0 && dir < 8) + { // take the reversed 'direction' and reverse it + dx = -dirx[dir]; + dy = -diry[dir]; + } + + return unit_blown(target, dx, dy, count, flag); // send over the proper flag +} + + +//Checks if 'bl' should reflect back a spell cast by 'src'. +//type is the type of magic attack: 0: indirect (aoe), 1: direct (targetted) +static int skill_magic_reflect(struct block_list* src, struct block_list* bl, int type) +{ + struct status_change *sc = status_get_sc(bl); + struct map_session_data* sd = BL_CAST(BL_PC, bl); + + if( sc && sc->data[SC_KYOMU] ) // Nullify reflecting ability + return 0; + + // item-based reflection + if( sd && sd->bonus.magic_damage_return && type && rnd()%100 < sd->bonus.magic_damage_return ) + return 1; + + if( is_boss(src) ) + return 0; + + // status-based reflection + if( !sc || sc->count == 0 ) + return 0; + + if( sc->data[SC_MAGICMIRROR] && rnd()%100 < sc->data[SC_MAGICMIRROR]->val2 ) + return 1; + + if( sc->data[SC_KAITE] && (src->type == BL_PC || status_get_lv(src) <= 80) ) + {// Kaite only works against non-players if they are low-level. + clif_specialeffect(bl, 438, AREA); + if( --sc->data[SC_KAITE]->val2 <= 0 ) + status_change_end(bl, SC_KAITE, INVALID_TIMER); + return 2; + } + + return 0; +} + +/* + * ========================================================================= + * Does a skill attack with the given properties. + * src is the master behind the attack (player/mob/pet) + * dsrc is the actual originator of the damage, can be the same as src, or a BL_SKILL + * bl is the target to be attacked. + * flag can hold a bunch of information: + * flag&0xFFF is passed to the underlying battle_calc_attack for processing + * (usually holds number of targets, or just 1 for simple splash attacks) + * flag&0x1000 is used to tag that this is a splash-attack (so the damage + * packet shouldn't display a skill animation) + * flag&0x2000 is used to signal that the skill_lv should be passed as -1 to the + * client (causes player characters to not scream skill name) + *-------------------------------------------------------------------------*/ +int skill_attack (int attack_type, struct block_list* src, struct block_list *dsrc, struct block_list *bl, uint16 skill_id, uint16 skill_lv, unsigned int tick, int flag) +{ + struct Damage dmg; + struct status_data *sstatus, *tstatus; + struct status_change *sc; + struct map_session_data *sd, *tsd; + int type,damage,rdamage=0; + int8 rmdamage=0;//magic reflected + + if(skill_id > 0 && !skill_lv) return 0; + + nullpo_ret(src); //Source is the master behind the attack (player/mob/pet) + nullpo_ret(dsrc); //dsrc is the actual originator of the damage, can be the same as src, or a skill casted by src. + nullpo_ret(bl); //Target to be attacked. + + if (src != dsrc) { + //When caster is not the src of attack, this is a ground skill, and as such, do the relevant target checking. [Skotlex] + if (!status_check_skilluse(battle_config.skill_caster_check?src:NULL, bl, skill_id, 2)) + return 0; + } else if ((flag&SD_ANIMATION) && skill_get_nk(skill_id)&NK_SPLASH) { + //Note that splash attacks often only check versus the targetted mob, those around the splash area normally don't get checked for being hidden/cloaked/etc. [Skotlex] + if (!status_check_skilluse(src, bl, skill_id, 2)) + return 0; + } + + sd = BL_CAST(BL_PC, src); + tsd = BL_CAST(BL_PC, bl); + + sstatus = status_get_status_data(src); + tstatus = status_get_status_data(bl); + sc= status_get_sc(bl); + if (sc && !sc->count) sc = NULL; //Don't need it. + + // Is this check really needed? FrostNova won't hurt you if you step right where the caster is? + if(skill_id == WZ_FROSTNOVA && dsrc->x == bl->x && dsrc->y == bl->y) + return 0; + //Trick Dead protects you from damage, but not from buffs and the like, hence it's placed here. + if (sc && sc->data[SC_TRICKDEAD] && !(sstatus->mode&MD_BOSS)) + return 0; + + dmg = battle_calc_attack(attack_type,src,bl,skill_id,skill_lv,flag&0xFFF); + + //Skotlex: Adjusted to the new system + if(src->type==BL_PET) + { // [Valaris] + struct pet_data *pd = (TBL_PET*)src; + if (pd->a_skill && pd->a_skill->div_ && pd->a_skill->id == skill_id) + { + int element = skill_get_ele(skill_id, skill_lv); + /*if (skill_id == -1) Does it ever worked? + element = sstatus->rhw.ele;*/ + if (element != ELE_NEUTRAL || !(battle_config.attack_attr_none&BL_PET)) + dmg.damage=battle_attr_fix(src, bl, skill_lv, element, tstatus->def_ele, tstatus->ele_lv); + else + dmg.damage= skill_lv; + dmg.damage2=0; + dmg.div_= pd->a_skill->div_; + } + } + + if( dmg.flag&BF_MAGIC && ( skill_id != NPC_EARTHQUAKE || (battle_config.eq_single_target_reflectable && (flag&0xFFF) == 1) ) ) + { // Earthquake on multiple targets is not counted as a target skill. [Inkfish] + if( (dmg.damage || dmg.damage2) && (type = skill_magic_reflect(src, bl, src==dsrc)) ) + { //Magic reflection, switch caster/target + struct block_list *tbl = bl; + rmdamage = 1; + bl = src; + src = tbl; + sd = BL_CAST(BL_PC, src); + tsd = BL_CAST(BL_PC, bl); + sc = status_get_sc(bl); + if (sc && !sc->count) + sc = NULL; //Don't need it. + /* bugreport:2564 flag&2 disables double casting trigger */ + flag |= 2; + + //Spirit of Wizard blocks Kaite's reflection + if( type == 2 && sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_WIZARD ) + { //Consume one Fragment per hit of the casted skill? [Skotlex] + type = tsd?pc_search_inventory (tsd, 7321):0; + if (type >= 0) { + if ( tsd ) pc_delitem(tsd, type, 1, 0, 1, LOG_TYPE_CONSUME); + dmg.damage = dmg.damage2 = 0; + dmg.dmg_lv = ATK_MISS; + sc->data[SC_SPIRIT]->val3 = skill_id; + sc->data[SC_SPIRIT]->val4 = dsrc->id; + } + } + + /** + * Official Magic Reflection Behavior : damage reflected depends on gears caster wears, not target + **/ + #if MAGIC_REFLECTION_TYPE + if( dmg.dmg_lv != ATK_MISS )//Wiz SL cancelled and consumed fragment + dmg = battle_calc_attack(BF_MAGIC,bl,bl,skill_id,skill_lv,flag&0xFFF); + #endif + } + if(sc && sc->data[SC_MAGICROD] && src == dsrc) { + int sp = skill_get_sp(skill_id,skill_lv); + dmg.damage = dmg.damage2 = 0; + dmg.dmg_lv = ATK_MISS; //This will prevent skill additional effect from taking effect. [Skotlex] + sp = sp * sc->data[SC_MAGICROD]->val2 / 100; + if(skill_id == WZ_WATERBALL && skill_lv > 1) + sp = sp/((skill_lv|1)*(skill_lv|1)); //Estimate SP cost of a single water-ball + status_heal(bl, 0, sp, 2); + } + } + + damage = dmg.damage + dmg.damage2; + + if( (skill_id == AL_INCAGI || skill_id == AL_BLESSING || + skill_id == CASH_BLESSING || skill_id == CASH_INCAGI || + skill_id == MER_INCAGI || skill_id == MER_BLESSING) && tsd->sc.data[SC_CHANGEUNDEAD] ) + damage = 1; + + if( damage > 0 && (( dmg.flag&BF_WEAPON && src != bl && ( src == dsrc || ( dsrc->type == BL_SKILL && ( skill_id == SG_SUN_WARM || skill_id == SG_MOON_WARM || skill_id == SG_STAR_WARM ) ) )) + || (sc && sc->data[SC_REFLECTDAMAGE])) ) + rdamage = battle_calc_return_damage(bl,src, &damage, dmg.flag, skill_id); + + if( damage && sc && sc->data[SC_GENSOU] && dmg.flag&BF_MAGIC ){ + struct block_list *nbl = NULL; + nbl = battle_getenemyarea(bl,bl->x,bl->y,2,BL_CHAR,bl->id); + if( nbl ){ // Only one target is chosen. + damage = damage / 2; // Deflect half of the damage to a target nearby + clif_skill_damage(bl, nbl, tick, status_get_amotion(src), 0, status_fix_damage(bl,nbl,damage,0), dmg.div_, OB_OBOROGENSOU_TRANSITION_ATK, -1, 6); + } + } + + //Skill hit type + type=(skill_id==0)?5:skill_get_hit(skill_id); + + if(damage < dmg.div_ + //Only skills that knockback even when they miss. [Skotlex] + && skill_id != CH_PALMSTRIKE) + dmg.blewcount = 0; + + if(skill_id == CR_GRANDCROSS||skill_id == NPC_GRANDDARKNESS) { + if(battle_config.gx_disptype) dsrc = src; + if(src == bl) type = 4; + else flag|=SD_ANIMATION; + } + if(skill_id == NJ_TATAMIGAESHI) { + dsrc = src; //For correct knockback. + flag|=SD_ANIMATION; + } + + if(sd) { + int flag = 0; //Used to signal if this skill can be combo'ed later on. + struct status_change_entry *sce; + if ((sce = sd->sc.data[SC_COMBO])) {//End combo state after skill is invoked. [Skotlex] + switch (skill_id) { + case TK_TURNKICK: + case TK_STORMKICK: + case TK_DOWNKICK: + case TK_COUNTER: + if (pc_famerank(sd->status.char_id,MAPID_TAEKWON)) {//Extend combo time. + sce->val1 = skill_id; //Update combo-skill + sce->val3 = skill_id; + if( sce->timer != INVALID_TIMER ) + delete_timer(sce->timer, status_change_timer); + sce->timer = add_timer(tick+sce->val4, status_change_timer, src->id, SC_COMBO); + break; + } + unit_cancel_combo(src); // Cancel combo wait + break; + default: + if( src == dsrc ) // Ground skills are exceptions. [Inkfish] + status_change_end(src, SC_COMBO, INVALID_TIMER); + } + } + switch(skill_id) { + case MO_TRIPLEATTACK: + if (pc_checkskill(sd, MO_CHAINCOMBO) > 0 || pc_checkskill(sd, SR_DRAGONCOMBO) > 0) + flag=1; + break; + case MO_CHAINCOMBO: + if(pc_checkskill(sd, MO_COMBOFINISH) > 0 && sd->spiritball > 0) + flag=1; + break; + case MO_COMBOFINISH: + if (sd->status.party_id>0) //bonus from SG_FRIEND [Komurka] + party_skill_check(sd, sd->status.party_id, MO_COMBOFINISH, skill_lv); + if (pc_checkskill(sd, CH_TIGERFIST) > 0 && sd->spiritball > 0) + flag=1; + case CH_TIGERFIST: + if (!flag && pc_checkskill(sd, CH_CHAINCRUSH) > 0 && sd->spiritball > 1) + flag=1; + case CH_CHAINCRUSH: + if (!flag && pc_checkskill(sd, MO_EXTREMITYFIST) > 0 && sd->spiritball > 0 && sd->sc.data[SC_EXPLOSIONSPIRITS]) + flag=1; + break; + case AC_DOUBLE: + if( (tstatus->race == RC_BRUTE || tstatus->race == RC_INSECT) && pc_checkskill(sd, HT_POWER)) + { + //TODO: This code was taken from Triple Blows, is this even how it should be? [Skotlex] + sc_start2(src,SC_COMBO,100,HT_POWER,bl->id,2000); + clif_combo_delay(src,2000); + } + break; + case TK_COUNTER: + { //bonus from SG_FRIEND [Komurka] + int level; + if(sd->status.party_id>0 && (level = pc_checkskill(sd,SG_FRIEND))) + party_skill_check(sd, sd->status.party_id, TK_COUNTER,level); + } + break; + case SL_STIN: + case SL_STUN: + if (skill_lv >= 7 && !sd->sc.data[SC_SMA]) + sc_start(src,SC_SMA,100,skill_lv,skill_get_time(SL_SMA, skill_lv)); + break; + case GS_FULLBUSTER: + //Can't attack nor use items until skill's delay expires. [Skotlex] + sd->ud.attackabletime = sd->canuseitem_tick = sd->ud.canact_tick; + break; + case SR_DRAGONCOMBO: + if( pc_checkskill(sd, SR_FALLENEMPIRE) > 0 ) + flag = 1; + break; + case SR_FALLENEMPIRE: + if( pc_checkskill(sd, SR_TIGERCANNON) > 0 || pc_checkskill(sd, SR_GATEOFHELL) > 0 ) + flag = 1; + break; + } //Switch End + if (flag) { //Possible to chain + flag = DIFF_TICK(sd->ud.canact_tick, tick); + if (flag < 1) flag = 1; + sc_start2(src,SC_COMBO,100,skill_id,bl->id,flag); + clif_combo_delay(src, flag); + } + } + + //Display damage. + switch( skill_id ) + { + case PA_GOSPEL: //Should look like Holy Cross [Skotlex] + dmg.dmotion = clif_skill_damage(dsrc,bl,tick,dmg.amotion,dmg.dmotion, damage, dmg.div_, CR_HOLYCROSS, -1, 5); + break; + //Skills that need be passed as a normal attack for the client to display correctly. + case HVAN_EXPLOSION: + case NPC_SELFDESTRUCTION: + if(src->type==BL_PC) + dmg.blewcount = 10; + dmg.amotion = 0; //Disable delay or attack will do no damage since source is dead by the time it takes effect. [Skotlex] + // fall through + case KN_AUTOCOUNTER: + case NPC_CRITICALSLASH: + case TF_DOUBLE: + case GS_CHAINACTION: + dmg.dmotion = clif_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,dmg.type,dmg.damage2); + break; + + case AS_SPLASHER: + if( flag&SD_ANIMATION ) // the surrounding targets + dmg.dmotion = clif_skill_damage(dsrc,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, -1, 5); // needs -1 as skill level + else // the central target doesn't display an animation + dmg.dmotion = clif_skill_damage(dsrc,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, -2, 5); // needs -2(!) as skill level + break; + case WL_HELLINFERNO: + case SR_EARTHSHAKER: + dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,1,skill_id,-2,6); + break; + case WL_SOULEXPANSION: + case WL_COMET: + case KO_MUCHANAGE: + case NJ_HUUMA: + dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,skill_id,skill_lv,8); + break; + case WL_CHAINLIGHTNING_ATK: + dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,1,WL_CHAINLIGHTNING,-2,6); + break; + case LG_OVERBRAND_BRANDISH: + case LG_OVERBRAND_PLUSATK: + case EL_FIRE_BOMB: + case EL_FIRE_BOMB_ATK: + case EL_FIRE_WAVE: + case EL_FIRE_WAVE_ATK: + case EL_FIRE_MANTLE: + case EL_CIRCLE_OF_FIRE: + case EL_FIRE_ARROW: + case EL_ICE_NEEDLE: + case EL_WATER_SCREW: + case EL_WATER_SCREW_ATK: + case EL_WIND_SLASH: + case EL_TIDAL_WEAPON: + case EL_ROCK_CRUSHER: + case EL_ROCK_CRUSHER_ATK: + case EL_HURRICANE: + case EL_HURRICANE_ATK: + case KO_BAKURETSU: + case GN_CRAZYWEED_ATK: + dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,skill_id,-1,5); + break; + case GN_SLINGITEM_RANGEMELEEATK: + dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,GN_SLINGITEM,-2,6); + break; + case EL_STONE_RAIN: + dmg.dmotion = clif_skill_damage(dsrc,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,skill_id,-1,(flag&1)?8:5); + break; + case WM_SEVERE_RAINSTORM_MELEE: + dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,WM_SEVERE_RAINSTORM,skill_lv,5); + break; + case WM_REVERBERATION_MELEE: + case WM_REVERBERATION_MAGIC: + dmg.dmotion = clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion,damage,dmg.div_,WM_REVERBERATION,-2,6); + break; + case HT_CLAYMORETRAP: + case HT_BLASTMINE: + case HT_FLASHER: + case HT_FREEZINGTRAP: + case RA_CLUSTERBOMB: + case RA_FIRINGTRAP: + case RA_ICEBOUNDTRAP: + dmg.dmotion = clif_skill_damage(src,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id,flag&SD_LEVEL?-1:skill_lv, 5); + if( dsrc != src ) // avoid damage display redundancy + break; + case HT_LANDMINE: + dmg.dmotion = clif_skill_damage(dsrc,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, -1, type); + break; + case WZ_SIGHTBLASTER: + dmg.dmotion = clif_skill_damage(src,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, flag&SD_LEVEL?-1:skill_lv, 5); + break; + case AB_DUPLELIGHT_MELEE: + case AB_DUPLELIGHT_MAGIC: + dmg.amotion = 300;/* makes the damage value not overlap with previous damage (when displayed by the client) */ + default: + if( flag&SD_ANIMATION && dmg.div_ < 2 ) //Disabling skill animation doesn't works on multi-hit. + type = 5; + if( bl->type == BL_SKILL ){ + TBL_SKILL *su = (TBL_SKILL*)bl; + if( su->group && skill_get_inf2(su->group->skill_id)&INF2_TRAP )// show damage on trap targets + clif_skill_damage(src,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, flag&SD_LEVEL?-1:skill_lv, 5); + } + dmg.dmotion = clif_skill_damage(dsrc,bl,tick, dmg.amotion, dmg.dmotion, damage, dmg.div_, skill_id, flag&SD_LEVEL?-1:skill_lv, type); + break; + } + + map_freeblock_lock(); + + if(damage > 0 && dmg.flag&BF_SKILL && tsd + && pc_checkskill(tsd,RG_PLAGIARISM) + && (!sc || !sc->data[SC_PRESERVE]) + && damage < tsd->battle_status.hp) + { //Updated to not be able to copy skills if the blow will kill you. [Skotlex] + int copy_skill = skill_id; + /** + * Copy Referal: dummy skills should point to their source upon copying + **/ + switch( skill_id ) { + case AB_DUPLELIGHT_MELEE: + case AB_DUPLELIGHT_MAGIC: + copy_skill = AB_DUPLELIGHT; + break; + case WL_CHAINLIGHTNING_ATK: + copy_skill = WL_CHAINLIGHTNING; + break; + case WM_REVERBERATION_MELEE: + case WM_REVERBERATION_MAGIC: + copy_skill = WM_REVERBERATION; + break; + case WM_SEVERE_RAINSTORM_MELEE: + copy_skill = WM_SEVERE_RAINSTORM; + break; + case GN_CRAZYWEED_ATK: + copy_skill = GN_CRAZYWEED; + break; + case GN_HELLS_PLANT_ATK: + copy_skill = GN_HELLS_PLANT; + break; + case LG_OVERBRAND_BRANDISH: + case LG_OVERBRAND_PLUSATK: + copy_skill = LG_OVERBRAND; + break; + } + + if ((tsd->status.skill[copy_skill].id == 0 || tsd->status.skill[copy_skill].flag == SKILL_FLAG_PLAGIARIZED) && + can_copy(tsd,copy_skill,bl)) // Split all the check into their own function [Aru] + { + int lv; + if( sc && sc->data[SC__REPRODUCE] && (lv = sc->data[SC__REPRODUCE]->val1) ) { + //Level dependent and limitation. + lv = min(lv,skill_get_max(copy_skill)); + if( tsd->reproduceskill_id && tsd->status.skill[tsd->reproduceskill_id].flag == SKILL_FLAG_PLAGIARIZED ) { + tsd->status.skill[tsd->reproduceskill_id].id = 0; + tsd->status.skill[tsd->reproduceskill_id].lv = 0; + tsd->status.skill[tsd->reproduceskill_id].flag = 0; + clif_deleteskill(tsd,tsd->reproduceskill_id); + } + + tsd->reproduceskill_id = copy_skill; + pc_setglobalreg(tsd, "REPRODUCE_SKILL", copy_skill); + pc_setglobalreg(tsd, "REPRODUCE_SKILL_LV", lv); + + tsd->status.skill[copy_skill].id = copy_skill; + tsd->status.skill[copy_skill].lv = lv; + tsd->status.skill[copy_skill].flag = SKILL_FLAG_PLAGIARIZED; + clif_addskill(tsd,copy_skill); + } else { + lv = skill_lv; + if (tsd->cloneskill_id && tsd->status.skill[tsd->cloneskill_id].flag == SKILL_FLAG_PLAGIARIZED){ + tsd->status.skill[tsd->cloneskill_id].id = 0; + tsd->status.skill[tsd->cloneskill_id].lv = 0; + tsd->status.skill[tsd->cloneskill_id].flag = 0; + clif_deleteskill(tsd,tsd->cloneskill_id); + } + + if ((type = pc_checkskill(tsd,RG_PLAGIARISM)) < lv) + lv = type; + + tsd->cloneskill_id = copy_skill; + pc_setglobalreg(tsd, "CLONE_SKILL", copy_skill); + pc_setglobalreg(tsd, "CLONE_SKILL_LV", lv); + + tsd->status.skill[skill_id].id = copy_skill; + tsd->status.skill[skill_id].lv = lv; + tsd->status.skill[skill_id].flag = SKILL_FLAG_PLAGIARIZED; + clif_addskill(tsd,skill_id); + } + } + } + + if (dmg.dmg_lv >= ATK_MISS && (type = skill_get_walkdelay(skill_id, skill_lv)) > 0) + { //Skills with can't walk delay also stop normal attacking for that + //duration when the attack connects. [Skotlex] + struct unit_data *ud = unit_bl2ud(src); + if (ud && DIFF_TICK(ud->attackabletime, tick + type) < 0) + ud->attackabletime = tick + type; + } + + if( !dmg.amotion ) + { //Instant damage + if( !sc || (!sc->data[SC_DEVOTION] && skill_id != CR_REFLECTSHIELD) ) + status_fix_damage(src,bl,damage,dmg.dmotion); //Deal damage before knockback to allow stuff like firewall+storm gust combo. + if( !status_isdead(bl) ) + skill_additional_effect(src,bl,skill_id,skill_lv,dmg.flag,dmg.dmg_lv,tick); + if( damage > 0 ) //Counter status effects [Skotlex] + skill_counter_additional_effect(src,bl,skill_id,skill_lv,dmg.flag,tick); + } + // Hell Inferno burning status only starts if Fire part hits. + if( skill_id == WL_HELLINFERNO && dmg.damage > 0 ) + sc_start4(bl,SC_BURNING,55+5*skill_lv,skill_lv,1000,src->id,0,skill_get_time(skill_id,skill_lv)); + // Apply knock back chance in SC_TRIANGLESHOT skill. + else if( skill_id == SC_TRIANGLESHOT && rnd()%100 > (1 + skill_lv) ) + dmg.blewcount = 0; + + //Only knockback if it's still alive, otherwise a "ghost" is left behind. [Skotlex] + //Reflected spells do not bounce back (bl == dsrc since it only happens for direct skills) + if (dmg.blewcount > 0 && bl!=dsrc && !status_isdead(bl)) { + int8 dir = -1; // default + switch(skill_id) {//direction + case MG_FIREWALL: + case PR_SANCTUARY: + case SC_TRIANGLESHOT: + case LG_OVERBRAND: + case SR_KNUCKLEARROW: + case GN_WALLOFTHORN: + case EL_FIRE_MANTLE: + dir = unit_getdir(bl);// backwards + break; + // This ensures the storm randomly pushes instead of exactly a cell backwards per official mechanics. + case WZ_STORMGUST: + dir = rand()%8; + break; + case WL_CRIMSONROCK: + dir = map_calc_dir(bl,skill_area_temp[4],skill_area_temp[5]); + break; + + } + //blown-specific handling + switch( skill_id ) { + case LG_OVERBRAND: + if( skill_blown(dsrc,bl,dmg.blewcount,dir,0) ) { + short dir_x, dir_y; + dir_x = dirx[(dir+4)%8]; + dir_y = diry[(dir+4)%8]; + if( map_getcell(bl->m, bl->x+dir_x, bl->y+dir_y, CELL_CHKNOPASS) != 0 ) + skill_addtimerskill(src, tick + status_get_amotion(src), bl->id, 0, 0, LG_OVERBRAND_PLUSATK, skill_lv, BF_WEAPON, flag ); + } else + skill_addtimerskill(src, tick + status_get_amotion(src), bl->id, 0, 0, LG_OVERBRAND_PLUSATK, skill_lv, BF_WEAPON, flag ); + break; + case SR_KNUCKLEARROW: + if( skill_blown(dsrc,bl,dmg.blewcount,dir,0) && !(flag&4) ) { + short dir_x, dir_y; + dir_x = dirx[(dir+4)%8]; + dir_y = diry[(dir+4)%8]; + if( map_getcell(bl->m, bl->x+dir_x, bl->y+dir_y, CELL_CHKNOPASS) != 0 ) + skill_addtimerskill(src, tick + 300 * ((flag&2) ? 1 : 2), bl->id, 0, 0, skill_id, skill_lv, BF_WEAPON, flag|4); + } + break; + case GN_WALLOFTHORN: + unit_stop_walking(bl,1); + skill_blown(dsrc,bl,dmg.blewcount,dir, 0x2 ); + clif_fixpos(bl); + break; + default: + skill_blown(dsrc,bl,dmg.blewcount,dir, 0x0 ); + if ( !dmg.blewcount && bl->type == BL_SKILL && damage > 0 ){ + TBL_SKILL *su = (TBL_SKILL*)bl; + if( su->group && su->group->skill_id == HT_BLASTMINE) + skill_blown(src, bl, 3, -1, 0); + } + break; + } + } + + //Delayed damage must be dealt after the knockback (it needs to know actual position of target) + if (dmg.amotion) + battle_delay_damage(tick, dmg.amotion,src,bl,dmg.flag,skill_id,skill_lv,damage,dmg.dmg_lv,dmg.dmotion); + + if( sc && sc->data[SC_DEVOTION] && skill_id != PA_PRESSURE ) + { + struct status_change_entry *sce = sc->data[SC_DEVOTION]; + struct block_list *d_bl = map_id2bl(sce->val1); + + if( d_bl && ( + (d_bl->type == BL_MER && ((TBL_MER*)d_bl)->master && ((TBL_MER*)d_bl)->master->bl.id == bl->id) || + (d_bl->type == BL_PC && ((TBL_PC*)d_bl)->devotion[sce->val2] == bl->id) + ) && check_distance_bl(bl, d_bl, sce->val3) ) + { + if(!rmdamage){ + clif_damage(d_bl,d_bl, gettick(), 0, 0, damage, 0, 0, 0); + status_fix_damage(NULL,d_bl, damage, 0); + } + else{//Reflected magics are done directly on the target not on paladin + //This check is only for magical skill. + //For BF_WEAPON skills types track var rdamage and function battle_calc_return_damage + clif_damage(bl,bl, gettick(), 0, 0, damage, 0, 0, 0); + status_fix_damage(bl,bl, damage, 0); + } + } + else { + status_change_end(bl, SC_DEVOTION, INVALID_TIMER); + if( !dmg.amotion ) + status_fix_damage(src,bl,damage,dmg.dmotion); + } + } + + if(damage > 0 && !(tstatus->mode&MD_BOSS)) { + if( skill_id == RG_INTIMIDATE ) { + int rate = 50 + skill_lv * 5; + rate = rate + (status_get_lv(src) - status_get_lv(bl)); + if(rnd()%100 < rate) + skill_addtimerskill(src,tick + 800,bl->id,0,0,skill_id,skill_lv,0,flag); + } else if( skill_id == SC_FATALMENACE ) + skill_addtimerskill(src,tick + 800,bl->id,skill_area_temp[4],skill_area_temp[5],skill_id,skill_lv,0,flag); + } + + if(skill_id == CR_GRANDCROSS || skill_id == NPC_GRANDDARKNESS) + dmg.flag |= BF_WEAPON; + + if( sd && src != bl && damage > 0 && ( dmg.flag&BF_WEAPON || + (dmg.flag&BF_MISC && (skill_id == RA_CLUSTERBOMB || skill_id == RA_FIRINGTRAP || skill_id == RA_ICEBOUNDTRAP || skill_id == RK_DRAGONBREATH)) ) ) + { + if (battle_config.left_cardfix_to_right) + battle_drain(sd, bl, dmg.damage, dmg.damage, tstatus->race, tstatus->mode&MD_BOSS); + else + battle_drain(sd, bl, dmg.damage, dmg.damage2, tstatus->race, tstatus->mode&MD_BOSS); + } + + if( rdamage > 0 ) { + if( sc && sc->data[SC_REFLECTDAMAGE] ) { + if( src != bl )// Don't reflect your own damage (Grand Cross) + map_foreachinshootrange(battle_damage_area,bl,skill_get_splash(LG_REFLECTDAMAGE,1),BL_CHAR,tick,bl,dmg.amotion,sstatus->dmotion,rdamage,tstatus->race); + } else { + if( dmg.amotion ) + battle_delay_damage(tick, dmg.amotion,bl,src,0,CR_REFLECTSHIELD,0,rdamage,ATK_DEF,0); + else + status_fix_damage(bl,src,rdamage,0); + clif_damage(src,src,tick, dmg.amotion,0,rdamage,1,4,0); // in aegis damage reflected is shown in single hit. + //Use Reflect Shield to signal this kind of skill trigger. [Skotlex] + if( tsd && src != bl ) + battle_drain(tsd, src, rdamage, rdamage, sstatus->race, is_boss(src)); + skill_additional_effect(bl, src, CR_REFLECTSHIELD, 1, BF_WEAPON|BF_SHORT|BF_NORMAL,ATK_DEF,tick); + } + } + if( damage > 0 ) { + /** + * Post-damage effects + **/ + switch( skill_id ) { + case RK_CRUSHSTRIKE: + skill_break_equip(src,EQP_WEAPON,2000,BCT_SELF); // 20% chance to destroy the weapon. + break; + case GC_VENOMPRESSURE: { + struct status_change *ssc = status_get_sc(src); + if( ssc && ssc->data[SC_POISONINGWEAPON] && rnd()%100 < 70 + 5*skill_lv ) { + sc_start(bl,ssc->data[SC_POISONINGWEAPON]->val2,100,ssc->data[SC_POISONINGWEAPON]->val1,skill_get_time2(GC_POISONINGWEAPON, 1)); + status_change_end(src,SC_POISONINGWEAPON,INVALID_TIMER); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + } + break; + case WM_METALICSOUND: + status_zap(bl, 0, damage*100/(100*(110-pc_checkskill(sd,WM_LESSON)*10))); + break; + case SR_TIGERCANNON: + status_zap(bl, 0, damage/10); // 10% of damage dealt + break; + } + if( sd ) + skill_onskillusage(sd, bl, skill_id, tick); + } + + if (!(flag&2) && + ( + skill_id == MG_COLDBOLT || skill_id == MG_FIREBOLT || skill_id == MG_LIGHTNINGBOLT + ) && + (sc = status_get_sc(src)) && + sc->data[SC_DOUBLECAST] && + rnd() % 100 < sc->data[SC_DOUBLECAST]->val2) + { +// skill_addtimerskill(src, tick + dmg.div_*dmg.amotion, bl->id, 0, 0, skill_id, skill_lv, BF_MAGIC, flag|2); + skill_addtimerskill(src, tick + dmg.amotion, bl->id, 0, 0, skill_id, skill_lv, BF_MAGIC, flag|2); + } + + map_freeblock_unlock(); + + return damage; +} + +/*========================================== + * sub fonction for recursive skill call. + * Checking bl battle flag and display dammage + * then call func with source,target,skill_id,skill_lv,tick,flag + *------------------------------------------*/ +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; + uint16 skill_id,skill_lv; + int flag; + unsigned int tick; + SkillFunc func; + + nullpo_ret(bl); + + src=va_arg(ap,struct block_list *); + 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) + { + // several splash skills need this initial dummy packet to display correctly + if (flag&SD_PREAMBLE && skill_area_temp[2] == 0) + clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + + if (flag&(SD_SPLASH|SD_PREAMBLE)) + skill_area_temp[2]++; + + return 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; + uint16 skill_id,g_skill_id; + + unit = (struct skill_unit *)bl; + + if(bl->prev == NULL || bl->type != BL_SKILL) + return 0; + + if(!unit->alive) + return 0; + + skill_id = va_arg(ap,int); + g_skill_id = unit->group->skill_id; + + switch (skill_id) { + case MH_STEINWAND: + case MG_SAFETYWALL: + case AL_PNEUMA: + case SC_MAELSTROM: + if(g_skill_id != MH_STEINWAND && g_skill_id != MG_SAFETYWALL && g_skill_id != AL_PNEUMA && g_skill_id != SC_MAELSTROM) + return 0; + break; + case AL_WARP: + case HT_SKIDTRAP: + case MA_SKIDTRAP: + case HT_LANDMINE: + case MA_LANDMINE: + case HT_ANKLESNARE: + case HT_SHOCKWAVE: + case HT_SANDMAN: + case MA_SANDMAN: + case HT_FLASHER: + case HT_FREEZINGTRAP: + case MA_FREEZINGTRAP: + case HT_BLASTMINE: + case HT_CLAYMORETRAP: + case HT_TALKIEBOX: + case HP_BASILICA: + case RA_ELECTRICSHOCKER: + case RA_CLUSTERBOMB: + case RA_MAGENTATRAP: + case RA_COBALTTRAP: + case RA_MAIZETRAP: + case RA_VERDURETRAP: + case RA_FIRINGTRAP: + case RA_ICEBOUNDTRAP: + case SC_DIMENSIONDOOR: + case SC_BLOODYLUST: + //Non stackable on themselves and traps (including venom dust which does not has the trap inf2 set) + if (skill_id != g_skill_id && !(skill_get_inf2(g_skill_id)&INF2_TRAP) && g_skill_id != AS_VENOMDUST && g_skill_id != MH_POISON_MIST) + return 0; + break; + default: //Avoid stacking with same kind of trap. [Skotlex] + if (g_skill_id != skill_id) + return 0; + break; + } + + return 1; +} + +static int skill_check_unit_range (struct block_list *bl, int x, int y, uint16 skill_id, uint16 skill_lv) +{ + //Non players do not check for the skill's splash-trigger area. + int range = bl->type==BL_PC?skill_get_unit_range(skill_id, skill_lv):0; + int layout_type = skill_get_unit_layout_type(skill_id,skill_lv); + if (layout_type==-1 || layout_type>MAX_SQUARE_LAYOUT) { + ShowError("skill_check_unit_range: unsupported layout type %d for skill %d\n",layout_type,skill_id); + return 0; + } + + range += layout_type; + return map_foreachinarea(skill_check_unit_range_sub,bl->m,x-range,y-range,x+range,y+range,BL_SKILL,skill_id); +} + +static int skill_check_unit_range2_sub (struct block_list *bl, va_list ap) +{ + uint16 skill_id; + + if(bl->prev == NULL) + return 0; + + skill_id = va_arg(ap,int); + + if( status_isdead(bl) && skill_id != AL_WARP ) + return 0; + + if( skill_id == HP_BASILICA && bl->type == BL_PC ) + return 0; + + if( skill_id == AM_DEMONSTRATION && bl->type == BL_MOB && ((TBL_MOB*)bl)->class_ == MOBID_EMPERIUM ) + return 0; //Allow casting Bomb/Demonstration Right under emperium [Skotlex] + return 1; +} + +static int skill_check_unit_range2 (struct block_list *bl, int x, int y, uint16 skill_id, uint16 skill_lv) +{ + int range, type; + + switch (skill_id) { // to be expanded later + case WZ_ICEWALL: + range = 2; + break; + default: + { + int layout_type = skill_get_unit_layout_type(skill_id,skill_lv); + if (layout_type==-1 || layout_type>MAX_SQUARE_LAYOUT) { + ShowError("skill_check_unit_range2: unsupported layout type %d for skill %d\n",layout_type,skill_id); + return 0; + } + range = skill_get_unit_range(skill_id,skill_lv) + layout_type; + } + break; + } + + // if the caster is a monster/NPC, only check for players + // otherwise just check characters + if (bl->type == BL_PC) + type = BL_CHAR; + else + type = BL_PC; + + return map_foreachinarea(skill_check_unit_range2_sub, bl->m, + x - range, y - range, x + range, y + range, + type, skill_id); +} + +int skill_guildaura_sub (struct map_session_data* sd, int id, int strvit, int agidex) +{ + if(id == sd->bl.id && battle_config.guild_aura&16) + return 0; // Do not affect guild leader + + if (sd->sc.data[SC_GUILDAURA]) { + struct status_change_entry *sce = sd->sc.data[SC_GUILDAURA]; + if( sce->val3 != strvit || sce->val4 != agidex ) { + sce->val3 = strvit; + sce->val4 = agidex; + status_calc_bl(&sd->bl, status_sc2scb_flag(SC_GUILDAURA)); + } + return 0; + } + sc_start4(&sd->bl, SC_GUILDAURA,100, 1, id, strvit, agidex, 1000); + return 1; +} + +/*========================================== + * Checks that you have the requirements for casting a skill for homunculus/mercenary. + * Flag: + * &1: finished casting the skill (invoke hp/sp/item consumption) + * &2: picked menu entry (Warp Portal, Teleport and other menu based skills) + *------------------------------------------*/ +static int skill_check_condition_mercenary(struct block_list *bl, int skill, int lv, int type) +{ + struct status_data *status; + struct map_session_data *sd = NULL; + int i, hp, sp, hp_rate, sp_rate, state, mhp; + uint16 idx; + int itemid[MAX_SKILL_ITEM_REQUIRE],amount[ARRAYLENGTH(itemid)],index[ARRAYLENGTH(itemid)]; + + if( lv < 1 || lv > MAX_SKILL_LEVEL ) + return 0; + nullpo_ret(bl); + + switch( bl->type ) + { + case BL_HOM: sd = ((TBL_HOM*)bl)->master; break; + case BL_MER: sd = ((TBL_MER*)bl)->master; break; + } + + status = status_get_status_data(bl); + if( (idx = skill_get_index(skill)) == 0 ) + return 0; + + // Requeriments + for( i = 0; i < ARRAYLENGTH(itemid); i++ ) + { + itemid[i] = skill_db[idx].itemid[i]; + amount[i] = skill_db[idx].amount[i]; + } + hp = skill_db[idx].hp[lv-1]; + sp = skill_db[idx].sp[lv-1]; + hp_rate = skill_db[idx].hp_rate[lv-1]; + sp_rate = skill_db[idx].sp_rate[lv-1]; + state = skill_db[idx].state; + if( (mhp = skill_db[idx].mhp[lv-1]) > 0 ) + hp += (status->max_hp * mhp) / 100; + if( hp_rate > 0 ) + hp += (status->hp * hp_rate) / 100; + else + hp += (status->max_hp * (-hp_rate)) / 100; + if( sp_rate > 0 ) + sp += (status->sp * sp_rate) / 100; + else + sp += (status->max_sp * (-sp_rate)) / 100; + + if( bl->type == BL_HOM ) + { // Intimacy Requeriments + struct homun_data *hd = BL_CAST(BL_HOM, bl); + switch( skill ) + { + case HFLI_SBR44: + if( hd->homunculus.intimacy <= 200 ) + return 0; + break; + case HVAN_EXPLOSION: + if( hd->homunculus.intimacy < (unsigned int)battle_config.hvan_explosion_intimate ) + return 0; + break; + } + } + + if( !(type&2) ) + { + if( hp > 0 && status->hp <= (unsigned int)hp ) + { + clif_skill_fail(sd, skill, USESKILL_FAIL_HP_INSUFFICIENT, 0); + return 0; + } + if( sp > 0 && status->sp <= (unsigned int)sp ) + { + clif_skill_fail(sd, skill, USESKILL_FAIL_SP_INSUFFICIENT, 0); + return 0; + } + } + + if( !type ) + switch( state ) + { + case ST_MOVE_ENABLE: + if( !unit_can_move(bl) ) + { + clif_skill_fail(sd, skill, USESKILL_FAIL_LEVEL, 0); + return 0; + } + break; + } + if( !(type&1) ) + return 1; + + // Check item existences + for( i = 0; i < ARRAYLENGTH(itemid); i++ ) + { + index[i] = -1; + if( itemid[i] < 1 ) continue; // No item + index[i] = pc_search_inventory(sd, itemid[i]); + if( index[i] < 0 || sd->status.inventory[index[i]].amount < amount[i] ) + { + clif_skill_fail(sd, skill, USESKILL_FAIL_LEVEL, 0); + return 0; + } + } + + // Consume items + for( i = 0; i < ARRAYLENGTH(itemid); i++ ) + { + if( index[i] >= 0 ) pc_delitem(sd, index[i], amount[i], 0, 1, LOG_TYPE_CONSUME); + } + + if( type&2 ) + return 1; + + if( sp || hp ) + status_zap(bl, hp, sp); + + return 1; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_area_sub_count (struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv, unsigned int tick, int flag) +{ + return 1; +} + +/*========================================== + * + *------------------------------------------*/ +static int skill_timerskill(int tid, unsigned int tick, int id, intptr_t data) +{ + struct block_list *src = map_id2bl(id),*target; + struct unit_data *ud = unit_bl2ud(src); + struct skill_timerskill *skl = NULL; + int range; + + nullpo_ret(src); + nullpo_ret(ud); + skl = ud->skilltimerskill[data]; + nullpo_ret(skl); + ud->skilltimerskill[data] = NULL; + + do { + if(src->prev == NULL) + break; // Source not on Map + if(skl->target_id) { + target = map_id2bl(skl->target_id); + if( ( skl->skill_id == RG_INTIMIDATE || skl->skill_id == SC_FATALMENACE ) && (!target || target->prev == NULL || !check_distance_bl(src,target,AREA_SIZE)) ) + target = src; //Required since it has to warp. + if(target == NULL) + break; // Target offline? + if(target->prev == NULL) + break; // Target not on Map + if(src->m != target->m) + break; // Different Maps + if(status_isdead(src)) + break; // Caster is Dead + if(status_isdead(target) && skl->skill_id != RG_INTIMIDATE && skl->skill_id != WZ_WATERBALL) + break; + + switch(skl->skill_id) { + case RG_INTIMIDATE: + if (unit_warp(src,-1,-1,-1,CLR_TELEPORT) == 0) { + short x,y; + map_search_freecell(src, 0, &x, &y, 1, 1, 0); + if (target != src && !status_isdead(target)) + unit_warp(target, -1, x, y, CLR_TELEPORT); + } + break; + case BA_FROSTJOKER: + case DC_SCREAM: + range= skill_get_splash(skl->skill_id, skl->skill_lv); + map_foreachinarea(skill_frostjoke_scream,skl->map,skl->x-range,skl->y-range, + skl->x+range,skl->y+range,BL_CHAR,src,skl->skill_id,skl->skill_lv,tick); + break; + case NPC_EARTHQUAKE: + if( skl->type > 1 ) + skill_addtimerskill(src,tick+250,src->id,0,0,skl->skill_id,skl->skill_lv,skl->type-1,skl->flag); + skill_area_temp[0] = map_foreachinrange(skill_area_sub, src, skill_get_splash(skl->skill_id, skl->skill_lv), BL_CHAR, src, skl->skill_id, skl->skill_lv, tick, BCT_ENEMY, skill_area_sub_count); + skill_area_temp[1] = src->id; + skill_area_temp[2] = 0; + map_foreachinrange(skill_area_sub, src, skill_get_splash(skl->skill_id, skl->skill_lv), splash_target(src), src, skl->skill_id, skl->skill_lv, tick, skl->flag, skill_castend_damage_id); + break; + case WZ_WATERBALL: + skill_toggle_magicpower(src, skl->skill_id); // only the first hit will be amplify + if (!status_isdead(target)) + skill_attack(BF_MAGIC,src,src,target,skl->skill_id,skl->skill_lv,tick,skl->flag); + if (skl->type>1 && !status_isdead(target) && !status_isdead(src)) { + skill_addtimerskill(src,tick+125,target->id,0,0,skl->skill_id,skl->skill_lv,skl->type-1,skl->flag); + } else { + struct status_change *sc = status_get_sc(src); + if(sc) { + if(sc->data[SC_SPIRIT] && + sc->data[SC_SPIRIT]->val2 == SL_WIZARD && + sc->data[SC_SPIRIT]->val3 == skl->skill_id) + sc->data[SC_SPIRIT]->val3 = 0; //Clear bounced spell check. + } + } + break; + /** + * Warlock + **/ + case WL_CHAINLIGHTNING_ATK: + { + struct block_list *nbl = NULL; // Next Target of Chain + skill_attack(BF_MAGIC,src,src,target,skl->skill_id,skl->skill_lv,tick,skl->flag); // Hit a Lightning on the current Target + skill_toggle_magicpower(src, skl->skill_id); // only the first hit will be amplify + if( skl->type > 1 ) + { // Remaining Chains Hit + nbl = battle_getenemyarea(src,target->x,target->y,2,BL_CHAR|BL_SKILL,target->id); // Search for a new Target around current one... + if( nbl == NULL && skl->x > 1 ) + { + nbl = target; + skl->x--; + } + else skl->x = 3; + } + + if( nbl ) + skill_addtimerskill(src,tick+status_get_adelay(src),nbl->id,skl->x,0,WL_CHAINLIGHTNING_ATK,skl->skill_lv,skl->type-1,skl->flag); + } + break; + case WL_TETRAVORTEX_FIRE: + case WL_TETRAVORTEX_WATER: + case WL_TETRAVORTEX_WIND: + case WL_TETRAVORTEX_GROUND: + skill_attack(BF_MAGIC,src,src,target,skl->skill_id,skl->skill_lv,tick,skl->flag|SD_ANIMATION); + skill_toggle_magicpower(src, skl->skill_id); // only the first hit will be amplify + if( skl->type >= 3 ) + { // Final Hit + if( !status_isdead(target) ) + { // Final Status Effect + int effects[4] = { SC_BURNING, SC_FREEZING, SC_BLEEDING, SC_STUN }, + applyeffects[4] = { 0, 0, 0, 0 }, + i, j = 0, k = 0; + for( i = 1; i <= 8; i = i + i ) + { + if( skl->x&i ) + { + applyeffects[j] = effects[k]; + j++; + } + k++; + } + if( j ) + { + i = applyeffects[rnd()%j]; + status_change_start(target, i, 10000, skl->skill_lv, + (i == SC_BURNING ? 1000 : 0), + (i == SC_BURNING ? src->id : 0), + 0, skill_get_time(WL_TETRAVORTEX,skl->skill_lv), 0); + } + } + } + break; + case WM_REVERBERATION_MELEE: + case WM_REVERBERATION_MAGIC: + skill_castend_damage_id(src, target, skl->skill_id, skl->skill_lv, tick, skl->flag|SD_LEVEL); // damage should split among targets + break; + case SC_FATALMENACE: + if( src == target ) // Casters Part + unit_warp(src, -1, skl->x, skl->y, 3); + else { // Target's Part + short x = skl->x, y = skl->y; + map_search_freecell(NULL, target->m, &x, &y, 2, 2, 1); + unit_warp(target,-1,x,y,3); + } + break; + case LG_MOONSLASHER: + case SR_WINDMILL: + if( target->type == BL_PC ) { + struct map_session_data *tsd = NULL; + if( (tsd = ((TBL_PC*)target)) && !pc_issit(tsd) ) { + pc_setsit(tsd); + skill_sit(tsd,1); + clif_sitting(&tsd->bl); + } + } + break; + case LG_OVERBRAND_BRANDISH: + case LG_OVERBRAND_PLUSATK: + case SR_KNUCKLEARROW: + skill_attack(BF_WEAPON, src, src, target, skl->skill_id, skl->skill_lv, tick, skl->flag|SD_LEVEL); + break; + case GN_SPORE_EXPLOSION: + map_foreachinrange(skill_area_sub, target, skill_get_splash(skl->skill_id, skl->skill_lv), BL_CHAR, + src, skl->skill_id, skl->skill_lv, 0, skl->flag|1|BCT_ENEMY, skill_castend_damage_id); + break; + case CH_PALMSTRIKE: + { + struct status_change* tsc = status_get_sc(target); + struct status_change* sc = status_get_sc(src); + if( tsc && tsc->option&OPTION_HIDE || + sc && sc->option&OPTION_HIDE ){ + skill_blown(src,target,skill_get_blewcount(skl->skill_id, skl->skill_lv), -1, 0x0 ); + 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) + break; + switch( skl->skill_id ) + { + case WZ_METEOR: + if( skl->type >= 0 ) + { + int x = skl->type>>16, y = skl->type&0xFFFF; + if( path_search_long(NULL, src->m, src->x, src->y, x, y, CELL_CHKWALL) ) + skill_unitsetting(src,skl->skill_id,skl->skill_lv,x,y,skl->flag); + if( path_search_long(NULL, src->m, src->x, src->y, skl->x, skl->y, CELL_CHKWALL) ) + clif_skill_poseffect(src,skl->skill_id,skl->skill_lv,skl->x,skl->y,tick); + } + else if( path_search_long(NULL, src->m, src->x, src->y, skl->x, skl->y, CELL_CHKWALL) ) + skill_unitsetting(src,skl->skill_id,skl->skill_lv,skl->x,skl->y,skl->flag); + break; + case GN_CRAZYWEED_ATK: + { + int dummy = 1, i = skill_get_unit_range(skl->skill_id,skl->skill_lv); + map_foreachinarea(skill_cell_overlap, src->m, skl->x-i, skl->y-i, skl->x+i, skl->y+i, BL_SKILL, skl->skill_id, &dummy, src); + } + case WL_EARTHSTRAIN: + skill_unitsetting(src,skl->skill_id,skl->skill_lv,skl->x,skl->y,(skl->type<<16)|skl->flag); + break; + + } + } + } while (0); + //Free skl now that it is no longer needed. + ers_free(skill_timer_ers, skl); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_addtimerskill (struct block_list *src, unsigned int tick, int target, int x,int y, uint16 skill_id, uint16 skill_lv, int type, int flag) +{ + int i; + struct unit_data *ud; + nullpo_retr(1, src); + if (src->prev == NULL) + return 0; + ud = unit_bl2ud(src); + nullpo_retr(1, ud); + + ARR_FIND( 0, MAX_SKILLTIMERSKILL, i, ud->skilltimerskill[i] == 0 ); + if( i == MAX_SKILLTIMERSKILL ) return 1; + + ud->skilltimerskill[i] = ers_alloc(skill_timer_ers, struct skill_timerskill); + ud->skilltimerskill[i]->timer = add_timer(tick, skill_timerskill, src->id, i); + ud->skilltimerskill[i]->src_id = src->id; + ud->skilltimerskill[i]->target_id = target; + ud->skilltimerskill[i]->skill_id = skill_id; + ud->skilltimerskill[i]->skill_lv = skill_lv; + ud->skilltimerskill[i]->map = src->m; + ud->skilltimerskill[i]->x = x; + ud->skilltimerskill[i]->y = y; + ud->skilltimerskill[i]->type = type; + ud->skilltimerskill[i]->flag = flag; + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_cleartimerskill (struct block_list *src) +{ + int i; + struct unit_data *ud; + nullpo_ret(src); + ud = unit_bl2ud(src); + nullpo_ret(ud); + + for(i=0;i<MAX_SKILLTIMERSKILL;i++) { + if(ud->skilltimerskill[i]) { + delete_timer(ud->skilltimerskill[i]->timer, skill_timerskill); + ers_free(skill_timer_ers, ud->skilltimerskill[i]); + ud->skilltimerskill[i]=NULL; + } + } + return 1; +} +static int skill_ative_reverberation( struct block_list *bl, va_list ap) { + struct skill_unit *su = (TBL_SKILL*)bl; + struct skill_unit_group *sg; + if( bl->type != BL_SKILL ) + return 0; + if( su->alive && (sg = su->group) && sg->skill_id == WM_REVERBERATION ) { + map_foreachinrange(skill_trap_splash, bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, bl, gettick()); + su->limit=DIFF_TICK(gettick(),sg->tick); + sg->unit_id = UNT_USED_TRAPS; + } + return 0; +} + +static int skill_reveal_trap (struct block_list *bl, va_list ap) +{ + TBL_SKILL *su = (TBL_SKILL*)bl; + if (su->alive && su->group && skill_get_inf2(su->group->skill_id)&INF2_TRAP) + { //Reveal trap. + //Change look is not good enough, the client ignores it as an actual trap still. [Skotlex] + //clif_changetraplook(bl, su->group->unit_id); + clif_skill_setunit(su); + return 1; + } + return 0; +} + +/*========================================== + * + * + *------------------------------------------*/ +int skill_castend_damage_id (struct block_list* src, struct block_list *bl, uint16 skill_id, uint16 skill_lv, unsigned int tick, int flag) +{ + struct map_session_data *sd = NULL; + struct status_data *tstatus; + struct status_change *sc; + + if (skill_id > 0 && !skill_lv) return 0; + + nullpo_retr(1, src); + nullpo_retr(1, bl); + + if (src->m != bl->m) + return 1; + + if (bl->prev == NULL) + return 1; + + sd = BL_CAST(BL_PC, src); + + if (status_isdead(bl)) + return 1; + + if (skill_id && skill_get_type(skill_id) == BF_MAGIC && status_isimmune(bl) == 100) + { //GTB makes all targetted magic display miss with a single bolt. + sc_type sct = status_skill2sc(skill_id); + if(sct != SC_NONE) + status_change_end(bl, sct, INVALID_TIMER); + clif_skill_damage(src, bl, tick, status_get_amotion(src), status_get_dmotion(bl), 0, 1, skill_id, skill_lv, skill_get_hit(skill_id)); + return 1; + } + + sc = status_get_sc(src); + if (sc && !sc->count) + sc = NULL; //Unneeded + + tstatus = status_get_status_data(bl); + + map_freeblock_lock(); + + switch(skill_id) + { + case MER_CRASH: + case SM_BASH: + case MS_BASH: + case MC_MAMMONITE: + case TF_DOUBLE: + case AC_DOUBLE: + case MA_DOUBLE: + case AS_SONICBLOW: + case KN_PIERCE: + case ML_PIERCE: + case KN_SPEARBOOMERANG: + case TF_POISON: + case TF_SPRINKLESAND: + case AC_CHARGEARROW: + case MA_CHARGEARROW: + case RG_INTIMIDATE: + case AM_ACIDTERROR: + case BA_MUSICALSTRIKE: + case DC_THROWARROW: + case BA_DISSONANCE: + case CR_HOLYCROSS: + case NPC_DARKCROSS: + case CR_SHIELDCHARGE: + case CR_SHIELDBOOMERANG: + case NPC_PIERCINGATT: + case NPC_MENTALBREAKER: + case NPC_RANGEATTACK: + case NPC_CRITICALSLASH: + case NPC_COMBOATTACK: + case NPC_GUIDEDATTACK: + case NPC_POISON: + case NPC_RANDOMATTACK: + case NPC_WATERATTACK: + case NPC_GROUNDATTACK: + case NPC_FIREATTACK: + case NPC_WINDATTACK: + case NPC_POISONATTACK: + case NPC_HOLYATTACK: + case NPC_DARKNESSATTACK: + case NPC_TELEKINESISATTACK: + case NPC_UNDEADATTACK: + case NPC_ARMORBRAKE: + case NPC_WEAPONBRAKER: + case NPC_HELMBRAKE: + case NPC_SHIELDBRAKE: + case NPC_BLINDATTACK: + case NPC_SILENCEATTACK: + case NPC_STUNATTACK: + case NPC_PETRIFYATTACK: + case NPC_CURSEATTACK: + case NPC_SLEEPATTACK: + case LK_AURABLADE: + case LK_SPIRALPIERCE: + case ML_SPIRALPIERCE: + case LK_HEADCRUSH: + case CG_ARROWVULCAN: + case HW_MAGICCRASHER: + case ITM_TOMAHAWK: + case MO_TRIPLEATTACK: + case CH_CHAINCRUSH: + case CH_TIGERFIST: + case PA_SHIELDCHAIN: // Shield Chain + case PA_SACRIFICE: + case WS_CARTTERMINATION: // Cart Termination + case AS_VENOMKNIFE: + case HT_PHANTASMIC: + case HT_POWER: + case TK_DOWNKICK: + case TK_COUNTER: + case GS_CHAINACTION: + case GS_TRIPLEACTION: + case GS_MAGICALBULLET: + case GS_TRACKING: + case GS_PIERCINGSHOT: + case GS_RAPIDSHOWER: + case GS_DUST: + case GS_DISARM: // Added disarm. [Reddozen] + case GS_FULLBUSTER: + case NJ_SYURIKEN: + case NJ_KUNAI: + case ASC_BREAKER: + case HFLI_MOON: //[orn] + case HFLI_SBR44: //[orn] + case NPC_BLEEDING: + case NPC_CRITICALWOUND: + case NPC_HELLPOWER: + case RK_SONICWAVE: + case RK_HUNDREDSPEAR: + case AB_DUPLELIGHT_MELEE: + case RA_AIMEDBOLT: + case NC_AXEBOOMERANG: + case NC_POWERSWING: + case GC_CROSSIMPACT: + case GC_VENOMPRESSURE: + case SC_TRIANGLESHOT: + case SC_FEINTBOMB: + case LG_BANISHINGPOINT: + case LG_SHIELDPRESS: + case LG_RAGEBURST: + case LG_RAYOFGENESIS: + case LG_HESPERUSLIT: + case SR_FALLENEMPIRE: + case SR_CRESCENTELBOW_AUTOSPELL: + case SR_GATEOFHELL: + case SR_GENTLETOUCH_QUIET: + case WM_SEVERE_RAINSTORM_MELEE: + case WM_GREAT_ECHO: + case GN_SLINGITEM_RANGEMELEEATK: + case KO_JYUMONJIKIRI: + case KO_SETSUDAN: + case KO_KAIHOU: + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + /** + * Mechanic (MADO GEAR) + **/ + case NC_BOOSTKNUCKLE: + case NC_PILEBUNKER: + case NC_VULCANARM: + case NC_COLDSLOWER: + case NC_ARMSCANNON: + if (sd) pc_overheat(sd,1); + case RK_WINDCUTTER: + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag|SD_ANIMATION); + break; + + case LK_JOINTBEAT: // decide the ailment first (affects attack damage and effect) + switch( rnd()%6 ){ + case 0: flag |= BREAK_ANKLE; break; + case 1: flag |= BREAK_WRIST; break; + case 2: flag |= BREAK_KNEE; break; + case 3: flag |= BREAK_SHOULDER; break; + case 4: flag |= BREAK_WAIST; break; + case 5: flag |= BREAK_NECK; break; + } + //TODO: is there really no cleaner way to do this? + sc = status_get_sc(bl); + if (sc) sc->jb_flag = flag; + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case MO_COMBOFINISH: + if (!(flag&1) && sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_MONK) + { //Becomes a splash attack when Soul Linked. + map_foreachinrange(skill_area_sub, bl, + skill_get_splash(skill_id, skill_lv),splash_target(src), + src,skill_id,skill_lv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + } else + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case TK_STORMKICK: // Taekwon kicks [Dralnu] + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + skill_area_temp[1] = 0; + map_foreachinrange(skill_attack_area, src, + skill_get_splash(skill_id, skill_lv), splash_target(src), + BF_WEAPON, src, src, skill_id, skill_lv, tick, flag, BCT_ENEMY); + break; + + case KN_CHARGEATK: + { + bool path = path_search_long(NULL, src->m, src->x, src->y, bl->x, bl->y,CELL_CHKWALL); + unsigned int dist = distance_bl(src, bl); + uint8 dir = map_calc_dir(bl, src->x, src->y); + + // teleport to target (if not on WoE grounds) + if( !map_flag_gvg(src->m) && !map[src->m].flag.battleground && unit_movepos(src, bl->x, bl->y, 0, 1) ) + clif_slide(src, bl->x, bl->y); + + // cause damage and knockback if the path to target was a straight one + if( path ) + { + skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, dist); + skill_blown(src, bl, dist, dir, 0); + //HACK: since knockback officially defaults to the left, the client also turns to the left... therefore, + // make the caster look in the direction of the target + unit_setdir(src, (dir+4)%8); + } + + } + break; + + case NC_FLAMELAUNCHER: + if (sd) pc_overheat(sd,1); + case SN_SHARPSHOOTING: + case MA_SHARPSHOOTING: + case NJ_KAMAITACHI: + case LG_CANNONSPEAR: + //It won't shoot through walls since on castend there has to be a direct + //line of sight between caster and target. + skill_area_temp[1] = bl->id; + map_foreachinpath (skill_attack_area,src->m,src->x,src->y,bl->x,bl->y, + skill_get_splash(skill_id, skill_lv),skill_get_maxcount(skill_id,skill_lv), splash_target(src), + skill_get_type(skill_id),src,src,skill_id,skill_lv,tick,flag,BCT_ENEMY); + break; + + case NPC_ACIDBREATH: + case NPC_DARKNESSBREATH: + case NPC_FIREBREATH: + case NPC_ICEBREATH: + case NPC_THUNDERBREATH: + skill_area_temp[1] = bl->id; + map_foreachinpath(skill_attack_area,src->m,src->x,src->y,bl->x,bl->y, + skill_get_splash(skill_id, skill_lv),skill_get_maxcount(skill_id,skill_lv), splash_target(src), + skill_get_type(skill_id),src,src,skill_id,skill_lv,tick,flag,BCT_ENEMY); + break; + + case MO_INVESTIGATE: + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + status_change_end(src, SC_BLADESTOP, INVALID_TIMER); + break; + + case RG_BACKSTAP: + { + uint8 dir = map_calc_dir(src, bl->x, bl->y), t_dir = unit_getdir(bl); + if ((!check_distance_bl(src, bl, 0) && !map_check_dir(dir, t_dir)) || bl->type == BL_SKILL) { + status_change_end(src, SC_HIDING, INVALID_TIMER); + skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); + dir = dir < 4 ? dir+4 : dir-4; // change direction [Celest] + unit_setdir(bl,dir); + } + else if (sd) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } + break; + + case MO_FINGEROFFENSIVE: + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + if (battle_config.finger_offensive_type && sd) { + int i; + for (i = 1; i < sd->spiritball_old; i++) + skill_addtimerskill(src, tick + i * 200, bl->id, 0, 0, skill_id, skill_lv, BF_WEAPON, flag); + } + status_change_end(src, SC_BLADESTOP, INVALID_TIMER); + break; + + case MO_CHAINCOMBO: + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + status_change_end(src, SC_BLADESTOP, INVALID_TIMER); + break; + + case NJ_ISSEN: + status_change_end(src, SC_NEN, INVALID_TIMER); + status_change_end(src, SC_HIDING, INVALID_TIMER); + // fall through + case MO_EXTREMITYFIST: + { + short x, y, i = 2; // Move 2 cells for Issen(from target) + struct block_list *mbl = bl; + short dir = 0; + + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + + if( skill_id == MO_EXTREMITYFIST ) + { + mbl = src; + i = 3; // for Asura(from caster) + status_set_sp(src, 0, 0); + status_change_end(src, SC_EXPLOSIONSPIRITS, INVALID_TIMER); + status_change_end(src, SC_BLADESTOP, INVALID_TIMER); +#ifdef RENEWAL + sc_start(src,SC_EXTREMITYFIST2,100,skill_lv,skill_get_time(skill_id,skill_lv)); +#endif + }else + status_set_hp(src, +#ifdef RENEWAL + max(status_get_max_hp(src)/100, 1) +#else + 1 +#endif + , 0); + + dir = map_calc_dir(src,bl->x,bl->y); + if( dir > 0 && dir < 4) x = -i; + else if( dir > 4 ) x = i; + else x = 0; + if( dir > 2 && dir < 6 ) y = -i; + else if( dir == 7 || dir < 2 ) y = i; + else y = 0; + if( (mbl == src || !map_flag_gvg(src->m) && !map[src->m].flag.battleground) && // only NJ_ISSEN don't have slide effect in GVG + unit_movepos(src, mbl->x+x, mbl->y+y, 1, 1) ) { + clif_slide(src, src->x, src->y); + //uncomment this if you want to remove MO_EXTREMITYFIST glitchy walking effect. [malufett] + //clif_fixpos(src); + } + } + break; + + //Splash attack skills. + case AS_GRIMTOOTH: + case MC_CARTREVOLUTION: + case NPC_SPLASHATTACK: + flag |= SD_PREAMBLE; // a fake packet will be sent for the first target to be hit + case AS_SPLASHER: + case SM_MAGNUM: + case MS_MAGNUM: + case HT_BLITZBEAT: + case AC_SHOWER: + case MA_SHOWER: + case MG_NAPALMBEAT: + case MG_FIREBALL: + case RG_RAID: + case HW_NAPALMVULCAN: + case NJ_HUUMA: + case NJ_BAKUENRYU: + case ASC_METEORASSAULT: + case GS_DESPERADO: + case GS_SPREADATTACK: + case NPC_EARTHQUAKE: + case NPC_PULSESTRIKE: + case NPC_HELLJUDGEMENT: + case NPC_VAMPIRE_GIFT: + case RK_IGNITIONBREAK: + case AB_JUDEX: + case WL_SOULEXPANSION: + case WL_CRIMSONROCK: + case WL_COMET: + case WL_JACKFROST: + case RA_ARROWSTORM: + case RA_WUGDASH: + case NC_SELFDESTRUCTION: + case NC_AXETORNADO: + case GC_ROLLINGCUTTER: + case GC_COUNTERSLASH: + case LG_MOONSLASHER: + case LG_EARTHDRIVE: + case SR_TIGERCANNON: + case SR_RAMPAGEBLASTER: + case SR_SKYNETBLOW: + case SR_WINDMILL: + case SR_RIDEINLIGHTNING: + case WM_SOUND_OF_DESTRUCTION: + case WM_REVERBERATION_MELEE: + case WM_REVERBERATION_MAGIC: + case SO_VARETYR_SPEAR: + case GN_CART_TORNADO: + case GN_CARTCANNON: + case KO_HAPPOKUNAI: + case KO_HUUMARANKA: + case KO_MUCHANAGE: + case KO_BAKURETSU: + if( flag&1 ) {//Recursive invocation + // skill_area_temp[0] holds number of targets in area + // skill_area_temp[1] holds the id of the original target + // skill_area_temp[2] counts how many targets have already been processed + int sflag = skill_area_temp[0] & 0xFFF, heal; + if( flag&SD_LEVEL ) + sflag |= SD_LEVEL; // -1 will be used in packets instead of the skill level + if( skill_area_temp[1] != bl->id && !(skill_get_inf2(skill_id)&INF2_NPC_SKILL) ) + sflag |= SD_ANIMATION; // original target gets no animation (as well as all NPC skills) + + heal = skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, sflag); + if( skill_id == NPC_VAMPIRE_GIFT && heal > 0 ) { + clif_skill_nodamage(NULL, src, AL_HEAL, heal, 1); + status_heal(src,heal,0,0); + } + } else { + switch ( skill_id ) { + case NJ_BAKUENRYU: + case LG_EARTHDRIVE: + case GN_CARTCANNON: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + case LG_MOONSLASHER: + clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + break; + case NPC_EARTHQUAKE://FIXME: Isn't EarthQuake a ground skill after all? + skill_addtimerskill(src,tick+250,src->id,0,0,skill_id,skill_lv,2,flag|BCT_ENEMY|SD_SPLASH|1); + default: + break; + } + + skill_area_temp[0] = 0; + skill_area_temp[1] = bl->id; + skill_area_temp[2] = 0; + if( skill_id == WL_CRIMSONROCK ) { + skill_area_temp[4] = bl->x; + skill_area_temp[5] = bl->y; + } + if( skill_id == WM_REVERBERATION_MELEE || skill_id == WM_REVERBERATION_MAGIC ) + skill_area_temp[1] = 0; + // if skill damage should be split among targets, count them + //SD_LEVEL -> Forced splash damage for Auto Blitz-Beat -> count targets + //special case: Venom Splasher uses a different range for searching than for splashing + if( flag&SD_LEVEL || skill_get_nk(skill_id)&NK_SPLASHSPLIT ) + skill_area_temp[0] = map_foreachinrange(skill_area_sub, bl, (skill_id == AS_SPLASHER)?1:skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, BCT_ENEMY, skill_area_sub_count); + + // recursive invocation of skill_castend_damage_id() with flag|1 + map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), ( skill_id == WM_REVERBERATION_MELEE || skill_id == WM_REVERBERATION_MAGIC )?BL_CHAR:splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_damage_id); + } + break; + + case KN_BRANDISHSPEAR: + case ML_BRANDISH: + //Coded apart for it needs the flag passed to the damage calculation. + if (skill_area_temp[1] != bl->id) + skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag|SD_ANIMATION); + else + skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); + break; + + case KN_BOWLINGBASH: + case MS_BOWLINGBASH: + if(flag&1){ + if(bl->id==skill_area_temp[1]) + break; + //two hits for 500% + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,SD_ANIMATION); + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,SD_ANIMATION); + } else { + int i,c; + c = skill_get_blewcount(skill_id,skill_lv); + // keep moving target in the direction that src is looking, square by square + for(i=0;i<c;i++){ + if (!skill_blown(src,bl,1,(unit_getdir(src)+4)%8,0x1)) + break; //Can't knockback + skill_area_temp[0] = map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY, skill_area_sub_count); + if( skill_area_temp[0] > 1 ) break; // collision + } + clif_blown(bl); //Update target pos. + if (i!=c) { //Splash + skill_area_temp[1] = bl->id; + map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_damage_id); + } + //Weirdo dual-hit property, two attacks for 500% + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,0); + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,0); + } + break; + + case KN_SPEARSTAB: + if(flag&1) { + if (bl->id==skill_area_temp[1]) + break; + if (skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,SD_ANIMATION)) + skill_blown(src,bl,skill_area_temp[2],-1,0); + } else { + int x=bl->x,y=bl->y,i,dir; + dir = map_calc_dir(bl,src->x,src->y); + skill_area_temp[1] = bl->id; + skill_area_temp[2] = skill_get_blewcount(skill_id,skill_lv); + // all the enemies between the caster and the target are hit, as well as the target + if (skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,0)) + skill_blown(src,bl,skill_area_temp[2],-1,0); + for (i=0;i<4;i++) { + map_foreachincell(skill_area_sub,bl->m,x,y,BL_CHAR, + src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); + x += dirx[dir]; + y += diry[dir]; + } + } + break; + + case TK_TURNKICK: + case MO_BALKYOUNG: //Active part of the attack. Skill-attack [Skotlex] + { + skill_area_temp[1] = bl->id; //NOTE: This is used in skill_castend_nodamage_id to avoid affecting the target. + if (skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag)) + map_foreachinrange(skill_area_sub,bl, + skill_get_splash(skill_id, skill_lv),BL_CHAR, + src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1, + skill_castend_nodamage_id); + } + break; + case CH_PALMSTRIKE: // Palm Strike takes effect 1sec after casting. [Skotlex] + // clif_skill_nodamage(src,bl,skill_id,skill_lv,0); //Can't make this one display the correct attack animation delay :/ + clif_damage(src,bl,tick,status_get_amotion(src),0,-1,1,4,0); //Display an absorbed damage attack. + skill_addtimerskill(src, tick + (1000+status_get_amotion(src)), bl->id, 0, 0, skill_id, skill_lv, BF_WEAPON, flag); + break; + + case PR_TURNUNDEAD: + case ALL_RESURRECTION: + if (!battle_check_undead(tstatus->race, tstatus->def_ele)) + break; + skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case MG_SOULSTRIKE: + case NPC_DARKSTRIKE: + case MG_COLDBOLT: + case MG_FIREBOLT: + case MG_LIGHTNINGBOLT: + case WZ_EARTHSPIKE: + case AL_HEAL: + case AL_HOLYLIGHT: + case WZ_JUPITEL: + case NPC_DARKTHUNDER: + case PR_ASPERSIO: + case MG_FROSTDIVER: + case WZ_SIGHTBLASTER: + case WZ_SIGHTRASHER: + case NJ_KOUENKA: + case NJ_HYOUSENSOU: + case NJ_HUUJIN: + case AB_ADORAMUS: + case AB_RENOVATIO: + case AB_HIGHNESSHEAL: + case AB_DUPLELIGHT_MAGIC: + case WM_METALICSOUND: + case MH_ERASER_CUTTER: + skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case NPC_MAGICALATTACK: + skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); + sc_start(src,status_skill2sc(skill_id),100,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + + case HVAN_CAPRICE: //[blackhole89] + { + int ran=rnd()%4; + int sid = 0; + switch(ran) + { + case 0: sid=MG_COLDBOLT; break; + case 1: sid=MG_FIREBOLT; break; + case 2: sid=MG_LIGHTNINGBOLT; break; + case 3: sid=WZ_EARTHSPIKE; break; + } + skill_attack(BF_MAGIC,src,src,bl,sid,skill_lv,tick,flag|SD_LEVEL); + } + break; + case WZ_WATERBALL: + { + int range = skill_lv / 2; + int maxlv = skill_get_max(skill_id); // learnable level + int count = 0; + int x, y; + struct skill_unit* unit; + + if( skill_lv > maxlv ) + { + if( src->type == BL_MOB && skill_lv == 10 ) + range = 4; + else + range = maxlv / 2; + } + + for( y = src->y - range; y <= src->y + range; ++y ) + for( x = src->x - range; x <= src->x + range; ++x ) + { + if( !map_find_skill_unit_oncell(src,x,y,SA_LANDPROTECTOR,NULL,1) ) + { + if( src->type != BL_PC || map_getcell(src->m,x,y,CELL_CHKWATER) ) // non-players bypass the water requirement + count++; // natural water cell + else if( (unit = map_find_skill_unit_oncell(src,x,y,SA_DELUGE,NULL,1)) != NULL || (unit = map_find_skill_unit_oncell(src,x,y,NJ_SUITON,NULL,1)) != NULL ) + { + count++; // skill-induced water cell + skill_delunit(unit); // consume cell + } + } + } + + if( count > 1 ) // queue the remaining count - 1 timerskill Waterballs + skill_addtimerskill(src,tick+150,bl->id,0,0,skill_id,skill_lv,count-1,flag); + } + skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case PR_BENEDICTIO: + //Should attack undead and demons. [Skotlex] + if (battle_check_undead(tstatus->race, tstatus->def_ele) || tstatus->race == RC_DEMON) + skill_attack(BF_MAGIC, src, src, bl, skill_id, skill_lv, tick, flag); + break; + + case SL_SMA: + status_change_end(src, SC_SMA, INVALID_TIMER); + case SL_STIN: + case SL_STUN: + if (sd && !battle_config.allow_es_magic_pc && bl->type != BL_MOB) { + status_change_start(src,SC_STUN,10000,skill_lv,0,0,0,500,10); + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case NPC_DARKBREATH: + clif_emotion(src,E_AG); + case SN_FALCONASSAULT: + case PA_PRESSURE: + case CR_ACIDDEMONSTRATION: + case TF_THROWSTONE: + case NPC_SMOKING: + case GS_FLING: + case NJ_ZENYNAGE: + case GN_THORNS_TRAP: + case GN_HELLS_PLANT_ATK: + skill_attack(BF_MISC,src,src,bl,skill_id,skill_lv,tick,flag); + break; + /** + * Rune Knight + **/ + case RK_DRAGONBREATH: { + struct status_change *tsc = NULL; + if( (tsc = status_get_sc(bl)) && (tsc->data[SC_HIDING] )) { + clif_skill_nodamage(src,src,skill_id,skill_lv,1); + } else + skill_attack(BF_MISC,src,src,bl,skill_id,skill_lv,tick,flag); + } + break; + + case NPC_SELFDESTRUCTION: { + struct status_change *tsc = NULL; + if( (tsc = status_get_sc(bl)) && tsc->data[SC_HIDING] ) + break; + } + case HVAN_EXPLOSION: + if (src != bl) + skill_attack(BF_MISC,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + // Celest + case PF_SOULBURN: + if (rnd()%100 < (skill_lv < 5 ? 30 + skill_lv * 10 : 70)) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if (skill_lv == 5) + skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); + status_percent_damage(src, bl, 0, 100, false); + } else { + clif_skill_nodamage(src,src,skill_id,skill_lv,1); + if (skill_lv == 5) + skill_attack(BF_MAGIC,src,src,src,skill_id,skill_lv,tick,flag); + status_percent_damage(src, src, 0, 100, false); + } + break; + + case NPC_BLOODDRAIN: + case NPC_ENERGYDRAIN: + { + int heal = skill_attack( (skill_id == NPC_BLOODDRAIN) ? BF_WEAPON : BF_MAGIC, + src, src, bl, skill_id, skill_lv, tick, flag); + if (heal > 0){ + clif_skill_nodamage(NULL, src, AL_HEAL, heal, 1); + status_heal(src, heal, 0, 0); + } + } + break; + + case GS_BULLSEYE: + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case NJ_KASUMIKIRI: + if (skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag) > 0) + sc_start(src,SC_HIDING,100,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + case NJ_KIRIKAGE: + if( !map_flag_gvg(src->m) && !map[src->m].flag.battleground ) + { //You don't move on GVG grounds. + short x, y; + map_search_freecell(bl, 0, &x, &y, 1, 1, 0); + if (unit_movepos(src, x, y, 0, 0)) + clif_slide(src,src->x,src->y); + } + status_change_end(src, SC_HIDING, INVALID_TIMER); + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + break; + case RK_PHANTOMTHRUST: + unit_setdir(src,map_calc_dir(src, bl->x, bl->y)); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + + skill_blown(src,bl,distance_bl(src,bl)-1,unit_getdir(src),0); + if( battle_check_target(src,bl,BCT_ENEMY) ) + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case RK_STORMBLAST: + case RK_CRUSHSTRIKE: + if( sd ) { + if( pc_checkskill(sd,RK_RUNEMASTERY) >= ( skill_id == RK_CRUSHSTRIKE ? 7 : 3 ) ) + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + else + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } else //non-sd support + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + break; + case GC_DARKILLUSION: + { + short x, y; + short dir = map_calc_dir(src,bl->x,bl->y); + + if( dir > 0 && dir < 4) x = 2; + else if( dir > 4 ) x = -2; + else x = 0; + if( dir > 2 && dir < 6 ) y = 2; + else if( dir == 7 || dir < 2 ) y = -2; + else y = 0; + + if( unit_movepos(src, bl->x+x, bl->y+y, 1, 1) ) + { + clif_slide(src,bl->x+x,bl->y+y); + clif_fixpos(src); // the official server send these two packts. + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + if( rnd()%100 < 4 * skill_lv ) + skill_castend_damage_id(src,bl,GC_CROSSIMPACT,skill_lv,tick,flag); + } + + } + break; + + case GC_WEAPONCRUSH: + if( sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == GC_WEAPONBLOCKING ) + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + else if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_GC_WEAPONBLOCKING,0); + break; + + case GC_CROSSRIPPERSLASHER: + if( sd && !(sc && sc->data[SC_ROLLINGCUTTER]) ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_CONDITION,0); + else + { + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + status_change_end(src,SC_ROLLINGCUTTER,INVALID_TIMER); + } + break; + + case GC_PHANTOMMENACE: + if( flag&1 ) + { // Only Hits Invisible Targets + struct status_change *tsc = status_get_sc(bl); + if(tsc && (tsc->option&(OPTION_HIDE|OPTION_CLOAK|OPTION_CHASEWALK) || tsc->data[SC__INVISIBILITY]) ) + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + } + break; + case WL_CHAINLIGHTNING: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + skill_addtimerskill(src,tick + 150,bl->id,3,0,WL_CHAINLIGHTNING_ATK,skill_lv,4+skill_lv,flag); + break; + case WL_DRAINLIFE: + { + int heal = skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); + int rate = 70 + 5 * skill_lv; + + heal = heal * (5 + 5 * skill_lv) / 100; + + if( bl->type == BL_SKILL ) + heal = 0; // Don't absorb heal from Ice Walls or other skill units. + + if( heal && rnd()%100 < rate ) + { + status_heal(src, heal, 0, 0); + clif_skill_nodamage(NULL, src, AL_HEAL, heal, 1); + } + } + break; + + case WL_TETRAVORTEX: + if( sd ) + { + int spheres[5] = { 0, 0, 0, 0, 0 }, + positions[5] = {-1,-1,-1,-1,-1 }, + i, j = 0, k, subskill = 0; + + for( i = SC_SPHERE_1; i <= SC_SPHERE_5; i++ ) + if( sc && sc->data[i] ) + { + spheres[j] = i; + positions[j] = sc->data[i]->val2; + j++; // + } + + if( j < 4 ) + { // Need 4 spheres minimum + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + + // Sphere Sort, this time from new to old + for( i = 0; i <= j - 2; i++ ) + for( k = i + 1; k <= j - 1; k++ ) + if( positions[i] < positions[k] ) + { + swap(positions[i],positions[k]); + swap(spheres[i],spheres[k]); + } + + k = 0; + for( i = 0; i < 4; i++ ) + { + switch( sc->data[spheres[i]]->val1 ) + { + case WLS_FIRE: subskill = WL_TETRAVORTEX_FIRE; k |= 1; break; + case WLS_WIND: subskill = WL_TETRAVORTEX_WIND; k |= 4; break; + case WLS_WATER: subskill = WL_TETRAVORTEX_WATER; k |= 2; break; + case WLS_STONE: subskill = WL_TETRAVORTEX_GROUND; k |= 8; break; + } + skill_addtimerskill(src, tick + i * 200, bl->id, k, 0, subskill, skill_lv, i, flag); + clif_skill_nodamage(src, bl, subskill, skill_lv, 1); + status_change_end(src, spheres[i], INVALID_TIMER); + } + } + break; + + case WL_RELEASE: + if( sd ) + { + int i; + // Priority is to release SpellBook + if( sc && sc->data[SC_READING_SB] ) + { // SpellBook + uint16 skill_id, skill_lv, point, s = 0; + int spell[SC_MAXSPELLBOOK-SC_SPELLBOOK1 + 1]; + + for(i = SC_MAXSPELLBOOK; i >= SC_SPELLBOOK1; i--) // List all available spell to be released + if( sc->data[i] ) spell[s++] = i; + + if ( s == 0 ) + break; + + i = spell[s==1?0:rand()%s];// Random select of spell to be released. + if( s && sc->data[i] ){// Now extract the data from the preserved spell + skill_id = sc->data[i]->val1; + skill_lv = sc->data[i]->val2; + point = sc->data[i]->val3; + status_change_end(src, (sc_type)i, INVALID_TIMER); + }else //something went wrong :( + break; + + if( sc->data[SC_READING_SB]->val2 > point ) + sc->data[SC_READING_SB]->val2 -= point; + else // Last spell to be released + status_change_end(src, SC_READING_SB, INVALID_TIMER); + + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + if( !skill_check_condition_castbegin(sd, skill_id, skill_lv) ) + break; + + switch( skill_get_casttype(skill_id) ) + { + case CAST_GROUND: + skill_castend_pos2(src, bl->x, bl->y, skill_id, skill_lv, tick, 0); + break; + case CAST_NODAMAGE: + skill_castend_nodamage_id(src, bl, skill_id, skill_lv, tick, 0); + break; + case CAST_DAMAGE: + skill_castend_damage_id(src, bl, skill_id, skill_lv, tick, 0); + break; + } + + sd->ud.canact_tick = tick + skill_delayfix(src, skill_id, skill_lv); + clif_status_change(src, SI_ACTIONDELAY, 1, skill_delayfix(src, skill_id, skill_lv), 0, 0, 0); + } + else + { // Summon Balls + int j = 0, k, skele; + int spheres[5] = { 0, 0, 0, 0, 0 }, + positions[5] = {-1,-1,-1,-1,-1 }; + + for( i = SC_SPHERE_1; i <= SC_SPHERE_5; i++ ) + if( sc && sc->data[i] ) + { + spheres[j] = i; + positions[j] = sc->data[i]->val2; + sc->data[i]->val2--; // Prepares for next position + j++; + } + + if( j == 0 ) + { // No Spheres + clif_skill_fail(sd,skill_id,USESKILL_FAIL_SUMMON_NONE,0); + break; + } + + // Sphere Sort + for( i = 0; i <= j - 2; i++ ) + for( k = i + 1; k <= j - 1; k++ ) + if( positions[i] > positions[k] ) + { + swap(positions[i],positions[k]); + swap(spheres[i],spheres[k]); + } + + if( skill_lv == 1 ) j = 1; // Limit only to one ball + for( i = 0; i < j; i++ ) + { + skele = WL_RELEASE - 5 + sc->data[spheres[i]]->val1 - WLS_FIRE; // Convert Ball Element into Skill ATK for balls + // WL_SUMMON_ATK_FIRE, WL_SUMMON_ATK_WIND, WL_SUMMON_ATK_WATER, WL_SUMMON_ATK_GROUND + skill_addtimerskill(src,tick+status_get_adelay(src)*i,bl->id,0,0,skele,sc->data[spheres[i]]->val3,BF_MAGIC,flag|SD_LEVEL); + status_change_end(src, spheres[i], INVALID_TIMER); // Eliminate ball + } + clif_skill_nodamage(src,bl,skill_id,0,1); + } + } + break; + case WL_FROSTMISTY: + // Causes Freezing status through walls. + sc_start(bl,status_skill2sc(skill_id),20+12*skill_lv+(sd ? sd->status.job_level : 50)/5,skill_lv,skill_get_time(skill_id,skill_lv)); + // Doesn't deal damage through non-shootable walls. + if( path_search(NULL,src->m,src->x,src->y,bl->x,bl->y,1,CELL_CHKWALL) ) + skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag|SD_ANIMATION); + break; + case WL_HELLINFERNO: + skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); + skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag|ELE_DARK); + break; + case RA_WUGSTRIKE: + if( sd && pc_isridingwug(sd) ){ + short x[8]={0,-1,-1,-1,0,1,1,1}; + short y[8]={1,1,0,-1,-1,-1,0,1}; + uint8 dir = map_calc_dir(bl, src->x, src->y); + + if( unit_movepos(src, bl->x+x[dir], bl->y+y[dir], 1, 1) ) + { + clif_slide(src, bl->x+x[dir], bl->y+y[dir]); + clif_fixpos(src); + skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); + } + break; + } + case RA_WUGBITE: + if( path_search(NULL,src->m,src->x,src->y,bl->x,bl->y,1,CELL_CHKNOREACH) ) { + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + }else if( sd && skill_id == RA_WUGBITE ) // Only RA_WUGBITE has the skill fail message. + clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); + + break; + + case RA_SENSITIVEKEEN: + if( bl->type != BL_SKILL ) { // Only Hits Invisible Targets + struct status_change * tsc = status_get_sc(bl); + if( tsc && tsc->option&(OPTION_HIDE|OPTION_CLOAK) ){ + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + status_change_end(bl, SC_CLOAKINGEXCEED, INVALID_TIMER); + } + } + else + { + struct skill_unit *su = BL_CAST(BL_SKILL,bl); + struct skill_unit_group* sg; + + if( su && (sg=su->group) && skill_get_inf2(sg->skill_id)&INF2_TRAP ) + { + if( !(sg->unit_id == UNT_USED_TRAPS || (sg->unit_id == UNT_ANKLESNARE && sg->val2 != 0 )) ) + { + struct item item_tmp; + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid = sg->item_id?sg->item_id:ITEMID_TRAP; + item_tmp.identify = 1; + if( item_tmp.nameid ) + map_addflooritem(&item_tmp,1,bl->m,bl->x,bl->y,0,0,0,0); + } + skill_delunit(su); + } + } + break; + case NC_INFRAREDSCAN: + if( flag&1 ) + { //TODO: Need a confirmation if the other type of hidden status is included to be scanned. [Jobbie] + if( rnd()%100 < 50 ) + sc_start(bl, SC_INFRAREDSCAN, 10000, skill_lv, skill_get_time(skill_id, skill_lv)); + status_change_end(bl, SC_HIDING, INVALID_TIMER); + status_change_end(bl, SC_CLOAKING, INVALID_TIMER); + status_change_end(bl, SC_CLOAKINGEXCEED, INVALID_TIMER); // Need confirm it. + } + else + { + map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_damage_id); + clif_skill_damage(src,src,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + if( sd ) pc_overheat(sd,1); + } + break; + + case NC_MAGNETICFIELD: + sc_start2(bl,SC_MAGNETICFIELD,100,skill_lv,src->id,skill_get_time(skill_id,skill_lv)); + break; + case SC_FATALMENACE: + if( flag&1 ) + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + else + { + short x, y; + map_search_freecell(src, 0, &x, &y, -1, -1, 0); + // Destination area + skill_area_temp[4] = x; + skill_area_temp[5] = y; + map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_damage_id); + skill_addtimerskill(src,tick + 800,src->id,x,y,skill_id,skill_lv,0,flag); // To teleport Self + clif_skill_damage(src,src,tick,status_get_amotion(src),0,-30000,1,skill_id,skill_lv,6); + } + break; + case LG_PINPOINTATTACK: + if( !map_flag_gvg(src->m) && !map[src->m].flag.battleground && unit_movepos(src, bl->x, bl->y, 1, 1) ) + clif_slide(src,bl->x,bl->y); + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case LG_SHIELDSPELL: + // flag&1: Phisycal Attack, flag&2: Magic Attack. + skill_attack((flag&1)?BF_WEAPON:BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case LG_OVERBRAND: + skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag|SD_LEVEL); + break; + + case LG_OVERBRAND_BRANDISH: + skill_addtimerskill(src, tick + status_get_amotion(src)*8/10, bl->id, 0, 0, skill_id, skill_lv, BF_WEAPON, flag|SD_LEVEL); + break; + case SR_DRAGONCOMBO: + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case SR_KNUCKLEARROW: + if( !map_flag_gvg(src->m) && !map[src->m].flag.battleground && unit_movepos(src, bl->x, bl->y, 1, 1) ) { + clif_slide(src,bl->x,bl->y); + clif_fixpos(src); // Aegis send this packet too. + } + + if( flag&1 ) + skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag|SD_LEVEL); + else + skill_addtimerskill(src, tick + 300, bl->id, 0, 0, skill_id, skill_lv, BF_WEAPON, flag|SD_LEVEL|2); + break; + + case SR_HOWLINGOFLION: + status_change_end(bl, SC_SWINGDANCE, INVALID_TIMER); + status_change_end(bl, SC_SYMPHONYOFLOVER, INVALID_TIMER); + status_change_end(bl, SC_MOONLITSERENADE, INVALID_TIMER); + status_change_end(bl, SC_RUSHWINDMILL, INVALID_TIMER); + status_change_end(bl, SC_ECHOSONG, INVALID_TIMER); + status_change_end(bl, SC_HARMONIZE, INVALID_TIMER); + status_change_end(bl, SC_SIRCLEOFNATURE, INVALID_TIMER); + status_change_end(bl, SC_SATURDAYNIGHTFEVER, INVALID_TIMER); + status_change_end(bl, SC_DANCEWITHWUG, INVALID_TIMER); + status_change_end(bl, SC_LERADSDEW, INVALID_TIMER); + status_change_end(bl, SC_MELODYOFSINK, INVALID_TIMER); + status_change_end(bl, SC_BEYONDOFWARCRY, INVALID_TIMER); + status_change_end(bl, SC_UNLIMITEDHUMMINGVOICE, INVALID_TIMER); + skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag|SD_ANIMATION); + break; + + case SR_EARTHSHAKER: + if( flag&1 ) { //by default cloaking skills are remove by aoe skills so no more checking/removing except hiding and cloaking exceed. + skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); + status_change_end(bl, SC_HIDING, INVALID_TIMER); + status_change_end(bl, SC_CLOAKINGEXCEED, INVALID_TIMER); + } else{ + map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_damage_id); + clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + } + break; + + case SO_POISON_BUSTER: { + struct status_change *tsc = status_get_sc(bl); + if( tsc && tsc->data[SC_POISON] ) { + skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); + status_change_end(bl, SC_POISON, INVALID_TIMER); + } + else if( sd ) + clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); + } + break; + + case GN_SPORE_EXPLOSION: + if( flag&1 ) + skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); + else { + clif_skill_nodamage(src, bl, skill_id, 0, 1); + skill_addtimerskill(src, gettick() + skill_get_time(skill_id, skill_lv) - 1000, bl->id, 0, 0, skill_id, skill_lv, 0, 0); + } + break; + + case EL_FIRE_BOMB: + case EL_FIRE_WAVE: + case EL_WATER_SCREW: + case EL_HURRICANE: + case EL_TYPOON_MIS: + if( flag&1 ) + skill_attack(skill_get_type(skill_id+1),src,src,bl,skill_id+1,skill_lv,tick,flag); + else { + int i = skill_get_splash(skill_id,skill_lv); + clif_skill_nodamage(src,battle_get_master(src),skill_id,skill_lv,1); + clif_skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + if( rnd()%100 < 30 ) + map_foreachinrange(skill_area_sub,bl,i,BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); + else + skill_attack(skill_get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); + } + break; + + case EL_ROCK_CRUSHER: + clif_skill_nodamage(src,battle_get_master(src),skill_id,skill_lv,1); + clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + if( rnd()%100 < 50 ) + skill_attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag); + else + skill_attack(BF_WEAPON,src,src,bl,EL_ROCK_CRUSHER_ATK,skill_lv,tick,flag); + break; + + case EL_STONE_RAIN: + if( flag&1 ) + skill_attack(skill_get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); + else { + int i = skill_get_splash(skill_id,skill_lv); + clif_skill_nodamage(src,battle_get_master(src),skill_id,skill_lv,1); + clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + if( rnd()%100 < 30 ) + map_foreachinrange(skill_area_sub,bl,i,BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); + else + skill_attack(skill_get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); + } + break; + + case EL_FIRE_ARROW: + case EL_ICE_NEEDLE: + case EL_WIND_SLASH: + case EL_STONE_HAMMER: + clif_skill_nodamage(src,battle_get_master(src),skill_id,skill_lv,1); + clif_skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + skill_attack(skill_get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); + break; + + case EL_TIDAL_WEAPON: + if( src->type == BL_ELEM ) { + struct elemental_data *ele = BL_CAST(BL_ELEM,src); + struct status_change *sc = status_get_sc(&ele->bl); + struct status_change *tsc = status_get_sc(bl); + sc_type type = status_skill2sc(skill_id), type2; + type2 = type-1; + + clif_skill_nodamage(src,battle_get_master(src),skill_id,skill_lv,1); + clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + if( (sc && sc->data[type2]) || (tsc && tsc->data[type]) ) { + elemental_clean_single_effect(ele, skill_id); + } + if( rnd()%100 < 50 ) + skill_attack(skill_get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); + else { + sc_start(src,type2,100,skill_lv,skill_get_time(skill_id,skill_lv)); + sc_start(battle_get_master(src),type,100,ele->bl.id,skill_get_time(skill_id,skill_lv)); + } + clif_skill_nodamage(src,src,skill_id,skill_lv,1); + } + break; + + + //recursive homon skill + case MH_MAGMA_FLOW: + case MH_XENO_SLASHER: + case MH_HEILIGE_STANGE: + if(flag & 1) + skill_attack(skill_get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); + else { + map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag | BCT_ENEMY | SD_SPLASH | 1, skill_castend_damage_id); + } + break; + + case MH_STAHL_HORN: + case MH_NEEDLE_OF_PARALYZE: + skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); + break; + case MH_TINDER_BREAKER: + if (unit_movepos(src, bl->x, bl->y, 1, 1)) { +#if PACKETVER >= 20111005 + clif_snap(src, bl->x, bl->y); +#else + clif_skill_poseffect(src,skill_id,skill_lv,bl->x,bl->y,tick); +#endif + } + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start4(bl,SC_CLOSECONFINE2,100,skill_lv,src->id,0,0,skill_get_time(skill_id,skill_lv))); + skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag); + break; + + case 0:/* no skill - basic/normal attack */ + if(sd) { + if (flag & 3){ + if (bl->id != skill_area_temp[1]) + skill_attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, SD_LEVEL|flag); + } else { + skill_area_temp[1] = bl->id; + map_foreachinrange(skill_area_sub, bl, + sd->bonus.splash_range, BL_CHAR, + src, skill_id, skill_lv, tick, flag | BCT_ENEMY | 1, + skill_castend_damage_id); + flag|=1; //Set flag to 1 so ammo is not double-consumed. [Skotlex] + } + } + break; + + default: + ShowWarning("skill_castend_damage_id: Unknown skill used:%d\n",skill_id); + clif_skill_damage(src, bl, tick, status_get_amotion(src), tstatus->dmotion, + 0, abs(skill_get_num(skill_id, skill_lv)), + skill_id, skill_lv, skill_get_hit(skill_id)); + map_freeblock_unlock(); + return 1; + } + + if( sc && sc->data[SC_CURSEDCIRCLE_ATKER] ) //Should only remove after the skill has been casted. + status_change_end(src,SC_CURSEDCIRCLE_ATKER,INVALID_TIMER); + + map_freeblock_unlock(); + + if( sd && !(flag&1) ) + {// ensure that the skill last-cast tick is recorded + sd->canskill_tick = gettick(); + + if( sd->state.arrow_atk ) + {// consume arrow on last invocation to this skill. + battle_consume_ammo(sd, skill_id, skill_lv); + } + + // perform skill requirement consumption + skill_consume_requirement(sd,skill_id,skill_lv,2); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, uint16 skill_id, uint16 skill_lv, unsigned int tick, int flag) +{ + struct map_session_data *sd, *dstsd; + struct mob_data *md, *dstmd; + struct homun_data *hd; + struct mercenary_data *mer; + struct status_data *sstatus, *tstatus; + struct status_change *tsc; + struct status_change_entry *tsce; + + int i = 0; + enum sc_type type; + + if(skill_id > 0 && !skill_lv) return 0; // celest + + nullpo_retr(1, src); + nullpo_retr(1, bl); + + if (src->m != bl->m) + return 1; + + sd = BL_CAST(BL_PC, src); + hd = BL_CAST(BL_HOM, src); + md = BL_CAST(BL_MOB, src); + mer = BL_CAST(BL_MER, src); + + dstsd = BL_CAST(BL_PC, bl); + dstmd = BL_CAST(BL_MOB, bl); + + if(bl->prev == NULL) + return 1; + if(status_isdead(src)) + return 1; + + if( src != bl && status_isdead(bl) ) { + /** + * Skills that may be cast on dead targets + **/ + switch( skill_id ) { + case NPC_WIDESOULDRAIN: + case PR_REDEMPTIO: + case ALL_RESURRECTION: + case WM_DEADHILLHERE: + break; + default: + return 1; + } + } + + tstatus = status_get_status_data(bl); + sstatus = status_get_status_data(src); + + //Check for undead skills that convert a no-damage skill into a damage one. [Skotlex] + switch (skill_id) { + case HLIF_HEAL: //[orn] + if (bl->type != BL_HOM) { + if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0) ; + break ; + } + case AL_HEAL: + case ALL_RESURRECTION: + case PR_ASPERSIO: + /** + * Arch Bishop + **/ + case AB_RENOVATIO: + case AB_HIGHNESSHEAL: + //Apparently only player casted skills can be offensive like this. + if (sd && battle_check_undead(tstatus->race,tstatus->def_ele)) { + if (battle_check_target(src, bl, BCT_ENEMY) < 1) { + //Offensive heal does not works on non-enemies. [Skotlex] + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + return skill_castend_damage_id (src, bl, skill_id, skill_lv, tick, flag); + } + break; + case NPC_SMOKING: //Since it is a self skill, this one ends here rather than in damage_id. [Skotlex] + return skill_castend_damage_id (src, bl, skill_id, skill_lv, tick, flag); + case MH_STEINWAND: { + struct block_list *s_src = battle_get_master(src); + short ret = 0; + if(!skill_check_unit_range(src, src->x, src->y, skill_id, skill_lv)) //prevent reiteration + ret = skill_castend_pos2(src,src->x,src->y,skill_id,skill_lv,tick,flag); //cast on homon + if(s_src && !skill_check_unit_range(s_src, s_src->x, s_src->y, skill_id, skill_lv)) + ret |= skill_castend_pos2(s_src,s_src->x,s_src->y,skill_id,skill_lv,tick,flag); //cast on master + if (hd) + skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); + return ret; + } + break; + default: + //Skill is actually ground placed. + if (src == bl && skill_get_unit_id(skill_id,0)) + return skill_castend_pos2(src,bl->x,bl->y,skill_id,skill_lv,tick,0); + } + + type = status_skill2sc(skill_id); + tsc = status_get_sc(bl); + tsce = (tsc && type != -1)?tsc->data[type]:NULL; + + if (src!=bl && type > -1 && + (i = skill_get_ele(skill_id, skill_lv)) > ELE_NEUTRAL && + skill_get_inf(skill_id) != INF_SUPPORT_SKILL && + battle_attr_fix(NULL, NULL, 100, i, tstatus->def_ele, tstatus->ele_lv) <= 0) + return 1; //Skills that cause an status should be blocked if the target element blocks its element. + + map_freeblock_lock(); + switch(skill_id) + { + case HLIF_HEAL: //[orn] + case AL_HEAL: + /** + * Arch Bishop + **/ + case AB_HIGHNESSHEAL: + { + int heal = skill_calc_heal(src, bl, (skill_id == AB_HIGHNESSHEAL)?AL_HEAL:skill_id, (skill_id == AB_HIGHNESSHEAL)?10:skill_lv, true); + int heal_get_jobexp; + //Highness Heal: starts at 1.5 boost + 0.5 for each level + if( skill_id == AB_HIGHNESSHEAL ) { + heal = heal * ( 15 + 5 * skill_lv ) / 10; + } + if( status_isimmune(bl) || + (dstmd && (dstmd->class_ == MOBID_EMPERIUM || mob_is_battleground(dstmd))) || + (dstsd && pc_ismadogear(dstsd)) )//Mado is immune to heal + heal=0; + + if( sd && dstsd && sd->status.partner_id == dstsd->status.char_id && (sd->class_&MAPID_UPPERMASK) == MAPID_SUPER_NOVICE && sd->status.sex == 0 ) + heal = heal*2; + + if( tsc && tsc->count ) + { + if( tsc->data[SC_KAITE] && !(sstatus->mode&MD_BOSS) ) + { //Bounce back heal + if (--tsc->data[SC_KAITE]->val2 <= 0) + status_change_end(bl, SC_KAITE, INVALID_TIMER); + if (src == bl) + heal=0; //When you try to heal yourself under Kaite, the heal is voided. + else { + bl = src; + dstsd = sd; + } + } + else if (tsc->data[SC_BERSERK] || tsc->data[SC_SATURDAYNIGHTFEVER] || tsc->data[SC__BLOODYLUST]) + heal = 0; //Needed so that it actually displays 0 when healing. + } + clif_skill_nodamage (src, bl, skill_id, heal, 1); + if( tsc && tsc->data[SC_AKAITSUKI] && heal && skill_id != HLIF_HEAL ) + heal = ~heal + 1; + heal_get_jobexp = status_heal(bl,heal,0,0); + + if(sd && dstsd && heal > 0 && sd != dstsd && 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 (sd, bl, 0, heal_get_jobexp, false); + } + } + break; + + case PR_REDEMPTIO: + if (sd && !(flag&1)) { + if (sd->status.party_id == 0) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + skill_area_temp[0] = 0; + party_foreachsamemap(skill_area_sub, + sd,skill_get_splash(skill_id, skill_lv), + src,skill_id,skill_lv,tick, flag|BCT_PARTY|1, + skill_castend_nodamage_id); + if (skill_area_temp[0] == 0) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + skill_area_temp[0] = 5 - skill_area_temp[0]; // The actual penalty... + if (skill_area_temp[0] > 0 && !map[src->m].flag.noexppenalty) { //Apply penalty + sd->status.base_exp -= min(sd->status.base_exp, pc_nextbaseexp(sd) * skill_area_temp[0] * 2/1000); //0.2% penalty per each. + sd->status.job_exp -= min(sd->status.job_exp, pc_nextjobexp(sd) * skill_area_temp[0] * 2/1000); + clif_updatestatus(sd,SP_BASEEXP); + clif_updatestatus(sd,SP_JOBEXP); + } + status_set_hp(src, 1, 0); + status_set_sp(src, 0, 0); + break; + } else if (status_isdead(bl) && flag&1) { //Revive + skill_area_temp[0]++; //Count it in, then fall-through to the Resurrection code. + skill_lv = 3; //Resurrection level 3 is used + } else //Invalid target, skip resurrection. + break; + + case ALL_RESURRECTION: + if(sd && (map_flag_gvg(bl->m) || map[bl->m].flag.battleground)) + { //No reviving in WoE grounds! + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + if (!status_isdead(bl)) + break; + { + int per = 0, sper = 0; + if (tsc && tsc->data[SC_HELLPOWER]) + break; + + if (map[bl->m].flag.pvp && dstsd && dstsd->pvp_point < 0) + break; + + switch(skill_lv){ + case 1: per=10; break; + case 2: per=30; break; + case 3: per=50; break; + case 4: per=80; break; + } + if(dstsd && dstsd->special_state.restart_full_recover) + per = sper = 100; + if (status_revive(bl, per, sper)) + { + clif_skill_nodamage(src,bl,ALL_RESURRECTION,skill_lv,1); //Both Redemptio and Res show this skill-animation. + if(sd && dstsd && battle_config.resurrection_exp > 0) + { + int exp = 0,jexp = 0; + int lv = dstsd->status.base_level - sd->status.base_level, jlv = dstsd->status.job_level - sd->status.job_level; + if(lv > 0 && pc_nextbaseexp(dstsd)) { + exp = (int)((double)dstsd->status.base_exp * (double)lv * (double)battle_config.resurrection_exp / 1000000.); + if (exp < 1) exp = 1; + } + if(jlv > 0 && pc_nextjobexp(dstsd)) { + jexp = (int)((double)dstsd->status.job_exp * (double)lv * (double)battle_config.resurrection_exp / 1000000.); + if (jexp < 1) jexp = 1; + } + if(exp > 0 || jexp > 0) + pc_gainexp (sd, bl, exp, jexp, false); + } + } + } + break; + + case AL_DECAGI: + case MER_DECAGI: + clif_skill_nodamage (src, bl, skill_id, skill_lv, + sc_start(bl, type, (40 + skill_lv * 2 + (status_get_lv(src) + sstatus->int_)/5), skill_lv, skill_get_time(skill_id,skill_lv))); + break; + + case AL_CRUCIS: + if (flag&1) + sc_start(bl,type, 23+skill_lv*4 +status_get_lv(src) -status_get_lv(bl), skill_lv,skill_get_time(skill_id,skill_lv)); + else { + map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_CHAR, + src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + } + break; + + case PR_LEXDIVINA: + case MER_LEXDIVINA: + if( tsce ) + status_change_end(bl,type, INVALID_TIMER); + else + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + clif_skill_nodamage (src, bl, skill_id, skill_lv, 1); + break; + + case SA_ABRACADABRA: + { + int abra_skill_id = 0, abra_skill_lv; + do { + i = rnd() % MAX_SKILL_ABRA_DB; + abra_skill_id = skill_abra_db[i].skill_id; + } while (abra_skill_id == 0 || + skill_abra_db[i].req_lv > skill_lv || //Required lv for it to appear + rnd()%10000 >= skill_abra_db[i].per + ); + abra_skill_lv = min(skill_lv, skill_get_max(abra_skill_id)); + clif_skill_nodamage (src, bl, skill_id, skill_lv, 1); + + if( sd ) + {// player-casted + sd->state.abra_flag = 1; + sd->skillitem = abra_skill_id; + sd->skillitemlv = abra_skill_lv; + clif_item_skill(sd, abra_skill_id, abra_skill_lv); + } + else + {// mob-casted + struct unit_data *ud = unit_bl2ud(src); + int inf = skill_get_inf(abra_skill_id); + int target_id = 0; + if (!ud) break; + if (inf&INF_SELF_SKILL || inf&INF_SUPPORT_SKILL) { + if (src->type == BL_PET) + bl = (struct block_list*)((TBL_PET*)src)->msd; + if (!bl) bl = src; + unit_skilluse_id(src, bl->id, abra_skill_id, abra_skill_lv); + } else { //Assume offensive skills + if (ud->target) + target_id = ud->target; + else switch (src->type) { + case BL_MOB: target_id = ((TBL_MOB*)src)->target_id; break; + case BL_PET: target_id = ((TBL_PET*)src)->target_id; break; + } + if (!target_id) + break; + if (skill_get_casttype(abra_skill_id) == CAST_GROUND) { + bl = map_id2bl(target_id); + if (!bl) bl = src; + unit_skilluse_pos(src, bl->x, bl->y, abra_skill_id, abra_skill_lv); + } else + unit_skilluse_id(src, target_id, abra_skill_id, abra_skill_lv); + } + } + } + break; + + case SA_COMA: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time2(skill_id,skill_lv))); + break; + case SA_FULLRECOVERY: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if (status_isimmune(bl)) + break; + status_percent_heal(bl, 100, 100); + break; + case NPC_ALLHEAL: + { + int heal; + if( status_isimmune(bl) ) + break; + heal = status_percent_heal(bl, 100, 0); + clif_skill_nodamage(NULL, bl, AL_HEAL, heal, 1); + if( dstmd ) + { // Reset Damage Logs + memset(dstmd->dmglog, 0, sizeof(dstmd->dmglog)); + dstmd->tdmg = 0; + } + } + break; + case SA_SUMMONMONSTER: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if (sd) mob_once_spawn(sd, src->m, src->x, src->y," --ja--", -1, 1, "", SZ_SMALL, AI_NONE); + break; + case SA_LEVELUP: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if (sd && pc_nextbaseexp(sd)) pc_gainexp(sd, NULL, pc_nextbaseexp(sd) * 10 / 100, 0, false); + break; + case SA_INSTANTDEATH: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + status_set_hp(bl,1,0); + break; + case SA_QUESTION: + case SA_GRAVITY: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + case SA_CLASSCHANGE: + case SA_MONOCELL: + if (dstmd) + { + int class_; + if ( sd && dstmd->status.mode&MD_BOSS ) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + class_ = skill_id==SA_MONOCELL?1002:mob_get_random_id(4, 1, 0); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + mob_class_change(dstmd,class_); + if( tsc && dstmd->status.mode&MD_BOSS ) + { + const enum sc_type scs[] = { SC_QUAGMIRE, SC_PROVOKE, SC_ROKISWEIL, SC_GRAVITATION, SC_SUITON, SC_STRIPWEAPON, SC_STRIPSHIELD, SC_STRIPARMOR, SC_STRIPHELM, SC_BLADESTOP }; + for (i = SC_COMMON_MIN; i <= SC_COMMON_MAX; i++) + if (tsc->data[i]) status_change_end(bl, (sc_type)i, INVALID_TIMER); + for (i = 0; i < ARRAYLENGTH(scs); i++) + if (tsc->data[scs[i]]) status_change_end(bl, scs[i], INVALID_TIMER); + } + } + break; + case SA_DEATH: + if ( sd && dstmd && dstmd->status.mode&MD_BOSS ) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + status_kill(bl); + break; + case SA_REVERSEORCISH: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id, skill_lv))); + break; + case SA_FORTUNE: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if(sd) pc_getzeny(sd,status_get_lv(bl)*100,LOG_TYPE_STEAL,NULL); + break; + case SA_TAMINGMONSTER: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if (sd && dstmd) { + ARR_FIND( 0, MAX_PET_DB, i, dstmd->class_ == pet_db[i].class_ ); + if( i < MAX_PET_DB ) + pet_catch_process1(sd, dstmd->class_); + } + break; + + case CR_PROVIDENCE: + if(sd && dstsd){ //Check they are not another crusader [Skotlex] + if ((dstsd->class_&MAPID_UPPERMASK) == MAPID_CRUSADER) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 1; + } + } + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + break; + + case CG_MARIONETTE: + { + struct status_change* sc = status_get_sc(src); + + if( sd && dstsd && (dstsd->class_&MAPID_UPPERMASK) == MAPID_BARDDANCER && dstsd->status.sex == sd->status.sex ) + {// Cannot cast on another bard/dancer-type class of the same gender as caster + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 1; + } + + if( sc && tsc ) + { + if( !sc->data[SC_MARIONETTE] && !tsc->data[SC_MARIONETTE2] ) + { + sc_start(src,SC_MARIONETTE,100,bl->id,skill_get_time(skill_id,skill_lv)); + sc_start(bl,SC_MARIONETTE2,100,src->id,skill_get_time(skill_id,skill_lv)); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + else + if( sc->data[SC_MARIONETTE ] && sc->data[SC_MARIONETTE ]->val1 == bl->id && + tsc->data[SC_MARIONETTE2] && tsc->data[SC_MARIONETTE2]->val1 == src->id ) + { + status_change_end(src, SC_MARIONETTE, INVALID_TIMER); + status_change_end(bl, SC_MARIONETTE2, INVALID_TIMER); + } + else + { + if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + + map_freeblock_unlock(); + return 1; + } + } + } + break; + + case RG_CLOSECONFINE: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start4(bl,type,100,skill_lv,src->id,0,0,skill_get_time(skill_id,skill_lv))); + 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 (dstsd) { + if(dstsd->status.weapon == W_FIST || + (dstsd->sc.count && !dstsd->sc.data[type] && + ( //Allow re-enchanting to lenghten time. [Skotlex] + dstsd->sc.data[SC_FIREWEAPON] || + dstsd->sc.data[SC_WATERWEAPON] || + dstsd->sc.data[SC_WINDWEAPON] || + dstsd->sc.data[SC_EARTHWEAPON] || + dstsd->sc.data[SC_SHADOWWEAPON] || + dstsd->sc.data[SC_GHOSTWEAPON] || + dstsd->sc.data[SC_ENCPOISON] + )) + ) { + if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + clif_skill_nodamage(src,bl,skill_id,skill_lv,0); + break; + } + } + // 100% success rate at lv4 & 5, but lasts longer at lv5 + if(!clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(bl,type,(60+skill_lv*10),skill_lv, skill_get_time(skill_id,skill_lv)))) { + if (sd) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + if (skill_break_equip(bl, EQP_WEAPON, 10000, BCT_PARTY) && sd && sd != dstsd) + clif_displaymessage(sd->fd, msg_txt(669)); + } + break; + + case PR_ASPERSIO: + if (sd && dstmd) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,0); + break; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + break; + + case ITEM_ENCHANTARMS: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start2(bl,type,100,skill_lv, + skill_get_ele(skill_id,skill_lv), skill_get_time(skill_id,skill_lv))); + break; + + case TK_SEVENWIND: + switch(skill_get_ele(skill_id,skill_lv)) { + case ELE_EARTH : type = SC_EARTHWEAPON; break; + case ELE_WIND : type = SC_WINDWEAPON; break; + case ELE_WATER : type = SC_WATERWEAPON; break; + case ELE_FIRE : type = SC_FIREWEAPON; break; + case ELE_GHOST : type = SC_GHOSTWEAPON; break; + case ELE_DARK : type = SC_SHADOWWEAPON; break; + case ELE_HOLY : type = SC_ASPERSIO; break; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + + sc_start(bl,SC_SEVENWIND,100,skill_lv,skill_get_time(skill_id,skill_lv)); + + break; + + case PR_KYRIE: + case MER_KYRIE: + clif_skill_nodamage(bl,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + break; + //Passive Magnum, should had been casted on yourself. + case SM_MAGNUM: + case MS_MAGNUM: + skill_area_temp[1] = 0; + map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_SKILL|BL_CHAR, + src,skill_id,skill_lv,tick, flag|BCT_ENEMY|1, skill_castend_damage_id); + clif_skill_nodamage (src,src,skill_id,skill_lv,1); + // Initiate 10% of your damage becomes fire element. + sc_start4(src,SC_WATK_ELEMENT,100,3,20,0,0,skill_get_time2(skill_id, skill_lv)); + if( sd ) + skill_blockpc_start(sd, skill_id, skill_get_time(skill_id, skill_lv)); + else if( bl->type == BL_MER ) + skill_blockmerc_start((TBL_MER*)bl, skill_id, skill_get_time(skill_id, skill_lv)); + break; + + case TK_JUMPKICK: + /* Check if the target is an enemy; if not, skill should fail so the character doesn't unit_movepos (exploitable) */ + if( battle_check_target(src, bl, BCT_ENEMY) > 0 ) + { + if( unit_movepos(src, bl->x, bl->y, 1, 1) ) + { + skill_attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag); + clif_slide(src,bl->x,bl->y); + } + } + else + clif_skill_fail(sd,skill_id,USESKILL_FAIL,0); + break; + + case AL_INCAGI: + case AL_BLESSING: + case MER_INCAGI: + case MER_BLESSING: + if (dstsd != NULL && tsc->data[SC_CHANGEUNDEAD]) { + skill_attack(BF_MISC,src,src,bl,skill_id,skill_lv,tick,flag); + break; + } + case PR_SLOWPOISON: + case PR_IMPOSITIO: + case PR_LEXAETERNA: + case PR_SUFFRAGIUM: + case PR_BENEDICTIO: + case LK_BERSERK: + case MS_BERSERK: + case KN_AUTOCOUNTER: + case KN_TWOHANDQUICKEN: + case KN_ONEHAND: + case MER_QUICKEN: + case CR_SPEARQUICKEN: + case CR_REFLECTSHIELD: + case MS_REFLECTSHIELD: + case AS_POISONREACT: + case MC_LOUD: + case MG_ENERGYCOAT: + case MO_EXPLOSIONSPIRITS: + case MO_STEELBODY: + case MO_BLADESTOP: + case LK_AURABLADE: + case LK_PARRYING: + case MS_PARRYING: + case LK_CONCENTRATION: + case WS_CARTBOOST: + case SN_SIGHT: + case WS_MELTDOWN: + case WS_OVERTHRUSTMAX: + case ST_REJECTSWORD: + case HW_MAGICPOWER: + case PF_MEMORIZE: + case PA_SACRIFICE: + case ASC_EDP: + case PF_DOUBLECASTING: + case SG_SUN_COMFORT: + case SG_MOON_COMFORT: + case SG_STAR_COMFORT: + case NPC_HALLUCINATION: + case GS_MADNESSCANCEL: + case GS_ADJUSTMENT: + case GS_INCREASING: + case NJ_KASUMIKIRI: + case NJ_UTSUSEMI: + case NJ_NEN: + case NPC_DEFENDER: + case NPC_MAGICMIRROR: + case ST_PRESERVE: + case NPC_INVINCIBLE: + case NPC_INVINCIBLEOFF: + case RK_DEATHBOUND: + case AB_RENOVATIO: + case AB_EXPIATIO: + case AB_DUPLELIGHT: + case AB_SECRAMENT: + case NC_ACCELERATION: + case NC_HOVERING: + case NC_SHAPESHIFT: + case WL_RECOGNIZEDSPELL: + case GC_VENOMIMPRESS: + case SC_DEADLYINFECT: + case LG_EXEEDBREAK: + case LG_PRESTIGE: + case SR_CRESCENTELBOW: + case SR_LIGHTNINGWALK: + case SR_GENTLETOUCH_ENERGYGAIN: + case GN_CARTBOOST: + case KO_MEIKYOUSISUI: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + break; + + case SO_STRIKING: + if (sd) { + int bonus = 25 + 10 * skill_lv; + bonus += (pc_checkskill(sd, SA_FLAMELAUNCHER)+pc_checkskill(sd, SA_FROSTWEAPON)+pc_checkskill(sd, SA_LIGHTNINGLOADER)+pc_checkskill(sd, SA_SEISMICWEAPON))*5; + clif_skill_nodamage( src, bl, skill_id, skill_lv, + battle_check_target(src,bl,BCT_PARTY) ? + sc_start2(bl, type, 100, skill_lv, bonus, skill_get_time(skill_id,skill_lv)) : + 0 + ); + } + break; + + case NPC_STOP: + if( clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start2(bl,type,100,skill_lv,src->id,skill_get_time(skill_id,skill_lv)) ) ) + sc_start2(src,type,100,skill_lv,bl->id,skill_get_time(skill_id,skill_lv)); + break; + case HP_ASSUMPTIO: + if( sd && dstmd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + else + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + break; + case MG_SIGHT: + case MER_SIGHT: + case AL_RUWACH: + case WZ_SIGHTBLASTER: + case NPC_WIDESIGHT: + case NPC_STONESKIN: + case NPC_ANTIMAGIC: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start2(bl,type,100,skill_lv,skill_id,skill_get_time(skill_id,skill_lv))); + break; + case HLIF_AVOID: + case HAMI_DEFENCE: + i = skill_get_time(skill_id,skill_lv); + clif_skill_nodamage(bl,bl,skill_id,skill_lv,sc_start(bl,type,100,skill_lv,i)); // Master + clif_skill_nodamage(src,src,skill_id,skill_lv,sc_start(src,type,100,skill_lv,i)); // Homunc + break; + case NJ_BUNSINJYUTSU: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + status_change_end(bl, SC_NEN, INVALID_TIMER); + break; +/* Was modified to only affect targetted char. [Skotlex] + case HP_ASSUMPTIO: + if (flag&1) + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + else + { + map_foreachinrange(skill_area_sub, bl, + skill_get_splash(skill_id, skill_lv), BL_PC, + src, skill_id, skill_lv, tick, flag|BCT_ALL|1, + skill_castend_nodamage_id); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; +*/ + case SM_ENDURE: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + if (sd) + skill_blockpc_start (sd, skill_id, skill_get_time2(skill_id,skill_lv)); + break; + + case AS_ENCHANTPOISON: // Prevent spamming [Valaris] + if (sd && dstsd && dstsd->sc.count) { + if (dstsd->sc.data[SC_FIREWEAPON] || + dstsd->sc.data[SC_WATERWEAPON] || + dstsd->sc.data[SC_WINDWEAPON] || + dstsd->sc.data[SC_EARTHWEAPON] || + dstsd->sc.data[SC_SHADOWWEAPON] || + dstsd->sc.data[SC_GHOSTWEAPON] + // dstsd->sc.data[SC_ENCPOISON] //People say you should be able to recast to lengthen the timer. [Skotlex] + ) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,0); + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + } + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + break; + + case LK_TENSIONRELAX: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start4(bl,type,100,skill_lv,0,0,skill_get_time2(skill_id,skill_lv), + skill_get_time(skill_id,skill_lv))); + break; + + case MC_CHANGECART: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + + case TK_MISSION: + if (sd) { + int id; + if (sd->mission_mobid && (sd->mission_count || rnd()%100)) { //Cannot change target when already have one + clif_mission_info(sd, sd->mission_mobid, sd->mission_count); + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + id = mob_get_random_id(0,0xF, sd->status.base_level); + if (!id) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + sd->mission_mobid = id; + sd->mission_count = 0; + pc_setglobalreg(sd,"TK_MISSION_ID", id); + clif_mission_info(sd, id, 0); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case AC_CONCENTRATION: + { + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + map_foreachinrange( status_change_timer_sub, src, + skill_get_splash(skill_id, skill_lv), BL_CHAR, + src,NULL,type,tick); + } + break; + + case SM_PROVOKE: + case SM_SELFPROVOKE: + case MER_PROVOKE: + if( (tstatus->mode&MD_BOSS) || battle_check_undead(tstatus->race,tstatus->def_ele) ) + { + map_freeblock_unlock(); + return 1; + } + //TODO: How much does base level affects? Dummy value of 1% per level difference used. [Skotlex] + clif_skill_nodamage(src,bl,skill_id == SM_SELFPROVOKE ? SM_PROVOKE : skill_id,skill_lv, + (i = sc_start(bl,type, skill_id == SM_SELFPROVOKE ? 100:( 50 + 3*skill_lv + status_get_lv(src) - status_get_lv(bl)), skill_lv, skill_get_time(skill_id,skill_lv)))); + if( !i ) + { + if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 0; + } + unit_skillcastcancel(bl, 2); + + if( tsc && tsc->count ) + { + status_change_end(bl, SC_FREEZE, INVALID_TIMER); + if( tsc->data[SC_STONE] && tsc->opt1 == OPT1_STONE ) + status_change_end(bl, SC_STONE, INVALID_TIMER); + status_change_end(bl, SC_SLEEP, INVALID_TIMER); + status_change_end(bl, SC_TRICKDEAD, INVALID_TIMER); + } + + if( dstmd ) + { + dstmd->state.provoke_flag = src->id; + mob_target(dstmd, src, skill_get_range2(src,skill_id,skill_lv)); + } + break; + + case ML_DEVOTION: + case CR_DEVOTION: + { + int count, lv; + if( !dstsd || (!sd && !mer) ) + { // Only players can be devoted + if( sd ) + clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); + break; + } + + if( (lv = status_get_lv(src) - dstsd->status.base_level) < 0 ) + lv = -lv; + if( lv > battle_config.devotion_level_difference || // Level difference requeriments + (dstsd->sc.data[type] && dstsd->sc.data[type]->val1 != src->id) || // Cannot Devote a player devoted from another source + (skill_id == ML_DEVOTION && (!mer || mer != dstsd->md)) || // Mercenary only can devote owner + (dstsd->class_&MAPID_UPPERMASK) == MAPID_CRUSADER || // Crusader Cannot be devoted + (dstsd->sc.data[SC_HELLPOWER])) // Players affected by SC_HELLPOWERR cannot be devoted. + { + if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 1; + } + + i = 0; + count = (sd)? min(skill_lv,5) : 1; // Mercenary only can Devote owner + if( sd ) + { // Player Devoting Player + ARR_FIND(0, count, i, sd->devotion[i] == bl->id ); + if( i == count ) + { + ARR_FIND(0, count, i, sd->devotion[i] == 0 ); + if( i == count ) + { // No free slots, skill Fail + clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); + map_freeblock_unlock(); + return 1; + } + } + + sd->devotion[i] = bl->id; + } + else + mer->devotion_flag = 1; // Mercenary Devoting Owner + + clif_skill_nodamage(src, bl, skill_id, skill_lv, + sc_start4(bl, type, 100, src->id, i, skill_get_range2(src,skill_id,skill_lv),0, skill_get_time2(skill_id, skill_lv))); + clif_devotion(src, NULL); + } + break; + + case MO_CALLSPIRITS: + if(sd) { + int limit = skill_lv; + if( sd->sc.data[SC_RAISINGDRAGON] ) + limit += sd->sc.data[SC_RAISINGDRAGON]->val1; + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + pc_addspiritball(sd,skill_get_time(skill_id,skill_lv),limit); + } + break; + + case CH_SOULCOLLECT: + if(sd) { + int limit = 5; + if( sd->sc.data[SC_RAISINGDRAGON] ) + limit += sd->sc.data[SC_RAISINGDRAGON]->val1; + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + for (i = 0; i < limit; i++) + pc_addspiritball(sd,skill_get_time(skill_id,skill_lv),limit); + } + break; + + case MO_KITRANSLATION: + if(dstsd && (dstsd->class_&MAPID_BASEMASK)!=MAPID_GUNSLINGER) { + pc_addspiritball(dstsd,skill_get_time(skill_id,skill_lv),5); + } + break; + + case TK_TURNKICK: + case MO_BALKYOUNG: //Passive part of the attack. Splash knock-back+stun. [Skotlex] + if (skill_area_temp[1] != bl->id) { + skill_blown(src,bl,skill_get_blewcount(skill_id,skill_lv),-1,0); + skill_additional_effect(src,bl,skill_id,skill_lv,BF_MISC,ATK_DEF,tick); //Use Misc rather than weapon to signal passive pushback + } + break; + + case MO_ABSORBSPIRITS: + i = 0; + if (dstsd && dstsd->spiritball && (sd == dstsd || map_flag_vs(src->m)) && (dstsd->class_&MAPID_BASEMASK)!=MAPID_GUNSLINGER) + { // split the if for readability, and included gunslingers in the check so that their coins cannot be removed [Reddozen] + i = dstsd->spiritball * 7; + pc_delspiritball(dstsd,dstsd->spiritball,0); + } else if (dstmd && !(tstatus->mode&MD_BOSS) && rnd() % 100 < 20) + { // check if target is a monster and not a Boss, for the 20% chance to absorb 2 SP per monster's level [Reddozen] + i = 2 * dstmd->level; + mob_target(dstmd,src,0); + } + if (i) status_heal(src, 0, i, 3); + clif_skill_nodamage(src,bl,skill_id,skill_lv,i?1:0); + break; + + case AC_MAKINGARROW: + if(sd) { + clif_arrow_create_list(sd); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case AM_PHARMACY: + if(sd) { + clif_skill_produce_mix_list(sd,skill_id,22); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case SA_CREATECON: + if(sd) { + clif_elementalconverter_list(sd); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case BS_HAMMERFALL: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,SC_STUN,(20 + 10 * skill_lv),skill_lv,skill_get_time2(skill_id,skill_lv))); + break; + case RG_RAID: + skill_area_temp[1] = 0; + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinrange(skill_area_sub, bl, + skill_get_splash(skill_id, skill_lv), splash_target(src), + src,skill_id,skill_lv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + status_change_end(src, SC_HIDING, INVALID_TIMER); + break; + + case ASC_METEORASSAULT: + case GS_SPREADATTACK: + case RK_STORMBLAST: + case NC_AXETORNADO: + case GC_COUNTERSLASH: + case SR_SKYNETBLOW: + case SR_RAMPAGEBLASTER: + case SR_HOWLINGOFLION: + case KO_HAPPOKUNAI: + skill_area_temp[1] = 0; + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + i = map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), + src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_damage_id); + if( !i && ( skill_id == NC_AXETORNADO || skill_id == SR_SKYNETBLOW || skill_id == KO_HAPPOKUNAI ) ) + clif_skill_damage(src,src,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + break; + + case NC_EMERGENCYCOOL: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + status_change_end(src,SC_OVERHEAT_LIMITPOINT,INVALID_TIMER); + status_change_end(src,SC_OVERHEAT,INVALID_TIMER); + break; + case SR_WINDMILL: + case GN_CART_TORNADO: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + case SR_EARTHSHAKER: + case NC_INFRAREDSCAN: + case NPC_EARTHQUAKE: + case NPC_VAMPIRE_GIFT: + case NPC_HELLJUDGEMENT: + case NPC_PULSESTRIKE: + case LG_MOONSLASHER: + skill_castend_damage_id(src, src, skill_id, skill_lv, tick, flag); + break; + + case KN_BRANDISHSPEAR: + case ML_BRANDISH: + skill_brandishspear(src, bl, skill_id, skill_lv, tick, flag); + break; + + case WZ_SIGHTRASHER: + //Passive side of the attack. + status_change_end(src, SC_SIGHT, INVALID_TIMER); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinrange(skill_area_sub,src, + skill_get_splash(skill_id, skill_lv),BL_CHAR|BL_SKILL, + src,skill_id,skill_lv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + break; + + case NJ_HYOUSYOURAKU: + case NJ_RAIGEKISAI: + case WZ_FROSTNOVA: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + skill_area_temp[1] = 0; + map_foreachinrange(skill_attack_area, src, + skill_get_splash(skill_id, skill_lv), splash_target(src), + BF_MAGIC, src, src, skill_id, skill_lv, tick, flag, BCT_ENEMY); + break; + + case HVAN_EXPLOSION: //[orn] + case NPC_SELFDESTRUCTION: + //Self Destruction hits everyone in range (allies+enemies) + //Except for Summoned Marine spheres on non-versus maps, where it's just enemy. + i = ((!md || md->special_state.ai == 2) && !map_flag_vs(src->m))? + BCT_ENEMY:BCT_ALL; + clif_skill_nodamage(src, src, skill_id, -1, 1); + map_delblock(src); //Required to prevent chain-self-destructions hitting back. + map_foreachinrange(skill_area_sub, bl, + skill_get_splash(skill_id, skill_lv), splash_target(src), + src, skill_id, skill_lv, tick, flag|i, + skill_castend_damage_id); + map_addblock(src); + status_damage(src, src, sstatus->max_hp,0,0,1); + break; + + case AL_ANGELUS: + case PR_MAGNIFICAT: + case PR_GLORIA: + case SN_WINDWALK: + case CASH_BLESSING: + case CASH_INCAGI: + case CASH_ASSUMPTIO: + if( sd == NULL || sd->status.party_id == 0 || (flag & 1) ) + clif_skill_nodamage(bl, bl, skill_id, skill_lv, sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + else if( sd ) + party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); + break; + case MER_MAGNIFICAT: + if( mer != NULL ) + { + clif_skill_nodamage(bl, bl, skill_id, skill_lv, sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + if( mer->master && mer->master->status.party_id != 0 && !(flag&1) ) + party_foreachsamemap(skill_area_sub, mer->master, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); + else if( mer->master && !(flag&1) ) + clif_skill_nodamage(src, &mer->master->bl, skill_id, skill_lv, sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + } + break; + + case BS_ADRENALINE: + case BS_ADRENALINE2: + case BS_WEAPONPERFECT: + case BS_OVERTHRUST: + if (sd == NULL || sd->status.party_id == 0 || (flag & 1)) { + clif_skill_nodamage(bl,bl,skill_id,skill_lv, + sc_start2(bl,type,100,skill_lv,(src == bl)? 1:0,skill_get_time(skill_id,skill_lv))); + } else if (sd) { + party_foreachsamemap(skill_area_sub, + sd,skill_get_splash(skill_id, skill_lv), + src,skill_id,skill_lv,tick, flag|BCT_PARTY|1, + skill_castend_nodamage_id); + } + break; + + case BS_MAXIMIZE: + case NV_TRICKDEAD: + case CR_DEFENDER: + case ML_DEFENDER: + case CR_AUTOGUARD: + case ML_AUTOGUARD: + case TK_READYSTORM: + case TK_READYDOWN: + case TK_READYTURN: + case TK_READYCOUNTER: + case TK_DODGE: + case CR_SHRINK: + case SG_FUSION: + case GS_GATLINGFEVER: + if( tsce ) + { + clif_skill_nodamage(src,bl,skill_id,skill_lv,status_change_end(bl, type, INVALID_TIMER)); + map_freeblock_unlock(); + return 0; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + break; + case SL_KAITE: + case SL_KAAHI: + case SL_KAIZEL: + case SL_KAUPE: + if (sd) { + if (!dstsd || !( + (sd->sc.data[SC_SPIRIT] && sd->sc.data[SC_SPIRIT]->val2 == SL_SOULLINKER) || + (dstsd->class_&MAPID_UPPERMASK) == MAPID_SOUL_LINKER || + dstsd->status.char_id == sd->status.char_id || + dstsd->status.char_id == sd->status.partner_id || + dstsd->status.char_id == sd->status.child + )) { + status_change_start(src,SC_STUN,10000,skill_lv,0,0,0,500,8); + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + } + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id, skill_lv))); + break; + case SM_AUTOBERSERK: + case MER_AUTOBERSERK: + if( tsce ) + i = status_change_end(bl, type, INVALID_TIMER); + else + i = sc_start(bl,type,100,skill_lv,60000); + clif_skill_nodamage(src,bl,skill_id,skill_lv,i); + break; + case TF_HIDING: + case ST_CHASEWALK: + case KO_YAMIKUMO: + if (tsce) + { + clif_skill_nodamage(src,bl,skill_id,-1,status_change_end(bl, type, INVALID_TIMER)); //Hide skill-scream animation. + map_freeblock_unlock(); + return 0; + } else if( tsc && tsc->option&OPTION_MADOGEAR ) { + //Mado Gear cannot hide + if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 0; + } + clif_skill_nodamage(src,bl,skill_id,-1,sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + break; + case TK_RUN: + if (tsce) + { + clif_skill_nodamage(src,bl,skill_id,skill_lv,status_change_end(bl, type, INVALID_TIMER)); + map_freeblock_unlock(); + return 0; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start4(bl,type,100,skill_lv,unit_getdir(bl),0,0,0)); + if (sd) // If the client receives a skill-use packet inmediately before a walkok packet, it will discard the walk packet! [Skotlex] + clif_walkok(sd); // So aegis has to resend the walk ok. + break; + case AS_CLOAKING: + case GC_CLOAKINGEXCEED: + case LG_FORCEOFVANGUARD: + case SC_REPRODUCE: + case SC_INVISIBILITY: + if (tsce) { + i = status_change_end(bl, type, INVALID_TIMER); + if( i ) + clif_skill_nodamage(src,bl,skill_id,( skill_id == LG_FORCEOFVANGUARD ) ? skill_lv : -1,i); + else if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 0; + } + case RA_CAMOUFLAGE: + i = sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + if( i ) + clif_skill_nodamage(src,bl,skill_id,( skill_id == LG_FORCEOFVANGUARD ) ? skill_lv : -1,i); + else if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + + case BD_ADAPTATION: + if(tsc && tsc->data[SC_DANCING]){ + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + status_change_end(bl, SC_DANCING, INVALID_TIMER); + } + break; + + case BA_FROSTJOKER: + case DC_SCREAM: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + skill_addtimerskill(src,tick+2000,bl->id,src->x,src->y,skill_id,skill_lv,0,flag); + + if (md) { + // custom hack to make the mob display the skill, because these skills don't show the skill use text themselves + //NOTE: mobs don't have the sprite animation that is used when performing this skill (will cause glitches) + char temp[70]; + snprintf(temp, sizeof(temp), "%s : %s !!",md->name,skill_db[skill_id].desc); + clif_message(&md->bl,temp); + } + break; + + case BA_PANGVOICE: + clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(bl,SC_CONFUSION,50,7,skill_get_time(skill_id,skill_lv))); + break; + + case DC_WINKCHARM: + if( dstsd ) + clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start(bl,SC_CONFUSION,30,7,skill_get_time2(skill_id,skill_lv))); + else + if( dstmd ) + { + if( status_get_lv(src) > status_get_lv(bl) + && (tstatus->race == RC_DEMON || tstatus->race == RC_DEMIHUMAN || tstatus->race == RC_ANGEL) + && !(tstatus->mode&MD_BOSS) ) + clif_skill_nodamage(src,bl,skill_id,skill_lv, sc_start2(bl,type,70,skill_lv,src->id,skill_get_time(skill_id,skill_lv))); + else + { + clif_skill_nodamage(src,bl,skill_id,skill_lv,0); + if(sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } + } + break; + + case TF_STEAL: + if(sd) { + if(pc_steal_item(sd,bl,skill_lv)) + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + else + clif_skill_fail(sd,skill_id,USESKILL_FAIL,0); + } + break; + + case RG_STEALCOIN: + if(sd) { + if(pc_steal_coin(sd,bl)) + { + dstmd->state.provoke_flag = src->id; + mob_target(dstmd, src, skill_get_range2(src,skill_id,skill_lv)); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + + } + else + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } + break; + + case MG_STONECURSE: + { + int brate = 0; + if (tstatus->mode&MD_BOSS) { + if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + if(status_isimmune(bl) || !tsc) + break; + + if (sd && sd->sc.data[SC_PETROLOGY_OPTION]) + brate = sd->sc.data[SC_PETROLOGY_OPTION]->val3; + + if (tsc->data[SC_STONE]) { + status_change_end(bl, SC_STONE, INVALID_TIMER); + if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + if (sc_start4(bl,SC_STONE,(skill_lv*4+20)+brate, + skill_lv, 0, 0, skill_get_time(skill_id, skill_lv), + skill_get_time2(skill_id,skill_lv))) + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + else if(sd) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + // Level 6-10 doesn't consume a red gem if it fails [celest] + if (skill_lv > 5) + { // not to consume items + map_freeblock_unlock(); + return 0; + } + } + } + break; + + case NV_FIRSTAID: + clif_skill_nodamage(src,bl,skill_id,5,1); + status_heal(bl,5,0,0); + break; + + case AL_CURE: + if(status_isimmune(bl)) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,0); + break; + } + status_change_end(bl, SC_SILENCE, INVALID_TIMER); + status_change_end(bl, SC_BLIND, INVALID_TIMER); + status_change_end(bl, SC_CONFUSION, INVALID_TIMER); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + + case TF_DETOXIFY: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + status_change_end(bl, SC_POISON, INVALID_TIMER); + status_change_end(bl, SC_DPOISON, INVALID_TIMER); + break; + + case PR_STRECOVERY: + if(status_isimmune(bl)) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,0); + break; + } + if (tsc && tsc->opt1) { + status_change_end(bl, SC_FREEZE, INVALID_TIMER); + status_change_end(bl, SC_STONE, INVALID_TIMER); + status_change_end(bl, SC_SLEEP, INVALID_TIMER); + status_change_end(bl, SC_STUN, INVALID_TIMER); + status_change_end(bl, SC_WHITEIMPRISON, INVALID_TIMER); + } + //Is this equation really right? It looks so... special. + if(battle_check_undead(tstatus->race,tstatus->def_ele)) + { + status_change_start(bl, SC_BLIND, + 100*(100-(tstatus->int_/2+tstatus->vit/3+tstatus->luk/10)), + 1,0,0,0, + skill_get_time2(skill_id, skill_lv) * (100-(tstatus->int_+tstatus->vit)/2)/100,0); + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if(dstmd) + mob_unlocktarget(dstmd,tick); + break; + + // Mercenary Supportive Skills + case MER_BENEDICTION: + status_change_end(bl, SC_CURSE, INVALID_TIMER); + status_change_end(bl, SC_BLIND, INVALID_TIMER); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + case MER_COMPRESS: + status_change_end(bl, SC_BLEEDING, INVALID_TIMER); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + case MER_MENTALCURE: + status_change_end(bl, SC_CONFUSION, INVALID_TIMER); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + case MER_RECUPERATE: + status_change_end(bl, SC_POISON, INVALID_TIMER); + status_change_end(bl, SC_SILENCE, INVALID_TIMER); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + case MER_REGAIN: + status_change_end(bl, SC_SLEEP, INVALID_TIMER); + status_change_end(bl, SC_STUN, INVALID_TIMER); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + case MER_TENDER: + status_change_end(bl, SC_FREEZE, INVALID_TIMER); + status_change_end(bl, SC_STONE, INVALID_TIMER); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + + case MER_SCAPEGOAT: + if( mer && mer->master ) + { + status_heal(&mer->master->bl, mer->battle_status.hp, 0, 2); + status_damage(src, src, mer->battle_status.max_hp, 0, 0, 1); + } + break; + + case MER_ESTIMATION: + if( !mer ) + break; + sd = mer->master; + case WZ_ESTIMATION: + if( sd == NULL ) + break; + if( dstsd ) + { // Fail on Players + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + if( dstmd && dstmd->class_ == MOBID_EMPERIUM ) + break; // Cannot be Used on Emperium + + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + clif_skill_estimation(sd, bl); + if( skill_id == MER_ESTIMATION ) + sd = NULL; + break; + + case BS_REPAIRWEAPON: + if(sd && dstsd) + clif_item_repair_list(sd,dstsd,skill_lv); + break; + + case MC_IDENTIFY: + if(sd) + clif_item_identify_list(sd); + break; + + // Weapon Refining [Celest] + case WS_WEAPONREFINE: + if(sd) + clif_item_refine_list(sd); + break; + + case MC_VENDING: + if(sd) + { //Prevent vending of GMs with unnecessary Level to trade/drop. [Skotlex] + if ( !pc_can_give_items(sd) ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + else { + sd->state.prevend = 1; + clif_openvendingreq(sd,2+skill_lv); + } + } + break; + + case AL_TELEPORT: + if(sd) + { + if (map[bl->m].flag.noteleport && skill_lv <= 2) { + clif_skill_teleportmessage(sd,0); + break; + } + if(!battle_config.duel_allow_teleport && sd->duel_group && skill_lv <= 2) { // duel restriction [LuzZza] + char output[128]; sprintf(output, msg_txt(365), skill_get_name(AL_TELEPORT)); + clif_displaymessage(sd->fd, output); //"Duel: Can't use %s in duel." + break; + } + + if( sd->state.autocast || ( (sd->skillitem == AL_TELEPORT || battle_config.skip_teleport_lv1_menu) && skill_lv == 1 ) || skill_lv == 3 ) + { + if( skill_lv == 1 ) + pc_randomwarp(sd,CLR_TELEPORT); + else + pc_setpos(sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,CLR_TELEPORT); + break; + } + + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if( skill_lv == 1 ) + clif_skill_warppoint(sd,skill_id,skill_lv, (unsigned short)-1,0,0,0); + else + clif_skill_warppoint(sd,skill_id,skill_lv, (unsigned short)-1,sd->status.save_point.map,0,0); + } else + unit_warp(bl,-1,-1,-1,CLR_TELEPORT); + break; + + case NPC_EXPULSION: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + unit_warp(bl,-1,-1,-1,CLR_TELEPORT); + break; + + case AL_HOLYWATER: + if(sd) { + if (skill_produce_mix(sd, skill_id, 523, 0, 0, 0, 1)) + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + else + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } + break; + + case TF_PICKSTONE: + if(sd) { + int eflag; + struct item item_tmp; + struct block_list tbl; + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + memset(&item_tmp,0,sizeof(item_tmp)); + memset(&tbl,0,sizeof(tbl)); // [MouseJstr] + item_tmp.nameid = ITEMID_STONE; + item_tmp.identify = 1; + tbl.id = 0; + clif_takeitem(&sd->bl,&tbl); + eflag = pc_additem(sd,&item_tmp,1,LOG_TYPE_PRODUCE); + if(eflag) { + clif_additem(sd,0,0,eflag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + } + break; + case ASC_CDP: + if(sd) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + skill_produce_mix(sd, skill_id, 678, 0, 0, 0, 1); //Produce a Deadly Poison Bottle. + } + break; + + case RG_STRIPWEAPON: + case RG_STRIPSHIELD: + case RG_STRIPARMOR: + case RG_STRIPHELM: + case ST_FULLSTRIP: + case GC_WEAPONCRUSH: + case SC_STRIPACCESSARY: { + unsigned short location = 0; + int d = 0; + + //Rate in percent + if ( skill_id == ST_FULLSTRIP ) { + i = 5 + 2*skill_lv + (sstatus->dex - tstatus->dex)/5; + } else if( skill_id == SC_STRIPACCESSARY ) { + i = 12 + 2 * skill_lv + (sstatus->dex - tstatus->dex)/5; + } else { + i = 5 + 5*skill_lv + (sstatus->dex - tstatus->dex)/5; + } + + if (i < 5) i = 5; //Minimum rate 5% + + //Duration in ms + if( skill_id == GC_WEAPONCRUSH){ + d = skill_get_time(skill_id,skill_lv); + if(bl->type == BL_PC) + d += skill_lv * 15 + (sstatus->dex - tstatus->dex); + else + d += skill_lv * 30 + (sstatus->dex - tstatus->dex) / 2; + }else + d = skill_get_time(skill_id,skill_lv) + (sstatus->dex - tstatus->dex)*500; + + if (d < 0) d = 0; //Minimum duration 0ms + + switch (skill_id) { + case RG_STRIPWEAPON: + case GC_WEAPONCRUSH: + location = EQP_WEAPON; + break; + case RG_STRIPSHIELD: + location = EQP_SHIELD; + break; + case RG_STRIPARMOR: + location = EQP_ARMOR; + break; + case RG_STRIPHELM: + location = EQP_HELM; + break; + case ST_FULLSTRIP: + location = EQP_WEAPON|EQP_SHIELD|EQP_ARMOR|EQP_HELM; + break; + case SC_STRIPACCESSARY: + location = EQP_ACC; + break; + } + + //Special message when trying to use strip on FCP [Jobbie] + if( sd && skill_id == ST_FULLSTRIP && tsc && tsc->data[SC_CP_WEAPON] && tsc->data[SC_CP_HELM] && tsc->data[SC_CP_ARMOR] && tsc->data[SC_CP_SHIELD]) + { + clif_gospel_info(sd, 0x28); + break; + } + + //Attempts to strip at rate i and duration d + if( (i = skill_strip_equip(bl, location, i, skill_lv, d)) || (skill_id != ST_FULLSTRIP && skill_id != GC_WEAPONCRUSH ) ) + clif_skill_nodamage(src,bl,skill_id,skill_lv,i); + + //Nothing stripped. + if( sd && !i ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + + case AM_BERSERKPITCHER: + case AM_POTIONPITCHER: { + int i,x,hp = 0,sp = 0,bonus=100; + if( dstmd && dstmd->class_ == MOBID_EMPERIUM ) { + map_freeblock_unlock(); + return 1; + } + if( sd ) { + x = skill_lv%11 - 1; + i = pc_search_inventory(sd,skill_db[skill_id].itemid[x]); + if( i < 0 || skill_db[skill_id].itemid[x] <= 0 ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 1; + } + if(sd->inventory_data[i] == NULL || sd->status.inventory[i].amount < skill_db[skill_id].amount[x]) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 1; + } + if( skill_id == AM_BERSERKPITCHER ) { + if( dstsd && dstsd->status.base_level < (unsigned int)sd->inventory_data[i]->elv ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 1; + } + } + potion_flag = 1; + potion_hp = potion_sp = potion_per_hp = potion_per_sp = 0; + potion_target = bl->id; + run_script(sd->inventory_data[i]->script,0,sd->bl.id,0); + potion_flag = potion_target = 0; + if( sd->sc.data[SC_SPIRIT] && sd->sc.data[SC_SPIRIT]->val2 == SL_ALCHEMIST ) + bonus += sd->status.base_level; + if( potion_per_hp > 0 || potion_per_sp > 0 ) { + hp = tstatus->max_hp * potion_per_hp / 100; + hp = hp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000; + if( dstsd ) { + sp = dstsd->status.max_sp * potion_per_sp / 100; + sp = sp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000; + } + } else { + if( potion_hp > 0 ) { + hp = potion_hp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000; + hp = hp * (100 + (tstatus->vit<<1)) / 100; + if( dstsd ) + hp = hp * (100 + pc_checkskill(dstsd,SM_RECOVERY)*10) / 100; + } + if( potion_sp > 0 ) { + sp = potion_sp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000; + sp = sp * (100 + (tstatus->int_<<1)) / 100; + if( dstsd ) + sp = sp * (100 + pc_checkskill(dstsd,MG_SRECOVERY)*10) / 100; + } + } + + if (sd->itemgrouphealrate[IG_POTION]>0) { + hp += hp * sd->itemgrouphealrate[IG_POTION] / 100; + sp += sp * sd->itemgrouphealrate[IG_POTION] / 100; + } + + if( (i = pc_skillheal_bonus(sd, skill_id)) ) { + hp += hp * i / 100; + sp += sp * i / 100; + } + } else { + hp = (1 + rnd()%400) * (100 + skill_lv*10) / 100; + hp = hp * (100 + (tstatus->vit<<1)) / 100; + if( dstsd ) + hp = hp * (100 + pc_checkskill(dstsd,SM_RECOVERY)*10) / 100; + } + if( dstsd && (i = pc_skillheal2_bonus(dstsd, skill_id)) ) { + hp += hp * i / 100; + sp += sp * i / 100; + } + if( tsc && tsc->count ) { + if( tsc->data[SC_CRITICALWOUND] ) { + hp -= hp * tsc->data[SC_CRITICALWOUND]->val2 / 100; + sp -= sp * tsc->data[SC_CRITICALWOUND]->val2 / 100; + } + if( tsc->data[SC_DEATHHURT] ) { + hp -= hp * 20 / 100; + sp -= sp * 20 / 100; + } + if( tsc->data[SC_WATER_INSIGNIA] && tsc->data[SC_WATER_INSIGNIA]->val1 == 2 ) { + hp += hp / 10; + sp += sp / 10; + } + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if( hp > 0 || (skill_id == AM_POTIONPITCHER && sp <= 0) ) + clif_skill_nodamage(NULL,bl,AL_HEAL,hp,1); + if( sp > 0 ) + clif_skill_nodamage(NULL,bl,MG_SRECOVERY,sp,1); +#ifdef RENEWAL + if( tsc && tsc->data[SC_EXTREMITYFIST2] ) + sp = 0; +#endif + status_heal(bl,hp,sp,0); + } + break; + case AM_CP_WEAPON: + case AM_CP_SHIELD: + case AM_CP_ARMOR: + case AM_CP_HELM: + { + unsigned int equip[] = {EQP_WEAPON, EQP_SHIELD, EQP_ARMOR, EQP_HEAD_TOP}; + + if( sd && ( bl->type != BL_PC || ( dstsd && pc_checkequip(dstsd,equip[skill_id - AM_CP_WEAPON]) < 0 ) ) ){ + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); // Don't consume item requirements + return 0; + } + + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + } + break; + case AM_TWILIGHT1: + if (sd) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + //Prepare 200 White Potions. + if (!skill_produce_mix(sd, skill_id, 504, 0, 0, 0, 200)) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } + break; + case AM_TWILIGHT2: + if (sd) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + //Prepare 200 Slim White Potions. + if (!skill_produce_mix(sd, skill_id, 547, 0, 0, 0, 200)) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } + break; + case AM_TWILIGHT3: + if (sd) { + int ebottle = pc_search_inventory(sd,713); + if( ebottle >= 0 ) + ebottle = sd->status.inventory[ebottle].amount; + //check if you can produce all three, if not, then fail: + if (!skill_can_produce_mix(sd,970,-1, 100) //100 Alcohol + || !skill_can_produce_mix(sd,7136,-1, 50) //50 Acid Bottle + || !skill_can_produce_mix(sd,7135,-1, 50) //50 Flame Bottle + || ebottle < 200 //200 empty bottle are required at total. + ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + skill_produce_mix(sd, skill_id, 970, 0, 0, 0, 100); + skill_produce_mix(sd, skill_id, 7136, 0, 0, 0, 50); + skill_produce_mix(sd, skill_id, 7135, 0, 0, 0, 50); + } + break; + case SA_DISPELL: + if (flag&1 || (i = skill_get_splash(skill_id, skill_lv)) < 1) + { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if((dstsd && (dstsd->class_&MAPID_UPPERMASK) == MAPID_SOUL_LINKER) + || (tsc && tsc->data[SC_SPIRIT] && tsc->data[SC_SPIRIT]->val2 == SL_ROGUE) //Rogue's spirit defends againt dispel. + || rnd()%100 >= 50+10*skill_lv + || ( tsc && tsc->option&OPTION_MADOGEAR ) )//Mado Gear is immune to dispell according to bug report 49 [Ind] + { + if (sd) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + if(status_isimmune(bl) || !tsc || !tsc->count) + break; + for(i=0;i<SC_MAX;i++) + { + if (!tsc->data[i]) + continue; + switch (i) { + case SC_WEIGHT50: case SC_WEIGHT90: case SC_HALLUCINATION: + 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: case SC_COMBO: + case SC_STRFOOD: case SC_AGIFOOD: case SC_VITFOOD: + case SC_INTFOOD: case SC_DEXFOOD: case SC_LUKFOOD: + case SC_HITFOOD: case SC_FLEEFOOD: case SC_BATKFOOD: + case SC_WATKFOOD: case SC_MATKFOOD: case SC_DANCING: + case SC_EDP: case SC_AUTOBERSERK: + case SC_CARTBOOST: case SC_MELTDOWN: case SC_SAFETYWALL: + case SC_SMA: case SC_SPEEDUP0: case SC_NOCHAT: + case SC_ANKLE: case SC_SPIDERWEB: case SC_JAILED: + case SC_ITEMBOOST: case SC_EXPBOOST: case SC_LIFEINSURANCE: + case SC_BOSSMAPINFO: case SC_PNEUMA: case SC_AUTOSPELL: + case SC_INCHITRATE: case SC_INCATKRATE: case SC_NEN: + case SC_READYSTORM: case SC_READYDOWN: case SC_READYTURN: + case SC_READYCOUNTER: case SC_DODGE: case SC_WARM: + case SC_SPEEDUP1: case SC_AUTOTRADE: case SC_CRITICALWOUND: + case SC_JEXPBOOST: case SC_INVINCIBLE: case SC_INVINCIBLEOFF: + case SC_HELLPOWER: case SC_MANU_ATK: case SC_MANU_DEF: + case SC_SPL_ATK: case SC_SPL_DEF: case SC_MANU_MATK: + case SC_SPL_MATK: case SC_RICHMANKIM: case SC_ETERNALCHAOS: + case SC_DRUMBATTLE: case SC_NIBELUNGEN: case SC_ROKISWEIL: + case SC_INTOABYSS: case SC_SIEGFRIED: case SC_FOOD_STR_CASH: + case SC_FOOD_AGI_CASH: case SC_FOOD_VIT_CASH: case SC_FOOD_DEX_CASH: + case SC_FOOD_INT_CASH: case SC_FOOD_LUK_CASH: case SC_SEVENWIND: + case SC_MIRACLE: case SC_S_LIFEPOTION: case SC_L_LIFEPOTION: + case SC_INCHEALRATE: case SC_ELECTRICSHOCKER: case SC__STRIPACCESSORY: + //case SC_SAVAGE_STEAK: case SC_COCKTAIL_WARG_BLOOD: case SC_MINOR_BBQ: + //case SC_SIROMA_ICE_TEA: case SC_DROCERA_HERB_STEAMED: case SC_PUTTI_TAILS_NOODLES: + case SC_NEUTRALBARRIER_MASTER: case SC_NEUTRALBARRIER: case SC_STEALTHFIELD_MASTER: + case SC_STEALTHFIELD: case SC_GIANTGROWTH: case SC_MILLENNIUMSHIELD: + case SC_REFRESH: case SC_STONEHARDSKIN: case SC_VITALITYACTIVATION: + case SC_FIGHTINGSPIRIT: case SC_ABUNDANCE: case SC__SHADOWFORM: + case SC_LEADERSHIP: case SC_GLORYWOUNDS: case SC_SOULCOLD: + case SC_HAWKEYES: case SC_GUILDAURA: case SC_PUSH_CART: + case SC_RAISINGDRAGON: case SC_GT_ENERGYGAIN: case SC_GT_CHANGE: + case SC_GT_REVITALIZE: case SC_REFLECTDAMAGE: case SC_INSPIRATION: + case SC_EXEEDBREAK: case SC_FORCEOFVANGUARD: case SC_BANDING: + case SC_DUPLELIGHT: case SC_EXPIATIO: case SC_LAUDAAGNUS: + case SC_LAUDARAMUS: case SC_GATLINGFEVER: case SC_INCREASING: + case SC_ADJUSTMENT: case SC_MADNESSCANCEL: +#ifdef RENEWAL + case SC_EXTREMITYFIST2: +#endif + continue; + /** + * bugreport:4888 these songs may only be dispelled if you're not in their song area anymore + **/ + case SC_WHISTLE: + case SC_ASSNCROS: + case SC_POEMBRAGI: + case SC_APPLEIDUN: + case SC_HUMMING: + case SC_DONTFORGETME: + case SC_FORTUNE: + case SC_SERVICE4U: + if( tsc->data[i]->val4 ) //val4 = out-of-song-area + continue; + break; + case SC_ASSUMPTIO: + if( bl->type == BL_MOB ) + continue; + break; + } + if(i==SC_BERSERK || i==SC_SATURDAYNIGHTFEVER) tsc->data[i]->val2=0; //Mark a dispelled berserk to avoid setting hp to 100 by setting hp penalty to 0. + status_change_end(bl, (sc_type)i, INVALID_TIMER); + } + break; + } + //Affect all targets on splash area. + map_foreachinrange(skill_area_sub, bl, i, BL_CHAR, + src, skill_id, skill_lv, tick, flag|1, + skill_castend_damage_id); + break; + + case TF_BACKSLIDING: //This is the correct implementation as per packet logging information. [Skotlex] + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + skill_blown(src,bl,skill_get_blewcount(skill_id,skill_lv),unit_getdir(bl),0); + break; + + case TK_HIGHJUMP: + { + int x,y, dir = unit_getdir(src); + + //Fails on noteleport maps, except for GvG and BG maps [Skotlex] + if( map[src->m].flag.noteleport && + !(map[src->m].flag.battleground || map_flag_gvg2(src->m) ) + ) { + x = src->x; + y = src->y; + } else { + x = src->x + dirx[dir]*skill_lv*2; + y = src->y + diry[dir]*skill_lv*2; + } + + clif_skill_nodamage(src,bl,TK_HIGHJUMP,skill_lv,1); + if(!map_count_oncell(src->m,x,y,BL_PC|BL_NPC|BL_MOB) && map_getcell(src->m,x,y,CELL_CHKREACH)) { + clif_slide(src,x,y); + unit_movepos(src, x, y, 1, 0); + } + } + break; + + case SA_CASTCANCEL: + case SO_SPELLFIST: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + unit_skillcastcancel(src,1); + if(sd) { + int sp = skill_get_sp(sd->skill_id_old,sd->skill_lv_old); + if( skill_id == SO_SPELLFIST ){ + sc_start4(src,type,100,skill_lv+1,skill_lv,sd->skill_id_old,sd->skill_lv_old,skill_get_time(skill_id,skill_lv)); + sd->skill_id_old = sd->skill_lv_old = 0; + break; + } + sp = sp * (90 - (skill_lv-1)*20) / 100; + if(sp < 0) sp = 0; + status_zap(src, 0, sp); + } + break; + case SA_SPELLBREAKER: + { + int sp; + if(tsc && tsc->data[SC_MAGICROD]) { + sp = skill_get_sp(skill_id,skill_lv); + sp = sp * tsc->data[SC_MAGICROD]->val2 / 100; + if(sp < 1) sp = 1; + status_heal(bl,0,sp,2); + status_percent_damage(bl, src, 0, -20, false); //20% max SP damage. + } else { + struct unit_data *ud = unit_bl2ud(bl); + int bl_skill_id=0,bl_skill_lv=0,hp = 0; + if (!ud || ud->skilltimer == INVALID_TIMER) + break; //Nothing to cancel. + bl_skill_id = ud->skill_id; + bl_skill_lv = ud->skill_lv; + if (tstatus->mode & MD_BOSS) + { //Only 10% success chance against bosses. [Skotlex] + if (rnd()%100 < 90) + { + if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + } else if (!dstsd || map_flag_vs(bl->m)) //HP damage only on pvp-maps when against players. + hp = tstatus->max_hp/50; //Recover 2% HP [Skotlex] + + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + unit_skillcastcancel(bl,0); + sp = skill_get_sp(bl_skill_id,bl_skill_lv); + status_zap(bl, hp, sp); + + if (hp && skill_lv >= 5) + hp>>=1; //Recover half damaged HP at level 5 [Skotlex] + else + hp = 0; + + if (sp) //Recover some of the SP used + sp = sp*(25*(skill_lv-1))/100; + + if(hp || sp) + status_heal(src, hp, sp, 2); + } + } + break; + case SA_MAGICROD: + clif_skill_nodamage(src,src,SA_MAGICROD,skill_lv,1); + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + case SA_AUTOSPELL: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if(sd) + clif_autospell(sd,skill_lv); + else { + int maxlv=1,spellid=0; + static const int spellarray[3] = { MG_COLDBOLT,MG_FIREBOLT,MG_LIGHTNINGBOLT }; + if(skill_lv >= 10) { + spellid = MG_FROSTDIVER; +// if (tsc && tsc->data[SC_SPIRIT] && tsc->data[SC_SPIRIT]->val2 == SA_SAGE) +// maxlv = 10; +// else + maxlv = skill_lv - 9; + } + else if(skill_lv >=8) { + spellid = MG_FIREBALL; + maxlv = skill_lv - 7; + } + else if(skill_lv >=5) { + spellid = MG_SOULSTRIKE; + maxlv = skill_lv - 4; + } + else if(skill_lv >=2) { + int i = rnd()%3; + spellid = spellarray[i]; + maxlv = skill_lv - 1; + } + else if(skill_lv > 0) { + spellid = MG_NAPALMBEAT; + maxlv = 3; + } + if(spellid > 0) + sc_start4(src,SC_AUTOSPELL,100,skill_lv,spellid,maxlv,0, + skill_get_time(SA_AUTOSPELL,skill_lv)); + } + break; + + case BS_GREED: + if(sd){ + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinrange(skill_greed,bl, + skill_get_splash(skill_id, skill_lv),BL_ITEM,bl); + } + break; + + case SA_ELEMENTWATER: + case SA_ELEMENTFIRE: + case SA_ELEMENTGROUND: + case SA_ELEMENTWIND: + if(sd && !dstmd) //Only works on monsters. + break; + if(tstatus->mode&MD_BOSS) + 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: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start2(bl, type, 100, skill_lv, skill_get_ele(skill_id,skill_lv), + skill_get_time(skill_id, skill_lv))); + break; + case NPC_CHANGEUNDEAD: + //This skill should fail if target is wearing bathory/evil druid card [Brainstorm] + //TO-DO This is ugly, fix it + if(tstatus->def_ele==ELE_UNDEAD || tstatus->def_ele==ELE_DARK) break; + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start2(bl, type, 100, skill_lv, skill_get_ele(skill_id,skill_lv), + skill_get_time(skill_id, skill_lv))); + break; + + case NPC_PROVOCATION: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if (md) mob_unlocktarget(md, tick); + break; + + case NPC_KEEPING: + case NPC_BARRIER: + { + int skill_time = skill_get_time(skill_id,skill_lv); + struct unit_data *ud = unit_bl2ud(bl); + if (clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_time)) + && ud) { //Disable attacking/acting/moving for skill's duration. + ud->attackabletime = + ud->canact_tick = + ud->canmove_tick = tick + skill_time; + } + } + break; + + case NPC_REBIRTH: + if( md && md->state.rebirth ) + break; // only works once + sc_start(bl,type,100,skill_lv,-1); + break; + + case NPC_DARKBLESSING: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start2(bl,type,(50+skill_lv*5),skill_lv,skill_lv,skill_get_time2(skill_id,skill_lv))); + break; + + case NPC_LICK: + status_zap(bl, 0, 100); + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,(skill_lv*5),skill_lv,skill_get_time2(skill_id,skill_lv))); + break; + + case NPC_SUICIDE: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + status_kill(src); //When suiciding, neither exp nor drops is given. + break; + + case NPC_SUMMONSLAVE: + case NPC_SUMMONMONSTER: + if(md && md->skill_idx >= 0) + mob_summonslave(md,md->db->skill[md->skill_idx].val,skill_lv,skill_id); + break; + + case NPC_CALLSLAVE: + mob_warpslave(src,MOB_SLAVEDISTANCE); + break; + + case NPC_RANDOMMOVE: + if (md) { + md->next_walktime = tick - 1; + mob_randomwalk(md,tick); + } + break; + + case NPC_SPEEDUP: + { + // or does it increase casting rate? just a guess xD + int i = SC_ASPDPOTION0 + skill_lv - 1; + if (i > SC_ASPDPOTION3) + i = SC_ASPDPOTION3; + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,(sc_type)i,100,skill_lv,skill_lv * 60000)); + } + break; + + case NPC_REVENGE: + // not really needed... but adding here anyway ^^ + if (md && md->master_id > 0) { + struct block_list *mbl, *tbl; + if ((mbl = map_id2bl(md->master_id)) == NULL || + (tbl = battle_gettargeted(mbl)) == NULL) + break; + md->state.provoke_flag = tbl->id; + mob_target(md, tbl, sstatus->rhw.range); + } + break; + + case NPC_RUN: + { + const int mask[8][2] = {{0,-1},{1,-1},{1,0},{1,1},{0,1},{-1,1},{-1,0},{-1,-1}}; + uint8 dir = (bl == src)?unit_getdir(src):map_calc_dir(src,bl->x,bl->y); //If cast on self, run forward, else run away. + unit_stop_attack(src); + //Run skillv tiles overriding the can-move check. + if (unit_walktoxy(src, src->x + skill_lv * mask[dir][0], src->y + skill_lv * mask[dir][1], 2) && md) + md->state.skillstate = MSS_WALK; //Otherwise it isn't updated in the ai. + } + break; + + case NPC_TRANSFORMATION: + case NPC_METAMORPHOSIS: + if(md && md->skill_idx >= 0) { + int class_ = mob_random_class (md->db->skill[md->skill_idx].val,0); + if (skill_lv > 1) //Multiply the rest of mobs. [Skotlex] + mob_summonslave(md,md->db->skill[md->skill_idx].val,skill_lv-1,skill_id); + if (class_) mob_class_change(md, class_); + } + break; + + case NPC_EMOTION_ON: + case NPC_EMOTION: + //va[0] is the emotion to use. + //NPC_EMOTION & NPC_EMOTION_ON can change a mob's mode 'permanently' [Skotlex] + //val[1] 'sets' the mode + //val[2] adds to the current mode + //val[3] removes from the current mode + //val[4] if set, asks to delete the previous mode change. + if(md && md->skill_idx >= 0 && tsc) + { + clif_emotion(bl, md->db->skill[md->skill_idx].val[0]); + if(md->db->skill[md->skill_idx].val[4] && tsce) + status_change_end(bl, type, INVALID_TIMER); + + if(md->db->skill[md->skill_idx].val[1] || md->db->skill[md->skill_idx].val[2]) + sc_start4(src, type, 100, skill_lv, + md->db->skill[md->skill_idx].val[1], + md->db->skill[md->skill_idx].val[2], + md->db->skill[md->skill_idx].val[3], + skill_get_time(skill_id, skill_lv)); + } + break; + + case NPC_POWERUP: + sc_start(bl,SC_INCATKRATE,100,200,skill_get_time(skill_id, skill_lv)); + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,100,skill_get_time(skill_id, skill_lv))); + break; + + case NPC_AGIUP: + sc_start(bl,SC_SPEEDUP1,100,skill_lv,skill_get_time(skill_id, skill_lv)); + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,100,skill_get_time(skill_id, skill_lv))); + break; + + case NPC_INVISIBLE: + //Have val4 passed as 6 is for "infinite cloak" (do not end on attack/skill use). + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start4(bl,type,100,skill_lv,0,0,6,skill_get_time(skill_id,skill_lv))); + break; + + case NPC_SIEGEMODE: + // not sure what it does + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + + case WE_MALE: + { + int hp_rate=(!skill_lv)? 0:skill_db[skill_id].hp_rate[skill_lv-1]; + int gain_hp= tstatus->max_hp*abs(hp_rate)/100; // The earned is the same % of the target HP than it costed the caster. [Skotlex] + clif_skill_nodamage(src,bl,skill_id,status_heal(bl, gain_hp, 0, 0),1); + } + break; + case WE_FEMALE: + { + int sp_rate=(!skill_lv)? 0:skill_db[skill_id].sp_rate[skill_lv-1]; + int gain_sp=tstatus->max_sp*abs(sp_rate)/100;// The earned is the same % of the target SP than it costed the caster. [Skotlex] + clif_skill_nodamage(src,bl,skill_id,status_heal(bl, 0, gain_sp, 0),1); + } + break; + + // parent-baby skills + case WE_BABY: + if(sd){ + struct map_session_data *f_sd = pc_get_father(sd); + struct map_session_data *m_sd = pc_get_mother(sd); + // if neither was found + if(!f_sd && !m_sd){ + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 0; + } + status_change_start(bl,SC_STUN,10000,skill_lv,0,0,0,skill_get_time2(skill_id,skill_lv),8); + if (f_sd) sc_start(&f_sd->bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + if (m_sd) sc_start(&m_sd->bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + } + break; + + case PF_HPCONVERSION: + { + int hp, sp; + hp = sstatus->max_hp/10; + sp = hp * 10 * skill_lv / 100; + if (!status_charge(src,hp,0)) { + if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + status_heal(bl,0,sp,2); + } + break; + + case MA_REMOVETRAP: + case HT_REMOVETRAP: + { + struct skill_unit* su; + struct skill_unit_group* sg; + su = BL_CAST(BL_SKILL, bl); + + // Mercenaries can remove any trap + // Players can only remove their own traps or traps on Vs maps. + if( su && (sg = su->group) && (src->type == BL_MER || sg->src_id == src->id || map_flag_vs(bl->m)) && (skill_get_inf2(sg->skill_id)&INF2_TRAP) ) + { + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + if( sd && !(sg->unit_id == UNT_USED_TRAPS || (sg->unit_id == UNT_ANKLESNARE && sg->val2 != 0 )) ) + { // prevent picking up expired traps + if( battle_config.skill_removetrap_type ) + { // get back all items used to deploy the trap + for( i = 0; i < 10; i++ ) + { + if( skill_db[su->group->skill_id].itemid[i] > 0 ) + { + int flag; + struct item item_tmp; + 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],LOG_TYPE_OTHER)) ) + { + 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,0,0,0,0); + } + } + } + } + else + { // get back 1 trap + struct item item_tmp; + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid = su->group->item_id?su->group->item_id:ITEMID_TRAP; + item_tmp.identify = 1; + if( item_tmp.nameid && (flag=pc_additem(sd,&item_tmp,1,LOG_TYPE_OTHER)) ) + { + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + } + } + skill_delunit(su); + }else if(sd) + clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); + + } + break; + case HT_SPRINGTRAP: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + { + struct skill_unit *su=NULL; + if((bl->type==BL_SKILL) && (su=(struct skill_unit *)bl) && (su->group) ){ + switch(su->group->unit_id){ + case UNT_ANKLESNARE: // ankle snare + if (su->group->val2 != 0) + // if it is already trapping something don't spring it, + // remove trap should be used instead + break; + // otherwise fallthrough to below + case UNT_BLASTMINE: + case UNT_SKIDTRAP: + case UNT_LANDMINE: + case UNT_SHOCKWAVE: + case UNT_SANDMAN: + case UNT_FLASHER: + case UNT_FREEZINGTRAP: + case UNT_CLAYMORETRAP: + case UNT_TALKIEBOX: + su->group->unit_id = UNT_USED_TRAPS; + clif_changetraplook(bl, UNT_USED_TRAPS); + 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,skill_id,skill_lv,1); + if(sd) + unit_skilluse_id(src,src->id,sd->skill_id_dance,sd->skill_lv_dance); + break; + + case AS_SPLASHER: + if(tstatus->mode&MD_BOSS + /** + * Renewal dropped the 3/4 hp requirement + **/ + #ifndef RENEWAL + || tstatus-> hp > tstatus->max_hp*3/4 + #endif + ) { + if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 1; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start4(bl,type,100,skill_lv,skill_id,src->id,skill_get_time(skill_id,skill_lv),1000)); +#ifndef RENEWAL + if (sd) skill_blockpc_start (sd, skill_id, skill_get_time(skill_id, skill_lv)+3000); +#endif + break; + + case PF_MINDBREAKER: + { + if(tstatus->mode&MD_BOSS || battle_check_undead(tstatus->race,tstatus->def_ele)) + { + map_freeblock_unlock(); + return 1; + } + + if (tsce) + { //HelloKitty2 (?) explained that this silently fails when target is + //already inflicted. [Skotlex] + map_freeblock_unlock(); + return 1; + } + + //Has a 55% + skill_lv*5% success chance. + if (!clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,55+5*skill_lv,skill_lv,skill_get_time(skill_id,skill_lv)))) + { + if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 0; + } + + unit_skillcastcancel(bl,0); + + if(tsc && tsc->count){ + status_change_end(bl, SC_FREEZE, INVALID_TIMER); + if(tsc->data[SC_STONE] && tsc->opt1 == OPT1_STONE) + status_change_end(bl, SC_STONE, INVALID_TIMER); + status_change_end(bl, SC_SLEEP, INVALID_TIMER); + } + + if(dstmd) + mob_target(dstmd,src,skill_get_range2(src,skill_id,skill_lv)); + } + break; + + case PF_SOULCHANGE: + { + unsigned int sp1 = 0, sp2 = 0; + if (dstmd) { + if (dstmd->state.soul_change_flag) { + if(sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + dstmd->state.soul_change_flag = 1; + sp2 = sstatus->max_sp * 3 /100; + status_heal(src, 0, sp2, 2); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + } + sp1 = sstatus->sp; + sp2 = tstatus->sp; + #ifdef RENEWAL + sp1 = sp1 / 2; + sp2 = sp2 / 2; + if( tsc && tsc->data[SC_EXTREMITYFIST2] ) + sp1 = tstatus->sp; + #endif + status_set_sp(src, sp2, 3); + status_set_sp(bl, sp1, 3); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + // Slim Pitcher + case CR_SLIMPITCHER: + // Updated to block Slim Pitcher from working on barricades and guardian stones. + if( dstmd && (dstmd->class_ == MOBID_EMPERIUM || (dstmd->class_ >= MOBID_BARRICADE1 && dstmd->class_ <= MOBID_GUARIDAN_STONE2)) ) + break; + if (potion_hp || potion_sp) { + int hp = potion_hp, sp = potion_sp; + hp = hp * (100 + (tstatus->vit<<1))/100; + sp = sp * (100 + (tstatus->int_<<1))/100; + if (dstsd) { + if (hp) + hp = hp * (100 + pc_checkskill(dstsd,SM_RECOVERY)*10 + pc_skillheal2_bonus(dstsd, skill_id))/100; + if (sp) + sp = sp * (100 + pc_checkskill(dstsd,MG_SRECOVERY)*10 + pc_skillheal2_bonus(dstsd, skill_id))/100; + } + if( tsc && tsc->count ) { + if (tsc->data[SC_CRITICALWOUND]) { + hp -= hp * tsc->data[SC_CRITICALWOUND]->val2 / 100; + sp -= sp * tsc->data[SC_CRITICALWOUND]->val2 / 100; + } + if (tsc->data[SC_DEATHHURT]) { + hp -= hp * 20 / 100; + sp -= sp * 20 / 100; + } + if( tsc->data[SC_WATER_INSIGNIA] && tsc->data[SC_WATER_INSIGNIA]->val1 == 2) { + hp += hp / 10; + sp += sp / 10; + } + } + if(hp > 0) + clif_skill_nodamage(NULL,bl,AL_HEAL,hp,1); + if(sp > 0) + clif_skill_nodamage(NULL,bl,MG_SRECOVERY,sp,1); + status_heal(bl,hp,sp,0); + } + break; + // Full Chemical Protection + case CR_FULLPROTECTION: + { + unsigned int equip[] = {EQP_WEAPON, EQP_SHIELD, EQP_ARMOR, EQP_HEAD_TOP}; + int i, s = 0, skilltime = skill_get_time(skill_id,skill_lv); + + for (i=0 ; i<4; i++) { + if( bl->type != BL_PC || ( dstsd && pc_checkequip(dstsd,equip[i]) < 0 ) ) + continue; + sc_start(bl,(sc_type)(SC_CP_WEAPON + i),100,skill_lv,skilltime); + s++; + } + if( sd && !s ){ + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); // Don't consume item requirements + return 0; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case RG_CLEANER: //AppleGirl + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + + case CG_LONGINGFREEDOM: + { + if (tsc && !tsce && (tsce=tsc->data[SC_DANCING]) && tsce->val4 + && (tsce->val1&0xFFFF) != CG_MOONLIT) //Can't use Longing for Freedom while under Moonlight Petals. [Skotlex] + { + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + } + } + break; + + case CG_TAROTCARD: + { + int eff, count = -1; + if( rnd() % 100 > skill_lv * 8 || (dstmd && ((dstmd->guardian_data && dstmd->class_ == MOBID_EMPERIUM) || mob_is_battleground(dstmd))) ) + { + if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + + map_freeblock_unlock(); + return 0; + } + status_zap(src,0,skill_db[skill_get_index(skill_id)].sp[skill_lv]); // consume sp only if succeeded [Inkfish] + do { + eff = rnd() % 14; + clif_specialeffect(bl, 523 + eff, AREA); + switch (eff) + { + case 0: // heals SP to 0 + status_percent_damage(src, bl, 0, 100, false); + break; + case 1: // matk halved + sc_start(bl,SC_INCMATKRATE,100,-50,skill_get_time2(skill_id,skill_lv)); + break; + case 2: // all buffs removed + status_change_clear_buffs(bl,1); + break; + case 3: // 1000 damage, random armor destroyed + { + int where[] = { EQP_ARMOR, EQP_SHIELD, EQP_HELM, EQP_SHOES, EQP_GARMENT }; + status_fix_damage(src, bl, 1000, 0); + clif_damage(src,bl,tick,0,0,1000,0,0,0); + if( !status_isdead(bl) ) + skill_break_equip(bl, where[rnd()%5], 10000, BCT_ENEMY); + } + break; + case 4: // atk halved + sc_start(bl,SC_INCATKRATE,100,-50,skill_get_time2(skill_id,skill_lv)); + break; + case 5: // 2000HP heal, random teleported + status_heal(src, 2000, 0, 0); + if( !map_flag_vs(bl->m) ) + unit_warp(bl, -1,-1,-1, CLR_TELEPORT); + break; + case 6: // random 2 other effects + if (count == -1) + count = 3; + else + count++; //Should not retrigger this one. + break; + case 7: // stop freeze or stoned + { + enum sc_type sc[] = { SC_STOP, SC_FREEZE, SC_STONE }; + sc_start(bl,sc[rnd()%3],100,skill_lv,skill_get_time2(skill_id,skill_lv)); + } + break; + case 8: // curse coma and poison + sc_start(bl,SC_COMA,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + sc_start(bl,SC_CURSE,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + sc_start(bl,SC_POISON,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case 9: // confusion + sc_start(bl,SC_CONFUSION,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + case 10: // 6666 damage, atk matk halved, cursed + status_fix_damage(src, bl, 6666, 0); + clif_damage(src,bl,tick,0,0,6666,0,0,0); + sc_start(bl,SC_INCATKRATE,100,-50,skill_get_time2(skill_id,skill_lv)); + sc_start(bl,SC_INCMATKRATE,100,-50,skill_get_time2(skill_id,skill_lv)); + sc_start(bl,SC_CURSE,skill_lv,100,skill_get_time2(skill_id,skill_lv)); + break; + case 11: // 4444 damage + status_fix_damage(src, bl, 4444, 0); + clif_damage(src,bl,tick,0,0,4444,0,0,0); + break; + case 12: // stun + sc_start(bl,SC_STUN,100,skill_lv,5000); + break; + case 13: // atk,matk,hit,flee,def reduced + sc_start(bl,SC_INCATKRATE,100,-20,skill_get_time2(skill_id,skill_lv)); + sc_start(bl,SC_INCMATKRATE,100,-20,skill_get_time2(skill_id,skill_lv)); + sc_start(bl,SC_INCHITRATE,100,-20,skill_get_time2(skill_id,skill_lv)); + sc_start(bl,SC_INCFLEERATE,100,-20,skill_get_time2(skill_id,skill_lv)); + sc_start(bl,SC_INCDEFRATE,100,-20,skill_get_time2(skill_id,skill_lv)); + break; + default: + break; + } + } while ((--count) > 0); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case SL_ALCHEMIST: + case SL_ASSASIN: + case SL_BARDDANCER: + case SL_BLACKSMITH: + case SL_CRUSADER: + case SL_HUNTER: + case SL_KNIGHT: + case SL_MONK: + case SL_PRIEST: + case SL_ROGUE: + case SL_SAGE: + case SL_SOULLINKER: + case SL_STAR: + case SL_SUPERNOVICE: + case SL_WIZARD: + //NOTE: here, 'type' has the value of the associated MAPID, not of the SC_SPIRIT constant. + if (sd && !(dstsd && (dstsd->class_&MAPID_UPPERMASK) == type)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + if (skill_id == SL_SUPERNOVICE && dstsd && dstsd->die_counter && !(rnd()%100)) + { //Erase death count 1% of the casts + dstsd->die_counter = 0; + pc_setglobalreg(dstsd,"PC_DIE_COUNTER", 0); + clif_specialeffect(bl, 0x152, AREA); + //SC_SPIRIT invokes status_calc_pc for us. + } + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start4(bl,SC_SPIRIT,100,skill_lv,skill_id,0,0,skill_get_time(skill_id,skill_lv))); + sc_start(src,SC_SMA,100,skill_lv,skill_get_time(SL_SMA,skill_lv)); + break; + case SL_HIGH: + if (sd && !(dstsd && (dstsd->class_&JOBL_UPPER) && !(dstsd->class_&JOBL_2) && dstsd->status.base_level < 70)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start4(bl,type,100,skill_lv,skill_id,0,0,skill_get_time(skill_id,skill_lv))); + sc_start(src,SC_SMA,100,skill_lv,skill_get_time(SL_SMA,skill_lv)); + break; + + case SL_SWOO: + if (tsce) { + if(sd) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + status_change_start(src,SC_STUN,10000,skill_lv,0,0,0,10000,8); + status_change_end(bl, SC_SWOO, INVALID_TIMER); + break; + } + case SL_SKA: // [marquis007] + case SL_SKE: + if (sd && !battle_config.allow_es_magic_pc && bl->type != BL_MOB) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + status_change_start(src,SC_STUN,10000,skill_lv,0,0,0,500,10); + break; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + if (skill_id == SL_SKE) + sc_start(src,SC_SMA,100,skill_lv,skill_get_time(SL_SMA,skill_lv)); + break; + + // New guild skills [Celest] + case GD_BATTLEORDER: + if(flag&1) { + if (status_get_guild_id(src) == status_get_guild_id(bl)) + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id, skill_lv)); + } else if (status_get_guild_id(src)) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinrange(skill_area_sub, src, + skill_get_splash(skill_id, skill_lv), BL_PC, + src,skill_id,skill_lv,tick, flag|BCT_GUILD|1, + skill_castend_nodamage_id); + if (sd) + guild_block_skill(sd,skill_get_time2(skill_id,skill_lv)); + } + break; + case GD_REGENERATION: + if(flag&1) { + if (status_get_guild_id(src) == status_get_guild_id(bl)) + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id, skill_lv)); + } else if (status_get_guild_id(src)) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinrange(skill_area_sub, src, + skill_get_splash(skill_id, skill_lv), BL_PC, + src,skill_id,skill_lv,tick, flag|BCT_GUILD|1, + skill_castend_nodamage_id); + if (sd) + guild_block_skill(sd,skill_get_time2(skill_id,skill_lv)); + } + break; + case GD_RESTORE: + if(flag&1) { + if (status_get_guild_id(src) == status_get_guild_id(bl)) + clif_skill_nodamage(src,bl,AL_HEAL,status_percent_heal(bl,90,90),1); + } else if (status_get_guild_id(src)) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinrange(skill_area_sub, src, + skill_get_splash(skill_id, skill_lv), BL_PC, + src,skill_id,skill_lv,tick, flag|BCT_GUILD|1, + skill_castend_nodamage_id); + if (sd) + guild_block_skill(sd,skill_get_time2(skill_id,skill_lv)); + } + break; + case GD_EMERGENCYCALL: + { + int dx[9]={-1, 1, 0, 0,-1, 1,-1, 1, 0}; + int dy[9]={ 0, 0, 1,-1, 1,-1,-1, 1, 0}; + int j = 0; + struct guild *g = NULL; + // i don't know if it actually summons in a circle, but oh well. ;P + g = sd?sd->state.gmaster_flag:guild_search(status_get_guild_id(src)); + if (!g) + break; + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + for(i = 0; i < g->max_member; i++, j++) { + if (j>8) j=0; + if ((dstsd = g->member[i].sd) != NULL && sd != dstsd && !dstsd->state.autotrade && !pc_isdead(dstsd)) { + if (map[dstsd->bl.m].flag.nowarp && !map_flag_gvg2(dstsd->bl.m)) + continue; + if(map_getcell(src->m,src->x+dx[j],src->y+dy[j],CELL_CHKNOREACH)) + dx[j] = dy[j] = 0; + pc_setpos(dstsd, map_id2index(src->m), src->x+dx[j], src->y+dy[j], CLR_RESPAWN); + } + } + if (sd) + guild_block_skill(sd,skill_get_time2(skill_id,skill_lv)); + } + break; + + case SG_FEEL: + //AuronX reported you CAN memorize the same map as all three. [Skotlex] + if (sd) { + if(!sd->feel_map[skill_lv-1].index) + clif_feel_req(sd->fd,sd, skill_lv); + else + clif_feel_info(sd, skill_lv-1, 1); + } + break; + + case SG_HATE: + if (sd) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if (!pc_set_hate_mob(sd, skill_lv-1, bl)) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } + break; + + case GS_GLITTERING: + if(sd) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if(rnd()%100 < (20+10*skill_lv)) + pc_addspiritball(sd,skill_get_time(skill_id,skill_lv),10); + else if(sd->spiritball > 0) + pc_delspiritball(sd,1,0); + } + break; + + case GS_CRACKER: + /* per official standards, this skill works on players and mobs. */ + if (sd && (dstsd || dstmd)) + { + i =65 -5*distance_bl(src,bl); //Base rate + if (i < 30) i = 30; + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + sc_start(bl,SC_STUN, i,skill_lv,skill_get_time2(skill_id,skill_lv)); + } + break; + + case AM_CALLHOMUN: //[orn] + if (sd && !merc_call_homunculus(sd)) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + + case AM_REST: + if (sd) { + if (merc_hom_vaporize(sd,1)) + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + else + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } + break; + + case HAMI_CASTLE: //[orn] + if(rnd()%100 < 20*skill_lv && src != bl) + { + int x,y; + x = src->x; + y = src->y; + if (hd) + skill_blockhomun_start(hd, skill_id, skill_get_time2(skill_id,skill_lv)); + + if (unit_movepos(src,bl->x,bl->y,0,0)) { + clif_skill_nodamage(src,src,skill_id,skill_lv,1); // Homunc + clif_slide(src,bl->x,bl->y) ; + if (unit_movepos(bl,x,y,0,0)) + { + clif_skill_nodamage(bl,bl,skill_id,skill_lv,1); // Master + clif_slide(bl,x,y) ; + } + + //TODO: Shouldn't also players and the like switch targets? + map_foreachinrange(skill_chastle_mob_changetarget,src, + AREA_SIZE, BL_MOB, bl, src); + } + } + // Failed + else if (hd && hd->master) + clif_skill_fail(hd->master, skill_id, USESKILL_FAIL_LEVEL, 0); + else if (sd) + clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); + break; + case HVAN_CHAOTIC: //[orn] + { + static const int per[5][2]={{20,50},{50,60},{25,75},{60,64},{34,67}}; + int r = rnd()%100; + i = (skill_lv-1)%5; + if(r<per[i][0]) //Self + bl = src; + else if(r<per[i][1]) //Master + bl = battle_get_master(src); + else //Enemy + bl = map_id2bl(battle_gettarget(src)); + + if (!bl) bl = src; + i = skill_calc_heal(src, bl, skill_id, 1+rnd()%skill_lv, true); + //Eh? why double skill packet? + clif_skill_nodamage(src,bl,AL_HEAL,i,1); + clif_skill_nodamage(src,bl,skill_id,i,1); + status_heal(bl, i, 0, 0); + } + break; + //Homun single-target support skills [orn] + case HAMI_BLOODLUST: + case HFLI_FLEET: + case HFLI_SPEED: + case HLIF_CHANGE: + case MH_ANGRIFFS_MODUS: + case MH_GOLDENE_FERSE: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + if (hd) + skill_blockhomun_start(hd, skill_id, skill_get_time2(skill_id,skill_lv)); + break; + + case NPC_DRAGONFEAR: + if (flag&1) { + const enum sc_type sc[] = { SC_STUN, SC_SILENCE, SC_CONFUSION, SC_BLEEDING }; + int j; + j = i = rnd()%ARRAYLENGTH(sc); + while ( !sc_start(bl,sc[i],100,skill_lv,skill_get_time2(skill_id,i+1)) ) { + i++; + if ( i == ARRAYLENGTH(sc) ) + i = 0; + if (i == j) + break; + } + break; + } + case NPC_WIDEBLEEDING: + case NPC_WIDECONFUSE: + case NPC_WIDECURSE: + case NPC_WIDEFREEZE: + case NPC_WIDESLEEP: + case NPC_WIDESILENCE: + case NPC_WIDESTONE: + case NPC_WIDESTUN: + case NPC_SLOWCAST: + case NPC_WIDEHELLDIGNITY: + if (flag&1) + sc_start(bl,type,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + else { + skill_area_temp[2] = 0; //For SD_PREAMBLE + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinrange(skill_area_sub, bl, + skill_get_splash(skill_id, skill_lv),BL_CHAR, + src,skill_id,skill_lv,tick, flag|BCT_ENEMY|SD_PREAMBLE|1, + skill_castend_nodamage_id); + } + break; + case NPC_WIDESOULDRAIN: + if (flag&1) + status_percent_damage(src,bl,0,((skill_lv-1)%5+1)*20,false); + else { + skill_area_temp[2] = 0; //For SD_PREAMBLE + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinrange(skill_area_sub, bl, + skill_get_splash(skill_id, skill_lv),BL_CHAR, + src,skill_id,skill_lv,tick, flag|BCT_ENEMY|SD_PREAMBLE|1, + skill_castend_nodamage_id); + } + break; + case ALL_PARTYFLEE: + if( sd && !(flag&1) ) + { + if( !sd->status.party_id ) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); + } + else + clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + break; + case NPC_TALK: + case ALL_WEWISH: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + case ALL_BUYING_STORE: + if( sd ) + {// players only, skill allows 5 buying slots + clif_skill_nodamage(src, bl, skill_id, skill_lv, buyingstore_setup(sd, MAX_BUYINGSTORE_SLOTS)); + } + break; + case RK_ENCHANTBLADE: + clif_skill_nodamage(src,bl,skill_id,skill_lv,// formula not confirmed + sc_start2(bl,type,100,skill_lv,100+20*skill_lv/*+sstatus->int_/2+status_get_lv(bl)/10*/,skill_get_time(skill_id,skill_lv))); + break; + case RK_DRAGONHOWLING: + if( flag&1) + sc_start(bl,type,50 + 6 * skill_lv,skill_lv,skill_get_time(skill_id,skill_lv)); + else + { + skill_area_temp[2] = 0; + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinrange(skill_area_sub, src, + skill_get_splash(skill_id,skill_lv),BL_CHAR, + src,skill_id,skill_lv,tick,flag|BCT_ENEMY|SD_PREAMBLE|1, + skill_castend_nodamage_id); + } + break; + case RK_IGNITIONBREAK: + case LG_EARTHDRIVE: + clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + i = skill_get_splash(skill_id,skill_lv); + if( skill_id == LG_EARTHDRIVE ) { + int dummy = 1; + map_foreachinarea(skill_cell_overlap, src->m, src->x-i, src->y-i, src->x+i, src->y+i, BL_SKILL, LG_EARTHDRIVE, &dummy, src); + } + map_foreachinrange(skill_area_sub, bl,i,BL_CHAR, + src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); + break; + case RK_STONEHARDSKIN: + if( sd && pc_checkskill(sd,RK_RUNEMASTERY) >= 4 ) + { + int heal = sstatus->hp / 4; // 25% HP + if( status_charge(bl,heal,0) ) + clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start2(bl,type,100,skill_lv,heal,skill_get_time(skill_id,skill_lv))); + else + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } + break; + case RK_REFRESH: + if( sd && pc_checkskill(sd,RK_RUNEMASTERY) >= 8 ) + { + int heal = status_get_max_hp(bl) * 25 / 100; + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + status_heal(bl,heal,0,1); + status_change_clear_buffs(bl,4); + } + break; + + case RK_MILLENNIUMSHIELD: + if( sd && pc_checkskill(sd,RK_RUNEMASTERY) >= 9 ) + { + short shields = (rnd()%100<50) ? 4 : ((rnd()%100<80) ? 3 : 2); + sc_start4(bl,type,100,skill_lv,shields,1000,0,skill_get_time(skill_id,skill_lv)); + clif_millenniumshield(sd,shields); + clif_skill_nodamage(src,bl,skill_id,1,1); + } + break; + + case RK_GIANTGROWTH: + case RK_VITALITYACTIVATION: + case RK_ABUNDANCE: + case RK_CRUSHSTRIKE: + if( sd ) + { + int lv = 1; // RK_GIANTGROWTH + if( skill_id == RK_VITALITYACTIVATION ) + lv = 2; + else if( skill_id == RK_ABUNDANCE ) + lv = 6; + else if( skill_id == RK_CRUSHSTRIKE ) + lv = 7; + if( pc_checkskill(sd,RK_RUNEMASTERY) >= lv ) + clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + } + break; + + case RK_FIGHTINGSPIRIT: + if( flag&1 ) { + if( src == bl ) + sc_start2(bl,type,100,skill_area_temp[5],10*(sd?pc_checkskill(sd,RK_RUNEMASTERY):10),skill_get_time(skill_id,skill_lv)); + else + sc_start(bl,type,100,skill_area_temp[5]/4,skill_get_time(skill_id,skill_lv)); + } else if( sd && pc_checkskill(sd,RK_RUNEMASTERY) >= 5 ) { + if( sd->status.party_id ) { + i = party_foreachsamemap(skill_area_sub,sd,skill_get_splash(skill_id,skill_lv),src,skill_id,skill_lv,tick,BCT_PARTY,skill_area_sub_count); + skill_area_temp[5] = 7 * i; // ATK + party_foreachsamemap(skill_area_sub,sd,skill_get_splash(skill_id,skill_lv),src,skill_id,skill_lv,tick,flag|BCT_PARTY|1,skill_castend_nodamage_id); + } else + sc_start2(bl,type,100,7,5,skill_get_time(skill_id,skill_lv)); + } + clif_skill_nodamage(src,bl,skill_id,1,1); + break; + /** + * Guilotine Cross + **/ + case GC_ROLLINGCUTTER: + { + short count = 1; + skill_area_temp[2] = 0; + map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|SD_PREAMBLE|SD_SPLASH|1,skill_castend_damage_id); + if( tsc && tsc->data[SC_ROLLINGCUTTER] ) + { // Every time the skill is casted the status change is reseted adding a counter. + count += (short)tsc->data[SC_ROLLINGCUTTER]->val1; + if( count > 10 ) + count = 10; // Max coounter + status_change_end(bl, SC_ROLLINGCUTTER, INVALID_TIMER); + } + sc_start(bl,SC_ROLLINGCUTTER,100,count,skill_get_time(skill_id,skill_lv)); + clif_skill_nodamage(src,src,skill_id,skill_lv,1); + } + break; + + case GC_WEAPONBLOCKING: + if( tsc && tsc->data[SC_WEAPONBLOCKING] ) + status_change_end(bl, SC_WEAPONBLOCKING, INVALID_TIMER); + else + sc_start(bl,SC_WEAPONBLOCKING,100,skill_lv,skill_get_time(skill_id,skill_lv)); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + + case GC_CREATENEWPOISON: + if( sd ) + { + clif_skill_produce_mix_list(sd,skill_id,25); + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + } + break; + + case GC_POISONINGWEAPON: + if( sd ) { + clif_poison_list(sd,skill_lv); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case GC_ANTIDOTE: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if( tsc ) + { + status_change_end(bl, SC_PARALYSE, INVALID_TIMER); + status_change_end(bl, SC_PYREXIA, INVALID_TIMER); + status_change_end(bl, SC_DEATHHURT, INVALID_TIMER); + status_change_end(bl, SC_LEECHESEND, INVALID_TIMER); + status_change_end(bl, SC_VENOMBLEED, INVALID_TIMER); + status_change_end(bl, SC_MAGICMUSHROOM, INVALID_TIMER); + status_change_end(bl, SC_TOXIN, INVALID_TIMER); + status_change_end(bl, SC_OBLIVIONCURSE, INVALID_TIMER); + } + break; + + case GC_PHANTOMMENACE: + clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),BL_CHAR, + src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); + break; + + case GC_HALLUCINATIONWALK: + { + int heal = status_get_max_hp(bl) / 10; + if( status_get_hp(bl) < heal ) { // if you haven't enough HP skill fails. + if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_HP_INSUFFICIENT,0); + break; + } + if( !status_charge(bl,heal,0) ) + { + if( sd ) clif_skill_fail(sd,skill_id,USESKILL_FAIL_HP_INSUFFICIENT,0); + break; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + } + break; + /** + * Arch Bishop + **/ + case AB_ANCILLA: + if( sd ) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + skill_produce_mix(sd, skill_id, ITEMID_ANCILLA, 0, 0, 0, 1); + } + break; + + case AB_CLEMENTIA: + case AB_CANTO: + { + int bless_lv = pc_checkskill(sd,AL_BLESSING) + (sd->status.job_level / 10); + int agi_lv = pc_checkskill(sd,AL_INCAGI) + (sd->status.job_level / 10); + if( sd == NULL || sd->status.party_id == 0 || flag&1 ) + clif_skill_nodamage(bl, bl, skill_id, skill_lv, sc_start(bl,type,100, + (skill_id == AB_CLEMENTIA)? bless_lv : (skill_id == AB_CANTO)? agi_lv : skill_lv, skill_get_time(skill_id,skill_lv))); + else if( sd ) + party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); + } + break; + + case AB_PRAEFATIO: + if( sd == NULL || sd->status.party_id == 0 || flag&1 ) + clif_skill_nodamage(bl, bl, skill_id, skill_lv, sc_start4(bl, type, 100, skill_lv, 0, 0, 1, skill_get_time(skill_id, skill_lv))); + else if( sd ) + party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); + break; + + case AB_CHEAL: + if( sd == NULL || sd->status.party_id == 0 || flag&1 ) + { + if( sd && tstatus && !battle_check_undead(tstatus->race, tstatus->def_ele) ) + { + i = skill_calc_heal(src, bl, AL_HEAL, pc_checkskill(sd, AL_HEAL), true); + + if( (dstsd && pc_ismadogear(dstsd)) || status_isimmune(bl)) + i = 0; // Should heal by 0 or won't do anything?? in iRO it breaks the healing to members.. [malufett] + + clif_skill_nodamage(bl, bl, skill_id, i, 1); + if( tsc && tsc->data[SC_AKAITSUKI] && i ) + i = ~i + 1; + status_heal(bl, i, 0, 0); + } + } + else if( sd ) + party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); + break; + + case AB_ORATIO: + if( flag&1 ) + sc_start(bl, type, 40 + 5 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv)); + else + { + map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_CHAR, + src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + } + break; + + case AB_LAUDAAGNUS: + if( flag&1 || sd == NULL ) { + if( tsc && (tsc->data[SC_FREEZE] || tsc->data[SC_STONE] || tsc->data[SC_BLIND] || + tsc->data[SC_BURNING] || tsc->data[SC_FREEZING] || tsc->data[SC_CRYSTALIZE])) { + // Success Chance: (40 + 10 * Skill Level) % + if( rnd()%100 > 40+10*skill_lv ) break; + status_change_end(bl, SC_FREEZE, INVALID_TIMER); + status_change_end(bl, SC_STONE, INVALID_TIMER); + status_change_end(bl, SC_BLIND, INVALID_TIMER); + status_change_end(bl, SC_BURNING, INVALID_TIMER); + status_change_end(bl, SC_FREEZING, INVALID_TIMER); + status_change_end(bl, SC_CRYSTALIZE, INVALID_TIMER); + }else //Success rate only applies to the curing effect and not stat bonus. Bonus status only applies to non infected targets + clif_skill_nodamage(bl, bl, skill_id, skill_lv, + sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); + } else if( sd ) + party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), + src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); + break; + + case AB_LAUDARAMUS: + if( flag&1 || sd == NULL ) { + if( tsc && (tsc->data[SC_SLEEP] || tsc->data[SC_STUN] || tsc->data[SC_MANDRAGORA] || tsc->data[SC_SILENCE]) ){ + // Success Chance: (40 + 10 * Skill Level) % + if( rnd()%100 > 40+10*skill_lv ) break; + status_change_end(bl, SC_SLEEP, INVALID_TIMER); + status_change_end(bl, SC_STUN, INVALID_TIMER); + status_change_end(bl, SC_MANDRAGORA, INVALID_TIMER); + status_change_end(bl, SC_SILENCE, INVALID_TIMER); + }else // Success rate only applies to the curing effect and not stat bonus. Bonus status only applies to non infected targets + clif_skill_nodamage(bl, bl, skill_id, skill_lv, + sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); + } else if( sd ) + party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), + src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); + break; + + case AB_CLEARANCE: + if( flag&1 || (i = skill_get_splash(skill_id, skill_lv)) < 1 ) + { //As of the behavior in official server Clearance is just a super version of Dispell skill. [Jobbie] + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if((dstsd && (dstsd->class_&MAPID_UPPERMASK) == MAPID_SOUL_LINKER) || rnd()%100 >= 30 + 10 * skill_lv) + { + if (sd) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + if(status_isimmune(bl) || !tsc || !tsc->count) + break; + for(i=0;i<SC_MAX;i++) + { + if (!tsc->data[i]) + continue; + switch (i) { + case SC_WEIGHT50: case SC_WEIGHT90: case SC_HALLUCINATION: + 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: case SC_COMBO: + case SC_STRFOOD: case SC_AGIFOOD: case SC_VITFOOD: + case SC_INTFOOD: case SC_DEXFOOD: case SC_LUKFOOD: + case SC_HITFOOD: case SC_FLEEFOOD: case SC_BATKFOOD: + case SC_WATKFOOD: case SC_MATKFOOD: case SC_DANCING: + case SC_SPIRIT: case SC_AUTOBERSERK: + case SC_CARTBOOST: case SC_MELTDOWN: case SC_SAFETYWALL: + case SC_SMA: case SC_SPEEDUP0: case SC_NOCHAT: + case SC_ANKLE: case SC_SPIDERWEB: case SC_JAILED: + case SC_ITEMBOOST: case SC_EXPBOOST: case SC_LIFEINSURANCE: + case SC_BOSSMAPINFO: case SC_PNEUMA: case SC_AUTOSPELL: + case SC_INCHITRATE: case SC_INCATKRATE: case SC_NEN: + case SC_READYSTORM: case SC_READYDOWN: case SC_READYTURN: + case SC_READYCOUNTER:case SC_DODGE: case SC_WARM: + case SC_SPEEDUP1: case SC_AUTOTRADE: case SC_CRITICALWOUND: + case SC_JEXPBOOST: case SC_INVINCIBLE: case SC_INVINCIBLEOFF: + case SC_HELLPOWER: case SC_MANU_ATK: case SC_MANU_DEF: + case SC_SPL_ATK: case SC_SPL_DEF: case SC_MANU_MATK: + case SC_SPL_MATK: case SC_RICHMANKIM: case SC_ETERNALCHAOS: + case SC_DRUMBATTLE: case SC_NIBELUNGEN: case SC_ROKISWEIL: + case SC_INTOABYSS: case SC_SIEGFRIED: case SC_WHISTLE: + case SC_ASSNCROS: case SC_POEMBRAGI: case SC_APPLEIDUN: + case SC_HUMMING: case SC_DONTFORGETME: case SC_FORTUNE: + case SC_SERVICE4U: case SC_FOOD_STR_CASH: case SC_FOOD_AGI_CASH: + case SC_FOOD_VIT_CASH: case SC_FOOD_DEX_CASH: case SC_FOOD_INT_CASH: + case SC_FOOD_LUK_CASH: case SC_ELECTRICSHOCKER: case SC_BITE: + case SC__STRIPACCESSORY: case SC__ENERVATION: case SC__GROOMY: + case SC__IGNORANCE: case SC__LAZINESS: case SC__UNLUCKY: + case SC__WEAKNESS: //case SC_SAVAGE_STEAK: case SC_COCKTAIL_WARG_BLOOD: + case SC_MAGNETICFIELD://case SC_MINOR_BBQ: case SC_SIROMA_ICE_TEA: + //case SC_DROCERA_HERB_STEAMED: case SC_PUTTI_TAILS_NOODLES: + case SC_NEUTRALBARRIER_MASTER: case SC_NEUTRALBARRIER: + case SC_STEALTHFIELD_MASTER: case SC_STEALTHFIELD: + case SC_LEADERSHIP: case SC_GLORYWOUNDS: case SC_SOULCOLD: + case SC_HAWKEYES: case SC_GUILDAURA: case SC_PUSH_CART: + case SC_PARTYFLEE: case SC_GT_REVITALIZE: + case SC_RAISINGDRAGON: case SC_GT_ENERGYGAIN: case SC_GT_CHANGE: +#ifdef RENEWAL + case SC_EXTREMITYFIST2: +#endif + continue; + case SC_ASSUMPTIO: + if( bl->type == BL_MOB ) + continue; + break; + } + if(i==SC_BERSERK || i==SC_SATURDAYNIGHTFEVER) tsc->data[i]->val2=0; //Mark a dispelled berserk to avoid setting hp to 100 by setting hp penalty to 0. + status_change_end(bl,(sc_type)i,INVALID_TIMER); + } + break; + } + map_foreachinrange(skill_area_sub, bl, i, BL_CHAR, src, skill_id, skill_lv, tick, flag|1, skill_castend_damage_id); + break; + + case AB_SILENTIUM: + // Should the level of Lex Divina be equivalent to the level of Silentium or should the highest level learned be used? [LimitLine] + map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id, skill_lv), BL_CHAR, + src, PR_LEXDIVINA, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + break; + /** + * Warlock + **/ + case WL_STASIS: + if( flag&1 ) + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + else + { + map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id, skill_lv),BL_CHAR,src,skill_id,skill_lv,tick,(map_flag_vs(src->m)?BCT_ALL:BCT_ENEMY|BCT_SELF)|flag|1,skill_castend_nodamage_id); + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + } + break; + + case WL_WHITEIMPRISON: + if( (src == bl || battle_check_target(src, bl, BCT_ENEMY)) && !is_boss(bl) )// Should not work with bosses. + { + int rate = ( sd? sd->status.job_level : 50 ) / 4; + + if( src == bl ) rate = 100; // Success Chance: On self, 100% + else if(bl->type == BL_PC) rate += 20 + 10 * skill_lv; // On Players, (20 + 10 * Skill Level) % + else rate += 40 + 10 * skill_lv; // On Monsters, (40 + 10 * Skill Level) % + + if( sd ) + skill_blockpc_start(sd,skill_id,4000); + + if( !(tsc && tsc->data[type]) ){ + i = sc_start2(bl,type,rate,skill_lv,src->id,(src == bl)?5000:(bl->type == BL_PC)?skill_get_time(skill_id,skill_lv):skill_get_time2(skill_id, skill_lv)); + clif_skill_nodamage(src,bl,skill_id,skill_lv,i); + if( !i ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } + }else + if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_TOTARGET,0); + break; + + case WL_FROSTMISTY: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinrange(skill_area_sub,bl,skill_get_splash(skill_id,skill_lv),BL_CHAR|BL_SKILL,src,skill_id,skill_lv,tick,flag|BCT_ENEMY,skill_castend_damage_id); + break; + + case WL_JACKFROST: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_foreachinshootrange(skill_area_sub,bl,skill_get_splash(skill_id,skill_lv),BL_CHAR|BL_SKILL,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); + break; + + case WL_MARSHOFABYSS: + // Should marsh of abyss still apply half reduction to players after the 28/10 patch? [LimitLine] + clif_skill_nodamage(src, bl, skill_id, skill_lv, + sc_start4(bl, type, 100, skill_lv, status_get_int(src), sd ? sd->status.job_level : 50, 0, + skill_get_time(skill_id, skill_lv))); + break; + + case WL_SIENNAEXECRATE: + if( status_isimmune(bl) || !tsc ) + break; + + if( flag&1 ) { + if( bl->id == skill_area_temp[1] ) + break; // Already work on this target + + if( tsc && tsc->data[SC_STONE] ) + status_change_end(bl,SC_STONE,INVALID_TIMER); + else + status_change_start(bl,SC_STONE,10000,skill_lv,0,0,1000,skill_get_time(skill_id, skill_lv),2); + } else { + int rate = 40 + 8 * skill_lv + ( sd? sd->status.job_level : 50 ) / 4; + // IroWiki says Rate should be reduced by target stats, but currently unknown + if( rnd()%100 < rate ) { // Success on First Target + if( !tsc->data[SC_STONE] ) + rate = status_change_start(bl,SC_STONE,10000,skill_lv,0,0,1000,skill_get_time(skill_id, skill_lv),2); + else { + rate = 1; + status_change_end(bl,SC_STONE,INVALID_TIMER); + } + + if( rate ) { + skill_area_temp[1] = bl->id; + map_foreachinrange(skill_area_sub,bl,skill_get_splash(skill_id,skill_lv),BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_nodamage_id); + } + // Doesn't send failure packet if it fails on defense. + } + else if( sd ) // Failure on Rate + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } + break; + + case WL_SUMMONFB: + case WL_SUMMONBL: + case WL_SUMMONWB: + case WL_SUMMONSTONE: + { + short element = 0, sctype = 0, pos = -1; + struct status_change *sc = status_get_sc(src); + if( !sc ) break; + + for( i = SC_SPHERE_1; i <= SC_SPHERE_5; i++ ) + { + if( !sctype && !sc->data[i] ) + sctype = i; // Take the free SC + if( sc->data[i] ) + pos = max(sc->data[i]->val2,pos); + } + + if( !sctype ) + { + if( sd ) // No free slots to put SC + clif_skill_fail(sd,skill_id,USESKILL_FAIL_SUMMON,0); + break; + } + + pos++; // Used in val2 for SC. Indicates the order of this ball + switch( skill_id ) + { // Set val1. The SC element for this ball + case WL_SUMMONFB: element = WLS_FIRE; break; + case WL_SUMMONBL: element = WLS_WIND; break; + case WL_SUMMONWB: element = WLS_WATER; break; + case WL_SUMMONSTONE: element = WLS_STONE; break; + } + + sc_start4(src,sctype,100,element,pos,skill_lv,0,skill_get_time(skill_id,skill_lv)); + clif_skill_nodamage(src,bl,skill_id,0,0); + } + break; + + case WL_READING_SB: + if( sd ) { + struct status_change *sc = status_get_sc(bl); + + for( i = SC_SPELLBOOK1; i <= SC_MAXSPELLBOOK; i++) + if( sc && !sc->data[i] ) + break; + if( i == SC_MAXSPELLBOOK ) { + clif_skill_fail(sd, WL_READING_SB, USESKILL_FAIL_SPELLBOOK_READING, 0); + break; + } + + sc_start(bl, SC_STOP, 100, skill_lv, INVALID_TIMER); //Can't move while selecting a spellbook. + clif_spellbook_list(sd); + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + } + break; + /** + * Ranger + **/ + case RA_FEARBREEZE: + clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); + break; + + case RA_WUGMASTERY: + if( sd ) { + if( !pc_iswug(sd) ) + pc_setoption(sd,sd->sc.option|OPTION_WUG); + else + pc_setoption(sd,sd->sc.option&~OPTION_WUG); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case RA_WUGRIDER: + if( sd ) { + if( !pc_isridingwug(sd) && pc_iswug(sd) ) { + pc_setoption(sd,sd->sc.option&~OPTION_WUG); + pc_setoption(sd,sd->sc.option|OPTION_WUGRIDER); + } else if( pc_isridingwug(sd) ) { + pc_setoption(sd,sd->sc.option&~OPTION_WUGRIDER); + pc_setoption(sd,sd->sc.option|OPTION_WUG); + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case RA_WUGDASH: + if( tsce ) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,status_change_end(bl, type, INVALID_TIMER)); + map_freeblock_unlock(); + return 0; + } + if( sd && pc_isridingwug(sd) ) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start4(bl,type,100,skill_lv,unit_getdir(bl),0,0,1)); + clif_walkok(sd); + } + break; + + case RA_SENSITIVEKEEN: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + clif_skill_damage(src,src,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),BL_CHAR|BL_SKILL,src,skill_id,skill_lv,tick,flag|BCT_ENEMY,skill_castend_damage_id); + break; + /** + * Mechanic + **/ + case NC_F_SIDESLIDE: + case NC_B_SIDESLIDE: + { + uint8 dir = (skill_id == NC_F_SIDESLIDE) ? (unit_getdir(src)+4)%8 : unit_getdir(src); + skill_blown(src,bl,skill_get_blewcount(skill_id,skill_lv),dir,0x1); + clif_slide(src,src->x,src->y); + clif_fixpos(src); //Aegis sent this packet + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case NC_SELFDESTRUCTION: + if( sd ) { + if( pc_ismadogear(sd) ) + pc_setmadogear(sd, 0); + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + skill_castend_damage_id(src, src, skill_id, skill_lv, tick, flag); + status_set_sp(src, 0, 0); + } + break; + + case NC_ANALYZE: + clif_skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + clif_skill_nodamage(src, bl, skill_id, skill_lv, + sc_start(bl,type, 30 + 12 * skill_lv,skill_lv,skill_get_time(skill_id,skill_lv))); + if( sd ) pc_overheat(sd,1); + break; + + case NC_MAGNETICFIELD: + if( (i = sc_start2(bl,type,100,skill_lv,src->id,skill_get_time(skill_id,skill_lv))) ) + { + map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),splash_target(src),src,skill_id,skill_lv,tick,flag|BCT_ENEMY|SD_SPLASH|1,skill_castend_damage_id);; + clif_skill_damage(src,src,tick,status_get_amotion(src),0,-30000,1,skill_id,skill_lv,6); + if (sd) pc_overheat(sd,1); + } + clif_skill_nodamage(src,src,skill_id,skill_lv,i); + break; + + case NC_REPAIR: + if( sd ) + { + int heal; + if( dstsd && pc_ismadogear(dstsd) ) + { + heal = dstsd->status.max_hp * (3+3*skill_lv) / 100; + status_heal(bl,heal,0,2); + } else { + heal = sd->status.max_hp * (3+3*skill_lv) / 100; + status_heal(src,heal,0,2); + } + + clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + clif_skill_nodamage(src, bl, skill_id, skill_lv, heal); + } + break; + + case NC_DISJOINT: + { + if( bl->type != BL_MOB ) break; + md = map_id2md(bl->id); + if( md && md->class_ >= MOBID_SILVERSNIPER && md->class_ <= MOBID_MAGICDECOY_WIND ) + status_kill(bl); + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + } + break; + case SC_AUTOSHADOWSPELL: + if( sd ) { + if( sd->status.skill[sd->reproduceskill_id].id || sd->status.skill[sd->cloneskill_id].id ) { + sc_start(src,SC_STOP,100,skill_lv,-1);// The skill_lv is stored in val1 used in skill_select_menu to determine the used skill lvl [Xazax] + clif_autoshadowspell_list(sd); + clif_skill_nodamage(src,bl,skill_id,1,1); + } + else + clif_skill_fail(sd,skill_id,USESKILL_FAIL_IMITATION_SKILL_NONE,0); + } + break; + + case SC_SHADOWFORM: + if( sd && dstsd && src != bl && !dstsd->shadowform_id ) { + if( clif_skill_nodamage(src,bl,skill_id,skill_lv,sc_start4(src,type,100,skill_lv,bl->id,4+skill_lv,0,skill_get_time(skill_id, skill_lv))) ) + dstsd->shadowform_id = src->id; + } + else if( sd ) + clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); + break; + + case SC_BODYPAINT: + if( flag&1 ) { + if( tsc && (tsc->data[SC_HIDING] || tsc->data[SC_CLOAKING] || + tsc->data[SC_CHASEWALK] || tsc->data[SC_CLOAKINGEXCEED] || + tsc->data[SC__INVISIBILITY]) ) { + status_change_end(bl, SC_HIDING, INVALID_TIMER); + status_change_end(bl, SC_CLOAKING, INVALID_TIMER); + status_change_end(bl, SC_CHASEWALK, INVALID_TIMER); + status_change_end(bl, SC_CLOAKINGEXCEED, INVALID_TIMER); + status_change_end(bl, SC__INVISIBILITY, INVALID_TIMER); + + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + sc_start(bl,SC_BLIND,53 + 2 * skill_lv,skill_lv,skill_get_time(skill_id,skill_lv)); + } + } else { + clif_skill_nodamage(src, bl, skill_id, 0, 1); + map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, + src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); + } + break; + + case SC_ENERVATION: + case SC_GROOMY: + case SC_LAZINESS: + case SC_UNLUCKY: + case SC_WEAKNESS: + if( !(tsc && tsc->data[type]) ) { + //((rand(myDEX / 12, myDEX / 4) + myJobLevel + 10 * skLevel) + myLevel / 10) - (targetLevel / 10 + targetLUK / 10 + (targetMaxWeight - targetWeight) / 1000 + rand(targetAGI / 6, targetAGI / 3)) + int rate = rnd_value(sstatus->dex/12,sstatus->dex/4) + 10*skill_lv + (sd?sd->status.job_level:0) + status_get_lv(src)/10 + - status_get_lv(bl)/10 - tstatus->luk/10 - (dstsd?(dstsd->max_weight-dstsd->weight)/10000:0) - rnd_value(tstatus->agi/6,tstatus->agi/3); + rate = cap_value(rate, skill_lv+sstatus->dex/20, 100); + clif_skill_nodamage(src,bl,skill_id,0,sc_start(bl,type,rate,skill_lv,skill_get_time(skill_id,skill_lv))); + } else if( sd ) + clif_skill_fail(sd,skill_id,0,0); + break; + + case SC_IGNORANCE: + if( !(tsc && tsc->data[type]) ) { + int rate = rnd_value(sstatus->dex/12,sstatus->dex/4) + 10*skill_lv + (sd?sd->status.job_level:0) + status_get_lv(src)/10 + - status_get_lv(bl)/10 - tstatus->luk/10 - (dstsd?(dstsd->max_weight-dstsd->weight)/10000:0) - rnd_value(tstatus->agi/6,tstatus->agi/3); + rate = cap_value(rate, skill_lv+sstatus->dex/20, 100); + if (clif_skill_nodamage(src,bl,skill_id,0,sc_start(bl,type,rate,skill_lv,skill_get_time(skill_id,skill_lv)))) { + int sp = 200 * skill_lv; + if( dstmd ) sp = dstmd->level * 2; + if( status_zap(bl,0,sp) ) + status_heal(src,0,sp/2,3); + } + else if( sd ) clif_skill_fail(sd,skill_id,0,0); + } else if( sd ) + clif_skill_fail(sd,skill_id,0,0); + break; + + case LG_TRAMPLE: + clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + map_foreachinrange(skill_destroy_trap,bl,skill_get_splash(skill_id,skill_lv),BL_SKILL,tick); + break; + + case LG_REFLECTDAMAGE: + if( tsc && tsc->data[type] ) + status_change_end(bl,type,INVALID_TIMER); + else + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + break; + + case LG_SHIELDSPELL: + if( flag&1 ) { + int duration = (sd) ? sd->bonus.shieldmdef * 2000 : 10000; + sc_start(bl,SC_SILENCE,100,skill_lv,duration); + } else if( sd ) { + int opt = skill_lv; + int rate = rnd()%100; + int val, brate; + switch( skill_lv ) { + case 1: + { + struct item_data *shield_data = sd->inventory_data[sd->equip_index[EQI_HAND_L]]; + if( !shield_data || shield_data->type != IT_ARMOR ) { // No shield? + clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); + break; + } + brate = shield_data->def * 10; + if( rate < 50 ) + opt = 1; + else if( rate < 75 ) + opt = 2; + else + opt = 3; + + switch( opt ) { + case 1: + sc_start(bl,SC_SHIELDSPELL_DEF,100,opt,-1); + clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + if( rate < brate ) + map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); + status_change_end(bl,SC_SHIELDSPELL_DEF,INVALID_TIMER); + break; + case 2: + val = shield_data->def / 10; // % Reflected damage. + sc_start2(bl,SC_SHIELDSPELL_DEF,brate,opt,val,shield_data->def * 1000); + break; + case 3: + val = shield_data->def; // Attack increase. + sc_start2(bl,SC_SHIELDSPELL_DEF,brate,opt,val,shield_data->def * 3000); + break; + } + } + break; + + case 2: + brate = sd->bonus.shieldmdef * 20; + if( rate < 30 ) + opt = 1; + else if( rate < 60 ) + opt = 2; + else + opt = 3; + switch( opt ) { + case 1: + sc_start(bl,SC_SHIELDSPELL_MDEF,100,opt,-1); + clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + if( rate < brate ) + map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|2,skill_castend_damage_id); + status_change_end(bl,SC_SHIELDSPELL_MDEF,INVALID_TIMER); + break; + case 2: + sc_start(bl,SC_SHIELDSPELL_MDEF,100,opt,-1); + clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + if( rate < brate ) + map_foreachinrange(skill_area_sub,src,skill_get_splash(skill_id,skill_lv),BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_nodamage_id); + break; + case 3: + if( sc_start(bl,SC_SHIELDSPELL_MDEF,brate,opt,sd->bonus.shieldmdef * 30000) ) + clif_skill_nodamage(src,bl,PR_MAGNIFICAT,skill_lv, + sc_start(bl,SC_MAGNIFICAT,100,1,sd->bonus.shieldmdef * 30000)); + break; + } + break; + + case 3: + { + struct item *it = &sd->status.inventory[sd->equip_index[EQI_HAND_L]]; + if( !it ) { // No shield? + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + brate = it->refine * 5; + if( rate < 25 ) + opt = 1; + else if( rate < 50 ) + opt = 2; + else + opt = 3; + switch( opt ) { + case 1: + val = 105 * it->refine / 10; + sc_start2(bl,SC_SHIELDSPELL_REF,brate,opt,val,skill_get_time(skill_id,skill_lv)); + break; + case 2: case 3: + if( rate < brate ) + { + val = sstatus->max_hp * (11 + it->refine) / 100; + status_heal(bl, val, 0, 3); + } + break; + /*case 3: + // Full protection. I need confirm what effect should be here. Moved to case 2 to until we got it. + break;*/ + } + } + break; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case LG_PIETY: + if( flag&1 ) + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + else { + skill_area_temp[2] = 0; + map_foreachinrange(skill_area_sub,bl,skill_get_splash(skill_id,skill_lv),BL_PC,src,skill_id,skill_lv,tick,flag|SD_PREAMBLE|BCT_PARTY|BCT_SELF|1,skill_castend_nodamage_id); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case LG_INSPIRATION: + if( sd && !map[sd->bl.m].flag.noexppenalty && sd->status.base_level != MAX_LEVEL ) { + sd->status.base_exp -= min(sd->status.base_exp, pc_nextbaseexp(sd) * 1 / 100); // 1% penalty. + sd->status.job_exp -= min(sd->status.job_exp, pc_nextjobexp(sd) * 1 / 100); + clif_updatestatus(sd,SP_BASEEXP); + clif_updatestatus(sd,SP_JOBEXP); + } + clif_skill_nodamage(bl,src,skill_id,skill_lv, + sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); + break; + case SR_CURSEDCIRCLE: + if( flag&1 ) { + if( is_boss(bl) ) break; + if( sc_start2(bl, type, 100, skill_lv, src->id, skill_get_time(skill_id, skill_lv))) { + if( bl->type == BL_MOB ) + mob_unlocktarget((TBL_MOB*)bl,gettick()); + unit_stop_attack(bl); + clif_bladestop(src, bl->id, 1); + map_freeblock_unlock(); + return 1; + } + } else { + int count = 0; + clif_skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + count = map_forcountinrange(skill_area_sub, src, skill_get_splash(skill_id,skill_lv), (sd)?sd->spiritball_old:15, // Assume 15 spiritballs in non-charactors + BL_CHAR, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); + if( sd ) pc_delspiritball(sd, count, 0); + clif_skill_nodamage(src, src, skill_id, skill_lv, + sc_start2(src, SC_CURSEDCIRCLE_ATKER, 100, skill_lv, count, skill_get_time(skill_id,skill_lv))); + } + break; + + case SR_RAISINGDRAGON: + if( sd ) { + short max = 5 + skill_lv; + sc_start(bl, SC_EXPLOSIONSPIRITS, 100, skill_lv, skill_get_time(skill_id, skill_lv)); + for( i = 0; i < max; i++ ) // Don't call more than max available spheres. + pc_addspiritball(sd, skill_get_time(skill_id, skill_lv), max); + clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(bl, type, 100, skill_lv,skill_get_time(skill_id, skill_lv))); + } + break; + + case SR_ASSIMILATEPOWER: + if( flag&1 ) { + i = 0; + if( dstsd && dstsd->spiritball && (sd == dstsd || map_flag_vs(src->m)) && (dstsd->class_&MAPID_BASEMASK)!=MAPID_GUNSLINGER ) + { + i = dstsd->spiritball; //1%sp per spiritball. + pc_delspiritball(dstsd, dstsd->spiritball, 0); + } + if( i ) status_percent_heal(src, 0, i); + clif_skill_nodamage(src, bl, skill_id, skill_lv, i ? 1:0); + } else { + clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|BCT_SELF|SD_SPLASH|1, skill_castend_nodamage_id); + } + break; + + case SR_POWERVELOCITY: + if( !dstsd ) + break; + if( sd && dstsd->spiritball <= 5 ) { + for(i = 0; i <= 5; i++) { + pc_addspiritball(dstsd, skill_get_time(MO_CALLSPIRITS, pc_checkskill(sd,MO_CALLSPIRITS)), i); + pc_delspiritball(sd, sd->spiritball, 0); + } + } + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + break; + + case SR_GENTLETOUCH_CURE: + { + int heal; + + if( status_isimmune(bl) ) + { + clif_skill_nodamage(src,bl,skill_id,skill_lv,0); + break; + } + + heal = 120 * skill_lv + status_get_max_hp(bl) * (2 + skill_lv) / 100; + status_heal(bl, heal, 0, 0); + + if( (tsc && tsc->opt1) && (rnd()%100 < ((skill_lv * 5) + (status_get_dex(src) + status_get_lv(src)) / 4) - (1 + (rnd() % 10))) ) + { + status_change_end(bl, SC_STONE, INVALID_TIMER); + status_change_end(bl, SC_FREEZE, INVALID_TIMER); + status_change_end(bl, SC_STUN, INVALID_TIMER); + status_change_end(bl, SC_POISON, INVALID_TIMER); + status_change_end(bl, SC_SILENCE, INVALID_TIMER); + status_change_end(bl, SC_BLIND, INVALID_TIMER); + status_change_end(bl, SC_HALLUCINATION, INVALID_TIMER); + status_change_end(bl, SC_BURNING, INVALID_TIMER); + status_change_end(bl, SC_FREEZING, INVALID_TIMER); + } + + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + case SR_GENTLETOUCH_CHANGE: + case SR_GENTLETOUCH_REVITALIZE: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start2(bl,type,100,skill_lv,src->id,skill_get_time(skill_id,skill_lv))); + break; + case WA_SWING_DANCE: + case WA_MOONLIT_SERENADE: + if( sd == NULL || sd->status.party_id == 0 || (flag & 1) ) + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + else if( sd ) { // Only shows effects on caster. + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); + } + break; + + case WA_SYMPHONY_OF_LOVER: + case MI_RUSH_WINDMILL: + case MI_ECHOSONG: + if( sd == NULL || sd->status.party_id == 0 || (flag & 1) ) + sc_start4(bl,type,100,skill_lv,6*skill_lv,(sd?pc_checkskill(sd,WM_LESSON):0),(sd?sd->status.job_level:0),skill_get_time(skill_id,skill_lv)); + else if( sd ) { // Only shows effects on caster. + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + party_foreachsamemap(skill_area_sub, sd, skill_get_splash(skill_id, skill_lv), src, skill_id, skill_lv, tick, flag|BCT_PARTY|1, skill_castend_nodamage_id); + } + break; + + case MI_HARMONIZE: + if( src != bl ) + clif_skill_nodamage(src, src, skill_id, skill_lv, sc_start(src, type, 100, skill_lv, skill_get_time(skill_id,skill_lv))); + clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id,skill_lv))); + break; + + case WM_DEADHILLHERE: + if( bl->type == BL_PC ) { + if( !status_isdead(bl) ) + break; + + if( rnd()%100 < 88 + 2 * skill_lv ) { + int heal = tstatus->sp; + if( heal <= 0 ) + heal = 1; + tstatus->hp = heal; + tstatus->sp -= tstatus->sp * ( 120 - 20 * skill_lv ) / 100; + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + pc_revive((TBL_PC*)bl,heal,0); + clif_resurrection(bl,1); + } + } + break; + + case WM_SIRCLEOFNATURE: + flag |= BCT_SELF|BCT_PARTY|BCT_GUILD; + case WM_VOICEOFSIREN: + if( skill_id != WM_SIRCLEOFNATURE ) + flag &= ~BCT_SELF; + if( flag&1 ) { + sc_start2(bl,type,(skill_id==WM_VOICEOFSIREN)?20+10*skill_lv:100,skill_lv,(skill_id==WM_VOICEOFSIREN)?src->id:0,skill_get_time(skill_id,skill_lv)); + } else { + map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id,skill_lv),(skill_id==WM_VOICEOFSIREN)?BL_CHAR|BL_SKILL:BL_PC, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case WM_GLOOMYDAY: + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + if( dstsd && ( pc_checkskill(dstsd,KN_BRANDISHSPEAR) || pc_checkskill(dstsd,LK_SPIRALPIERCE) || + pc_checkskill(dstsd,CR_SHIELDCHARGE) || pc_checkskill(dstsd,CR_SHIELDBOOMERANG) || + pc_checkskill(dstsd,PA_SHIELDCHAIN) || pc_checkskill(dstsd,LG_SHIELDPRESS) ) ) + { + sc_start(bl,SC_GLOOMYDAY_SK,100,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + } + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + break; + + case WM_SATURDAY_NIGHT_FEVER: + if( flag&1 ) { // Affect to all targets arround the caster and caster too. + if( !(tsc && tsc->data[type]) ) + sc_start(bl, type, 100, skill_lv,skill_get_time(skill_id, skill_lv)); + } else if( flag&2 ) { + if( src->id != bl->id && battle_check_target(src,bl,BCT_ENEMY) > 0 ) + status_fix_damage(src,bl,9999,clif_damage(src,bl,tick,0,0,9999,0,0,0)); + } else if( sd ) { + short chance = sstatus->int_/6 + sd->status.job_level/5 + skill_lv*4; + if( !sd->status.party_id || (rnd()%100 > chance)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_NEED_HELPER,0); + break; + } + if( map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id,skill_lv), + BL_PC, src, skill_id, skill_lv, tick, BCT_ENEMY, skill_area_sub_count) > 7 ) + flag |= 2; + else + flag |= 1; + map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id,skill_lv),BL_PC, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|BCT_SELF, skill_castend_nodamage_id); + clif_skill_nodamage(src, bl, skill_id, skill_lv, + sc_start(src,SC_STOP,100,skill_lv,skill_get_time2(skill_id,skill_lv))); + if( flag&2 ) // Dealed here to prevent conflicts + status_fix_damage(src,bl,9999,clif_damage(src,bl,tick,0,0,9999,0,0,0)); + } + break; + + case WM_SONG_OF_MANA: + case WM_DANCE_WITH_WUG: + case WM_LERADS_DEW: + if( flag&1 ) { // These affect to to all party members near the caster. + struct status_change *sc = status_get_sc(src); + if( sc && sc->data[type] ) { + sc_start2(bl,type,100,skill_lv,sc->data[type]->val2,skill_get_time(skill_id,skill_lv)); + } + } else if( sd ) { + short lv = (short)skill_lv; + int count = skill_check_pc_partner(sd,skill_id,&lv,skill_get_splash(skill_id,skill_lv),1); + if( sc_start2(bl,type,100,skill_lv,count,skill_get_time(skill_id,skill_lv)) ) + party_foreachsamemap(skill_area_sub,sd,skill_get_splash(skill_id,skill_lv),src,skill_id,skill_lv,tick,flag|BCT_PARTY|1,skill_castend_nodamage_id); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + + } + break; + + case WM_MELODYOFSINK: + case WM_BEYOND_OF_WARCRY: + case WM_UNLIMITED_HUMMING_VOICE: + if( flag&1 ) { + sc_start2(bl,type,100,skill_lv,skill_area_temp[0],skill_get_time(skill_id,skill_lv)); + } else { // These affect to all targets arround the caster. + short lv = (short)skill_lv; + skill_area_temp[0] = (sd) ? skill_check_pc_partner(sd,skill_id,&lv,skill_get_splash(skill_id,skill_lv),1) : 50; // 50% chance in non BL_PC (clones). + map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id,skill_lv),BL_PC, src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case WM_RANDOMIZESPELL: { + int improv_skill_id = 0, improv_skill_lv; + do { + i = rnd() % MAX_SKILL_IMPROVISE_DB; + improv_skill_id = skill_improvise_db[i].skill_id; + } while( improv_skill_id == 0 || rnd()%10000 >= skill_improvise_db[i].per ); + improv_skill_lv = 4 + skill_lv; + clif_skill_nodamage (src, bl, skill_id, skill_lv, 1); + + if( sd ) { + sd->state.abra_flag = 2; + sd->skillitem = improv_skill_id; + sd->skillitemlv = improv_skill_lv; + clif_item_skill(sd, improv_skill_id, improv_skill_lv); + } else { + struct unit_data *ud = unit_bl2ud(src); + int inf = skill_get_inf(improv_skill_id); + int target_id = 0; + if (!ud) break; + if (inf&INF_SELF_SKILL || inf&INF_SUPPORT_SKILL) { + if (src->type == BL_PET) + bl = (struct block_list*)((TBL_PET*)src)->msd; + if (!bl) bl = src; + unit_skilluse_id(src, bl->id, improv_skill_id, improv_skill_lv); + } else { + if (ud->target) + target_id = ud->target; + else switch (src->type) { + case BL_MOB: target_id = ((TBL_MOB*)src)->target_id; break; + case BL_PET: target_id = ((TBL_PET*)src)->target_id; break; + } + if (!target_id) + break; + if (skill_get_casttype(improv_skill_id) == CAST_GROUND) { + bl = map_id2bl(target_id); + if (!bl) bl = src; + unit_skilluse_pos(src, bl->x, bl->y, improv_skill_id, improv_skill_lv); + } else + unit_skilluse_id(src, target_id, improv_skill_id, improv_skill_lv); + } + } + } + break; + + + case RETURN_TO_ELDICASTES: + case ALL_GUARDIAN_RECALL: + if( sd ) + { + short x, y; // Destiny position. + unsigned short mapindex; + + if( skill_id == RETURN_TO_ELDICASTES) + { + x = 198; + y = 187; + mapindex = mapindex_name2id(MAP_DICASTES); + } + else + { + x = 44; + y = 151; + mapindex = mapindex_name2id(MAP_MORA); + } + + if(!mapindex) + { //Given map not found? + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + map_freeblock_unlock(); + return 0; + } + pc_setpos(sd, mapindex, x, y, CLR_TELEPORT); + } + break; + + case GM_SANDMAN: + if( tsc ) { + if( tsc->opt1 == OPT1_SLEEP ) + tsc->opt1 = 0; + else + tsc->opt1 = OPT1_SLEEP; + clif_changeoption(bl); + clif_skill_nodamage (src, bl, skill_id, skill_lv, 1); + } + break; + + case SO_ARRULLO: + { + // [(15 + 5 * Skill Level) + ( Caster’s INT / 5 ) + ( Caster’s Job Level / 5 ) - ( Target’s INT / 6 ) - ( Target’s LUK / 10 )] % + int rate = (15 + 5 * skill_lv) + status_get_int(src)/5 + (sd)?sd->status.job_level:0; + rate -= status_get_int(bl)/6 - status_get_luk(bl)/10; + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + sc_start2(bl, type, rate, skill_lv, 1, skill_get_time(skill_id, skill_lv)); + } + break; + + case WM_LULLABY_DEEPSLEEP: + if( flag&1 ){ + //[(Skill Level x 4) + (Voice Lessons Skill Level x 2) + (Caster’s Base Level / 15) + (Caster’s Job Level / 5)] % + int rate = (4 * skill_lv) + ( (sd) ? pc_checkskill(sd,WM_LESSON)*2 + sd->status.job_level/5 : 0 ) + status_get_lv(src) / 15; + if( bl != src ) + sc_start(bl,type,rate,skill_lv,skill_get_time(skill_id,skill_lv)); + }else { + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, + src, skill_id, skill_lv, tick, flag|BCT_ALL|1, skill_castend_nodamage_id); + } + break; + + case SO_SUMMON_AGNI: + case SO_SUMMON_AQUA: + case SO_SUMMON_VENTUS: + case SO_SUMMON_TERA: + if( sd ) { + int elemental_class = skill_get_elemental_type(skill_id,skill_lv); + + // Remove previous elemental fisrt. + if( sd->ed ) + elemental_delete(sd->ed,0); + + // Summoning the new one. + if( !elemental_create(sd,elemental_class,skill_get_time(skill_id,skill_lv)) ) { + clif_skill_fail(sd,skill_id,0,0); + break; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case SO_EL_CONTROL: + if( sd ) { + int mode = EL_MODE_PASSIVE; // Standard mode. + + if( !sd->ed ) break; + + if( skill_lv == 4 ) {// At level 4 delete elementals. + elemental_delete(sd->ed, 0); + break; + } + switch( skill_lv ) {// Select mode bassed on skill level used. + case 2: mode = EL_MODE_ASSIST; break; + case 3: mode = EL_MODE_AGGRESSIVE; break; + } + if( !elemental_change_mode(sd->ed,mode) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + + case SO_EL_ACTION: + if( sd ) { + int duration = 3000; + if( !sd->ed ) break; + sd->skill_id_old = skill_id; + elemental_action(sd->ed, bl, tick); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + switch(sd->ed->db->class_){ + case 2115:case 2124: + case 2118:case 2121: + duration = 6000; + break; + case 2116:case 2119: + case 2122:case 2125: + duration = 9000; + break; + } + skill_blockpc_start(sd, skill_id, duration); + } + break; + + case SO_EL_CURE: + if( sd ) { + struct elemental_data *ed = sd->ed; + int s_hp = sd->battle_status.hp * 10 / 100, s_sp = sd->battle_status.sp * 10 / 100; + int e_hp, e_sp; + + if( !ed ) break; + if( !status_charge(&sd->bl,s_hp,s_sp) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + e_hp = ed->battle_status.max_hp * 10 / 100; + e_sp = ed->battle_status.max_sp * 10 / 100; + status_heal(&ed->bl,e_hp,e_sp,3); + clif_skill_nodamage(src,&ed->bl,skill_id,skill_lv,1); + } + break; + + case GN_CHANGEMATERIAL: + case SO_EL_ANALYSIS: + if( sd ) { + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + clif_skill_itemlistwindow(sd,skill_id,skill_lv); + } + break; + + case GN_BLOOD_SUCKER: + { + struct status_change *sc = status_get_sc(src); + + if( sc && sc->bs_counter < skill_get_maxcount( skill_id , skill_lv) ) { + if( tsc && tsc->data[type] ){ + (sc->bs_counter)--; + status_change_end(src, type, INVALID_TIMER); // the first one cancels and the last one will take effect resetting the timer + } + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + sc_start2(bl, type, 100, skill_lv, src->id, skill_get_time(skill_id,skill_lv)); + (sc->bs_counter)++; + } else if( sd ) { + clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); + break; + } + } + break; + + case GN_MANDRAGORA: + if( flag&1 ) { + if ( clif_skill_nodamage(bl, src, skill_id, skill_lv, + sc_start(bl, type, 25 + 10 * skill_lv, skill_lv, skill_get_time(skill_id, skill_lv))) ) + status_zap(bl, 0, status_get_max_sp(bl) * (25 + 5 * skill_lv) / 100); + } else + map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), BL_CHAR, + src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); + break; + + case GN_SLINGITEM: + if( sd ) { + short ammo_id; + i = sd->equip_index[EQI_AMMO]; + if( i <= 0 ) + break; // No ammo. + ammo_id = sd->inventory_data[i]->nameid; + if( ammo_id <= 0 ) + break; + sd->itemid = ammo_id; + if( itemdb_is_GNbomb(ammo_id) ) { + if(battle_check_target(src,bl,BCT_ENEMY) > 0) {// Only attack if the target is an enemy. + if( ammo_id == 13263 ) + map_foreachincell(skill_area_sub,bl->m,bl->x,bl->y,BL_CHAR,src,GN_SLINGITEM_RANGEMELEEATK,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); + else + skill_attack(BF_WEAPON,src,src,bl,GN_SLINGITEM_RANGEMELEEATK,skill_lv,tick,flag); + } else //Otherwise, it fails, shows animation and removes items. + clif_skill_fail(sd,GN_SLINGITEM_RANGEMELEEATK,0xa,0); + } else if( itemdb_is_GNthrowable(ammo_id) ){ + struct script_code *script = sd->inventory_data[i]->script; + if( !script ) + break; + if( dstsd ) + run_script(script,0,dstsd->bl.id,fake_nd->bl.id); + else + run_script(script,0,src->id,0); + } + } + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1);// This packet is received twice actually, I think it is to show the animation. + break; + + case GN_MIX_COOKING: + case GN_MAKEBOMB: + case GN_S_PHARMACY: + if( sd ) { + int qty = 1; + sd->skill_id_old = skill_id; + sd->skill_lv_old = skill_lv; + if( skill_id != GN_S_PHARMACY && skill_lv > 1 ) + qty = 10; + clif_cooking_list(sd,(skill_id - GN_MIX_COOKING) + 27,skill_id,qty,skill_id==GN_MAKEBOMB?5:6); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + } + break; + case EL_CIRCLE_OF_FIRE: + case EL_PYROTECHNIC: + case EL_HEATER: + case EL_TROPIC: + case EL_AQUAPLAY: + case EL_COOLER: + case EL_CHILLY_AIR: + case EL_GUST: + case EL_BLAST: + case EL_WILD_STORM: + case EL_PETROLOGY: + case EL_CURSED_SOIL: + case EL_UPHEAVAL: + case EL_FIRE_CLOAK: + case EL_WATER_DROP: + case EL_WIND_CURTAIN: + case EL_SOLID_SKIN: + case EL_STONE_SHIELD: + case EL_WIND_STEP: { + struct elemental_data *ele = BL_CAST(BL_ELEM, src); + if( ele ) { + sc_type type2 = type-1; + struct status_change *sc = status_get_sc(&ele->bl); + + if( (sc && sc->data[type2]) || (tsc && tsc->data[type]) ) { + elemental_clean_single_effect(ele, skill_id); + } else { + clif_skill_nodamage(src,src,skill_id,skill_lv,1); + clif_skill_damage(src, ( skill_id == EL_GUST || skill_id == EL_BLAST || skill_id == EL_WILD_STORM )?src:bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + if( skill_id == EL_WIND_STEP ) // There aren't teleport, just push the master away. + skill_blown(src,bl,(rnd()%skill_get_blewcount(skill_id,skill_lv))+1,rand()%8,0); + sc_start(src,type2,100,skill_lv,skill_get_time(skill_id,skill_lv)); + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv)); + } + } + } + break; + + case EL_FIRE_MANTLE: + case EL_WATER_BARRIER: + case EL_ZEPHYR: + case EL_POWER_OF_GAIA: + clif_skill_nodamage(src,src,skill_id,skill_lv,1); + clif_skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + skill_unitsetting(src,skill_id,skill_lv,bl->x,bl->y,0); + break; + + case EL_WATER_SCREEN: { + struct elemental_data *ele = BL_CAST(BL_ELEM, src); + if( ele ) { + struct status_change *sc = status_get_sc(&ele->bl); + sc_type type2 = type-1; + + clif_skill_nodamage(src,src,skill_id,skill_lv,1); + if( (sc && sc->data[type2]) || (tsc && tsc->data[type]) ) { + elemental_clean_single_effect(ele, skill_id); + } else { + // This not heals at the end. + clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + sc_start(src,type2,100,skill_lv,skill_get_time(skill_id,skill_lv)); + sc_start(bl,type,100,src->id,skill_get_time(skill_id,skill_lv)); + } + } + } + break; + + case KO_KAHU_ENTEN: + case KO_HYOUHU_HUBUKI: + case KO_KAZEHU_SEIRAN: + case KO_DOHU_KOUKAI: + if(sd) { + int ttype = skill_get_ele(skill_id, skill_lv); + clif_skill_nodamage(src, bl, skill_id, skill_lv, 1); + pc_add_talisman(sd, skill_get_time(skill_id, skill_lv), 10, ttype); + } + break; + + case KO_ZANZOU: + if(sd){ + struct mob_data *md; + + md = mob_once_spawn_sub(src, src->m, src->x, src->y, status_get_name(src), 2308, "", SZ_SMALL, AI_NONE); + if( md ) + { + md->master_id = src->id; + md->special_state.ai = AI_ZANZOU; + if( md->deletetimer != INVALID_TIMER ) + delete_timer(md->deletetimer, mob_timer_delete); + md->deletetimer = add_timer (gettick() + skill_get_time(skill_id, skill_lv), mob_timer_delete, md->bl.id, 0); + mob_spawn( md ); + pc_setinvincibletimer(sd,500);// unlock target lock + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + skill_blown(src,bl,skill_get_blewcount(skill_id,skill_lv),unit_getdir(bl),0); + } + } + break; + + case KO_KYOUGAKU: + if( dstsd && tsc && !tsc->data[type] && rand()%100 < tstatus->int_/2 ){ + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + }else if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + + case KO_JYUSATSU: + if( dstsd && tsc && !tsc->data[type] && + rand()%100 < ((45+5*skill_lv) + skill_lv*5 - status_get_int(bl)/2) ){//[(Base chance of success) + (Skill Level x 5) - (int / 2)]%. + clif_skill_nodamage(src,bl,skill_id,skill_lv, + status_change_start(bl,type,10000,skill_lv,0,0,0,skill_get_time(skill_id,skill_lv),1)); + status_zap(bl, tstatus->max_hp*skill_lv*5/100 , 0); + if( status_get_lv(bl) <= status_get_lv(src) ) + status_change_start(bl,SC_COMA,10,skill_lv,0,src->id,0,0,0); + }else if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + + case KO_GENWAKU: + if ( !map_flag_gvg(src->m) && ( dstsd || dstmd ) && battle_check_target(src,bl,BCT_ENEMY) > 0 ) { + int x = src->x, y = src->y; + + if( sd && rnd()%100 > ((45+5*skill_lv) - status_get_int(bl)/10) ){//[(Base chance of success) - (Intelligence Objectives / 10)]%. + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + + if (unit_movepos(src,bl->x,bl->y,0,0)) { + clif_skill_nodamage(src,src,skill_id,skill_lv,1); + clif_slide(src,bl->x,bl->y) ; + sc_start(src,SC_CONFUSION,80,skill_lv,skill_get_time(skill_id,skill_lv)); + if (unit_movepos(bl,x,y,0,0)) + { + clif_skill_damage(bl,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, -1, 6); + if( bl->type == BL_PC && pc_issit((TBL_PC*)bl)) + clif_sitting(bl); //Avoid sitting sync problem + clif_slide(bl,x,y) ; + sc_start(bl,SC_CONFUSION,80,skill_lv,skill_get_time(skill_id,skill_lv)); + } + } + } + break; + + case OB_AKAITSUKI: + case OB_OBOROGENSOU: + if( sd && ( (skill_id == OB_OBOROGENSOU && bl->type == BL_MOB) // This skill does not work on monsters. + || is_boss(bl) ) ){ // Does not work on Boss monsters. + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + case KO_IZAYOI: + case OB_ZANGETSU: + case KG_KYOMU: + case KG_KAGEMUSYA: + clif_skill_nodamage(src,bl,skill_id,skill_lv, + sc_start(bl,type,100,skill_lv,skill_get_time(skill_id,skill_lv))); + clif_skill_damage(src,bl,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + break; + + case KG_KAGEHUMI: + if( flag&1 ){ + if(tsc && ( tsc->option&(OPTION_CLOAK|OPTION_HIDE) || + tsc->data[SC_CAMOUFLAGE] || tsc->data[SC__SHADOWFORM] || + tsc->data[SC_MARIONETTE] || tsc->data[SC_HARMONIZE])){ + sc_start(src, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); + sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); + status_change_end(bl, SC_HIDING, INVALID_TIMER); + status_change_end(bl, SC_CLOAKING, INVALID_TIMER); + status_change_end(bl, SC_CLOAKINGEXCEED, INVALID_TIMER); + status_change_end(bl, SC_CAMOUFLAGE, INVALID_TIMER); + status_change_end(bl, SC__SHADOWFORM, INVALID_TIMER); + status_change_end(bl, SC_MARIONETTE, INVALID_TIMER); + status_change_end(bl, SC_HARMONIZE, INVALID_TIMER); + } + if( skill_area_temp[2] == 1 ){ + clif_skill_damage(src,src,tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + sc_start(src, SC_STOP, 100, skill_lv, skill_get_time(skill_id, skill_lv)); + } + }else{ + skill_area_temp[2] = 0; + map_foreachinrange(skill_area_sub, bl, skill_get_splash(skill_id, skill_lv), splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY|SD_SPLASH|1, skill_castend_nodamage_id); + } + break; + + case MH_SILENT_BREEZE: { + struct status_change *ssc = status_get_sc(src); + struct block_list *m_bl = battle_get_master(src); + const enum sc_type scs[] = { + SC_MANDRAGORA, SC_HARMONIZE, SC_DEEPSLEEP, SC_VOICEOFSIREN, SC_SLEEP, SC_CONFUSION, SC_HALLUCINATION + }; + int heal; + if(tsc){ + for (i = 0; i < ARRAYLENGTH(scs); i++) { + if (tsc->data[scs[i]]) status_change_end(bl, scs[i], INVALID_TIMER); + } + if (!tsc->data[SC_SILENCE]) //put inavoidable silence on target + status_change_start(bl, SC_SILENCE, 100, skill_lv, 0,0,0, skill_get_time(skill_id, skill_lv),1|2|8); + } + heal = status_get_matk_min(src)*4; + status_heal(bl, heal, 0, 7); + + //now inflict silence on everyone + if(ssc && !ssc->data[SC_SILENCE]) //put inavoidable silence on homun + status_change_start(src, SC_SILENCE, 100, skill_lv, 0,0,0, skill_get_time(skill_id, skill_lv),1|2|8); + if(m_bl){ + struct status_change *msc = status_get_sc(m_bl); + if(msc && !msc->data[SC_SILENCE]) //put inavoidable silence on master + status_change_start(m_bl, SC_SILENCE, 100, skill_lv, 0,0,0, skill_get_time(skill_id, skill_lv),1|2|8); + } + if (hd) + skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); + } + break; + case MH_OVERED_BOOST: + if (hd){ + struct block_list *s_bl = battle_get_master(src); + if(hd->homunculus.hunger>50) //reduce hunger + hd->homunculus.hunger = hd->homunculus.hunger/2; + else + hd->homunculus.hunger = min(1,hd->homunculus.hunger); + if(s_bl && s_bl->type==BL_PC){ + status_set_sp(s_bl,status_get_max_sp(s_bl)/2,0); //master drain 50% sp + clif_send_homdata(((TBL_PC *)s_bl), SP_HUNGRY, hd->homunculus.hunger); //refresh hunger info + sc_start(s_bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); //gene bonus + } + sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); + skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); + } + break; + case MH_GRANITIC_ARMOR: + case MH_PYROCLASTIC: { + struct block_list *s_bl = battle_get_master(src); + if(s_bl) sc_start2(s_bl, type, 100, skill_lv, hd->homunculus.level, skill_get_time(skill_id, skill_lv)); //start on master + sc_start2(bl, type, 100, skill_lv, hd->homunculus.level, skill_get_time(skill_id, skill_lv)); + if (hd) skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); + } + break; + + case MH_LIGHT_OF_REGENE: + if(hd){ + hd->homunculus.intimacy = 251; //change to neutral (can't be cast if < 750) + if(sd) clif_send_homdata(sd, SP_INTIMATE, hd->homunculus.intimacy); //refresh intimacy info + } + //don't break need to start status and start block timer + case MH_STYLE_CHANGE: + case MH_MAGMA_FLOW: + case MH_PAIN_KILLER: + sc_start(bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv)); + if (hd) + skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); + break; + case MH_SUMMON_LEGION: + { + int summons[5] = {1004, 1303, 1303, 1994, 1994}; + int qty[5] = {3 , 3 , 4 , 4 , 5}; + struct mob_data *md; + int i; + + for(i=0; i<qty[skill_lv - 1]; i++){ //easy way + md = mob_once_spawn_sub(src, src->m, src->x, src->y, status_get_name(src), summons[skill_lv - 1], "", SZ_SMALL, AI_ATTACK); + if (md) { + md->master_id = src->id; + if (md->deletetimer != INVALID_TIMER) + delete_timer(md->deletetimer, mob_timer_delete); + md->deletetimer = add_timer(gettick() + skill_get_time(skill_id, skill_lv), mob_timer_delete, md->bl.id, 0); + mob_spawn(md); //Now it is ready for spawning. + sc_start4(&md->bl, SC_MODECHANGE, 100, 1, 0, MD_ASSIST, 0, 60000); + } + } + if (hd) + skill_blockhomun_start(hd, skill_id, skill_get_cooldown(skill_id, skill_lv)); + } + break; + default: + ShowWarning("skill_castend_nodamage_id: Unknown skill used:%d\n",skill_id); + clif_skill_nodamage(src,bl,skill_id,skill_lv,1); + map_freeblock_unlock(); + return 1; + } + + if(skill_id != SR_CURSEDCIRCLE){ + struct status_change *sc = status_get_sc(src); + if( sc && sc->data[SC_CURSEDCIRCLE_ATKER] )//Should only remove after the skill had been casted. + status_change_end(src,SC_CURSEDCIRCLE_ATKER,INVALID_TIMER); + } + + if (dstmd) { //Mob skill event for no damage skills (damage ones are handled in battle_calc_damage) [Skotlex] + mob_log_damage(dstmd, src, 0); //Log interaction (counts as 'attacker' for the exp bonus) + mobskill_event(dstmd, src, tick, MSC_SKILLUSED|(skill_id<<16)); + } + + if( sd && !(flag&1) ) + {// ensure that the skill last-cast tick is recorded + sd->canskill_tick = gettick(); + + if( sd->state.arrow_atk ) + {// consume arrow on last invocation to this skill. + battle_consume_ammo(sd, skill_id, skill_lv); + } + skill_onskillusage(sd, bl, skill_id, tick); + // perform skill requirement consumption + skill_consume_requirement(sd,skill_id,skill_lv,2); + } + + map_freeblock_unlock(); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_castend_id(int tid, unsigned int tick, int id, intptr_t data) +{ + struct block_list *target, *src; + struct map_session_data *sd; + struct mob_data *md; + struct unit_data *ud; + struct status_change *sc = NULL; + int inf,inf2,flag = 0; + + src = map_id2bl(id); + if( src == NULL ) + { + ShowDebug("skill_castend_id: src == NULL (tid=%d, id=%d)\n", tid, id); + return 0;// not found + } + + ud = unit_bl2ud(src); + if( ud == NULL ) + { + ShowDebug("skill_castend_id: ud == NULL (tid=%d, id=%d)\n", tid, id); + return 0;// ??? + } + + sd = BL_CAST(BL_PC, src); + md = BL_CAST(BL_MOB, src); + + if( src->prev == NULL ) { + ud->skilltimer = INVALID_TIMER; + return 0; + } + + if(ud->skill_id != SA_CASTCANCEL && ud->skill_id != SO_SPELLFIST) {// otherwise handled in unit_skillcastcancel() + if( ud->skilltimer != tid ) { + ShowError("skill_castend_id: Timer mismatch %d!=%d!\n", ud->skilltimer, tid); + ud->skilltimer = INVALID_TIMER; + return 0; + } + + if( sd && ud->skilltimer != INVALID_TIMER && (pc_checkskill(sd,SA_FREECAST) > 0 || ud->skill_id == LG_EXEEDBREAK) ) + {// restore original walk speed + ud->skilltimer = INVALID_TIMER; + status_calc_bl(&sd->bl, SCB_SPEED); + } + + ud->skilltimer = INVALID_TIMER; + } + + if (ud->skilltarget == id) + target = src; + else + target = map_id2bl(ud->skilltarget); + + // Use a do so that you can break out of it when the skill fails. + do { + if(!target || target->prev==NULL) break; + + if(src->m != target->m || status_isdead(src)) break; + + switch (ud->skill_id) { + //These should become skill_castend_pos + case WE_CALLPARTNER: + if(sd) clif_callpartner(sd); + case WE_CALLPARENT: + case WE_CALLBABY: + case AM_RESURRECTHOMUN: + case PF_SPIDERWEB: + //Find a random spot to place the skill. [Skotlex] + inf2 = skill_get_splash(ud->skill_id, ud->skill_lv); + ud->skillx = target->x + inf2; + ud->skilly = target->y + inf2; + if (inf2 && !map_random_dir(target, &ud->skillx, &ud->skilly)) { + ud->skillx = target->x; + ud->skilly = target->y; + } + ud->skilltimer=tid; + return skill_castend_pos(tid,tick,id,data); + case GN_WALLOFTHORN: + ud->skillx = target->x; + ud->skilly = target->y; + ud->skilltimer = tid; + return skill_castend_pos(tid,tick,id,data); + } + + if(ud->skill_id == RG_BACKSTAP) { + uint8 dir = map_calc_dir(src,target->x,target->y),t_dir = unit_getdir(target); + if(check_distance_bl(src, target, 0) || map_check_dir(dir,t_dir)) { + break; + } + } + + if( ud->skill_id == PR_TURNUNDEAD ) + { + struct status_data *tstatus = status_get_status_data(target); + if( !battle_check_undead(tstatus->race, tstatus->def_ele) ) + break; + } + + if( ud->skill_id == RA_WUGSTRIKE ){ + if( !path_search(NULL,src->m,src->x,src->y,target->x,target->y,1,CELL_CHKNOREACH)) + break; + } + + if( ud->skill_id == PR_LEXDIVINA || ud->skill_id == MER_LEXDIVINA ) + { + sc = status_get_sc(target); + if( battle_check_target(src,target, BCT_ENEMY) <= 0 && (!sc || !sc->data[SC_SILENCE]) ) + { //If it's not an enemy, and not silenced, you can't use the skill on them. [Skotlex] + clif_skill_nodamage (src, target, ud->skill_id, ud->skill_lv, 0); + break; + } + } + else + { // Check target validity. + inf = skill_get_inf(ud->skill_id); + inf2 = skill_get_inf2(ud->skill_id); + + if(inf&INF_ATTACK_SKILL || + (inf&INF_SELF_SKILL && inf2&INF2_NO_TARGET_SELF) //Combo skills + ) // Casted through combo. + inf = BCT_ENEMY; //Offensive skill. + else if(inf2&INF2_NO_ENEMY) + inf = BCT_NOENEMY; + else + inf = 0; + + if(inf2 & (INF2_PARTY_ONLY|INF2_GUILD_ONLY) && src != target) + { + inf |= + (inf2&INF2_PARTY_ONLY?BCT_PARTY:0)| + (inf2&INF2_GUILD_ONLY?BCT_GUILD:0); + //Remove neutral targets (but allow enemy if skill is designed to be so) + inf &= ~BCT_NEUTRAL; + } + + if( ud->skill_id >= SL_SKE && ud->skill_id <= SL_SKA && target->type == BL_MOB ) + { + if( ((TBL_MOB*)target)->class_ == MOBID_EMPERIUM ) + break; + } + else if (inf && battle_check_target(src, target, inf) <= 0){ + if (sd) clif_skill_fail(sd,ud->skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + + if(inf&BCT_ENEMY && (sc = status_get_sc(target)) && + sc->data[SC_FOGWALL] && + rnd() % 100 < 75) { //Fogwall makes all offensive-type targetted skills fail at 75% + if (sd) clif_skill_fail(sd, ud->skill_id, USESKILL_FAIL_LEVEL, 0); + break; + } + } + + //Avoid doing double checks for instant-cast skills. + if (tid != INVALID_TIMER && !status_check_skilluse(src, target, ud->skill_id, 1)) + break; + + if(md) { + md->last_thinktime=tick +MIN_MOBTHINKTIME; + if(md->skill_idx >= 0 && md->db->skill[md->skill_idx].emotion >= 0) + clif_emotion(src, md->db->skill[md->skill_idx].emotion); + } + + if(src != target && battle_config.skill_add_range && + !check_distance_bl(src, target, skill_get_range2(src,ud->skill_id,ud->skill_lv)+battle_config.skill_add_range)) + { + if (sd) { + clif_skill_fail(sd,ud->skill_id,USESKILL_FAIL_LEVEL,0); + if(battle_config.skill_out_range_consume) //Consume items anyway. [Skotlex] + skill_consume_requirement(sd,ud->skill_id,ud->skill_lv,3); + } + break; + } + + if( sd ) + { + if( !skill_check_condition_castend(sd, ud->skill_id, ud->skill_lv) ) + break; + else + skill_consume_requirement(sd,ud->skill_id,ud->skill_lv,1); + } +#ifdef OFFICIAL_WALKPATH + if( !path_search_long(NULL, src->m, src->x, src->y, target->x, target->y, CELL_CHKWALL) ) + break; +#endif + if( (src->type == BL_MER || src->type == BL_HOM) && !skill_check_condition_mercenary(src, ud->skill_id, ud->skill_lv, 1) ) + break; + + if (ud->state.running && ud->skill_id == TK_JUMPKICK) + { + ud->state.running = 0; + status_change_end(src, SC_RUN, INVALID_TIMER); + flag = 1; + } + + if (ud->walktimer != INVALID_TIMER && ud->skill_id != TK_RUN && ud->skill_id != RA_WUGDASH) + unit_stop_walking(src,1); + + if( !sd || sd->skillitem != ud->skill_id || skill_get_delay(ud->skill_id,ud->skill_lv) ) + ud->canact_tick = tick + skill_delayfix(src, ud->skill_id, ud->skill_lv); //Tests show wings don't overwrite the delay but skill scrolls do. [Inkfish] + if (sd) { //Cooldown application + int i, cooldown = skill_get_cooldown(ud->skill_id, ud->skill_lv); + for (i = 0; i < ARRAYLENGTH(sd->skillcooldown) && sd->skillcooldown[i].id; i++) { // Increases/Decreases cooldown of a skill by item/card bonuses. + if (sd->skillcooldown[i].id == ud->skill_id){ + cooldown += sd->skillcooldown[i].val; + break; + } + } + if(cooldown) + skill_blockpc_start(sd, ud->skill_id, cooldown); + } + if( battle_config.display_status_timers && sd ) + clif_status_change(src, SI_ACTIONDELAY, 1, skill_delayfix(src, ud->skill_id, ud->skill_lv), 0, 0, 0); + if( sd ) + { + switch( ud->skill_id ) + { + case GS_DESPERADO: + sd->canequip_tick = tick + skill_get_time(ud->skill_id, ud->skill_lv); + break; + case CR_GRANDCROSS: + case NPC_GRANDDARKNESS: + if( (sc = status_get_sc(src)) && sc->data[SC_STRIPSHIELD] ) + { + const struct TimerData *timer = get_timer(sc->data[SC_STRIPSHIELD]->timer); + if( timer && timer->func == status_change_timer && DIFF_TICK(timer->tick,gettick()+skill_get_time(ud->skill_id, ud->skill_lv)) > 0 ) + break; + } + sc_start2(src, SC_STRIPSHIELD, 100, 0, 1, skill_get_time(ud->skill_id, ud->skill_lv)); + break; + } + } + if (skill_get_state(ud->skill_id) != ST_MOVE_ENABLE) + unit_set_walkdelay(src, tick, battle_config.default_walk_delay+skill_get_walkdelay(ud->skill_id, ud->skill_lv), 1); + + if(battle_config.skill_log && battle_config.skill_log&src->type) + ShowInfo("Type %d, ID %d skill castend id [id =%d, lv=%d, target ID %d]\n", + src->type, src->id, ud->skill_id, ud->skill_lv, target->id); + + map_freeblock_lock(); + + // SC_MAGICPOWER needs to switch states before any damage is actually dealt + skill_toggle_magicpower(src, ud->skill_id); + if( ud->skill_id != RA_CAMOUFLAGE ) // only normal attack and auto cast skills benefit from its bonuses + status_change_end(src,SC_CAMOUFLAGE, INVALID_TIMER); + + if (skill_get_casttype(ud->skill_id) == CAST_NODAMAGE) + skill_castend_nodamage_id(src,target,ud->skill_id,ud->skill_lv,tick,flag); + else + skill_castend_damage_id(src,target,ud->skill_id,ud->skill_lv,tick,flag); + + sc = status_get_sc(src); + if(sc && sc->count) { + if(sc->data[SC_SPIRIT] && + sc->data[SC_SPIRIT]->val2 == SL_WIZARD && + sc->data[SC_SPIRIT]->val3 == ud->skill_id && + ud->skill_id != WZ_WATERBALL) + sc->data[SC_SPIRIT]->val3 = 0; //Clear bounced spell check. + + if( sc->data[SC_DANCING] && skill_get_inf2(ud->skill_id)&INF2_SONG_DANCE && sd ) + skill_blockpc_start(sd,BD_ADAPTATION,3000); + } + + if( sd && ud->skill_id != SA_ABRACADABRA && ud->skill_id != WM_RANDOMIZESPELL ) // they just set the data so leave it as it is.[Inkfish] + sd->skillitem = sd->skillitemlv = 0; + + if (ud->skilltimer == INVALID_TIMER) { + if(md) md->skill_idx = -1; + else ud->skill_id = 0; //mobs can't clear this one as it is used for skill condition 'afterskill' + ud->skill_lv = ud->skilltarget = 0; + } + map_freeblock_unlock(); + return 1; + } while(0); + + //Skill failed. + if (ud->skill_id == MO_EXTREMITYFIST && sd && !(sc && sc->data[SC_FOGWALL])) + { //When Asura fails... (except when it fails from Fog of Wall) + //Consume SP/spheres + skill_consume_requirement(sd,ud->skill_id, ud->skill_lv,1); + status_set_sp(src, 0, 0); + sc = &sd->sc; + if (sc->count) + { //End states + status_change_end(src, SC_EXPLOSIONSPIRITS, INVALID_TIMER); + status_change_end(src, SC_BLADESTOP, INVALID_TIMER); +#ifdef RENEWAL + sc_start(src, SC_EXTREMITYFIST2, 100, ud->skill_lv, skill_get_time(ud->skill_id, ud->skill_lv)); +#endif + } + if (target && target->m == src->m) + { //Move character to target anyway. + if (unit_movepos(src, src->x+3, src->y+3, 1, 1)) + { //Display movement + animation. + clif_slide(src,src->x,src->y); + clif_skill_damage(src,target,tick,sd->battle_status.amotion,0,0,1,ud->skill_id, ud->skill_lv, 5); + } + clif_skill_fail(sd,ud->skill_id,USESKILL_FAIL_LEVEL,0); + } + } + + ud->skill_id = ud->skill_lv = ud->skilltarget = 0; + if( !sd || sd->skillitem != ud->skill_id || skill_get_delay(ud->skill_id,ud->skill_lv) ) + ud->canact_tick = tick; + //You can't place a skill failed packet here because it would be + //sent in ALL cases, even cases where skill_check_condition fails + //which would lead to double 'skill failed' messages u.u [Skotlex] + if(sd) + sd->skillitem = sd->skillitemlv = 0; + else if(md) + md->skill_idx = -1; + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_castend_pos(int tid, unsigned int tick, int id, intptr_t data) +{ + struct block_list* src = map_id2bl(id); + int maxcount; + struct map_session_data *sd; + struct unit_data *ud = unit_bl2ud(src); + struct mob_data *md; + + nullpo_ret(ud); + + sd = BL_CAST(BL_PC , src); + md = BL_CAST(BL_MOB, src); + + if( src->prev == NULL ) { + ud->skilltimer = INVALID_TIMER; + return 0; + } + + if( ud->skilltimer != tid ) + { + ShowError("skill_castend_pos: Timer mismatch %d!=%d\n", ud->skilltimer, tid); + ud->skilltimer = INVALID_TIMER; + return 0; + } + + if( sd && ud->skilltimer != INVALID_TIMER && ( pc_checkskill(sd,SA_FREECAST) > 0 || ud->skill_id == LG_EXEEDBREAK ) ) + {// restore original walk speed + ud->skilltimer = INVALID_TIMER; + status_calc_bl(&sd->bl, SCB_SPEED); + } + ud->skilltimer = INVALID_TIMER; + + do { + if( status_isdead(src) ) + break; + + if( !(src->type&battle_config.skill_reiteration) && + skill_get_unit_flag(ud->skill_id)&UF_NOREITERATION && + skill_check_unit_range(src,ud->skillx,ud->skilly,ud->skill_id,ud->skill_lv) + ) + { + if (sd) clif_skill_fail(sd,ud->skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + if( src->type&battle_config.skill_nofootset && + skill_get_unit_flag(ud->skill_id)&UF_NOFOOTSET && + skill_check_unit_range2(src,ud->skillx,ud->skilly,ud->skill_id,ud->skill_lv) + ) + { + if (sd) clif_skill_fail(sd,ud->skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + if( src->type&battle_config.land_skill_limit && + (maxcount = skill_get_maxcount(ud->skill_id, ud->skill_lv)) > 0 + ) { + int i; + for(i=0;i<MAX_SKILLUNITGROUP && ud->skillunit[i] && maxcount;i++) { + if(ud->skillunit[i]->skill_id == ud->skill_id) + maxcount--; + } + if( maxcount == 0 ) + { + if (sd) clif_skill_fail(sd,ud->skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + } + + if(tid != INVALID_TIMER) + { //Avoid double checks on instant cast skills. [Skotlex] + if (!status_check_skilluse(src, NULL, ud->skill_id, 1)) + break; + if(battle_config.skill_add_range && + !check_distance_blxy(src, ud->skillx, ud->skilly, skill_get_range2(src,ud->skill_id,ud->skill_lv)+battle_config.skill_add_range)) { + if (sd && battle_config.skill_out_range_consume) //Consume items anyway. + skill_consume_requirement(sd,ud->skill_id,ud->skill_lv,3); + break; + } + } + + if( sd ) + { + if( !skill_check_condition_castend(sd, ud->skill_id, ud->skill_lv) ) + break; + else + skill_consume_requirement(sd,ud->skill_id,ud->skill_lv,1); + } + + if( (src->type == BL_MER || src->type == BL_HOM) && !skill_check_condition_mercenary(src, ud->skill_id, ud->skill_lv, 1) ) + break; + + if(md) { + md->last_thinktime=tick +MIN_MOBTHINKTIME; + if(md->skill_idx >= 0 && md->db->skill[md->skill_idx].emotion >= 0) + clif_emotion(src, md->db->skill[md->skill_idx].emotion); + } + + if(battle_config.skill_log && battle_config.skill_log&src->type) + ShowInfo("Type %d, ID %d skill castend pos [id =%d, lv=%d, (%d,%d)]\n", + src->type, src->id, ud->skill_id, ud->skill_lv, ud->skillx, ud->skilly); + + if (ud->walktimer != INVALID_TIMER) + unit_stop_walking(src,1); + + if( !sd || sd->skillitem != ud->skill_id || skill_get_delay(ud->skill_id,ud->skill_lv) ) + ud->canact_tick = tick + skill_delayfix(src, ud->skill_id, ud->skill_lv); + if (sd) { //Cooldown application + int i, cooldown = skill_get_cooldown(ud->skill_id, ud->skill_lv); + for (i = 0; i < ARRAYLENGTH(sd->skillcooldown) && sd->skillcooldown[i].id; i++) { // Increases/Decreases cooldown of a skill by item/card bonuses. + if (sd->skillcooldown[i].id == ud->skill_id){ + cooldown += sd->skillcooldown[i].val; + break; + } + } + if(cooldown) + skill_blockpc_start(sd, ud->skill_id, cooldown); + } + if( battle_config.display_status_timers && sd ) + clif_status_change(src, SI_ACTIONDELAY, 1, skill_delayfix(src, ud->skill_id, ud->skill_lv), 0, 0, 0); +// if( sd ) +// { +// switch( ud->skill_id ) +// { +// case ????: +// sd->canequip_tick = tick + ????; +// break; +// } +// } + unit_set_walkdelay(src, tick, battle_config.default_walk_delay+skill_get_walkdelay(ud->skill_id, ud->skill_lv), 1); + status_change_end(src,SC_CAMOUFLAGE, INVALID_TIMER);// only normal attack and auto cast skills benefit from its bonuses + map_freeblock_lock(); + skill_castend_pos2(src,ud->skillx,ud->skilly,ud->skill_id,ud->skill_lv,tick,0); + + if( sd && sd->skillitem != AL_WARP ) // Warp-Portal thru items will clear data in skill_castend_map. [Inkfish] + sd->skillitem = sd->skillitemlv = 0; + + if (ud->skilltimer == INVALID_TIMER) { + if (md) md->skill_idx = -1; + else ud->skill_id = 0; //Non mobs can't clear this one as it is used for skill condition 'afterskill' + ud->skill_lv = ud->skillx = ud->skilly = 0; + } + + map_freeblock_unlock(); + return 1; + } while(0); + + if( !sd || sd->skillitem != ud->skill_id || skill_get_delay(ud->skill_id,ud->skill_lv) ) + ud->canact_tick = tick; + ud->skill_id = ud->skill_lv = 0; + if(sd) + sd->skillitem = sd->skillitemlv = 0; + else if(md) + md->skill_idx = -1; + return 0; + +} + +/*========================================== + * + *------------------------------------------*/ +int skill_castend_pos2(struct block_list* src, int x, int y, uint16 skill_id, uint16 skill_lv, unsigned int tick, int flag) +{ + struct map_session_data* sd; + struct status_change* sc; + struct status_change_entry *sce; + struct skill_unit_group* sg; + enum sc_type type; + int i; + + //if(skill_lv <= 0) return 0; + if(skill_id > 0 && !skill_lv) return 0; // celest + + nullpo_ret(src); + + if(status_isdead(src)) + return 0; + + sd = BL_CAST(BL_PC, src); + + sc = status_get_sc(src); + type = status_skill2sc(skill_id); + sce = (sc && type != -1)?sc->data[type]:NULL; + + switch (skill_id) { //Skill effect. + case WZ_METEOR: + case MO_BODYRELOCATION: + case CR_CULTIVATION: + case HW_GANBANTEIN: + case LG_EARTHDRIVE: + break; //Effect is displayed on respective switch case. + default: + if(skill_get_inf(skill_id)&INF_SELF_SKILL) + clif_skill_nodamage(src,src,skill_id,skill_lv,1); + else + clif_skill_poseffect(src,skill_id,skill_lv,x,y,tick); + } + + // SC_MAGICPOWER needs to switch states before any damage is actually dealt + skill_toggle_magicpower(src, skill_id); + + switch(skill_id) + { + case PR_BENEDICTIO: + skill_area_temp[1] = src->id; + i = skill_get_splash(skill_id, skill_lv); + map_foreachinarea(skill_area_sub, + src->m, x-i, y-i, x+i, y+i, BL_PC, + src, skill_id, skill_lv, tick, flag|BCT_ALL|1, + skill_castend_nodamage_id); + map_foreachinarea(skill_area_sub, + src->m, x-i, y-i, x+i, y+i, BL_CHAR, + src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + break; + + case BS_HAMMERFALL: + i = skill_get_splash(skill_id, skill_lv); + map_foreachinarea (skill_area_sub, + src->m, x-i, y-i, x+i, y+i, BL_CHAR, + src, skill_id, skill_lv, tick, flag|BCT_ENEMY|2, + skill_castend_nodamage_id); + break; + + case HT_DETECTING: + i = skill_get_splash(skill_id, skill_lv); + map_foreachinarea( status_change_timer_sub, + src->m, x-i, y-i, x+i,y+i,BL_CHAR, + src,NULL,SC_SIGHT,tick); + if(battle_config.traps_setting&1) + map_foreachinarea( skill_reveal_trap, + src->m, x-i, y-i, x+i,y+i,BL_SKILL); + break; + + case SR_RIDEINLIGHTNING: + i = skill_get_splash(skill_id, skill_lv); + map_foreachinarea(skill_area_sub, src->m, x-i, y-i, x+i, y+i, BL_CHAR, + src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_damage_id); + break; + + case SA_VOLCANO: + case SA_DELUGE: + case SA_VIOLENTGALE: + { //Does not consumes if the skill is already active. [Skotlex] + struct skill_unit_group *sg; + if ((sg= skill_locate_element_field(src)) != NULL && ( sg->skill_id == SA_VOLCANO || sg->skill_id == SA_DELUGE || sg->skill_id == SA_VIOLENTGALE )) + { + if (sg->limit - DIFF_TICK(gettick(), sg->tick) > 0) + { + skill_unitsetting(src,skill_id,skill_lv,x,y,0); + return 0; // not to consume items + } + else + sg->limit = 0; //Disable it. + } + skill_unitsetting(src,skill_id,skill_lv,x,y,0); + break; + } + case MG_SAFETYWALL: + case MG_FIREWALL: + case MG_THUNDERSTORM: + + case AL_PNEUMA: + case WZ_ICEWALL: + case WZ_FIREPILLAR: + case WZ_QUAGMIRE: + case WZ_VERMILION: + case WZ_STORMGUST: + case WZ_HEAVENDRIVE: + case PR_SANCTUARY: + case PR_MAGNUS: + case CR_GRANDCROSS: + case NPC_GRANDDARKNESS: + case HT_SKIDTRAP: + case MA_SKIDTRAP: + case HT_LANDMINE: + case MA_LANDMINE: + case HT_ANKLESNARE: + case HT_SHOCKWAVE: + case HT_SANDMAN: + case MA_SANDMAN: + case HT_FLASHER: + case HT_FREEZINGTRAP: + case MA_FREEZINGTRAP: + case HT_BLASTMINE: + case HT_CLAYMORETRAP: + case AS_VENOMDUST: + case AM_DEMONSTRATION: + case PF_FOGWALL: + case PF_SPIDERWEB: + case HT_TALKIEBOX: + case WE_CALLPARTNER: + case WE_CALLPARENT: + case WE_CALLBABY: + case AC_SHOWER: //Ground-placed skill implementation. + case MA_SHOWER: + case SA_LANDPROTECTOR: + 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: + case GS_DESPERADO: + case NJ_KAENSIN: + case NJ_BAKUENRYU: + case NJ_SUITON: + case NJ_HYOUSYOURAKU: + case NJ_RAIGEKISAI: + case NJ_KAMAITACHI: +#ifdef RENEWAL + case NJ_HUUMA: +#endif + case NPC_EVILLAND: + case RA_ELECTRICSHOCKER: + case RA_CLUSTERBOMB: + case RA_MAGENTATRAP: + case RA_COBALTTRAP: + case RA_MAIZETRAP: + case RA_VERDURETRAP: + case RA_FIRINGTRAP: + case RA_ICEBOUNDTRAP: + case SC_MANHOLE: + case SC_DIMENSIONDOOR: + case SC_CHAOSPANIC: + case SC_MAELSTROM: + case WM_REVERBERATION: + case WM_SEVERE_RAINSTORM: + case WM_POEMOFNETHERWORLD: + case SO_PSYCHIC_WAVE: + case SO_VACUUM_EXTREME: + case GN_WALLOFTHORN: + case GN_THORNS_TRAP: + case GN_DEMONIC_FIRE: + case GN_HELLS_PLANT: + case SO_EARTHGRAVE: + case SO_DIAMONDDUST: + case SO_FIRE_INSIGNIA: + case SO_WATER_INSIGNIA: + case SO_WIND_INSIGNIA: + case SO_EARTH_INSIGNIA: + case KO_HUUMARANKA: + case KO_MUCHANAGE: + case KO_BAKURETSU: + case KO_ZENKAI: + case MH_LAVA_SLIDE: + case MH_VOLCANIC_ASH: + case MH_POISON_MIST: + case MH_STEINWAND: + case MH_XENO_SLASHER: + flag|=1;//Set flag to 1 to prevent deleting ammo (it will be deleted on group-delete). + case GS_GROUNDDRIFT: //Ammo should be deleted right away. + skill_unitsetting(src,skill_id,skill_lv,x,y,0); + break; + case RG_GRAFFITI: /* Graffiti [Valaris] */ + skill_clear_unitgroup(src); + skill_unitsetting(src,skill_id,skill_lv,x,y,0); + flag|=1; + break; + case HP_BASILICA: + if( sc->data[SC_BASILICA] ) + status_change_end(src, SC_BASILICA, INVALID_TIMER); // Cancel Basilica + else + { // Create Basilica. Start SC on caster. Unit timer start SC on others. + skill_clear_unitgroup(src); + if( skill_unitsetting(src,skill_id,skill_lv,x,y,0) ) + sc_start4(src,type,100,skill_lv,0,0,src->id,skill_get_time(skill_id,skill_lv)); + flag|=1; + } + break; + case CG_HERMODE: + skill_clear_unitgroup(src); + if ((sg = skill_unitsetting(src,skill_id,skill_lv,x,y,0))) + sc_start4(src,SC_DANCING,100, + skill_id,0,skill_lv,sg->group_id,skill_get_time(skill_id,skill_lv)); + flag|=1; + break; + case RG_CLEANER: // [Valaris] + i = skill_get_splash(skill_id, skill_lv); + map_foreachinarea(skill_graffitiremover,src->m,x-i,y-i,x+i,y+i,BL_SKILL); + break; + + case SO_WARMER: + flag|= 8; + case SO_CLOUD_KILL: + skill_unitsetting(src,skill_id,skill_lv,x,y,0); + break; + + case WZ_METEOR: { + int area = skill_get_splash(skill_id, skill_lv); + short tmpx = 0, tmpy = 0, x1 = 0, y1 = 0; + + for( i = 0; i < 2 + (skill_lv>>1); i++ ) { + // Creates a random Cell in the Splash Area + tmpx = x - area + rnd()%(area * 2 + 1); + tmpy = y - area + rnd()%(area * 2 + 1); + + if( i == 0 && path_search_long(NULL, src->m, src->x, src->y, tmpx, tmpy, CELL_CHKWALL) ) + clif_skill_poseffect(src,skill_id,skill_lv,tmpx,tmpy,tick); + + if( i > 0 ) + skill_addtimerskill(src,tick+i*1000,0,tmpx,tmpy,skill_id,skill_lv,(x1<<16)|y1,0); + + x1 = tmpx; + y1 = tmpy; + } + + skill_addtimerskill(src,tick+i*1000,0,tmpx,tmpy,skill_id,skill_lv,-1,0); + } + break; + + case AL_WARP: + if(sd) + { + clif_skill_warppoint(sd, skill_id, skill_lv, sd->status.save_point.map, + (skill_lv >= 2) ? sd->status.memo_point[0].map : 0, + (skill_lv >= 3) ? sd->status.memo_point[1].map : 0, + (skill_lv >= 4) ? sd->status.memo_point[2].map : 0 + ); + } + if( sc && sc->data[SC_CURSEDCIRCLE_ATKER] ) //Should only remove after the skill has been casted. + status_change_end(src,SC_CURSEDCIRCLE_ATKER,INVALID_TIMER); + return 0; // not to consume item. + + case MO_BODYRELOCATION: + if (unit_movepos(src, x, y, 1, 1)) { +#if PACKETVER >= 20111005 + clif_snap(src, src->x, src->y); +#else + clif_skill_poseffect(src,skill_id,skill_lv,src->x,src->y,tick); +#endif + if (sd) + skill_blockpc_start (sd, MO_EXTREMITYFIST, 2000); + } + break; + case NJ_SHADOWJUMP: + if( !map_flag_gvg(src->m) && !map[src->m].flag.battleground ) { //You don't move on GVG grounds. + unit_movepos(src, x, y, 1, 0); + clif_slide(src,x,y); + } + status_change_end(src, SC_HIDING, INVALID_TIMER); + break; + case AM_SPHEREMINE: + case AM_CANNIBALIZE: + { + int summons[5] = { 1589, 1579, 1575, 1555, 1590 }; + //int summons[5] = { 1020, 1068, 1118, 1500, 1368 }; + int class_ = skill_id==AM_SPHEREMINE?1142:summons[skill_lv-1]; + struct mob_data *md; + + // Correct info, don't change any of this! [celest] + md = mob_once_spawn_sub(src, src->m, x, y, status_get_name(src), class_, "", SZ_SMALL, AI_NONE); + if (md) { + md->master_id = src->id; + md->special_state.ai = (skill_id == AM_SPHEREMINE) ? AI_SPHERE : AI_FLORA; + if( md->deletetimer != INVALID_TIMER ) + delete_timer(md->deletetimer, mob_timer_delete); + md->deletetimer = add_timer (gettick() + skill_get_time(skill_id,skill_lv), mob_timer_delete, md->bl.id, 0); + mob_spawn (md); //Now it is ready for spawning. + } + } + break; + + // Slim Pitcher [Celest] + case CR_SLIMPITCHER: + if (sd) { + int i = skill_lv%11 - 1; + int j = pc_search_inventory(sd,skill_db[skill_id].itemid[i]); + if( j < 0 || skill_db[skill_id].itemid[i] <= 0 || sd->inventory_data[j] == NULL || sd->status.inventory[j].amount < skill_db[skill_id].amount[i] ) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 1; + } + potion_flag = 1; + potion_hp = 0; + potion_sp = 0; + run_script(sd->inventory_data[j]->script,0,sd->bl.id,0); + potion_flag = 0; + //Apply skill bonuses + i = pc_checkskill(sd,CR_SLIMPITCHER)*10 + + pc_checkskill(sd,AM_POTIONPITCHER)*10 + + pc_checkskill(sd,AM_LEARNINGPOTION)*5 + + pc_skillheal_bonus(sd, skill_id); + + potion_hp = potion_hp * (100+i)/100; + potion_sp = potion_sp * (100+i)/100; + + if(potion_hp > 0 || potion_sp > 0) { + i = skill_get_splash(skill_id, skill_lv); + map_foreachinarea(skill_area_sub, + src->m,x-i,y-i,x+i,y+i,BL_CHAR, + src,skill_id,skill_lv,tick,flag|BCT_PARTY|BCT_GUILD|1, + skill_castend_nodamage_id); + } + } else { + int i = skill_lv%11 - 1; + struct item_data *item; + i = skill_db[skill_id].itemid[i]; + item = itemdb_search(i); + potion_flag = 1; + potion_hp = 0; + potion_sp = 0; + run_script(item->script,0,src->id,0); + potion_flag = 0; + i = skill_get_max(CR_SLIMPITCHER)*10; + + potion_hp = potion_hp * (100+i)/100; + potion_sp = potion_sp * (100+i)/100; + + if(potion_hp > 0 || potion_sp > 0) { + i = skill_get_splash(skill_id, skill_lv); + map_foreachinarea(skill_area_sub, + src->m,x-i,y-i,x+i,y+i,BL_CHAR, + src,skill_id,skill_lv,tick,flag|BCT_PARTY|BCT_GUILD|1, + skill_castend_nodamage_id); + } + } + break; + + case HW_GANBANTEIN: + if (rnd()%100 < 80) { + int dummy = 1; + clif_skill_poseffect(src,skill_id,skill_lv,x,y,tick); + i = skill_get_splash(skill_id, skill_lv); + map_foreachinarea(skill_cell_overlap, src->m, x-i, y-i, x+i, y+i, BL_SKILL, HW_GANBANTEIN, &dummy, src); + } else { + if (sd) clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 1; + } + break; + + case HW_GRAVITATION: + if ((sg = skill_unitsetting(src,skill_id,skill_lv,x,y,0))) + sc_start4(src,type,100,skill_lv,0,BCT_SELF,sg->group_id,skill_get_time(skill_id,skill_lv)); + flag|=1; + break; + + // Plant Cultivation [Celest] + case CR_CULTIVATION: + if (sd) { + if( map_count_oncell(src->m,x,y,BL_CHAR) > 0 ) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 1; + } + clif_skill_poseffect(src,skill_id,skill_lv,x,y,tick); + if (rnd()%100 < 50) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + } else { + TBL_MOB* md = mob_once_spawn_sub(src, src->m, x, y, "--ja--",(skill_lv < 2 ? 1084+rnd()%2 : 1078+rnd()%6),"", SZ_SMALL, AI_NONE); + int i; + if (!md) break; + if ((i = skill_get_time(skill_id, skill_lv)) > 0) + { + if( md->deletetimer != INVALID_TIMER ) + delete_timer(md->deletetimer, mob_timer_delete); + md->deletetimer = add_timer (tick + i, mob_timer_delete, md->bl.id, 0); + } + mob_spawn (md); + } + } + break; + + case SG_SUN_WARM: + case SG_MOON_WARM: + case SG_STAR_WARM: + skill_clear_unitgroup(src); + if ((sg = skill_unitsetting(src,skill_id,skill_lv,src->x,src->y,0))) + sc_start4(src,type,100,skill_lv,0,0,sg->group_id,skill_get_time(skill_id,skill_lv)); + flag|=1; + break; + + case PA_GOSPEL: + if (sce && sce->val4 == BCT_SELF) + { + status_change_end(src, SC_GOSPEL, INVALID_TIMER); + return 0; + } + else + { + sg = skill_unitsetting(src,skill_id,skill_lv,src->x,src->y,0); + if (!sg) break; + if (sce) + status_change_end(src, type, INVALID_TIMER); //Was under someone else's Gospel. [Skotlex] + sc_start4(src,type,100,skill_lv,0,sg->group_id,BCT_SELF,skill_get_time(skill_id,skill_lv)); + clif_skill_poseffect(src, skill_id, skill_lv, 0, 0, tick); // PA_GOSPEL music packet + } + break; + case NJ_TATAMIGAESHI: + if (skill_unitsetting(src,skill_id,skill_lv,src->x,src->y,0)) + sc_start(src,type,100,skill_lv,skill_get_time2(skill_id,skill_lv)); + break; + + case AM_RESURRECTHOMUN: //[orn] + if (sd) + { + if (!merc_resurrect_homunculus(sd, 20*skill_lv, x, y)) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + break; + } + } + break; + + case RK_WINDCUTTER: + clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + case NC_COLDSLOWER: + case NC_ARMSCANNON: + case RK_DRAGONBREATH: + i = skill_get_splash(skill_id,skill_lv); + map_foreachinarea(skill_area_sub,src->m,x-i,y-i,x+i,y+i,splash_target(src), + src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); + break; + + case SO_ARRULLO: + i = skill_get_splash(skill_id,skill_lv); + map_foreachinarea(skill_area_sub,src->m,x-i,y-i,x+i,y+i,splash_target(src), + src, skill_id, skill_lv, tick, flag|BCT_ENEMY|1, skill_castend_nodamage_id); + break; + /** + * Guilotine Cross + **/ + case GC_POISONSMOKE: + if( !(sc && sc->data[SC_POISONINGWEAPON]) ) { + if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_GC_POISONINGWEAPON,0); + return 0; + } + clif_skill_damage(src,src,tick,status_get_amotion(src),0,-30000,1,skill_id,skill_lv,6); + skill_unitsetting(src, skill_id, skill_lv, x, y, flag); + //status_change_end(src,SC_POISONINGWEAPON,INVALID_TIMER); // 08/31/2011 - When using poison smoke, you no longer lose the poisoning weapon effect. + break; + /** + * Arch Bishop + **/ + case AB_EPICLESIS: + if( (sg = skill_unitsetting(src, skill_id, skill_lv, x, y, 0)) ) { + i = sg->unit->range; + map_foreachinarea(skill_area_sub, src->m, x - i, y - i, x + i, y + i, BL_CHAR, src, ALL_RESURRECTION, 1, tick, flag|BCT_NOENEMY|1,skill_castend_nodamage_id); + } + break; + /** + * Warlock + **/ + case WL_COMET: + if( sc ) { + sc->comet_x = x; + sc->comet_y = y; + } + i = skill_get_splash(skill_id,skill_lv); + map_foreachinarea(skill_area_sub,src->m,x-i,y-i,x+i,y+i,splash_target(src),src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); + break; + + case WL_EARTHSTRAIN: + { + int i, wave = skill_lv + 4, dir = map_calc_dir(src,x,y); + int sx = x = src->x, sy = y = src->y; // Store first caster's location to avoid glitch on unit setting + + for( i = 1; i <= wave; i++ ) + { + switch( dir ){ + case 0: case 1: case 7: sy = y + i; break; + case 3: case 4: case 5: sy = y - i; break; + case 2: sx = x - i; break; + case 6: sx = x + i; break; + } + skill_addtimerskill(src,gettick() + (150 * i),0,sx,sy,skill_id,skill_lv,dir,flag&2); + } + } + break; + /** + * Ranger + **/ + case RA_DETONATOR: + i = skill_get_splash(skill_id, skill_lv); + map_foreachinarea(skill_detonator, src->m, x-i, y-i, x+i, y+i, BL_SKILL, src); + clif_skill_damage(src, src, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, 6); + break; + /** + * Mechanic + **/ + case NC_NEUTRALBARRIER: + case NC_STEALTHFIELD: + skill_clear_unitgroup(src); // To remove previous skills - cannot used combined + if( (sg = skill_unitsetting(src,skill_id,skill_lv,src->x,src->y,0)) != NULL ) { + sc_start2(src,skill_id == NC_NEUTRALBARRIER ? SC_NEUTRALBARRIER_MASTER : SC_STEALTHFIELD_MASTER,100,skill_lv,sg->group_id,skill_get_time(skill_id,skill_lv)); + if( sd ) pc_overheat(sd,1); + } + break; + + case NC_SILVERSNIPER: + { + int class_ = 2042; + struct mob_data *md; + + md = mob_once_spawn_sub(src, src->m, x, y, status_get_name(src), class_, "", SZ_SMALL, AI_NONE); + if( md ) + { + md->master_id = src->id; + md->special_state.ai = AI_FLORA; + if( md->deletetimer != INVALID_TIMER ) + delete_timer(md->deletetimer, mob_timer_delete); + md->deletetimer = add_timer (gettick() + skill_get_time(skill_id, skill_lv), mob_timer_delete, md->bl.id, 0); + mob_spawn( md ); + } + } + break; + + case NC_MAGICDECOY: + if( sd ) clif_magicdecoy_list(sd,skill_lv,x,y); + break; + + case SC_FEINTBOMB: + clif_skill_nodamage(src,src,skill_id,skill_lv,1); + skill_unitsetting(src,skill_id,skill_lv,x,y,0); // Set bomb on current Position + if( skill_blown(src,src,6,unit_getdir(src),0) ) + skill_castend_nodamage_id(src,src,TF_HIDING,1,tick,0); + break; + + case LG_OVERBRAND: + { + int width;//according to data from irowiki it actually is a square + for( width = 0; width < 7; width++ ) + for( i = 0; i < 7; i++ ) + map_foreachincell(skill_area_sub, src->m, x-2+i, y-2+width, splash_target(src), src, LG_OVERBRAND_BRANDISH, skill_lv, tick, flag|BCT_ENEMY,skill_castend_damage_id); + for( width = 0; width < 7; width++ ) + for( i = 0; i < 7; i++ ) + map_foreachincell(skill_area_sub, src->m, x-2+i, y-2+width, splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY,skill_castend_damage_id); + } + break; + + case LG_BANDING: + if( sc && sc->data[SC_BANDING] ) + status_change_end(src,SC_BANDING,INVALID_TIMER); + else if( (sg = skill_unitsetting(src,skill_id,skill_lv,src->x,src->y,0)) != NULL ) { + sc_start4(src,SC_BANDING,100,skill_lv,0,0,sg->group_id,skill_get_time(skill_id,skill_lv)); + if( sd ) pc_banding(sd,skill_lv); + } + clif_skill_nodamage(src,src,skill_id,skill_lv,1); + break; + + case LG_RAYOFGENESIS: + if( status_charge(src,status_get_max_hp(src)*3*skill_lv / 100,0) ) { + i = skill_get_splash(skill_id,skill_lv); + map_foreachinarea(skill_area_sub,src->m,x-i,y-i,x+i,y+i,splash_target(src), + src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill_castend_damage_id); + } else if( sd ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL,0); + break; + + case WM_DOMINION_IMPULSE: + i = skill_get_splash(skill_id, skill_lv); + map_foreachinarea( skill_ative_reverberation, + src->m, x-i, y-i, x+i,y+i,BL_SKILL); + break; + + case WM_GREAT_ECHO: + flag|=1; // Should counsume 1 item per skill usage. + map_foreachinrange(skill_area_sub, src, skill_get_splash(skill_id,skill_lv),splash_target(src), src, skill_id, skill_lv, tick, flag|BCT_ENEMY, skill_castend_damage_id); + break; + case GN_CRAZYWEED: { + int area = skill_get_splash(GN_CRAZYWEED_ATK, skill_lv); + short x1 = 0, y1 = 0; + + for( i = 0; i < 3 + (skill_lv/2); i++ ) { + x1 = x - area + rnd()%(area * 2 + 1); + y1 = y - area + rnd()%(area * 2 + 1); + skill_addtimerskill(src,tick+i*150,0,x1,y1,GN_CRAZYWEED_ATK,skill_lv,-1,0); + } + } + break; + case GN_FIRE_EXPANSION: { + int i; + struct unit_data *ud = unit_bl2ud(src); + + if( !ud ) break; + + for( i = 0; i < MAX_SKILLUNITGROUP && ud->skillunit[i]; i ++ ) { + if( ud->skillunit[i]->skill_id == GN_DEMONIC_FIRE && + distance_xy(x, y, ud->skillunit[i]->unit->bl.x, ud->skillunit[i]->unit->bl.y) < 4 ) { + switch( skill_lv ) { + case 3: + ud->skillunit[i]->unit_id = UNT_FIRE_EXPANSION_SMOKE_POWDER; + clif_changetraplook(&ud->skillunit[i]->unit->bl, UNT_FIRE_EXPANSION_SMOKE_POWDER); + break; + case 4: + ud->skillunit[i]->unit_id = UNT_FIRE_EXPANSION_TEAR_GAS; + clif_changetraplook(&ud->skillunit[i]->unit->bl, UNT_FIRE_EXPANSION_TEAR_GAS); + break; + case 5: + map_foreachinarea(skill_area_sub, src->m, + ud->skillunit[i]->unit->bl.x - 3, ud->skillunit[i]->unit->bl.y - 3, + ud->skillunit[i]->unit->bl.x + 3, ud->skillunit[i]->unit->bl.y + 3, BL_CHAR, + src, CR_ACIDDEMONSTRATION, sd ? pc_checkskill(sd, CR_ACIDDEMONSTRATION) : skill_lv, tick, flag|BCT_ENEMY|1|SD_LEVEL, skill_castend_damage_id); + skill_delunit(ud->skillunit[i]->unit); + break; + default: + ud->skillunit[i]->unit->val2 = skill_lv; + ud->skillunit[i]->unit->group->val2 = skill_lv; + break; + } + } + } + } + break; + + case SO_FIREWALK: + case SO_ELECTRICWALK: + if( sc && sc->data[type] ) + status_change_end(src,type,INVALID_TIMER); + clif_skill_nodamage(src, src ,skill_id, skill_lv, + sc_start2(src, type, 100, skill_id, skill_lv, skill_get_time(skill_id, skill_lv))); + break; + + case SC_BLOODYLUST: //set in another group so instance will move if recasted + flag |= 33; + skill_unitsetting(src, skill_id, skill_lv, x, y, 0); + break; + + case KO_MAKIBISHI: + for( i = 0; i < (skill_lv+2); i++ ) { + x = src->x - 1 + rnd()%3; + y = src->y - 1 + rnd()%3; + skill_unitsetting(src,skill_id,skill_lv,x,y,0); + } + break; + + default: + ShowWarning("skill_castend_pos2: Unknown skill used:%d\n",skill_id); + return 1; + } + + if( sc && sc->data[SC_CURSEDCIRCLE_ATKER] ) //Should only remove after the skill has been casted. + status_change_end(src,SC_CURSEDCIRCLE_ATKER,INVALID_TIMER); + + if( sd ) + {// ensure that the skill last-cast tick is recorded + sd->canskill_tick = gettick(); + + if( sd->state.arrow_atk && !(flag&1) ) + {// consume arrow if this is a ground skill + battle_consume_ammo(sd, skill_id, skill_lv); + } + + // perform skill requirement consumption + skill_consume_requirement(sd,skill_id,skill_lv,2); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_castend_map (struct map_session_data *sd, uint16 skill_id, const char *map) +{ + nullpo_ret(sd); + +//Simplify skill_failed code. +#define skill_failed(sd) { sd->menuskill_id = sd->menuskill_val = 0; } + if(skill_id != sd->menuskill_id) + return 0; + + if( sd->bl.prev == NULL || pc_isdead(sd) ) { + skill_failed(sd); + return 0; + } + + if( ( sd->sc.opt1 && sd->sc.opt1 != OPT1_BURNING ) || sd->sc.option&OPTION_HIDE ) { + skill_failed(sd); + return 0; + } + if(sd->sc.count && ( + sd->sc.data[SC_SILENCE] || + sd->sc.data[SC_ROKISWEIL] || + sd->sc.data[SC_AUTOCOUNTER] || + sd->sc.data[SC_STEELBODY] || + (sd->sc.data[SC_DANCING] && skill_id < RK_ENCHANTBLADE && !pc_checkskill(sd, WM_LESSON)) || + sd->sc.data[SC_BERSERK] || sd->sc.data[SC__BLOODYLUST] || + sd->sc.data[SC_BASILICA] || + sd->sc.data[SC_MARIONETTE] || + sd->sc.data[SC_WHITEIMPRISON] || + (sd->sc.data[SC_STASIS] && skill_block_check(&sd->bl, SC_STASIS, skill_id)) || + (sd->sc.data[SC_KAGEHUMI] && skill_block_check(&sd->bl, SC_KAGEHUMI, skill_id)) || + sd->sc.data[SC_OBLIVIONCURSE] || + sd->sc.data[SC__MANHOLE] || + (sd->sc.data[SC_ASH] && rnd()%2) //50% fail chance under ASH + )) { + skill_failed(sd); + return 0; + } + + pc_stop_attack(sd); + pc_stop_walking(sd,0); + + if(battle_config.skill_log && battle_config.skill_log&BL_PC) + ShowInfo("PC %d skill castend skill =%d map=%s\n",sd->bl.id,skill_id,map); + + if(strcmp(map,"cancel")==0) { + skill_failed(sd); + return 0; + } + + switch(skill_id) + { + case AL_TELEPORT: + if(strcmp(map,"Random")==0) + pc_randomwarp(sd,CLR_TELEPORT); + else if (sd->menuskill_val > 1) //Need lv2 to be able to warp here. + pc_setpos(sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,CLR_TELEPORT); + break; + + case AL_WARP: + { + const struct point *p[4]; + struct skill_unit_group *group; + int i, lv, wx, wy; + int maxcount=0; + int x,y; + unsigned short mapindex; + + mapindex = mapindex_name2id((char*)map); + if(!mapindex) { //Given map not found? + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + skill_failed(sd); + return 0; + } + p[0] = &sd->status.save_point; + p[1] = &sd->status.memo_point[0]; + p[2] = &sd->status.memo_point[1]; + p[3] = &sd->status.memo_point[2]; + + if((maxcount = skill_get_maxcount(skill_id, sd->menuskill_val)) > 0) { + for(i=0;i<MAX_SKILLUNITGROUP && sd->ud.skillunit[i] && maxcount;i++) { + if(sd->ud.skillunit[i]->skill_id == skill_id) + maxcount--; + } + if(!maxcount) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + skill_failed(sd); + return 0; + } + } + + lv = sd->skillitem==skill_id?sd->skillitemlv:pc_checkskill(sd,skill_id); + wx = sd->menuskill_val>>16; + wy = sd->menuskill_val&0xffff; + + if( lv <= 0 ) return 0; + if( lv > 4 ) lv = 4; // crash prevention + + // check if the chosen map exists in the memo list + ARR_FIND( 0, lv, i, mapindex == p[i]->map ); + if( i < lv ) { + x=p[i]->x; + y=p[i]->y; + } else { + skill_failed(sd); + return 0; + } + + if(!skill_check_condition_castend(sd, sd->menuskill_id, lv)) + { // This checks versus skill_id/skill_lv... + skill_failed(sd); + return 0; + } + + skill_consume_requirement(sd,sd->menuskill_id,lv,2); + sd->skillitem = sd->skillitemlv = 0; // Clear data that's skipped in 'skill_castend_pos' [Inkfish] + + if((group=skill_unitsetting(&sd->bl,skill_id,lv,wx,wy,0))==NULL) { + skill_failed(sd); + return 0; + } + + group->val1 = (group->val1<<16)|(short)0; + // record the destination coordinates + group->val2 = (x<<16)|y; + group->val3 = mapindex; + } + break; + } + + sd->menuskill_id = sd->menuskill_val = 0; + return 0; +#undef skill_failed +} + +/// transforms 'target' skill unit into dissonance (if conditions are met) +static int skill_dance_overlap_sub(struct block_list* bl, va_list ap) +{ + struct skill_unit* target = (struct skill_unit*)bl; + struct skill_unit* src = va_arg(ap, struct skill_unit*); + int flag = va_arg(ap, int); + + if (src == target) + return 0; + if (!target->group || !(target->group->state.song_dance&0x1)) + return 0; + if (!(target->val2 & src->val2 & ~UF_ENSEMBLE)) //They don't match (song + dance) is valid. + return 0; + + if (flag) //Set dissonance + target->val2 |= UF_ENSEMBLE; //Add ensemble to signal this unit is overlapping. + else //Remove dissonance + target->val2 &= ~UF_ENSEMBLE; + + clif_skill_setunit(target); //Update look of affected cell. + + return 1; +} + +//Does the song/dance overlapping -> dissonance check. [Skotlex] +//When flag is 0, this unit is about to be removed, cancel the dissonance effect +//When 1, this unit has been positioned, so start the cancel effect. +int skill_dance_overlap(struct skill_unit* unit, int flag) +{ + if (!unit || !unit->group || !(unit->group->state.song_dance&0x1)) + return 0; + if (!flag && !(unit->val2&UF_ENSEMBLE)) + return 0; //Nothing to remove, this unit is not overlapped. + + if (unit->val1 != unit->group->skill_id) + { //Reset state + unit->val1 = unit->group->skill_id; + unit->val2 &= ~UF_ENSEMBLE; + } + + return map_foreachincell(skill_dance_overlap_sub, unit->bl.m,unit->bl.x,unit->bl.y,BL_SKILL, unit,flag); +} + +/*========================================== + * Converts this group information so that it is handled as a Dissonance or Ugly Dance cell. + * Flag: 0 - Convert, 1 - Revert. + *------------------------------------------*/ +static bool skill_dance_switch(struct skill_unit* unit, int flag) +{ + static int prevflag = 1; // by default the backup is empty + static struct skill_unit_group backup; + struct skill_unit_group* group = unit->group; + + // val2&UF_ENSEMBLE is a hack to indicate dissonance + if ( !(group->state.song_dance&0x1 && unit->val2&UF_ENSEMBLE) ) + return false; + + if( flag == prevflag ) + {// protection against attempts to read an empty backup / write to a full backup + ShowError("skill_dance_switch: Attempted to %s (skill_id=%d, skill_lv=%d, src_id=%d).\n", + flag ? "read an empty backup" : "write to a full backup", + group->skill_id, group->skill_lv, group->src_id); + return false; + } + prevflag = flag; + + if( !flag ) + { //Transform + uint16 skill_id = unit->val2&UF_SONG ? BA_DISSONANCE : DC_UGLYDANCE; + + // backup + backup.skill_id = group->skill_id; + backup.skill_lv = group->skill_lv; + backup.unit_id = group->unit_id; + backup.target_flag = group->target_flag; + backup.bl_flag = group->bl_flag; + backup.interval = group->interval; + + // replace + group->skill_id = skill_id; + group->skill_lv = 1; + group->unit_id = skill_get_unit_id(skill_id,0); + group->target_flag = skill_get_unit_target(skill_id); + group->bl_flag = skill_get_unit_bl_target(skill_id); + group->interval = skill_get_unit_interval(skill_id); + } + else + { //Restore + group->skill_id = backup.skill_id; + group->skill_lv = backup.skill_lv; + group->unit_id = backup.unit_id; + group->target_flag = backup.target_flag; + group->bl_flag = backup.bl_flag; + group->interval = backup.interval; + } + + return true; +} +/** + * Upon Ice Wall cast it checks all nearby mobs to find any who may be blocked by the IW + **/ +static int skill_icewall_block(struct block_list *bl,va_list ap) { + struct block_list *target = NULL; + struct mob_data *md = ((TBL_MOB*)bl); + + nullpo_ret(bl); + nullpo_ret(md); + if( !md->target_id || ( target = map_id2bl(md->target_id) ) == NULL ) + return 0; + + if( path_search_long(NULL,bl->m,bl->x,bl->y,target->x,target->y,CELL_CHKICEWALL) ) + return 0; + + if( !check_distance_bl(bl, target, status_get_range(bl) ) ) { + mob_unlocktarget(md,gettick()); + mob_stop_walking(md,1); + } + + return 0; +} +/*========================================== + * Initializes and sets a ground skill. + * flag&1 is used to determine when the skill 'morphs' (Warp portal becomes active, or Fire Pillar becomes active) + *------------------------------------------*/ +struct skill_unit_group* skill_unitsetting (struct block_list *src, uint16 skill_id, uint16 skill_lv, int16 x, int16 y, int flag) +{ + struct skill_unit_group *group; + int i,limit,val1=0,val2=0,val3=0; + int target,interval,range,unit_flag,req_item=0; + struct s_skill_unit_layout *layout; + struct map_session_data *sd; + struct status_data *status; + struct status_change *sc; + int active_flag=1; + int subunt=0; + + nullpo_retr(NULL, src); + + limit = skill_get_time(skill_id,skill_lv); + range = skill_get_unit_range(skill_id,skill_lv); + interval = skill_get_unit_interval(skill_id); + target = skill_get_unit_target(skill_id); + unit_flag = skill_get_unit_flag(skill_id); + layout = skill_get_unit_layout(skill_id,skill_lv,src,x,y); + + sd = BL_CAST(BL_PC, src); + status = status_get_status_data(src); + sc = status_get_sc(src); // for traps, firewall and fogwall - celest + + switch( skill_id ) { + case MH_STEINWAND: + val2 = 4 + skill_lv; //nb of attack blocked + break; + case MG_SAFETYWALL: + #ifdef RENEWAL + /** + * According to data provided in RE, SW life is equal to 3 times caster's health + **/ + val2 = status_get_max_hp(src) * 3; + #else + val2 = skill_lv+1; + #endif + break; + case MG_FIREWALL: + if(sc && sc->data[SC_VIOLENTGALE]) + limit = limit*3/2; + val2=4+skill_lv; + break; + + case AL_WARP: + val1=skill_lv+6; + if(!(flag&1)) + limit=2000; + else // previous implementation (not used anymore) + { //Warp Portal morphing to active mode, extract relevant data from src. [Skotlex] + if( src->type != BL_SKILL ) return NULL; + group = ((TBL_SKILL*)src)->group; + src = map_id2bl(group->src_id); + if( !src ) return NULL; + val2 = group->val2; //Copy the (x,y) position you warp to + val3 = group->val3; //as well as the mapindex to warp to. + } + break; + case HP_BASILICA: + val1 = src->id; // Store caster id. + break; + + case PR_SANCTUARY: + case NPC_EVILLAND: + val1=(skill_lv+3)*2; + break; + + case WZ_FIREPILLAR: + if((flag&1)!=0) + limit=1000; + val1=skill_lv+2; + break; + case WZ_QUAGMIRE: //The target changes to "all" if used in a gvg map. [Skotlex] + case AM_DEMONSTRATION: + case GN_HELLS_PLANT: + if (map_flag_vs(src->m) && battle_config.vs_traps_bctall + && (src->type&battle_config.vs_traps_bctall)) + target = BCT_ALL; + break; + case HT_SHOCKWAVE: + val1=skill_lv*15+10; + case HT_SANDMAN: + case MA_SANDMAN: + case HT_CLAYMORETRAP: + case HT_SKIDTRAP: + case MA_SKIDTRAP: + case HT_LANDMINE: + case MA_LANDMINE: + case HT_ANKLESNARE: + case HT_FLASHER: + case HT_FREEZINGTRAP: + case MA_FREEZINGTRAP: + case HT_BLASTMINE: + /** + * Ranger + **/ + case RA_ELECTRICSHOCKER: + case RA_CLUSTERBOMB: + case RA_MAGENTATRAP: + case RA_COBALTTRAP: + case RA_MAIZETRAP: + case RA_VERDURETRAP: + case RA_FIRINGTRAP: + case RA_ICEBOUNDTRAP: + { + struct skill_condition req = skill_get_requirement(sd,skill_id,skill_lv); + ARR_FIND(0, MAX_SKILL_ITEM_REQUIRE, i, req.itemid[i] && (req.itemid[i] == ITEMID_TRAP || req.itemid[i] == ITEMID_TRAP_ALLOY)); + if( req.itemid[i] ) + req_item = req.itemid[i]; + if( map_flag_gvg(src->m) || map[src->m].flag.battleground ) + limit *= 4; // longer trap times in WOE [celest] + if( battle_config.vs_traps_bctall && map_flag_vs(src->m) && (src->type&battle_config.vs_traps_bctall) ) + target = BCT_ALL; + } + break; + + case SA_LANDPROTECTOR: + case SA_VOLCANO: + case SA_DELUGE: + case SA_VIOLENTGALE: + { + struct skill_unit_group *old_sg; + if ((old_sg = skill_locate_element_field(src)) != NULL) + { //HelloKitty confirmed that these are interchangeable, + //so you can change element and not consume gemstones. + if (( + old_sg->skill_id == SA_VOLCANO || + old_sg->skill_id == SA_DELUGE || + old_sg->skill_id == SA_VIOLENTGALE + ) && old_sg->limit > 0) + { //Use the previous limit (minus the elapsed time) [Skotlex] + limit = old_sg->limit - DIFF_TICK(gettick(), old_sg->tick); + if (limit < 0) //This can happen... + limit = skill_get_time(skill_id,skill_lv); + } + skill_clear_group(src,1); + } + break; + } + + case BA_DISSONANCE: + case DC_UGLYDANCE: + val1 = 10; //FIXME: This value is not used anywhere, what is it for? [Skotlex] + break; + case BA_WHISTLE: + val1 = skill_lv +status->agi/10; // Flee increase + val2 = ((skill_lv+1)/2)+status->luk/10; // Perfect dodge increase + if(sd){ + val1 += pc_checkskill(sd,BA_MUSICALLESSON); + val2 += pc_checkskill(sd,BA_MUSICALLESSON); + } + break; + case DC_HUMMING: + val1 = 2*skill_lv+status->dex/10; // Hit increase + #ifdef RENEWAL + val1 *= 2; + #endif + if(sd) + val1 += pc_checkskill(sd,DC_DANCINGLESSON); + break; + case BA_POEMBRAGI: + val1 = 3*skill_lv+status->dex/10; // Casting time reduction + //For some reason at level 10 the base delay reduction is 50%. + val2 = (skill_lv<10?3*skill_lv:50)+status->int_/5; // After-cast delay reduction + if(sd){ + val1 += 2*pc_checkskill(sd,BA_MUSICALLESSON); + val2 += 2*pc_checkskill(sd,BA_MUSICALLESSON); + } + break; + case DC_DONTFORGETME: + val1 = status->dex/10 + 3*skill_lv + 5; // ASPD decrease + val2 = status->agi/10 + 3*skill_lv + 5; // Movement speed adjustment. + if(sd){ + val1 += pc_checkskill(sd,DC_DANCINGLESSON); + val2 += pc_checkskill(sd,DC_DANCINGLESSON); + } + break; + case BA_APPLEIDUN: + val1 = 5+2*skill_lv+status->vit/10; // MaxHP percent increase + if(sd) + val1 += pc_checkskill(sd,BA_MUSICALLESSON); + break; + case DC_SERVICEFORYOU: + val1 = 15+skill_lv+(status->int_/10); // MaxSP percent increase TO-DO: this INT bonus value is guessed + val2 = 20+3*skill_lv+(status->int_/10); // SP cost reduction + if(sd){ + val1 += pc_checkskill(sd,DC_DANCINGLESSON); //TO-DO This bonus value is guessed + val2 += pc_checkskill(sd,DC_DANCINGLESSON); //TO-DO Should be half this value + } + break; + case BA_ASSASSINCROSS: + val1 = 100+(10*skill_lv)+(status->agi/10); // ASPD increase + if(sd) + val1 += 5*pc_checkskill(sd,BA_MUSICALLESSON); + break; + case DC_FORTUNEKISS: + val1 = 10+skill_lv+(status->luk/10); // Critical increase + if(sd) + val1 += pc_checkskill(sd,DC_DANCINGLESSON); + val1*=10; //Because every 10 crit is an actual cri point. + break; + case BD_DRUMBATTLEFIELD: + #ifdef RENEWAL + val1 = (skill_lv+5)*25; //Watk increase + val2 = skill_lv*10; //Def increase + #else + val1 = (skill_lv+1)*25; //Watk increase + val2 = (skill_lv+1)*2; //Def increase + #endif + break; + case BD_RINGNIBELUNGEN: + val1 = (skill_lv+2)*25; //Watk increase + break; + case BD_RICHMANKIM: + val1 = 25 + 11*skill_lv; //Exp increase bonus. + break; + case BD_SIEGFRIED: + val1 = 55 + skill_lv*5; //Elemental Resistance + val2 = skill_lv*10; //Status ailment resistance + break; + case WE_CALLPARTNER: + if (sd) val1 = sd->status.partner_id; + break; + case WE_CALLPARENT: + if (sd) { + val1 = sd->status.father; + val2 = sd->status.mother; + } + break; + case WE_CALLBABY: + if (sd) val1 = sd->status.child; + break; + case NJ_KAENSIN: + skill_clear_group(src, 1); //Delete previous Kaensins/Suitons + val2 = (skill_lv+1)/2 + 4; + break; + case NJ_SUITON: + skill_clear_group(src, 1); + break; + + case GS_GROUNDDRIFT: + { + int element[5]={ELE_WIND,ELE_DARK,ELE_POISON,ELE_WATER,ELE_FIRE}; + + val1 = status->rhw.ele; + if (!val1) + val1=element[rnd()%5]; + + switch (val1) + { + case ELE_FIRE: + subunt++; + case ELE_WATER: + subunt++; + case ELE_POISON: + subunt++; + case ELE_DARK: + subunt++; + case ELE_WIND: + break; + default: + subunt=rnd()%5; + break; + } + + break; + } + case GC_POISONSMOKE: + if( !(sc && sc->data[SC_POISONINGWEAPON]) ) + return NULL; + val2 = sc->data[SC_POISONINGWEAPON]->val2; // Type of Poison + val3 = sc->data[SC_POISONINGWEAPON]->val1; + limit = 4000 + 2000 * skill_lv; + break; + case GD_LEADERSHIP: + case GD_GLORYWOUNDS: + case GD_SOULCOLD: + case GD_HAWKEYES: + limit = 1000000;//it doesn't matter + break; + case LG_BANDING: + limit = -1; + break; + case WM_REVERBERATION: + interval = limit; + val2 = 1; + case WM_POEMOFNETHERWORLD: // Can't be placed on top of Land Protector. + if( map_getcell(src->m, x, y, CELL_CHKLANDPROTECTOR) ) + return NULL; + break; + case SO_CLOUD_KILL: + skill_clear_group(src, 4); + break; + case SO_WARMER: + skill_clear_group(src, 8); + break; + case SO_VACUUM_EXTREME: + range++; + + break; + case SC_BLOODYLUST: + skill_clear_group(src, 32); + break; + case GN_WALLOFTHORN: + if( flag&1 ) + limit = 3000; + val3 = (x<<16)|y; + break; + case KO_ZENKAI: + if( sd ){ + ARR_FIND(1, 6, i, sd->talisman[i] > 0); + if( i < 5 ){ + val1 = sd->talisman[i]; // no. of aura + val2 = i; // aura type + limit += val1 * 1000; + subunt = i - 1; + pc_del_talisman(sd, sd->talisman[i], i); + } + } + break; + } + + nullpo_retr(NULL, group=skill_initunitgroup(src,layout->count,skill_id,skill_lv,skill_get_unit_id(skill_id,flag&1)+subunt, limit, interval)); + group->val1=val1; + group->val2=val2; + group->val3=val3; + group->target_flag=target; + group->bl_flag= skill_get_unit_bl_target(skill_id); + group->state.ammo_consume = (sd && sd->state.arrow_atk && skill_id != GS_GROUNDDRIFT); //Store if this skill needs to consume ammo. + group->state.song_dance = (unit_flag&(UF_DANCE|UF_SONG)?1:0)|(unit_flag&UF_ENSEMBLE?2:0); //Signals if this is a song/dance/duet + group->state.guildaura = ( skill_id >= GD_LEADERSHIP && skill_id <= GD_HAWKEYES )?1:0; + group->item_id = req_item; + //if tick is greater than current, do not invoke onplace function just yet. [Skotlex] + if (DIFF_TICK(group->tick, gettick()) > SKILLUNITTIMER_INTERVAL) + active_flag = 0; + + if(skill_id==HT_TALKIEBOX || skill_id==RG_GRAFFITI){ + group->valstr=(char *) aMalloc(MESSAGE_SIZE*sizeof(char)); + if (sd) + safestrncpy(group->valstr, sd->message, MESSAGE_SIZE); + else //Eh... we have to write something here... even though mobs shouldn't use this. [Skotlex] + safestrncpy(group->valstr, "Boo!", MESSAGE_SIZE); + } + + if (group->state.song_dance) { + if(sd){ + sd->skill_id_dance = skill_id; + sd->skill_lv_dance = skill_lv; + } + if ( + sc_start4(src, SC_DANCING, 100, skill_id, group->group_id, skill_lv, + (group->state.song_dance&2?BCT_SELF:0), limit+1000) && + sd && group->state.song_dance&2 && skill_id != CG_HERMODE //Hermod is a encore with a warp! + ) + skill_check_pc_partner(sd, skill_id, &skill_lv, 1, 1); + } + + limit = group->limit; + for( i = 0; i < layout->count; i++ ) + { + struct skill_unit *unit; + int ux = x + layout->dx[i]; + int uy = y + layout->dy[i]; + int val1 = skill_lv; + int val2 = 0; + int alive = 1; + + if( !group->state.song_dance && !map_getcell(src->m,ux,uy,CELL_CHKREACH) ) + continue; // don't place skill units on walls (except for songs/dances/encores) + if( battle_config.skill_wall_check && skill_get_unit_flag(skill_id)&UF_PATHCHECK && !path_search_long(NULL,src->m,ux,uy,x,y,CELL_CHKWALL) ) + continue; // no path between cell and center of casting. + + switch( skill_id ) + { + case MG_FIREWALL: + case NJ_KAENSIN: + val2=group->val2; + break; + case WZ_ICEWALL: + val1 = (skill_lv <= 1) ? 500 : 200 + 200*skill_lv; + val2 = map_getcell(src->m, ux, uy, CELL_GETTYPE); + break; + case HT_LANDMINE: + case MA_LANDMINE: + case HT_ANKLESNARE: + case HT_SHOCKWAVE: + case HT_SANDMAN: + case MA_SANDMAN: + case HT_FLASHER: + case HT_FREEZINGTRAP: + case MA_FREEZINGTRAP: + case HT_TALKIEBOX: + case HT_SKIDTRAP: + case MA_SKIDTRAP: + case HT_CLAYMORETRAP: + case HT_BLASTMINE: + /** + * Ranger + **/ + case RA_ELECTRICSHOCKER: + case RA_CLUSTERBOMB: + case RA_MAGENTATRAP: + case RA_COBALTTRAP: + case RA_MAIZETRAP: + case RA_VERDURETRAP: + case RA_FIRINGTRAP: + case RA_ICEBOUNDTRAP: + val1 = 3500; + break; + case GS_DESPERADO: + val1 = abs(layout->dx[i]); + val2 = abs(layout->dy[i]); + if (val1 < 2 || val2 < 2) { //Nearby cross, linear decrease with no diagonals + if (val2 > val1) val1 = val2; + if (val1) val1--; + val1 = 36 -12*val1; + } else //Diagonal edges + val1 = 28 -4*val1 -4*val2; + if (val1 < 1) val1 = 1; + val2 = 0; + break; + case WM_REVERBERATION: + val1 = 1 + skill_lv; + break; + case GN_WALLOFTHORN: + val1 = 1000 * skill_lv; // Need official value. [LimitLine] + break; + default: + if (group->state.song_dance&0x1) + val2 = unit_flag&(UF_DANCE|UF_SONG); //Store whether this is a song/dance + break; + } + if (skill_get_unit_flag(skill_id) & UF_RANGEDSINGLEUNIT && i == (layout->count / 2)) + val2 |= UF_RANGEDSINGLEUNIT; // center. + + if( range <= 0 ) + map_foreachincell(skill_cell_overlap,src->m,ux,uy,BL_SKILL,skill_id, &alive, src); + if( !alive ) + continue; + + nullpo_retr(NULL, unit=skill_initunit(group,i,ux,uy,val1,val2)); + unit->limit=limit; + unit->range=range; + + if (skill_id == PF_FOGWALL && alive == 2) + { //Double duration of cells on top of Deluge/Suiton + unit->limit *= 2; + group->limit = unit->limit; + } + + // execute on all targets standing on this cell + if (range==0 && active_flag) + map_foreachincell(skill_unit_effect,unit->bl.m,unit->bl.x,unit->bl.y,group->bl_flag,&unit->bl,gettick(),1); + } + + if (!group->alive_count) + { //No cells? Something that was blocked completely by Land Protector? + skill_delunitgroup(group); + return NULL; + } + + //success, unit created. + switch( skill_id ) { + case WZ_ICEWALL: + map_foreachinrange(skill_icewall_block, src, AREA_SIZE, BL_MOB); + break; + case NJ_TATAMIGAESHI: //Store number of tiles. + group->val1 = group->alive_count; + break; + } + + return group; +} + +/*========================================== + * + *------------------------------------------*/ +void ext_skill_unit_onplace(struct skill_unit *src, struct block_list *bl, unsigned int tick){skill_unit_onplace(src, bl, tick);} +static 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 status_change *sc; + struct status_change_entry *sce; + enum sc_type type; + uint16 skill_id; + + nullpo_ret(src); + nullpo_ret(bl); + + if(bl->prev==NULL || !src->alive || status_isdead(bl)) + return 0; + + nullpo_ret(sg=src->group); + nullpo_ret(ss=map_id2bl(sg->src_id)); + + if( skill_get_type(sg->skill_id) == BF_MAGIC && map_getcell(bl->m, bl->x, bl->y, CELL_CHKLANDPROTECTOR) && sg->skill_id != SA_LANDPROTECTOR ) + return 0; //AoE skills are ineffective. [Skotlex] + + sc = status_get_sc(bl); + + if (sc && sc->option&OPTION_HIDE && sg->skill_id != WZ_HEAVENDRIVE && sg->skill_id != WL_EARTHSTRAIN ) + return 0; //Hidden characters are immune to AoE skills except to these. [Skotlex] + + type = status_skill2sc(sg->skill_id); + sce = (sc && type != -1)?sc->data[type]:NULL; + skill_id = sg->skill_id; //In case the group is deleted, we need to return the correct skill id, still. + switch (sg->unit_id) + { + case UNT_SPIDERWEB: + if( sc && sc->data[SC_SPIDERWEB] && sc->data[SC_SPIDERWEB]->val1 > 0 ) + { // If you are fiberlocked and can't move, it will only increase your fireweakness level. [Inkfish] + sc->data[SC_SPIDERWEB]->val2++; + break; + } + else if( sc ) + { + int sec = skill_get_time2(sg->skill_id,sg->skill_lv); + if( status_change_start(bl,type,10000,sg->skill_lv,1,sg->group_id,0,sec,8) ) + { + const struct TimerData* td = sc->data[type]?get_timer(sc->data[type]->timer):NULL; + if( td ) + sec = DIFF_TICK(td->tick, tick); + map_moveblock(bl, src->bl.x, src->bl.y, tick); + clif_fixpos(bl); + sg->val2 = bl->id; + } + else + sec = 3000; //Couldn't trap it? + sg->limit = DIFF_TICK(tick,sg->tick)+sec; + } + break; + case UNT_SAFETYWALL: + if (!sce) + sc_start4(bl,type,100,sg->skill_lv,sg->skill_id,sg->group_id,0,sg->limit); + break; + + case UNT_PNEUMA: + case UNT_CHAOSPANIC: + case UNT_MAELSTROM: + if (!sce) + sc_start4(bl,type,100,sg->skill_lv,sg->group_id,0,0,sg->limit); + break; + case UNT_BLOODYLUST: + if (sg->src_id == bl->id) + break; //Does not affect the caster. + if (!sce) { + TBL_PC *sd = BL_CAST(BL_PC, bl); //prevent fullheal exploit + if (sd && sd->bloodylust_tick && DIFF_TICK(gettick(), sd->bloodylust_tick) < skill_get_time2(SC_BLOODYLUST, 1)) + clif_skill_nodamage(&src->bl,bl,sg->skill_id,sg->skill_lv, + sc_start4(bl, type, 100, sg->skill_lv, 1, 0, 0, skill_get_time(LK_BERSERK, sg->skill_lv))); + else { + if (sd) sd->bloodylust_tick = gettick(); + clif_skill_nodamage(&src->bl,bl,sg->skill_id,sg->skill_lv, + sc_start4(bl, type, 100, sg->skill_lv, 0, 0, 0, skill_get_time(LK_BERSERK, sg->skill_lv))); + } + } + break; + + case UNT_WARP_WAITING: { + int working = sg->val1&0xffff; + + if(bl->type==BL_PC && !working){ + struct map_session_data *sd = (struct map_session_data *)bl; + if((!sd->chatID || battle_config.chat_warpportal) + && sd->ud.to_x == src->bl.x && sd->ud.to_y == src->bl.y) + { + int x = sg->val2>>16; + int y = sg->val2&0xffff; + int count = sg->val1>>16; + unsigned short m = sg->val3; + + if( --count <= 0 ) + skill_delunitgroup(sg); + + if ( map_mapindex2mapid(sg->val3) == sd->bl.m && x == sd->bl.x && y == sd->bl.y ) + working = 1;/* we break it because officials break it, lovely stuff. */ + + sg->val1 = (count<<16)|working; + + pc_setpos(sd,m,x,y,CLR_TELEPORT); + } + } else if(bl->type == BL_MOB && battle_config.mob_warp&2) { + int16 m = map_mapindex2mapid(sg->val3); + if (m < 0) break; //Map not available on this map-server. + unit_warp(bl,m,sg->val2>>16,sg->val2&0xffff,CLR_TELEPORT); + } + } + break; + + case UNT_QUAGMIRE: + if( !sce && battle_check_target(&sg->unit->bl,bl,sg->target_flag) > 0 ) + sc_start4(bl,type,100,sg->skill_lv,sg->group_id,0,0,sg->limit); + break; + + case UNT_VOLCANO: + case UNT_DELUGE: + case UNT_VIOLENTGALE: + if(!sce) + sc_start(bl,type,100,sg->skill_lv,sg->limit); + break; + + case UNT_SUITON: + if(!sce) + sc_start4(bl,type,100,sg->skill_lv, + map_flag_vs(bl->m) || battle_check_target(&src->bl,bl,BCT_ENEMY)>0?1:0, //Send val3 =1 to reduce agi. + 0,0,sg->limit); + break; + + case UNT_HERMODE: + if (sg->src_id!=bl->id && battle_check_target(&src->bl,bl,BCT_PARTY|BCT_GUILD) > 0) + status_change_clear_buffs(bl,1); //Should dispell only allies. + case UNT_RICHMANKIM: + case UNT_ETERNALCHAOS: + case UNT_DRUMBATTLEFIELD: + case UNT_RINGNIBELUNGEN: + case UNT_ROKISWEIL: + case UNT_INTOABYSS: + case UNT_SIEGFRIED: + //Needed to check when a dancer/bard leaves their ensemble area. + if (sg->src_id==bl->id && !(sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_BARDDANCER)) + return skill_id; + if (!sce) + sc_start4(bl,type,100,sg->skill_lv,sg->val1,sg->val2,0,sg->limit); + break; + case UNT_WHISTLE: + case UNT_ASSASSINCROSS: + case UNT_POEMBRAGI: + case UNT_APPLEIDUN: + case UNT_HUMMING: + case UNT_DONTFORGETME: + case UNT_FORTUNEKISS: + case UNT_SERVICEFORYOU: + if (sg->src_id==bl->id && !(sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_BARDDANCER)) + return 0; + + if (!sc) return 0; + if (!sce) + sc_start4(bl,type,100,sg->skill_lv,sg->val1,sg->val2,0,sg->limit); + else if (sce->val4 == 1) { + //Readjust timers since the effect will not last long. + sce->val4 = 0; + delete_timer(sce->timer, status_change_timer); + sce->timer = add_timer(tick+sg->limit, status_change_timer, bl->id, type); + } + break; + + case UNT_FOGWALL: + if (!sce) + { + sc_start4(bl, type, 100, sg->skill_lv, sg->val1, sg->val2, sg->group_id, sg->limit); + if (battle_check_target(&src->bl,bl,BCT_ENEMY)>0) + skill_additional_effect (ss, bl, sg->skill_id, sg->skill_lv, BF_MISC, ATK_DEF, tick); + } + break; + + case UNT_GRAVITATION: + if (!sce) + sc_start4(bl,type,100,sg->skill_lv,0,BCT_ENEMY,sg->group_id,sg->limit); + break; + +// officially, icewall has no problems existing on occupied cells [ultramage] +// case UNT_ICEWALL: //Destroy the cell. [Skotlex] +// src->val1 = 0; +// if(src->limit + sg->tick > tick + 700) +// src->limit = DIFF_TICK(tick+700,sg->tick); +// break; + + case UNT_MOONLIT: + //Knockback out of area if affected char isn't in Moonlit effect + if (sc && sc->data[SC_DANCING] && (sc->data[SC_DANCING]->val1&0xFFFF) == CG_MOONLIT) + break; + if (ss == bl) //Also needed to prevent infinite loop crash. + break; + skill_blown(ss,bl,skill_get_blewcount(sg->skill_id,sg->skill_lv),unit_getdir(bl),0); + break; + + case UNT_WALLOFTHORN: + if( status_get_mode(bl)&MD_BOSS ) + break; // iRO Wiki says that this skill don't affect to Boss monsters. + if( map_flag_vs(bl->m) || bl->id == src->bl.id || battle_check_target(&src->bl,bl, BCT_ENEMY) == 1 ) + skill_attack(skill_get_type(sg->skill_id), ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); + break; + + case UNT_VOLCANIC_ASH: + if (!sce) + sc_start(bl, SC_ASH, 100, sg->skill_lv, skill_get_time(MH_VOLCANIC_ASH, sg->skill_lv)); + break; + + case UNT_GD_LEADERSHIP: + case UNT_GD_GLORYWOUNDS: + case UNT_GD_SOULCOLD: + case UNT_GD_HAWKEYES: + if ( !sce ) + sc_start4(bl,type,100,sg->skill_lv,0,0,0,1000); + break; + } + return skill_id; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_unit_onplace_timer (struct skill_unit *src, struct block_list *bl, unsigned int tick) +{ + struct skill_unit_group *sg; + struct block_list *ss; + TBL_PC* tsd; + struct status_data *tstatus; + struct status_change *tsc; + struct skill_unit_group_tickset *ts; + enum sc_type type; + uint16 skill_id; + int diff=0; + + nullpo_ret(src); + nullpo_ret(bl); + + if (bl->prev==NULL || !src->alive || status_isdead(bl)) + return 0; + + nullpo_ret(sg=src->group); + nullpo_ret(ss=map_id2bl(sg->src_id)); + tsd = BL_CAST(BL_PC, bl); + tsc = status_get_sc(bl); + + if ( tsc && tsc->data[SC_HOVERING] ) + return 0; //Under hovering characters are immune to trap and ground target skills. + + tstatus = status_get_status_data(bl); + type = status_skill2sc(sg->skill_id); + skill_id = sg->skill_id; + + if (sg->interval == -1) { + switch (sg->unit_id) { + case UNT_ANKLESNARE: //These happen when a trap is splash-triggered by multiple targets on the same cell. + case UNT_FIREPILLAR_ACTIVE: + case UNT_ELECTRICSHOCKER: + case UNT_MANHOLE: + return 0; + default: + ShowError("skill_unit_onplace_timer: interval error (unit id %x)\n", sg->unit_id); + return 0; + } + } + + if ((ts = skill_unitgrouptickset_search(bl,sg,tick))) + { //Not all have it, eg: Traps don't have it even though they can be hit by Heaven's Drive [Skotlex] + diff = DIFF_TICK(tick,ts->tick); + if (diff < 0) + return 0; + ts->tick = tick+sg->interval; + + if ((skill_id==CR_GRANDCROSS || skill_id==NPC_GRANDDARKNESS) && !battle_config.gx_allhit) + ts->tick += sg->interval*(map_count_oncell(bl->m,bl->x,bl->y,BL_CHAR)-1); + } + + switch (sg->unit_id) + { + case UNT_FIREWALL: + case UNT_KAEN: + { + int count=0; + const int x = bl->x, y = bl->y; + + if( sg->skill_id == GN_WALLOFTHORN && !map_flag_vs(bl->m) ) + break; + + //Take into account these hit more times than the timer interval can handle. + do + skill_attack(BF_MAGIC,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick+count*sg->interval,0); + while(--src->val2 && x == bl->x && y == bl->y && + ++count < SKILLUNITTIMER_INTERVAL/sg->interval && !status_isdead(bl)); + + if (src->val2<=0) + skill_delunit(src); + } + break; + + case UNT_SANCTUARY: + if( battle_check_undead(tstatus->race, tstatus->def_ele) || tstatus->race==RC_DEMON ) + { //Only damage enemies with offensive Sanctuary. [Skotlex] + if( battle_check_target(&src->bl,bl,BCT_ENEMY) > 0 && skill_attack(BF_MAGIC, ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0) ) + sg->val1 -= 2; // reduce healing count if this was meant for damaging [hekate] + } + else + { + int heal = skill_calc_heal(ss,bl,sg->skill_id,sg->skill_lv,true); + struct mob_data *md = BL_CAST(BL_MOB, bl); +#ifdef RENEWAL + if( md && md->class_ == MOBID_EMPERIUM ) + break; +#endif + if( md && mob_is_battleground(md) ) + break; + if( tstatus->hp >= tstatus->max_hp ) + break; + if( status_isimmune(bl) ) + heal = 0; + clif_skill_nodamage(&src->bl, bl, AL_HEAL, heal, 1); + if( tsc && tsc->data[SC_AKAITSUKI] && heal ) + heal = ~heal + 1; + status_heal(bl, heal, 0, 0); + if( diff >= 500 ) + sg->val1--; + } + if( sg->val1 <= 0 ) + skill_delunitgroup(sg); + break; + + case UNT_EVILLAND: + //Will heal demon and undead element monsters, but not players. + if ((bl->type == BL_PC) || (!battle_check_undead(tstatus->race, tstatus->def_ele) && tstatus->race!=RC_DEMON)) + { //Damage enemies + if(battle_check_target(&src->bl,bl,BCT_ENEMY)>0) + skill_attack(BF_MISC, ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); + } else { + int heal = skill_calc_heal(ss,bl,sg->skill_id,sg->skill_lv,true); + if (tstatus->hp >= tstatus->max_hp) + break; + if (status_isimmune(bl)) + heal = 0; + clif_skill_nodamage(&src->bl, bl, AL_HEAL, heal, 1); + status_heal(bl, heal, 0, 0); + } + break; + + case UNT_MAGNUS: + if (!battle_check_undead(tstatus->race,tstatus->def_ele) && tstatus->race!=RC_DEMON) + break; + skill_attack(BF_MAGIC,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + break; + + case UNT_DUMMYSKILL: + switch (sg->skill_id) + { + case SG_SUN_WARM: //SG skills [Komurka] + case SG_MOON_WARM: + case SG_STAR_WARM: + { + int count = 0; + const int x = bl->x, y = bl->y; + + //If target isn't knocked back it should hit every "interval" ms [Playtester] + do + { + if( bl->type == BL_PC ) + status_zap(bl, 0, 15); // sp damage to players + else // mobs + if( status_charge(ss, 0, 2) ) // costs 2 SP per hit + { + if( !skill_attack(BF_WEAPON,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick+count*sg->interval,0) ) + status_charge(ss, 0, 8); //costs additional 8 SP if miss + } + else + { //should end when out of sp. + sg->limit = DIFF_TICK(tick,sg->tick); + break; + } + } while( x == bl->x && y == bl->y && + ++count < SKILLUNITTIMER_INTERVAL/sg->interval && !status_isdead(bl) ); + } + break; + /** + * The storm gust counter was dropped in renewal + **/ + #ifndef RENEWAL + case WZ_STORMGUST: //SG counter does not reset per stormgust. IE: One hit from a SG and two hits from another will freeze you. + if (tsc) + tsc->sg_counter++; //SG hit counter. + if (skill_attack(skill_get_type(sg->skill_id),ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0) <= 0 && tsc) + tsc->sg_counter=0; //Attack absorbed. + break; + #endif + case GS_DESPERADO: + if (rnd()%100 < src->val1) + skill_attack(BF_WEAPON,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + break; + case GN_CRAZYWEED_ATK: + if( bl->type == BL_SKILL ){ + struct skill_unit *su = (struct skill_unit *)bl; + if( su && !(skill_get_inf2(su->group->skill_id)&INF2_TRAP) ) + break; + } + default: + skill_attack(skill_get_type(sg->skill_id),ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + } + break; + + case UNT_FIREPILLAR_WAITING: + skill_unitsetting(ss,sg->skill_id,sg->skill_lv,src->bl.x,src->bl.y,1); + skill_delunit(src); + break; + + case UNT_SKIDTRAP: + { + skill_blown(&src->bl,bl,skill_get_blewcount(sg->skill_id,sg->skill_lv),unit_getdir(bl),0); + sg->unit_id = UNT_USED_TRAPS; + clif_changetraplook(&src->bl, UNT_USED_TRAPS); + sg->limit=DIFF_TICK(tick,sg->tick)+1500; + } + break; + + case UNT_ANKLESNARE: + case UNT_MANHOLE: + if( sg->val2 == 0 && tsc && (sg->unit_id == UNT_ANKLESNARE || bl->id != sg->src_id) ) { + int sec = skill_get_time2(sg->skill_id,sg->skill_lv); + if( status_change_start(bl,type,10000,sg->skill_lv,sg->group_id,0,0,sec, 8) ) { + const struct TimerData* td = tsc->data[type]?get_timer(tsc->data[type]->timer):NULL; + if( td ) + sec = DIFF_TICK(td->tick, tick); + unit_movepos(bl, src->bl.x, src->bl.y, 0, 0); + clif_fixpos(bl); + sg->val2 = bl->id; + } else + sec = 3000; //Couldn't trap it? + if( sg->unit_id == UNT_ANKLESNARE ) { + clif_skillunit_update(&src->bl); + /** + * If you're snared from a trap that was invisible this makes the trap be + * visible again -- being you stepped on it (w/o this the trap remains invisible and you go "WTF WHY I CANT MOVE") + * bugreport:3961 + **/ + clif_changetraplook(&src->bl, UNT_ANKLESNARE); + } + sg->limit = DIFF_TICK(tick,sg->tick)+sec; + sg->interval = -1; + src->range = 0; + } + break; + + case UNT_ELECTRICSHOCKER: + if( bl->id != ss->id ) { + if( status_get_mode(bl)&MD_BOSS ) + break; + if( status_change_start(bl,type,10000,sg->skill_lv,sg->group_id,0,0,skill_get_time2(sg->skill_id, sg->skill_lv), 8) ) { + + map_moveblock(bl, src->bl.x, src->bl.y, tick); + clif_fixpos(bl); + + } + + map_foreachinrange(skill_trap_splash, &src->bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, &src->bl, tick); + sg->unit_id = UNT_USED_TRAPS; //Changed ID so it does not invoke a for each in area again. + } + break; + + case UNT_VENOMDUST: + if(tsc && !tsc->data[type]) + status_change_start(bl,type,10000,sg->skill_lv,sg->group_id,0,0,skill_get_time2(sg->skill_id,sg->skill_lv),0); + break; + + + case UNT_MAGENTATRAP: + case UNT_COBALTTRAP: + case UNT_MAIZETRAP: + case UNT_VERDURETRAP: + if( bl->type == BL_PC )// it won't work on players + break; + case UNT_FIRINGTRAP: + case UNT_ICEBOUNDTRAP: + case UNT_CLUSTERBOMB: + if( bl->id == ss->id )// it won't trigger on caster + break; + case UNT_LANDMINE: + case UNT_CLAYMORETRAP: + case UNT_BLASTMINE: + case UNT_SHOCKWAVE: + case UNT_SANDMAN: + case UNT_FLASHER: + case UNT_FREEZINGTRAP: + case UNT_FIREPILLAR_ACTIVE: + map_foreachinrange(skill_trap_splash,&src->bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, &src->bl,tick); + if (sg->unit_id != UNT_FIREPILLAR_ACTIVE) + clif_changetraplook(&src->bl, sg->unit_id==UNT_LANDMINE?UNT_FIREPILLAR_ACTIVE:UNT_USED_TRAPS); + sg->limit=DIFF_TICK(tick,sg->tick)+1500 + + (sg->unit_id== UNT_CLUSTERBOMB || sg->unit_id== UNT_ICEBOUNDTRAP?1000:0);// Cluster Bomb/Icebound has 1s to disappear once activated. + sg->unit_id = UNT_USED_TRAPS; //Changed ID so it does not invoke a for each in area again. + break; + + case UNT_TALKIEBOX: + if (sg->src_id == bl->id) + break; + if (sg->val2 == 0){ + clif_talkiebox(&src->bl, sg->valstr); + sg->unit_id = UNT_USED_TRAPS; + clif_changetraplook(&src->bl, UNT_USED_TRAPS); + sg->limit = DIFF_TICK(tick, sg->tick) + 5000; + sg->val2 = -1; + } + break; + + case UNT_LULLABY: + if (ss->id == bl->id) + break; + skill_additional_effect(ss, bl, sg->skill_id, sg->skill_lv, BF_LONG|BF_SKILL|BF_MISC, ATK_DEF, tick); + break; + + case UNT_UGLYDANCE: //Ugly Dance [Skotlex] + if (ss->id != bl->id) + skill_additional_effect(ss, bl, sg->skill_id, sg->skill_lv, BF_LONG|BF_SKILL|BF_MISC, ATK_DEF, tick); + break; + + case UNT_DISSONANCE: + skill_attack(BF_MISC, ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); + break; + + case UNT_APPLEIDUN: //Apple of Idun [Skotlex] + { + int heal; +#ifdef RENEWAL + struct mob_data *md = BL_CAST(BL_MOB, bl); + if( md && md->class_ == MOBID_EMPERIUM ) + break; +#endif + if( sg->src_id == bl->id && !(tsc && tsc->data[SC_SPIRIT] && tsc->data[SC_SPIRIT]->val2 == SL_BARDDANCER) ) + break; // affects self only when soullinked + heal = skill_calc_heal(ss,bl,sg->skill_id, sg->skill_lv, true); + if( tsc->data[SC_AKAITSUKI] && heal ) + heal = ~heal + 1; + clif_skill_nodamage(&src->bl, bl, AL_HEAL, heal, 1); + status_heal(bl, heal, 0, 0); + break; + } + + case UNT_TATAMIGAESHI: + case UNT_DEMONSTRATION: + skill_attack(BF_WEAPON,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + break; + + case UNT_GOSPEL: + if (rnd()%100 > sg->skill_lv*10 || ss == bl) + break; + if (battle_check_target(ss,bl,BCT_PARTY)>0) + { // Support Effect only on party, not guild + int heal; + int i = rnd()%13; // Positive buff count + int time = skill_get_time2(sg->skill_id, sg->skill_lv); //Duration + switch (i) + { + case 0: // Heal 1~9999 HP + heal = rnd() %9999+1; + clif_skill_nodamage(ss,bl,AL_HEAL,heal,1); + status_heal(bl,heal,0,0); + break; + case 1: // End all negative status + status_change_clear_buffs(bl,6); + if (tsd) clif_gospel_info(tsd, 0x15); + break; + case 2: // Immunity to all status + sc_start(bl,SC_SCRESIST,100,100,time); + if (tsd) clif_gospel_info(tsd, 0x16); + break; + case 3: // MaxHP +100% + sc_start(bl,SC_INCMHPRATE,100,100,time); + if (tsd) clif_gospel_info(tsd, 0x17); + break; + case 4: // MaxSP +100% + sc_start(bl,SC_INCMSPRATE,100,100,time); + if (tsd) clif_gospel_info(tsd, 0x18); + break; + case 5: // All stats +20 + sc_start(bl,SC_INCALLSTATUS,100,20,time); + if (tsd) clif_gospel_info(tsd, 0x19); + break; + case 6: // Level 10 Blessing + sc_start(bl,SC_BLESSING,100,10,time); + break; + case 7: // Level 10 Increase AGI + sc_start(bl,SC_INCREASEAGI,100,10,time); + break; + case 8: // Enchant weapon with Holy element + sc_start(bl,SC_ASPERSIO,100,1,time); + if (tsd) clif_gospel_info(tsd, 0x1c); + break; + case 9: // Enchant armor with Holy element + sc_start(bl,SC_BENEDICTIO,100,1,time); + if (tsd) clif_gospel_info(tsd, 0x1d); + break; + case 10: // DEF +25% + sc_start(bl,SC_INCDEFRATE,100,25,time); + if (tsd) clif_gospel_info(tsd, 0x1e); + break; + case 11: // ATK +100% + sc_start(bl,SC_INCATKRATE,100,100,time); + if (tsd) clif_gospel_info(tsd, 0x1f); + break; + case 12: // HIT/Flee +50 + sc_start(bl,SC_INCHIT,100,50,time); + sc_start(bl,SC_INCFLEE,100,50,time); + if (tsd) clif_gospel_info(tsd, 0x20); + break; + } + } + else if (battle_check_target(&src->bl,bl,BCT_ENEMY)>0) + { // Offensive Effect + int i = rnd()%9; // Negative buff count + int time = skill_get_time2(sg->skill_id, sg->skill_lv); + switch (i) + { + case 0: // Deal 1~9999 damage + skill_attack(BF_MISC,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + break; + case 1: // Curse + sc_start(bl,SC_CURSE,100,1,time); + break; + case 2: // Blind + sc_start(bl,SC_BLIND,100,1,time); + break; + case 3: // Poison + sc_start(bl,SC_POISON,100,1,time); + break; + case 4: // Level 10 Provoke + sc_start(bl,SC_PROVOKE,100,10,time); + break; + case 5: // DEF -100% + sc_start(bl,SC_INCDEFRATE,100,-100,time); + break; + case 6: // ATK -100% + sc_start(bl,SC_INCATKRATE,100,-100,time); + break; + case 7: // Flee -100% + sc_start(bl,SC_INCFLEERATE,100,-100,time); + break; + case 8: // Speed/ASPD -25% + sc_start4(bl,SC_GOSPEL,100,1,0,0,BCT_ENEMY,time); + break; + } + } + break; + + case UNT_BASILICA: + { + int i = battle_check_target(&src->bl, bl, BCT_ENEMY); + if( i > 0 && !(status_get_mode(bl)&MD_BOSS) ) + { // knock-back any enemy except Boss + skill_blown(&src->bl, bl, 2, unit_getdir(bl), 0); + clif_fixpos(bl); + } + + if( sg->src_id != bl->id && i <= 0 ) + sc_start4(bl, type, 100, 0, 0, 0, src->bl.id, sg->interval + 100); + } + break; + + case UNT_GRAVITATION: + case UNT_EARTHSTRAIN: + case UNT_FIREWALK: + case UNT_ELECTRICWALK: + case UNT_PSYCHIC_WAVE: + skill_attack(skill_get_type(sg->skill_id),ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + break; + + case UNT_GROUNDDRIFT_WIND: + case UNT_GROUNDDRIFT_DARK: + case UNT_GROUNDDRIFT_POISON: + case UNT_GROUNDDRIFT_WATER: + case UNT_GROUNDDRIFT_FIRE: + map_foreachinrange(skill_trap_splash,&src->bl, + skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, + &src->bl,tick); + sg->unit_id = UNT_USED_TRAPS; + //clif_changetraplook(&src->bl, UNT_FIREPILLAR_ACTIVE); + sg->limit=DIFF_TICK(tick,sg->tick)+1500; + break; + /** + * 3rd stuff + **/ + case UNT_POISONSMOKE: + if( battle_check_target(ss,bl,BCT_ENEMY) > 0 && !(tsc && tsc->data[sg->val2]) && rnd()%100 < 20 ) + sc_start(bl,sg->val2,100,sg->val3,skill_get_time2(GC_POISONINGWEAPON, 1)); + break; + + case UNT_EPICLESIS: + if( bl->type == BL_PC && !battle_check_undead(tstatus->race, tstatus->def_ele) && tstatus->race != RC_DEMON ) + { + if( ++sg->val2 % 3 == 0 ) { + int hp, sp; + switch( sg->skill_lv ) + { + case 1: case 2: hp = 3; sp = 2; break; + case 3: case 4: hp = 4; sp = 3; break; + case 5: default: hp = 5; sp = 4; break; + } + hp = tstatus->max_hp * hp / 100; + sp = tstatus->max_sp * sp / 100; + status_heal(bl, hp, sp, 2); + sc_start(bl, type, 100, sg->skill_lv, (sg->interval * 3) + 100); + } + // Reveal hidden players every 5 seconds. + if( sg->val2 % 5 == 0 ) { + // TODO: check if other hidden status can be removed. + status_change_end(bl,SC_HIDING,INVALID_TIMER); + status_change_end(bl,SC_CLOAKING,INVALID_TIMER); + } + } + /* Enable this if kRO fix the current skill. Currently no damage on undead and demon monster. [Jobbie] + else if( battle_check_target(ss, bl, BCT_ENEMY) > 0 && battle_check_undead(tstatus->race, tstatus->def_ele) ) + skill_castend_damage_id(&src->bl, bl, sg->skill_id, sg->skill_lv, 0, 0);*/ + break; + + case UNT_STEALTHFIELD: + if( bl->id == sg->src_id ) + break; // Dont work on Self (video shows that) + case UNT_NEUTRALBARRIER: + sc_start(bl,type,100,sg->skill_lv,sg->interval + 100); + break; + + case UNT_DIMENSIONDOOR: + if( tsd && !map[bl->m].flag.noteleport ) + pc_randomwarp(tsd,3); + else if( bl->type == BL_MOB && battle_config.mob_warp&8 ) + unit_warp(bl,-1,-1,-1,3); + break; + + case UNT_REVERBERATION: + clif_changetraplook(&src->bl,UNT_USED_TRAPS); + map_foreachinrange(skill_trap_splash,&src->bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, &src->bl,tick); + sg->limit = DIFF_TICK(tick,sg->tick)+1000; + sg->unit_id = UNT_USED_TRAPS; + break; + + case UNT_SEVERE_RAINSTORM: + if( battle_check_target(&src->bl, bl, BCT_ENEMY) ) + skill_attack(BF_WEAPON,ss,&src->bl,bl,WM_SEVERE_RAINSTORM_MELEE,sg->skill_lv,tick,0); + break; + case UNT_NETHERWORLD: + if( !(status_get_mode(bl)&MD_BOSS) && ss != bl && battle_check_target(&src->bl, bl, BCT_PARTY) ) { + if( !(tsc && tsc->data[type]) ){ + sc_start(bl, type, 100, sg->skill_lv, skill_get_time2(sg->skill_id,sg->skill_lv)); + sg->limit = DIFF_TICK(tick,sg->tick); + sg->unit_id = UNT_USED_TRAPS; + } + } + break; + case UNT_THORNS_TRAP: + if( tsc ) { + if( !sg->val2 ) { + int sec = skill_get_time2(sg->skill_id, sg->skill_lv); + if( sc_start(bl, type, 100, sg->skill_lv, sec) ) { + const struct TimerData* td = tsc->data[type]?get_timer(tsc->data[type]->timer):NULL; + if( td ) + sec = DIFF_TICK(td->tick, tick); + ///map_moveblock(bl, src->bl.x, src->bl.y, tick); // in official server it doesn't behave like this. [malufett] + clif_fixpos(bl); + sg->val2 = bl->id; + } else + sec = 3000; // Couldn't trap it? + sg->limit = DIFF_TICK(tick, sg->tick) + sec; + } else if( tsc->data[SC_THORNSTRAP] && bl->id == sg->val2 ) + skill_attack(skill_get_type(GN_THORNS_TRAP), ss, ss, bl, sg->skill_id, sg->skill_lv, tick, SD_LEVEL|SD_ANIMATION); + } + break; + + case UNT_DEMONIC_FIRE: { + TBL_PC* sd = BL_CAST(BL_PC, ss); + switch( sg->val2 ) { + case 1: + case 2: + default: + sc_start(bl, SC_BURNING, 4 + 4 * sg->skill_lv, sg->skill_lv, + skill_get_time2(sg->skill_id, sg->skill_lv)); + skill_attack(skill_get_type(sg->skill_id), ss, &src->bl, bl, + sg->skill_id, sg->skill_lv + 10 * sg->val2, tick, 0); + break; + case 3: + skill_attack(skill_get_type(CR_ACIDDEMONSTRATION), ss, &src->bl, bl, + CR_ACIDDEMONSTRATION, sd ? pc_checkskill(sd, CR_ACIDDEMONSTRATION) : sg->skill_lv, tick, 0); + break; + + } + } + break; + + case UNT_FIRE_EXPANSION_SMOKE_POWDER: + sc_start(bl, status_skill2sc(GN_FIRE_EXPANSION_SMOKE_POWDER), 100, sg->skill_lv, 1000); + break; + + case UNT_FIRE_EXPANSION_TEAR_GAS: + sc_start(bl, status_skill2sc(GN_FIRE_EXPANSION_TEAR_GAS), 100, sg->skill_lv, 1000); + break; + + case UNT_HELLS_PLANT: + if( battle_check_target(&src->bl,bl,BCT_ENEMY) > 0 ) + skill_attack(skill_get_type(GN_HELLS_PLANT_ATK), ss, &src->bl, bl, GN_HELLS_PLANT_ATK, sg->skill_lv, tick, 0); + if( ss != bl) //The caster is the only one who can step on the Plants, without destroying them + sg->limit = DIFF_TICK(tick, sg->tick) + 100; + break; + + case UNT_CLOUD_KILL: + if(tsc && !tsc->data[type]) + status_change_start(bl,type,10000,sg->skill_lv,sg->group_id,0,0,skill_get_time2(sg->skill_id,sg->skill_lv),8); + skill_attack(skill_get_type(sg->skill_id),ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + break; + + case UNT_WARMER: + if( bl->type == BL_PC && !battle_check_undead(tstatus->race, tstatus->def_ele) && tstatus->race != RC_DEMON ) { + int hp = 125 * sg->skill_lv; // Officially is 125 * skill_lv. + struct status_change *ssc = status_get_sc(ss); + if( ssc && ssc->data[SC_HEATER_OPTION] ) + hp += hp * ssc->data[SC_HEATER_OPTION]->val3 / 100; + if( tstatus->hp != tstatus->max_hp ) + clif_skill_nodamage(&src->bl, bl, AL_HEAL, hp, 0); + if( tsc && tsc->data[SC_AKAITSUKI] && hp ) + hp = ~hp + 1; + status_heal(bl, hp, 0, 0); + sc_start(bl, SC_WARMER, 100, sg->skill_lv, skill_get_time2(sg->skill_id,sg->skill_lv)); + } + break; + + case UNT_FIRE_INSIGNIA: + case UNT_WATER_INSIGNIA: + case UNT_WIND_INSIGNIA: + case UNT_EARTH_INSIGNIA: + case UNT_ZEPHYR: + sc_start(bl,type, 100, sg->skill_lv, sg->interval); + if (sg->unit_id != UNT_ZEPHYR && !battle_check_undead(tstatus->race, tstatus->def_ele)) { + int hp = tstatus->max_hp / 100; //+1% each 5s + if ((sg->val3) % 5) { //each 5s + if (tstatus->def_ele == skill_get_ele(sg->skill_id,sg->skill_lv)){ + status_heal(bl, hp, 0, 2); + } else if((sg->unit_id == UNT_FIRE_INSIGNIA && tstatus->def_ele == ELE_EARTH) + ||(sg->unit_id == UNT_WATER_INSIGNIA && tstatus->def_ele == ELE_FIRE) + ||(sg->unit_id == UNT_WIND_INSIGNIA && tstatus->def_ele == ELE_WATER) + ||(sg->unit_id == UNT_EARTH_INSIGNIA && tstatus->def_ele == ELE_WIND) + ){ + status_heal(bl, -hp, 0, 0); + } + } + sg->val3++; //timer + if (sg->val3 > 5) sg->val3 = 0; + } + break; + + case UNT_VACUUM_EXTREME: + {// TODO: official behavior in gvg area. [malufett] + int sec = sg->limit - DIFF_TICK(tick, sg->tick); + int range = skill_get_unit_range(sg->skill_id, sg->skill_lv); + + if( tsc && !tsc->data[type] && + distance_xy(src->bl.x, src->bl.y, bl->x, bl->y) <= range)// don't consider outer bounderies + sc_start(bl, type, 100, sg->skill_lv, sec); + + if( unit_is_walking(bl) && // wait until target stop walking + ( tsc && tsc->data[type] && tsc->data[type]->val4 >= tsc->data[type]->val3-range )) + break; + + if( tsc && ( !tsc->data[type] || (tsc->data[type] && tsc->data[type]->val4 < 1 ) ) ) + break; + + if( unit_is_walking(bl) && + distance_xy(src->bl.x, src->bl.y, bl->x, bl->y) > range )// going outside of boundaries? then force it to stop + unit_stop_walking(bl,1); + + if( !unit_is_walking(bl) && + distance_xy(src->bl.x, src->bl.y, bl->x, bl->y) <= range && // only snap if the target is inside the range or + src->bl.x != bl->x && src->bl.y != bl->y){// diagonal position parallel to VE's center + unit_movepos(bl, src->bl.x, src->bl.y, 0, 0); + clif_fixpos(bl); + } + } + break; + + case UNT_FIRE_MANTLE: + if( battle_check_target(&src->bl, bl, BCT_ENEMY) ) + skill_attack(BF_MAGIC,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + break; + + case UNT_ZENKAI_WATER: + case UNT_ZENKAI_LAND: + case UNT_ZENKAI_FIRE: + case UNT_ZENKAI_WIND: + if( battle_check_target(&src->bl,bl,BCT_ENEMY) > 0 ){ + switch( sg->unit_id ){ + case UNT_ZENKAI_WATER: + sc_start(bl, SC_CRYSTALIZE, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); + sc_start(bl, SC_FREEZE, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); + sc_start(bl, SC_FREEZING, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); + break; + case UNT_ZENKAI_LAND: + sc_start(bl, SC_STONE, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); + sc_start(bl, SC_POISON, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); + break; + case UNT_ZENKAI_FIRE: + sc_start(bl, SC_BURNING, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); + break; + case UNT_ZENKAI_WIND: + sc_start(bl, SC_SILENCE, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); + sc_start(bl, SC_SLEEP, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); + sc_start(bl, SC_DEEPSLEEP, sg->val1*5, sg->skill_lv, skill_get_time2(sg->skill_id, sg->skill_lv)); + break; + } + }else + sc_start2(bl,type,100,sg->val1,sg->val2,skill_get_time2(sg->skill_id, sg->skill_lv)); + break; + + case UNT_MAKIBISHI: + skill_attack(BF_MISC, ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); + sg->limit = DIFF_TICK(tick, sg->tick); + sg->unit_id = UNT_USED_TRAPS; + break; + + case UNT_LAVA_SLIDE: + skill_attack(BF_WEAPON, ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); + if(++sg->val1 > 4) //after 5 stop hit and destroy me + sg->limit = DIFF_TICK(tick, sg->tick); + break; + case UNT_POISON_MIST: + skill_attack(BF_MAGIC, ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); + status_change_start(bl, SC_BLIND, rnd() % 100 > sg->skill_lv * 10, sg->skill_lv, sg->skill_id, 0, 0, skill_get_time2(sg->skill_id, sg->skill_lv), 2|8); + break; + } + + if (bl->type == BL_MOB && ss != bl) + mobskill_event((TBL_MOB*)bl, ss, tick, MSC_SKILLUSED|(skill_id<<16)); + + return skill_id; +} +/*========================================== + * Triggered when a char steps out of a skill cell + *------------------------------------------*/ +int skill_unit_onout (struct skill_unit *src, struct block_list *bl, unsigned int tick) +{ + struct skill_unit_group *sg; + struct status_change *sc; + struct status_change_entry *sce; + enum sc_type type; + + nullpo_ret(src); + nullpo_ret(bl); + nullpo_ret(sg=src->group); + sc = status_get_sc(bl); + type = status_skill2sc(sg->skill_id); + sce = (sc && type != -1)?sc->data[type]:NULL; + + if( bl->prev==NULL || + (status_isdead(bl) && sg->unit_id != UNT_ANKLESNARE && sg->unit_id != UNT_SPIDERWEB) ) //Need to delete the trap if the source died. + return 0; + + switch(sg->unit_id){ + case UNT_SAFETYWALL: + case UNT_PNEUMA: + case UNT_EPICLESIS://Arch Bishop + case UNT_NEUTRALBARRIER: + case UNT_STEALTHFIELD: + if (sce) + status_change_end(bl, type, INVALID_TIMER); + break; + + case UNT_BASILICA: + if( sce && sce->val4 == src->bl.id ) + status_change_end(bl, type, INVALID_TIMER); + break; + case UNT_HERMODE: //Clear Hermode if the owner moved. + if (sce && sce->val3 == BCT_SELF && sce->val4 == sg->group_id) + status_change_end(bl, type, INVALID_TIMER); + break; + + case UNT_SPIDERWEB: + { + struct block_list *target = map_id2bl(sg->val2); + if (target && target==bl) + { + if (sce && sce->val3 == sg->group_id) + status_change_end(bl, type, INVALID_TIMER); + sg->limit = DIFF_TICK(tick,sg->tick)+1000; + } + break; + } + } + return sg->skill_id; +} + +/*========================================== + * Triggered when a char steps out of a skill group (entirely) [Skotlex] + *------------------------------------------*/ +static int skill_unit_onleft (uint16 skill_id, struct block_list *bl, unsigned int tick) +{ + struct status_change *sc; + struct status_change_entry *sce; + enum sc_type type; + + sc = status_get_sc(bl); + if (sc && !sc->count) + sc = NULL; + + type = status_skill2sc(skill_id); + sce = (sc && type != -1)?sc->data[type]:NULL; + + switch (skill_id) + { + case WZ_QUAGMIRE: + if (bl->type==BL_MOB) + break; + if (sce) + status_change_end(bl, type, INVALID_TIMER); + 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: + if(sc && sc->data[SC_DANCING] && (sc->data[SC_DANCING]->val1&0xFFFF) == skill_id) + { //Check if you just stepped out of your ensemble skill to cancel dancing. [Skotlex] + //We don't check for SC_LONGING because someone could always have knocked you back and out of the song/dance. + //FIXME: This code is not perfect, it doesn't checks for the real ensemble's owner, + //it only checks if you are doing the same ensemble. So if there's two chars doing an ensemble + //which overlaps, by stepping outside of the other parther's ensemble will cause you to cancel + //your own. Let's pray that scenario is pretty unlikely and noone will complain too much about it. + status_change_end(bl, SC_DANCING, INVALID_TIMER); + } + case MH_STEINWAND: + case MG_SAFETYWALL: + case AL_PNEUMA: + case SA_VOLCANO: + case SA_DELUGE: + case SA_VIOLENTGALE: + case CG_HERMODE: + case HW_GRAVITATION: + case NJ_SUITON: + case SC_MAELSTROM: + case EL_WATER_BARRIER: + case EL_ZEPHYR: + case EL_POWER_OF_GAIA: + case SO_FIRE_INSIGNIA: + case SO_WATER_INSIGNIA: + case SO_WIND_INSIGNIA: + case SO_EARTH_INSIGNIA: + if (sce) + status_change_end(bl, type, INVALID_TIMER); + break; + case SC_BLOODYLUST: + if (sce) { + status_change_end(bl, type, INVALID_TIMER); + status_set_sp(bl, 0, 0); //set sp to 0 when quitting zone + } + break; + + case BA_POEMBRAGI: + case BA_WHISTLE: + case BA_ASSASSINCROSS: + case BA_APPLEIDUN: + case DC_HUMMING: + case DC_DONTFORGETME: + case DC_FORTUNEKISS: + case DC_SERVICEFORYOU: + if (sce) + { + delete_timer(sce->timer, status_change_timer); + //NOTE: It'd be nice if we could get the skill_lv for a more accurate extra time, but alas... + //not possible on our current implementation. + sce->val4 = 1; //Store the fact that this is a "reduced" duration effect. + sce->timer = add_timer(tick+skill_get_time2(skill_id,1), status_change_timer, bl->id, type); + } + break; + case PF_FOGWALL: + if (sce) + { + status_change_end(bl, type, INVALID_TIMER); + if ((sce=sc->data[SC_BLIND])) + { + if (bl->type == BL_PC) //Players get blind ended inmediately, others have it still for 30 secs. [Skotlex] + status_change_end(bl, SC_BLIND, INVALID_TIMER); + else { + delete_timer(sce->timer, status_change_timer); + sce->timer = add_timer(30000+tick, status_change_timer, bl->id, SC_BLIND); + } + } + } + break; + case GD_LEADERSHIP: + case GD_GLORYWOUNDS: + case GD_SOULCOLD: + case GD_HAWKEYES: + if( !(sce && sce->val4) ) + status_change_end(bl, type, INVALID_TIMER); + break; + } + + return skill_id; +} + +/*========================================== + * Invoked when a unit cell has been placed/removed/deleted. + * flag values: + * flag&1: Invoke onplace function (otherwise invoke onout) + * flag&4: Invoke a onleft call (the unit might be scheduled for deletion) + *------------------------------------------*/ +static int skill_unit_effect (struct block_list* bl, va_list ap) +{ + struct skill_unit* unit = va_arg(ap,struct skill_unit*); + struct skill_unit_group* group = unit->group; + unsigned int tick = va_arg(ap,unsigned int); + unsigned int flag = va_arg(ap,unsigned int); + uint16 skill_id; + bool dissonance; + + if( (!unit->alive && !(flag&4)) || bl->prev == NULL ) + return 0; + + nullpo_ret(group); + + dissonance = skill_dance_switch(unit, 0); + + //Necessary in case the group is deleted after calling on_place/on_out [Skotlex] + skill_id = group->skill_id; + //Target-type check. + if( !(group->bl_flag&bl->type && battle_check_target(&unit->bl,bl,group->target_flag)>0) && (flag&4) ) { + if( group->state.song_dance&0x1 || (group->src_id == bl->id && group->state.song_dance&0x2) ) + skill_unit_onleft(skill_id, bl, tick);//Ensemble check to terminate it. + } else { + if( flag&1 ) + skill_unit_onplace(unit,bl,tick); + else + skill_unit_onout(unit,bl,tick); + + if( flag&4 ) + skill_unit_onleft(skill_id, bl, tick); + } + + if( dissonance ) skill_dance_switch(unit, 1); + + 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_ret(src); + nullpo_ret(sg=src->group); + + switch( sg->unit_id ) { + case UNT_BLASTMINE: + case UNT_SKIDTRAP: + case UNT_LANDMINE: + case UNT_SHOCKWAVE: + case UNT_SANDMAN: + case UNT_FLASHER: + case UNT_CLAYMORETRAP: + case UNT_FREEZINGTRAP: + case UNT_TALKIEBOX: + case UNT_ANKLESNARE: + case UNT_ICEWALL: + case UNT_REVERBERATION: + case UNT_WALLOFTHORN: + src->val1-=damage; + break; + default: + damage = 0; + break; + } + return damage; +} + +/*========================================== + * + *------------------------------------------*/ +static int skill_check_condition_char_sub (struct block_list *bl, va_list ap) +{ + int *c, skill_id; + struct block_list *src; + struct map_session_data *sd; + struct map_session_data *tsd; + int *p_sd; //Contains the list of characters found. + + nullpo_ret(bl); + nullpo_ret(tsd=(struct map_session_data*)bl); + nullpo_ret(src=va_arg(ap,struct block_list *)); + nullpo_ret(sd=(struct map_session_data*)src); + + c=va_arg(ap,int *); + p_sd = va_arg(ap, int *); + skill_id = va_arg(ap,int); + + if ( ((skill_id != PR_BENEDICTIO && *c >=1) || *c >=2) && !(skill_get_inf2(skill_id)&INF2_CHORUS_SKILL) ) + return 0; //Partner found for ensembles, or the two companions for Benedictio. [Skotlex] + + if (bl == src) + return 0; + + if(pc_isdead(tsd)) + return 0; + + if (tsd->sc.data[SC_SILENCE] || ( tsd->sc.opt1 && tsd->sc.opt1 != OPT1_BURNING )) + return 0; + + if( skill_get_inf2(skill_id)&INF2_CHORUS_SKILL ) { + if( tsd->status.party_id == sd->status.party_id && (tsd->class_&MAPID_THIRDMASK) == MAPID_MINSTRELWANDERER ) + p_sd[(*c)++] = tsd->bl.id; + return 1; + } else { + + switch(skill_id) { + case PR_BENEDICTIO: { + uint8 dir = map_calc_dir(&sd->bl,tsd->bl.x,tsd->bl.y); + dir = (unit_getdir(&sd->bl) + dir)%8; //This adjusts dir to account for the direction the sd is facing. + if ((tsd->class_&MAPID_BASEMASK) == MAPID_ACOLYTE && (dir == 2 || dir == 6) //Must be standing to the left/right of Priest. + && sd->status.sp >= 10) + p_sd[(*c)++]=tsd->bl.id; + return 1; + } + case AB_ADORAMUS: + // Adoramus does not consume Blue Gemstone when there is at least 1 Priest class next to the caster + if( (tsd->class_&MAPID_UPPERMASK) == MAPID_PRIEST ) + p_sd[(*c)++] = tsd->bl.id; + return 1; + case WL_COMET: + // Comet does not consume Red Gemstones when there is at least 1 Warlock class next to the caster + if( ( sd->class_&MAPID_THIRDMASK ) == MAPID_WARLOCK ) + p_sd[(*c)++] = tsd->bl.id; + return 1; + case LG_RAYOFGENESIS: + if( tsd->status.party_id == sd->status.party_id && (tsd->class_&MAPID_THIRDMASK) == MAPID_ROYAL_GUARD && + tsd->sc.data[SC_BANDING] ) + p_sd[(*c)++] = tsd->bl.id; + return 1; + default: //Warning: Assuming Ensemble Dance/Songs for code speed. [Skotlex] + { + uint16 skill_lv; + if(pc_issit(tsd) || !unit_can_move(&tsd->bl)) + return 0; + if (sd->status.sex != tsd->status.sex && + (tsd->class_&MAPID_UPPERMASK) == MAPID_BARDDANCER && + (skill_lv = pc_checkskill(tsd, skill_id)) > 0 && + (tsd->weapontype1==W_MUSICAL || tsd->weapontype1==W_WHIP) && + sd->status.party_id && tsd->status.party_id && + sd->status.party_id == tsd->status.party_id && + !tsd->sc.data[SC_DANCING]) + { + p_sd[(*c)++]=tsd->bl.id; + return skill_lv; + } else { + return 0; + } + } + break; + } + + } + return 0; +} + +/*========================================== + * Checks and stores partners for ensemble skills [Skotlex] + *------------------------------------------*/ +int skill_check_pc_partner (struct map_session_data *sd, uint16 skill_id, short* skill_lv, int range, int cast_flag) +{ + static int c=0; + static int p_sd[2] = { 0, 0 }; + int i; + bool is_chorus = ( skill_get_inf2(skill_id)&INF2_CHORUS_SKILL ); + + if (!battle_config.player_skill_partner_check || pc_has_permission(sd, PC_PERM_SKILL_UNCONDITIONAL)) + return is_chorus ? MAX_PARTY : 99; //As if there were infinite partners. + + if (cast_flag) { //Execute the skill on the partners. + struct map_session_data* tsd; + switch (skill_id) { + case PR_BENEDICTIO: + for (i = 0; i < c; i++) { + if ((tsd = map_id2sd(p_sd[i])) != NULL) + status_charge(&tsd->bl, 0, 10); + } + return c; + case AB_ADORAMUS: + if( c > 0 && (tsd = map_id2sd(p_sd[0])) != NULL ) { + i = 2 * (*skill_lv); + status_charge(&tsd->bl, 0, i); + } + break; + case WM_GREAT_ECHO: + for( i = 0; i < c; i++ ) { + if( (tsd = map_id2sd(p_sd[i])) != NULL ) + status_zap(&tsd->bl,0,skill_get_sp(skill_id,*skill_lv)/c); + } + break; + default: //Warning: Assuming Ensemble skills here (for speed) + if( is_chorus ) + break;//Chorus skills are not to be parsed as ensambles + if (c > 0 && sd->sc.data[SC_DANCING] && (tsd = map_id2sd(p_sd[0])) != NULL) { + sd->sc.data[SC_DANCING]->val4 = tsd->bl.id; + sc_start4(&tsd->bl,SC_DANCING,100,skill_id,sd->sc.data[SC_DANCING]->val2,*skill_lv,sd->bl.id,skill_get_time(skill_id,*skill_lv)+1000); + clif_skill_nodamage(&tsd->bl, &sd->bl, skill_id, *skill_lv, 1); + tsd->skill_id_dance = skill_id; + tsd->skill_lv_dance = *skill_lv; + } + return c; + } + } + + //Else: new search for partners. + c = 0; + memset (p_sd, 0, sizeof(p_sd)); + if( is_chorus ) + i = party_foreachsamemap(skill_check_condition_char_sub,sd,AREA_SIZE,&sd->bl, &c, &p_sd, skill_id, *skill_lv); + else + i = map_foreachinrange(skill_check_condition_char_sub, &sd->bl, range, BL_PC, &sd->bl, &c, &p_sd, skill_id); + + if ( skill_id != PR_BENEDICTIO && skill_id != AB_ADORAMUS && skill_id != WL_COMET ) //Apply the average lv to encore skills. + *skill_lv = (i+(*skill_lv))/(c+1); //I know c should be one, but this shows how it could be used for the average of n partners. + return c; +} + +/*========================================== + * + *------------------------------------------*/ +static int skill_check_condition_mob_master_sub (struct block_list *bl, va_list ap) +{ + int *c,src_id,mob_class,skill; + struct mob_data *md; + + md=(struct mob_data*)bl; + src_id=va_arg(ap,int); + mob_class=va_arg(ap,int); + skill=va_arg(ap,int); + c=va_arg(ap,int *); + + if( md->master_id != src_id || md->special_state.ai != (unsigned)(skill == AM_SPHEREMINE?2:skill == KO_ZANZOU?4:3) ) + return 0; //Non alchemist summoned mobs have nothing to do here. + + if(md->class_==mob_class) + (*c)++; + + return 1; +} + +/*========================================== + * Determines if a given skill should be made to consume ammo + * when used by the player. [Skotlex] + *------------------------------------------*/ +int skill_isammotype (struct map_session_data *sd, int skill) +{ + return ( + battle_config.arrow_decrement==2 && + (sd->status.weapon == W_BOW || (sd->status.weapon >= W_REVOLVER && sd->status.weapon <= W_GRENADE)) && + skill != HT_PHANTASMIC && + skill_get_type(skill) == BF_WEAPON && + !(skill_get_nk(skill)&NK_NO_DAMAGE) && + !skill_get_spiritball(skill,1) //Assume spirit spheres are used as ammo instead. + ); +} + +int skill_check_condition_castbegin(struct map_session_data* sd, uint16 skill_id, uint16 skill_lv) { + struct status_data *status; + struct status_change *sc; + struct skill_condition require; + int i; + + nullpo_ret(sd); + + if (sd->chatID) return 0; + + if( pc_has_permission(sd, PC_PERM_SKILL_UNCONDITIONAL) && sd->skillitem != skill_id ) + { //GMs don't override the skillItem check, otherwise they can use items without them being consumed! [Skotlex] + sd->state.arrow_atk = skill_get_ammotype(skill_id)?1:0; //Need to do arrow state check. + sd->spiritball_old = sd->spiritball; //Need to do Spiritball check. + return 1; + } + + switch( sd->menuskill_id ) { + case AM_PHARMACY: + switch( skill_id ) { + case AM_PHARMACY: + case AC_MAKINGARROW: + case BS_REPAIRWEAPON: + case AM_TWILIGHT1: + case AM_TWILIGHT2: + case AM_TWILIGHT3: + return 0; + } + break; + case GN_MIX_COOKING: + case GN_MAKEBOMB: + case GN_S_PHARMACY: + case GN_CHANGEMATERIAL: + if( sd->menuskill_id != skill_id ) + return 0; + break; + } + status = &sd->battle_status; + sc = &sd->sc; + if( !sc->count ) + sc = NULL; + + if( sd->skillitem == skill_id ) + { + if( sd->state.abra_flag ) // Hocus-Pocus was used. [Inkfish] + sd->state.abra_flag = 0; + else + { // When a target was selected, consume items that were skipped in pc_use_item [Skotlex] + if( (i = sd->itemindex) == -1 || + sd->status.inventory[i].nameid != sd->itemid || + sd->inventory_data[i] == NULL || + !sd->inventory_data[i]->flag.delay_consume || + sd->status.inventory[i].amount < 1 + ) + { //Something went wrong, item exploit? + sd->itemid = sd->itemindex = -1; + return 0; + } + //Consume + sd->itemid = sd->itemindex = -1; + if( skill_id == WZ_EARTHSPIKE && sc && sc->data[SC_EARTHSCROLL] && rnd()%100 > sc->data[SC_EARTHSCROLL]->val2 ) // [marquis007] + ; //Do not consume item. + else if( sd->status.inventory[i].expire_time == 0 ) + pc_delitem(sd,i,1,0,0,LOG_TYPE_CONSUME); // Rental usable items are not consumed until expiration + } + return 1; + } + + if( pc_is90overweight(sd) ) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_WEIGHTOVER,0); + return 0; + } + + if( sc && ( sc->data[SC__SHADOWFORM] || sc->data[SC__IGNORANCE] ) ) + return 0; + + switch( skill_id ) { // Turn off check. + case BS_MAXIMIZE: case NV_TRICKDEAD: case TF_HIDING: case AS_CLOAKING: case CR_AUTOGUARD: + case ML_AUTOGUARD: case CR_DEFENDER: case ML_DEFENDER: case ST_CHASEWALK: case PA_GOSPEL: + case CR_SHRINK: case TK_RUN: case GS_GATLINGFEVER: case TK_READYCOUNTER: case TK_READYDOWN: + case TK_READYSTORM: case TK_READYTURN: case SG_FUSION: case RA_WUGDASH: case KO_YAMIKUMO: + if( sc && sc->data[status_skill2sc(skill_id)] ) + return 1; + } + + // Check the skills that can be used while mounted on a warg + if( pc_isridingwug(sd) ) { + switch( skill_id ) { + 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_SPRINGTRAP: case RA_DETONATOR: case RA_CLUSTERBOMB: + case HT_TALKIEBOX: case RA_FIRINGTRAP: case RA_ICEBOUNDTRAP: + case RA_WUGDASH: case RA_WUGRIDER: case RA_WUGSTRIKE: + break; + default: // in official there is no message. + return 0; + } + + } + if( pc_ismadogear(sd) ) { + switch( skill_id ) { //None Mado skills are unusable when Mado is equipped. [Jobbie] + case BS_REPAIRWEAPON: case WS_MELTDOWN: + case BS_HAMMERFALL: case WS_CARTBOOST: + case BS_ADRENALINE: case WS_WEAPONREFINE: + case BS_WEAPONPERFECT: case WS_CARTTERMINATION: + case BS_OVERTHRUST: case WS_OVERTHRUSTMAX: + case BS_MAXIMIZE: case NC_AXEBOOMERANG: + case BS_ADRENALINE2: case NC_POWERSWING: + case BS_UNFAIRLYTRICK: case NC_AXETORNADO: + case BS_GREED: + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + default: //Only Mechanic exlcusive skill can be used. + break; + } + } + if( skill_lv < 1 || skill_lv > MAX_SKILL_LEVEL ) + return 0; + + require = skill_get_requirement(sd,skill_id,skill_lv); + + //Can only update state when weapon/arrow info is checked. + sd->state.arrow_atk = require.ammo?1:0; + + // perform skill-specific checks (and actions) + switch( skill_id ) { + case SO_SPELLFIST: + if(sd->skill_id_old != MG_FIREBOLT && sd->skill_id_old != MG_COLDBOLT && sd->skill_id_old != MG_LIGHTNINGBOLT){ + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + case SA_CASTCANCEL: + if(sd->ud.skilltimer == INVALID_TIMER) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case AL_WARP: + if(!battle_config.duel_allow_teleport && sd->duel_group) { // duel restriction [LuzZza] + char output[128]; sprintf(output, msg_txt(365), skill_get_name(AL_WARP)); + clif_displaymessage(sd->fd, output); //"Duel: Can't use %s in duel." + return 0; + } + break; + case MO_CALLSPIRITS: + if(sc && sc->data[SC_RAISINGDRAGON]) + skill_lv += sc->data[SC_RAISINGDRAGON]->val1; + if(sd->spiritball >= skill_lv) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case MO_FINGEROFFENSIVE: + case GS_FLING: + case SR_RAMPAGEBLASTER: + case SR_RIDEINLIGHTNING: + if( sd->spiritball > 0 && sd->spiritball < require.spiritball ) + sd->spiritball_old = require.spiritball = sd->spiritball; + else + sd->spiritball_old = require.spiritball; + break; + case MO_CHAINCOMBO: + if(!sc) + return 0; + if(sc->data[SC_BLADESTOP]) + break; + if(sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == MO_TRIPLEATTACK) + break; + return 0; + case MO_COMBOFINISH: + if(!(sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == MO_CHAINCOMBO)) + return 0; + break; + case CH_TIGERFIST: + if(!(sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == MO_COMBOFINISH)) + return 0; + break; + case CH_CHAINCRUSH: + if(!(sc && sc->data[SC_COMBO])) + return 0; + if(sc->data[SC_COMBO]->val1 != MO_COMBOFINISH && sc->data[SC_COMBO]->val1 != CH_TIGERFIST) + return 0; + break; + case MO_EXTREMITYFIST: + // if(sc && sc->data[SC_EXTREMITYFIST]) //To disable Asura during the 5 min skill block uncomment this... + // return 0; + if( sc && (sc->data[SC_BLADESTOP] || sc->data[SC_CURSEDCIRCLE_ATKER]) ) + break; + if( sc && sc->data[SC_COMBO] ) + { + switch(sc->data[SC_COMBO]->val1) { + case MO_COMBOFINISH: + case CH_TIGERFIST: + case CH_CHAINCRUSH: + break; + default: + return 0; + } + } + else if( !unit_can_move(&sd->bl) ) + { //Placed here as ST_MOVE_ENABLE should not apply if rooted or on a combo. [Skotlex] + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + + case TK_MISSION: + if( (sd->class_&MAPID_UPPERMASK) != MAPID_TAEKWON ) + {// Cannot be used by Non-Taekwon classes + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + + case TK_READYCOUNTER: + case TK_READYDOWN: + case TK_READYSTORM: + case TK_READYTURN: + case TK_JUMPKICK: + if( (sd->class_&MAPID_UPPERMASK) == MAPID_SOUL_LINKER ) + {// Soul Linkers cannot use this skill + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + + case TK_TURNKICK: + case TK_STORMKICK: + case TK_DOWNKICK: + case TK_COUNTER: + if ((sd->class_&MAPID_UPPERMASK) == MAPID_SOUL_LINKER) + return 0; //Anti-Soul Linker check in case you job-changed with Stances active. + if(!(sc && sc->data[SC_COMBO]) || sc->data[SC_COMBO]->val1 == TK_JUMPKICK) + return 0; //Combo needs to be ready + + if (sc->data[SC_COMBO]->val3) { //Kick chain + //Do not repeat a kick. + if (sc->data[SC_COMBO]->val3 != skill_id) + break; + status_change_end(&sd->bl, SC_COMBO, INVALID_TIMER); + return 0; + } + if(sc->data[SC_COMBO]->val1 != skill_id && !( sd && sd->status.base_level >= 90 && pc_famerank(sd->status.char_id, MAPID_TAEKWON) )) { //Cancel combo wait. + unit_cancel_combo(&sd->bl); + return 0; + } + break; //Combo ready. + case BD_ADAPTATION: + { + int time; + if(!(sc && sc->data[SC_DANCING])) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + time = 1000*(sc->data[SC_DANCING]->val3>>16); + if (skill_get_time( + (sc->data[SC_DANCING]->val1&0xFFFF), //Dance Skill ID + (sc->data[SC_DANCING]->val1>>16)) //Dance Skill LV + - time < skill_get_time2(skill_id,skill_lv)) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + } + break; + + case PR_BENEDICTIO: + if (skill_check_pc_partner(sd, skill_id, &skill_lv, 1, 0) < 2) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + + case SL_SMA: + if(!(sc && sc->data[SC_SMA])) + return 0; + break; + + case HT_POWER: + if(!(sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == skill_id)) + return 0; + break; + + case CG_HERMODE: + if(!npc_check_areanpc(1,sd->bl.m,sd->bl.x,sd->bl.y,skill_get_splash(skill_id, skill_lv))) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case CG_MOONLIT: //Check there's no wall in the range+1 area around the caster. [Skotlex] + { + int i,x,y,range = skill_get_splash(skill_id, skill_lv)+1; + int size = range*2+1; + for (i=0;i<size*size;i++) { + x = sd->bl.x+(i%size-range); + y = sd->bl.y+(i/size-range); + if (map_getcell(sd->bl.m,x,y,CELL_CHKWALL)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + } + } + break; + case PR_REDEMPTIO: + { + int exp; + if( ((exp = pc_nextbaseexp(sd)) > 0 && get_percentage(sd->status.base_exp, exp) < 1) || + ((exp = pc_nextjobexp(sd)) > 0 && get_percentage(sd->status.job_exp, exp) < 1)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); //Not enough exp. + return 0; + } + break; + } + case AM_TWILIGHT2: + case AM_TWILIGHT3: + if (!party_skill_check(sd, sd->status.party_id, skill_id, skill_lv)) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case SG_SUN_WARM: + case SG_MOON_WARM: + case SG_STAR_WARM: + if (sc && sc->data[SC_MIRACLE]) + break; + i = skill_id-SG_SUN_WARM; + if (sd->bl.m == sd->feel_map[i].m) + break; + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + break; + case SG_SUN_COMFORT: + case SG_MOON_COMFORT: + case SG_STAR_COMFORT: + if (sc && sc->data[SC_MIRACLE]) + break; + i = skill_id-SG_SUN_COMFORT; + if (sd->bl.m == sd->feel_map[i].m && + (battle_config.allow_skill_without_day || sg_info[i].day_func())) + break; + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + case SG_FUSION: + if (sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_STAR) + break; + //Auron insists we should implement SP consumption when you are not Soul Linked. [Skotlex] + //Only invoke on skill begin cast (instant cast skill). [Kevin] + if( require.sp > 0 ) + { + if (status->sp < (unsigned int)require.sp) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_SP_INSUFFICIENT,0); + else + status_zap(&sd->bl, 0, require.sp); + } + return 0; + case GD_BATTLEORDER: + case GD_REGENERATION: + case GD_RESTORE: + if (!map_flag_gvg2(sd->bl.m)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + case GD_EMERGENCYCALL: + // other checks were already done in skillnotok() + if (!sd->status.guild_id || !sd->state.gmaster_flag) + return 0; + break; + + case GS_GLITTERING: + if(sd->spiritball >= 10) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + + case NJ_ISSEN: +#ifdef RENEWAL + if (status->hp < (status->hp/100)) { +#else + if (status->hp < 2) { +#endif + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + case NJ_BUNSINJYUTSU: + if (!(sc && sc->data[SC_NEN])) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + + case NJ_ZENYNAGE: + case KO_MUCHANAGE: + if(sd->status.zeny < require.zeny) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_MONEY,0); + return 0; + } + break; + case PF_HPCONVERSION: + if (status->sp == status->max_sp) + return 0; //Unusable when at full SP. + break; + case AM_CALLHOMUN: //Can't summon if a hom is already out + if (sd->status.hom_id && sd->hd && !sd->hd->homunculus.vaporize) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case AM_REST: //Can't vapo homun if you don't have an active homunc or it's hp is < 80% + if (!merc_is_hom_active(sd->hd) || sd->hd->battle_status.hp < (sd->hd->battle_status.max_hp*80/100)) + { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + /** + * Arch Bishop + **/ + case AB_ANCILLA: + { + int count = 0; + for( i = 0; i < MAX_INVENTORY; i ++ ) + if( sd->status.inventory[i].nameid == ITEMID_ANCILLA ) + count += sd->status.inventory[i].amount; + if( count >= 3 ) { + clif_skill_fail(sd, skill_id, USESKILL_FAIL_ANCILLA_NUMOVER, 0); + return 0; + } + } + break; + /** + * Keeping as a note: + * Bug Report #17 provides a link to a sep-2011 changelog that shows this requirement was removed + **/ + //case AB_LAUDAAGNUS: + //case AB_LAUDARAMUS: + // if( !sd->status.party_id ) { + // clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + // return 0; + // } + // break; + + case AB_ADORAMUS: + /** + * Warlock + **/ + case WL_COMET: + if( skill_check_pc_partner(sd,skill_id,&skill_lv,1,0) <= 0 && ((i = pc_search_inventory(sd,require.itemid[0])) < 0 || sd->status.inventory[i].amount < require.amount[0]) ) + { + //clif_skill_fail(sd,skill_id,USESKILL_FAIL_NEED_ITEM,require.amount[0],require.itemid[0]); + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case WL_SUMMONFB: + case WL_SUMMONBL: + case WL_SUMMONWB: + case WL_SUMMONSTONE: + if( sc ) + { + ARR_FIND(SC_SPHERE_1,SC_SPHERE_5+1,i,!sc->data[i]); + if( i == SC_SPHERE_5+1 ) + { // No more free slots + clif_skill_fail(sd,skill_id,USESKILL_FAIL_SUMMON,0); + return 0; + } + } + break; + /** + * Guilotine Cross + **/ + case GC_HALLUCINATIONWALK: + if( sc && (sc->data[SC_HALLUCINATIONWALK] || sc->data[SC_HALLUCINATIONWALK_POSTDELAY]) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case GC_COUNTERSLASH: + case GC_WEAPONCRUSH: + if( !(sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == GC_WEAPONBLOCKING) ) { + clif_skill_fail(sd, skill_id, USESKILL_FAIL_GC_WEAPONBLOCKING, 0); + return 0; + } + break; + /** + * Ranger + **/ + case RA_WUGMASTERY: + if( pc_isfalcon(sd) || pc_isridingwug(sd) || sd->sc.data[SC__GROOMY]) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case RA_WUGSTRIKE: + if( !pc_iswug(sd) && !pc_isridingwug(sd) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case RA_WUGRIDER: + if( pc_isfalcon(sd) || ( !pc_isridingwug(sd) && !pc_iswug(sd) ) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case RA_WUGDASH: + if(!pc_isridingwug(sd)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + /** + * Royal Guard + **/ + case LG_BANDING: + if( sc && sc->data[SC_INSPIRATION] ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case LG_PRESTIGE: + if( sc && (sc->data[SC_BANDING] || sc->data[SC_INSPIRATION]) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case LG_RAGEBURST: + if( sd->spiritball == 0 ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_SKILLINTERVAL,0); + return 0; + } + sd->spiritball_old = require.spiritball = sd->spiritball; + break; + case LG_RAYOFGENESIS: + if( sc && sc->data[SC_INSPIRATION] ) + return 1; // Don't check for partner. + if( !(sc && sc->data[SC_BANDING]) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL,0); + return 0; + } else if( skill_check_pc_partner(sd,skill_id,&skill_lv,skill_get_range(skill_id,skill_lv),0) < 1 ) + return 0; // Just fails, no msg here. + break; + case LG_HESPERUSLIT: + if( !sc || !sc->data[SC_BANDING] ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case SR_FALLENEMPIRE: + if( !(sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == SR_DRAGONCOMBO) ) + return 0; + break; + + case SR_CRESCENTELBOW: + if( sc && sc->data[SC_CRESCENTELBOW] ) { + clif_skill_fail(sd, skill_id, USESKILL_FAIL_DUPLICATE, 0); + return 0; + } + break; + case SR_CURSEDCIRCLE: + if (map_flag_gvg(sd->bl.m)) { + if (map_foreachinrange(mob_count_sub, &sd->bl, skill_get_splash(skill_id, skill_lv), BL_MOB, + MOBID_EMPERIUM, MOBID_GUARIDAN_STONE1, MOBID_GUARIDAN_STONE2)) { + char output[128]; + sprintf(output, "You're too close to a stone or emperium to do this skill"); + clif_colormes(sd, COLOR_RED, output); + return 0; + } + } + if( sd->spiritball > 0 ) + sd->spiritball_old = require.spiritball = sd->spiritball; + else { + clif_skill_fail(sd,skill_id,0,0); + return 0; + } + break; + case SR_GATEOFHELL: + if( sd->spiritball > 0 ) + sd->spiritball_old = require.spiritball; + break; + case SC_MANHOLE: + case SC_DIMENSIONDOOR: + if( sc && sc->data[SC_MAGNETICFIELD] ) { + clif_skill_fail(sd,skill_id,0,0); + return 0; + } + break; + case WM_GREAT_ECHO: { + int count; + count = skill_check_pc_partner(sd, skill_id, &skill_lv, skill_get_splash(skill_id,skill_lv), 0); + if( count < 1 ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_NEED_HELPER,0); + return 0; + } else + require.sp -= require.sp * 20 * count / 100; // -20% each W/M in the party. + } + break; + case SO_FIREWALK: + case SO_ELECTRICWALK: // Can't be casted until you've walked all cells. + if( sc && sc->data[SC_PROPERTYWALK] && + sc->data[SC_PROPERTYWALK]->val3 < skill_get_maxcount(sc->data[SC_PROPERTYWALK]->val1,sc->data[SC_PROPERTYWALK]->val2) ) { + clif_skill_fail(sd,skill_id,0x0,0); + return 0; + } + break; + case SO_EL_CONTROL: + if( !sd->status.ele_id || !sd->ed ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case RETURN_TO_ELDICASTES: + if( pc_ismadogear(sd) ) { //Cannot be used if Mado is equipped. + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case LG_REFLECTDAMAGE: + case CR_REFLECTSHIELD: + if( sc && sc->data[SC_KYOMU] && rand()%100 < 30){ + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case KO_KAHU_ENTEN: + case KO_HYOUHU_HUBUKI: + case KO_KAZEHU_SEIRAN: + case KO_DOHU_KOUKAI: + { + int ttype = skill_get_ele(skill_id, skill_lv); + ARR_FIND(1, 5, i, sd->talisman[i] > 0 && i != ttype); + if( (i < 5 && i != ttype) || sd->talisman[ttype] >= 10 ){ + clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); + return 0; + } + } + break; + case KO_KAIHOU: + case KO_ZENKAI: + ARR_FIND(1, 6, i, sd->talisman[i] > 0); + if( i > 4 ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + } + + switch(require.state) { + case ST_HIDING: + if(!(sc && sc->option&OPTION_HIDE)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case ST_CLOAKING: + if(!pc_iscloaking(sd)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case ST_HIDDEN: + if(!pc_ishiding(sd)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case ST_RIDING: + if(!pc_isriding(sd)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case ST_FALCON: + if(!pc_isfalcon(sd)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case ST_CARTBOOST: + if(!(sc && sc->data[SC_CARTBOOST])) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + case ST_CART: + if(!pc_iscarton(sd)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case ST_SHIELD: + if(sd->status.shield <= 0) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case ST_SIGHT: + if(!(sc && sc->data[SC_SIGHT])) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case ST_EXPLOSIONSPIRITS: + if(!(sc && sc->data[SC_EXPLOSIONSPIRITS])) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case ST_RECOV_WEIGHT_RATE: + if(battle_config.natural_heal_weight_rate <= 100 && sd->weight*100/sd->max_weight >= (unsigned int)battle_config.natural_heal_weight_rate) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case ST_MOVE_ENABLE: + if (sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == skill_id) + sd->ud.canmove_tick = gettick(); //When using a combo, cancel the can't move delay to enable the skill. [Skotlex] + + if (!unit_can_move(&sd->bl)) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + case ST_WATER: + if (sc && (sc->data[SC_DELUGE] || sc->data[SC_SUITON])) + break; + if (map_getcell(sd->bl.m,sd->bl.x,sd->bl.y,CELL_CHKWATER)) + break; + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + /** + * Rune Knight + **/ + case ST_RIDINGDRAGON: + if( !pc_isridingdragon(sd) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + /** + * Wug + **/ + case ST_WUG: + if( !pc_iswug(sd) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + /** + * Riding Wug + **/ + case ST_RIDINGWUG: + if( !pc_isridingwug(sd) ){ + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + /** + * Mechanic + **/ + case ST_MADO: + if( !pc_ismadogear(sd) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + break; + /** + * Sorcerer + **/ + case ST_ELEMENTALSPIRIT: + if(!sd->ed) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_EL_SUMMON,0); + return 0; + } + break; + case ST_POISONINGWEAPON: + if (!(sc && sc->data[SC_POISONINGWEAPON])) { + clif_skill_fail(sd, skill_id, USESKILL_FAIL_GC_POISONINGWEAPON, 0); + return 0; + } + break; + case ST_ROLLINGCUTTER: + if (!(sc && sc->data[SC_ROLLINGCUTTER])) { + clif_skill_fail(sd, skill_id, USESKILL_FAIL_CONDITION, 0); + return 0; + } + break; + case ST_MH_FIGHTING: + if (!(sc && sc->data[SC_STYLE_CHANGE] && sc->data[SC_STYLE_CHANGE]->val2 == MH_MD_FIGHTING)){ + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + case ST_MH_GRAPPLING: + if (!(sc && sc->data[SC_STYLE_CHANGE] && sc->data[SC_STYLE_CHANGE]->val2 == MH_MD_GRAPPLING)){ + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + } + + if(require.mhp > 0 && get_percentage(status->hp, status->max_hp) > require.mhp) { + //mhp is the max-hp-requirement, that is, + //you must have this % or less of HP to cast it. + clif_skill_fail(sd,skill_id,USESKILL_FAIL_HP_INSUFFICIENT,0); + return 0; + } + + if( require.weapon && !pc_check_weapontype(sd,require.weapon) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_THIS_WEAPON,0); + return 0; + } + + if( require.sp > 0 && status->sp < (unsigned int)require.sp) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_SP_INSUFFICIENT,0); + return 0; + } + + if( require.zeny > 0 && sd->status.zeny < require.zeny ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_MONEY,0); + return 0; + } + + if( require.spiritball > 0 && sd->spiritball < require.spiritball) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_SPIRITS,require.spiritball); + return 0; + } + + return 1; +} + +int skill_check_condition_castend(struct map_session_data* sd, uint16 skill_id, uint16 skill_lv) +{ + struct skill_condition require; + struct status_data *status; + int i; + int index[MAX_SKILL_ITEM_REQUIRE]; + + nullpo_ret(sd); + + if( sd->chatID ) + return 0; + + if( pc_has_permission(sd, PC_PERM_SKILL_UNCONDITIONAL) && sd->skillitem != skill_id ) { + //GMs don't override the skillItem check, otherwise they can use items without them being consumed! [Skotlex] + sd->state.arrow_atk = skill_get_ammotype(skill_id)?1:0; //Need to do arrow state check. + sd->spiritball_old = sd->spiritball; //Need to do Spiritball check. + return 1; + } + + switch( sd->menuskill_id ) { // Cast start or cast end?? + case AM_PHARMACY: + switch( skill_id ) { + case AM_PHARMACY: + case AC_MAKINGARROW: + case BS_REPAIRWEAPON: + case AM_TWILIGHT1: + case AM_TWILIGHT2: + case AM_TWILIGHT3: + return 0; + } + break; + case GN_MIX_COOKING: + case GN_MAKEBOMB: + case GN_S_PHARMACY: + case GN_CHANGEMATERIAL: + if( sd->menuskill_id != skill_id ) + return 0; + break; + } + + if( sd->skillitem == skill_id ) // Casting finished (Item skill or Hocus-Pocus) + return 1; + + if( pc_is90overweight(sd) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_WEIGHTOVER,0); + return 0; + } + + // perform skill-specific checks (and actions) + switch( skill_id ) { + case PR_BENEDICTIO: + skill_check_pc_partner(sd, skill_id, &skill_lv, 1, 1); + break; + case AM_CANNIBALIZE: + case AM_SPHEREMINE: { + int c=0; + int summons[5] = { 1589, 1579, 1575, 1555, 1590 }; + //int summons[5] = { 1020, 1068, 1118, 1500, 1368 }; + int maxcount = (skill_id==AM_CANNIBALIZE)? 6-skill_lv : skill_get_maxcount(skill_id,skill_lv); + int mob_class = (skill_id==AM_CANNIBALIZE)? summons[skill_lv-1] :1142; + if(battle_config.land_skill_limit && maxcount>0 && (battle_config.land_skill_limit&BL_PC)) { + i = map_foreachinmap(skill_check_condition_mob_master_sub ,sd->bl.m, BL_MOB, sd->bl.id, mob_class, skill_id, &c); + if(c >= maxcount || + (skill_id==AM_CANNIBALIZE && c != i && battle_config.summon_flora&2)) + { //Fails when: exceed max limit. There are other plant types already out. + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + } + break; + } + case NC_SILVERSNIPER: + case NC_MAGICDECOY: { + int c = 0, j; + int maxcount = skill_get_maxcount(skill_id,skill_lv); + int mob_class = 2042; + if( skill_id == NC_MAGICDECOY ) + mob_class = 2043; + + if( battle_config.land_skill_limit && maxcount > 0 && ( battle_config.land_skill_limit&BL_PC ) ) { + if( skill_id == NC_MAGICDECOY ) { + for( j = mob_class; j <= 2046; j++ ) + map_foreachinmap(skill_check_condition_mob_master_sub, sd->bl.m, BL_MOB, sd->bl.id, j, skill_id, &c); + } else + map_foreachinmap(skill_check_condition_mob_master_sub, sd->bl.m, BL_MOB, sd->bl.id, mob_class, skill_id, &c); + if( c >= maxcount ) { + clif_skill_fail(sd , skill_id, USESKILL_FAIL_LEVEL, 0); + return 0; + } + } + } + break; + case KO_ZANZOU: { + int c = 0; + i = map_foreachinmap(skill_check_condition_mob_master_sub, sd->bl.m, BL_MOB, sd->bl.id, 2308, skill_id, &c); + if( c >= skill_get_maxcount(skill_id,skill_lv) || c != i) + { + clif_skill_fail(sd , skill_id, USESKILL_FAIL_LEVEL, 0); + return 0; + } + } + break; + } + + status = &sd->battle_status; + + require = skill_get_requirement(sd,skill_id,skill_lv); + + if( require.hp > 0 && status->hp <= (unsigned int)require.hp) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_HP_INSUFFICIENT,0); + return 0; + } + + if( require.weapon && !pc_check_weapontype(sd,require.weapon) ) { + clif_skill_fail(sd,skill_id,USESKILL_FAIL_THIS_WEAPON,0); + return 0; + } + + if( require.ammo ) { //Skill requires stuff equipped in the arrow slot. + if((i=sd->equip_index[EQI_AMMO]) < 0 || !sd->inventory_data[i] ) { + clif_arrow_fail(sd,0); + return 0; + } else if( sd->status.inventory[i].amount < require.ammo_qty ) { + char e_msg[100]; + sprintf(e_msg,"Skill Failed. [%s] requires %dx %s.", + skill_get_desc(skill_id), + require.ammo_qty, + itemdb_jname(sd->status.inventory[i].nameid)); + clif_colormes(sd,COLOR_RED,e_msg); + return 0; + } + if (!(require.ammo&1<<sd->inventory_data[i]->look)) { //Ammo type check. Send the "wrong weapon type" message + //which is the closest we have to wrong ammo type. [Skotlex] + clif_arrow_fail(sd,0); //Haplo suggested we just send the equip-arrows message instead. [Skotlex] + //clif_skill_fail(sd,skill_id,USESKILL_FAIL_THIS_WEAPON,0); + return 0; + } + } + + for( i = 0; i < MAX_SKILL_ITEM_REQUIRE; ++i ) { + if( !require.itemid[i] ) + continue; + index[i] = pc_search_inventory(sd,require.itemid[i]); + if( index[i] < 0 || sd->status.inventory[index[i]].amount < require.amount[i] ) { + if( require.itemid[i] == ITEMID_RED_GEMSTONE ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_REDJAMSTONE,0);// red gemstone required + else if( require.itemid[i] == ITEMID_BLUE_GEMSTONE ) + clif_skill_fail(sd,skill_id,USESKILL_FAIL_BLUEJAMSTONE,0);// blue gemstone required + else + clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + return 0; + } + } + + return 1; +} + +// type&2: consume items (after skill was used) +// type&1: consume the others (before skill was used) +int skill_consume_requirement( struct map_session_data *sd, uint16 skill_id, uint16 skill_lv, short type) +{ + struct skill_condition req; + + nullpo_ret(sd); + + req = skill_get_requirement(sd,skill_id,skill_lv); + + if( type&1 ) + { + if( skill_id == CG_TAROTCARD || sd->state.autocast ) + req.sp = 0; // TarotCard will consume sp in skill_cast_nodamage_id [Inkfish] + if(req.hp || req.sp) + status_zap(&sd->bl, req.hp, req.sp); + + if(req.spiritball > 0) + pc_delspiritball(sd,req.spiritball,0); + + if(req.zeny > 0) + { + if( skill_id == NJ_ZENYNAGE ) + req.zeny = 0; //Zeny is reduced on skill_attack. + if( sd->status.zeny < req.zeny ) + req.zeny = sd->status.zeny; + pc_payzeny(sd,req.zeny,LOG_TYPE_CONSUME,NULL); + } + } + + if( type&2 ) + { + struct status_change *sc = &sd->sc; + int n,i; + + if( !sc->count ) + sc = NULL; + + for( i = 0; i < MAX_SKILL_ITEM_REQUIRE; ++i ) + { + if( !req.itemid[i] ) + continue; + + if( itemid_isgemstone(req.itemid[i]) && skill_id != HW_GANBANTEIN && sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_WIZARD ) + continue; //Gemstones are checked, but not substracted from inventory. + + switch( skill_id ){ + case SA_SEISMICWEAPON: + if( sc && sc->data[SC_UPHEAVAL_OPTION] && rnd()%100 < 50 ) + continue; + break; + case SA_FLAMELAUNCHER: + case SA_VOLCANO: + if( sc && sc->data[SC_TROPIC_OPTION] && rnd()%100 < 50 ) + continue; + break; + case SA_FROSTWEAPON: + case SA_DELUGE: + if( sc && sc->data[SC_CHILLY_AIR_OPTION] && rnd()%100 < 50 ) + continue; + break; + case SA_LIGHTNINGLOADER: + case SA_VIOLENTGALE: + if( sc && sc->data[SC_WILD_STORM_OPTION] && rnd()%100 < 50 ) + continue; + break; + } + + if( (n = pc_search_inventory(sd,req.itemid[i])) >= 0 ) + pc_delitem(sd,n,req.amount[i],0,1,LOG_TYPE_CONSUME); + } + } + + return 1; +} + +struct skill_condition skill_get_requirement(struct map_session_data* sd, uint16 skill_id, uint16 skill_lv) +{ + struct skill_condition req; + struct status_data *status; + struct status_change *sc; + int i,hp_rate,sp_rate, sp_skill_rate_bonus = 100; + uint16 idx; + + memset(&req,0,sizeof(req)); + + if( !sd ) + return req; + + if( sd->skillitem == skill_id ) + return req; // Item skills and Hocus-Pocus don't have requirements.[Inkfish] + + sc = &sd->sc; + if( !sc->count ) + sc = NULL; + + switch( skill_id ) + { // Turn off check. + case BS_MAXIMIZE: case NV_TRICKDEAD: case TF_HIDING: case AS_CLOAKING: case CR_AUTOGUARD: + case ML_AUTOGUARD: case CR_DEFENDER: case ML_DEFENDER: case ST_CHASEWALK: case PA_GOSPEL: + case CR_SHRINK: case TK_RUN: case GS_GATLINGFEVER: case TK_READYCOUNTER: case TK_READYDOWN: + case TK_READYSTORM: case TK_READYTURN: case SG_FUSION: case KO_YAMIKUMO: + if( sc && sc->data[status_skill2sc(skill_id)] ) + return req; + } + + idx = skill_get_index(skill_id); + if( idx == 0 ) // invalid skill id + return req; + if( skill_lv < 1 || skill_lv > MAX_SKILL_LEVEL ) + return req; + + status = &sd->battle_status; + + req.hp = skill_db[idx].hp[skill_lv-1]; + hp_rate = skill_db[idx].hp_rate[skill_lv-1]; + if(hp_rate > 0) + req.hp += (status->hp * hp_rate)/100; + else + req.hp += (status->max_hp * (-hp_rate))/100; + + req.sp = skill_db[idx].sp[skill_lv-1]; + if((sd->skill_id_old == BD_ENCORE) && skill_id == sd->skill_id_dance) + req.sp /= 2; + sp_rate = skill_db[idx].sp_rate[skill_lv-1]; + if(sp_rate > 0) + req.sp += (status->sp * sp_rate)/100; + else + req.sp += (status->max_sp * (-sp_rate))/100; + if( sd->dsprate != 100 ) + req.sp = req.sp * sd->dsprate / 100; + + ARR_FIND(0, ARRAYLENGTH(sd->skillusesprate), i, sd->skillusesprate[i].id == skill_id); + if( i < ARRAYLENGTH(sd->skillusesprate) ) + sp_skill_rate_bonus += sd->skillusesprate[i].val; + ARR_FIND(0, ARRAYLENGTH(sd->skillusesp), i, sd->skillusesp[i].id == skill_id); + if( i < ARRAYLENGTH(sd->skillusesp) ) + req.sp -= sd->skillusesp[i].val; + + req.sp = cap_value(req.sp * sp_skill_rate_bonus / 100, 0, SHRT_MAX); + + if( sc ) { + if( sc->data[SC__LAZINESS] ) + req.sp += req.sp + sc->data[SC__LAZINESS]->val1 * 10; + if (sc->data[SC_UNLIMITEDHUMMINGVOICE]) + req.sp += req.sp * sc->data[SC_UNLIMITEDHUMMINGVOICE]->val2 / 100; + if( sc->data[SC_RECOGNIZEDSPELL] ) + req.sp += req.sp / 4; + } + + req.zeny = skill_db[idx].zeny[skill_lv-1]; + + if( sc && sc->data[SC__UNLUCKY] ) + req.zeny += sc->data[SC__UNLUCKY]->val1 * 500; + + req.spiritball = skill_db[idx].spiritball[skill_lv-1]; + + req.state = skill_db[idx].state; + + req.mhp = skill_db[idx].mhp[skill_lv-1]; + + req.weapon = skill_db[idx].weapon; + + req.ammo_qty = skill_db[idx].ammo_qty[skill_lv-1]; + if (req.ammo_qty) + req.ammo = skill_db[idx].ammo; + + if (!req.ammo && skill_id && skill_isammotype(sd, skill_id)) + { //Assume this skill is using the weapon, therefore it requires arrows. + req.ammo = 0xFFFFFFFF; //Enable use on all ammo types. + req.ammo_qty = 1; + } + + for( i = 0; i < MAX_SKILL_ITEM_REQUIRE; i++ ) { + if( (skill_id == AM_POTIONPITCHER || skill_id == CR_SLIMPITCHER || skill_id == CR_CULTIVATION) && i != skill_lv%11 - 1 ) + continue; + + switch( skill_id ) { + case AM_CALLHOMUN: + if (sd->status.hom_id) //Don't delete items when hom is already out. + continue; + break; + case NC_SHAPESHIFT: + if( i < 4 ) + continue; + break; + case WZ_FIREPILLAR: // celest + if (skill_lv <= 5) // no gems required at level 1-5 + continue; + break; + case AB_ADORAMUS: + if( itemid_isgemstone(skill_db[idx].itemid[i]) && skill_check_pc_partner(sd,skill_id,&skill_lv, 1, 2) ) + continue; + break; + case WL_COMET: + if( itemid_isgemstone(skill_db[idx].itemid[i]) && skill_check_pc_partner(sd,skill_id,&skill_lv, 1, 0) ) + continue; + break; + case GN_FIRE_EXPANSION: + if( i < 5 ) + continue; + break; + case SO_SUMMON_AGNI: + case SO_SUMMON_AQUA: + case SO_SUMMON_VENTUS: + case SO_SUMMON_TERA: + case SO_WATER_INSIGNIA: + case SO_FIRE_INSIGNIA: + case SO_WIND_INSIGNIA: + case SO_EARTH_INSIGNIA: + if( i < 3 ) + continue; + break; + } + + req.itemid[i] = skill_db[idx].itemid[i]; + req.amount[i] = skill_db[idx].amount[i]; + + if( itemid_isgemstone(req.itemid[i]) && skill_id != HW_GANBANTEIN ) + { + if( sd->special_state.no_gemstone ) + { //Make it substract 1 gem rather than skipping the cost. + if( --req.amount[i] < 1 ) + req.itemid[i] = 0; + } + if(sc && sc->data[SC_INTOABYSS]) + { + if( skill_id != SA_ABRACADABRA ) + req.itemid[i] = req.amount[i] = 0; + else if( --req.amount[i] < 1 ) + req.amount[i] = 1; // Hocus Pocus allways use at least 1 gem + } + } + if( skill_id >= HT_SKIDTRAP && skill_id <= HT_TALKIEBOX && pc_checkskill(sd, RA_RESEARCHTRAP) > 0){ + int16 itIndex; + if( (itIndex = pc_search_inventory(sd,req.itemid[i])) < 0 || ( itIndex >= 0 && sd->status.inventory[itIndex].amount < req.amount[i] ) ){ + req.itemid[i] = ITEMID_TRAP_ALLOY; + req.amount[i] = 1; + } + break; + } + } + + /* requirements are level-dependent */ + switch( skill_id ) { + case NC_SHAPESHIFT: + case GN_FIRE_EXPANSION: + case SO_SUMMON_AGNI: + case SO_SUMMON_AQUA: + case SO_SUMMON_VENTUS: + case SO_SUMMON_TERA: + case SO_WATER_INSIGNIA: + case SO_FIRE_INSIGNIA: + case SO_WIND_INSIGNIA: + case SO_EARTH_INSIGNIA: + req.itemid[skill_lv-1] = skill_db[idx].itemid[skill_lv-1]; + req.amount[skill_lv-1] = skill_db[idx].amount[skill_lv-1]; + break; + } + + // Check for cost reductions due to skills & SCs + switch(skill_id) { + case MC_MAMMONITE: + if(pc_checkskill(sd,BS_UNFAIRLYTRICK)>0) + req.zeny -= req.zeny*10/100; + break; + case AL_HOLYLIGHT: + if(sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_PRIEST) + req.sp *= 5; + break; + case SL_SMA: + case SL_STUN: + case SL_STIN: + { + int kaina_lv = pc_checkskill(sd,SL_KAINA); + + if(kaina_lv==0 || sd->status.base_level<70) + break; + if(sd->status.base_level>=90) + req.sp -= req.sp*7*kaina_lv/100; + else if(sd->status.base_level>=80) + req.sp -= req.sp*5*kaina_lv/100; + else if(sd->status.base_level>=70) + req.sp -= req.sp*3*kaina_lv/100; + } + break; + case MO_TRIPLEATTACK: + case MO_CHAINCOMBO: + case MO_COMBOFINISH: + case CH_TIGERFIST: + case CH_CHAINCRUSH: + if(sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_MONK) + req.sp -= req.sp*25/100; //FIXME: Need real data. this is a custom value. + break; + case MO_BODYRELOCATION: + if( sc && sc->data[SC_EXPLOSIONSPIRITS] ) + req.spiritball = 0; + break; + case MO_EXTREMITYFIST: + if( sc ) + { + if( sc->data[SC_BLADESTOP] ) + req.spiritball--; + else if( sc->data[SC_COMBO] ) + { + switch( sc->data[SC_COMBO]->val1 ) + { + case MO_COMBOFINISH: + req.spiritball = 4; + break; + case CH_TIGERFIST: + req.spiritball = 3; + break; + case CH_CHAINCRUSH: //It should consume whatever is left as long as it's at least 1. + req.spiritball = sd->spiritball?sd->spiritball:1; + break; + } + }else if( sc->data[SC_RAISINGDRAGON] && sd->spiritball > 5) + req.spiritball = sd->spiritball; // must consume all regardless of the amount required + } + break; + case SR_RAMPAGEBLASTER: + req.spiritball = sd->spiritball?sd->spiritball:15; + break; + case SR_GATEOFHELL: + if( sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == SR_FALLENEMPIRE ) + req.sp -= req.sp * 10 / 100; + break; + case SO_SUMMON_AGNI: + case SO_SUMMON_AQUA: + case SO_SUMMON_VENTUS: + case SO_SUMMON_TERA: + req.sp -= req.sp * (5 + 5 * pc_checkskill(sd,SO_EL_SYMPATHY)) / 100; + break; + case SO_PSYCHIC_WAVE: + if( sc && sc->data[SC_BLAST_OPTION] ) + req.sp += req.sp * 150 / 100; + break; + } + + return req; +} + +/*========================================== + * Does cast-time reductions based on dex, item bonuses and config setting + *------------------------------------------*/ +int skill_castfix (struct block_list *bl, uint16 skill_id, uint16 skill_lv) { + int time = skill_get_cast(skill_id, skill_lv); + + nullpo_ret(bl); +#ifndef RENEWAL_CAST + { + struct map_session_data *sd; + + sd = BL_CAST(BL_PC, bl); + + // calculate base cast time (reduced by dex) + if( !(skill_get_castnodex(skill_id, skill_lv)&1) ) { + int scale = battle_config.castrate_dex_scale - status_get_dex(bl); + if( scale > 0 ) // not instant cast + time = time * scale / battle_config.castrate_dex_scale; + else + return 0; // instant cast + } + + // calculate cast time reduced by item/card bonuses + if( !(skill_get_castnodex(skill_id, skill_lv)&4) && sd ) + { + int i; + if( sd->castrate != 100 ) + time = time * sd->castrate / 100; + for( i = 0; i < ARRAYLENGTH(sd->skillcast) && sd->skillcast[i].id; i++ ) + { + if( sd->skillcast[i].id == skill_id ) + { + time+= time * sd->skillcast[i].val / 100; + break; + } + } + } + + } +#endif + // config cast time multiplier + if (battle_config.cast_rate != 100) + time = time * battle_config.cast_rate / 100; + // return final cast time + time = max(time, 0); + +// ShowInfo("Castime castfix = %d\n",time); + return time; +} + +/*========================================== + * Does cast-time reductions based on sc data. + *------------------------------------------*/ +int skill_castfix_sc (struct block_list *bl, int time) +{ + struct status_change *sc = status_get_sc(bl); + + if( time < 0 ) + return 0; + + if (sc && sc->count) { + if (sc->data[SC_SLOWCAST]) + time += time * sc->data[SC_SLOWCAST]->val2 / 100; + if (sc->data[SC_PARALYSIS]) + time += sc->data[SC_PARALYSIS]->val3; + if (sc->data[SC_SUFFRAGIUM]) { + time -= time * sc->data[SC_SUFFRAGIUM]->val2 / 100; + status_change_end(bl, SC_SUFFRAGIUM, INVALID_TIMER); + } + if (sc->data[SC_MEMORIZE]) { + time>>=1; + if ((--sc->data[SC_MEMORIZE]->val2) <= 0) + status_change_end(bl, SC_MEMORIZE, INVALID_TIMER); + } + if (sc->data[SC_POEMBRAGI]) + time -= time * sc->data[SC_POEMBRAGI]->val2 / 100; + if (sc->data[SC_IZAYOI]) + time -= time * 50 / 100; + } + time = max(time, 0); + +// ShowInfo("Castime castfix_sc = %d\n",time); + return time; +} +#ifdef RENEWAL_CAST +int skill_vfcastfix (struct block_list *bl, double time, uint16 skill_id, uint16 skill_lv) +{ + struct status_change *sc = status_get_sc(bl); + struct map_session_data *sd = BL_CAST(BL_PC,bl); + int fixed = skill_get_fixed_cast(skill_id, skill_lv), fixcast_r = 0, varcast_r = 0, i = 0; + + if( time < 0 ) + return 0; + + if( fixed == 0 ){ + fixed = (int)time * 20 / 100; // fixed time + time = time * 80 / 100; // variable time + }else if( fixed < 0 ) // no fixed cast time + fixed = 0; + + if(sd && !(skill_get_castnodex(skill_id, skill_lv)&4) ){ // Increases/Decreases fixed/variable cast time of a skill by item/card bonuses. + if( sd->bonus.varcastrate < 0 ) + VARCAST_REDUCTION(sd->bonus.varcastrate); + for (i = 0; i < ARRAYLENGTH(sd->skillfixcast) && sd->skillfixcast[i].id; i++) + if (sd->skillfixcast[i].id == skill_id){ // bonus2 bSkillFixedCast + fixed += sd->skillfixcast[i].val; + break; + } + for( i = 0; i < ARRAYLENGTH(sd->skillvarcast) && sd->skillvarcast[i].id; i++ ) + if( sd->skillvarcast[i].id == skill_id ){ // bonus2 bSkillVariableCast + time += sd->skillvarcast[i].val; + break; + } + for( i = 0; i < ARRAYLENGTH(sd->skillcast) && sd->skillcast[i].id; i++ ) + if( sd->skillcast[i].id == skill_id ){ // bonus2 bVariableCastrate + if( (i=sd->skillcast[i].val) < 0) + VARCAST_REDUCTION(i); + break; + } + } + + if (sc && sc->count && !(skill_get_castnodex(skill_id, skill_lv)&2) ) { + // All variable cast additive bonuses must come first + if (sc->data[SC_SLOWCAST]) + VARCAST_REDUCTION(-sc->data[SC_SLOWCAST]->val2); + + // Variable cast reduction bonuses + if (sc->data[SC_SUFFRAGIUM]) { + VARCAST_REDUCTION(sc->data[SC_SUFFRAGIUM]->val2); + status_change_end(bl, SC_SUFFRAGIUM, INVALID_TIMER); + } + if (sc->data[SC_MEMORIZE]) { + VARCAST_REDUCTION(50); + if ((--sc->data[SC_MEMORIZE]->val2) <= 0) + status_change_end(bl, SC_MEMORIZE, INVALID_TIMER); + } + if (sc->data[SC_POEMBRAGI]) + VARCAST_REDUCTION(sc->data[SC_POEMBRAGI]->val2); + if (sc->data[SC_IZAYOI]) + VARCAST_REDUCTION(50); + if (sc->data[SC_WATER_INSIGNIA] && sc->data[SC_WATER_INSIGNIA]->val1 == 3 && (skill_get_ele(skill_id, skill_lv) == ELE_WATER)) + VARCAST_REDUCTION(30); //Reduces 30% Variable Cast Time of Water spells. + // Fixed cast reduction bonuses + if( sc->data[SC__LAZINESS] ) + fixcast_r = max(fixcast_r, sc->data[SC__LAZINESS]->val2); + if( sc->data[SC_SECRAMENT] ) + fixcast_r = max(fixcast_r, sc->data[SC_SECRAMENT]->val2); + if( sd && ( skill_lv = pc_checkskill(sd, WL_RADIUS) ) && skill_id >= WL_WHITEIMPRISON && skill_id <= WL_FREEZE_SP ) + fixcast_r = max(fixcast_r, 5 + skill_lv * 5); + // Fixed cast non percentage bonuses + if( sc->data[SC_MANDRAGORA] ) + fixed += sc->data[SC_MANDRAGORA]->val1 * 1000 / 2; + if (sc->data[SC_IZAYOI] && (skill_id >= NJ_TOBIDOUGU && skill_id <= NJ_ISSEN)) + fixed = 0; + if( sc->data[SC_GUST_OPTION] || sc->data[SC_BLAST_OPTION] || sc->data[SC_WILD_STORM_OPTION] ) + fixed -= 1000; + } + + if( sd && !(skill_get_castnodex(skill_id, skill_lv)&4) ){ + VARCAST_REDUCTION( max(sd->bonus.varcastrate, 0) + max(i, 0) ); + fixcast_r = max(fixcast_r, sd->bonus.fixcastrate) + min(sd->bonus.fixcastrate,0); + } + + if( varcast_r < 0 ) // now compute overall factors + time = time * (1 - (float)varcast_r / 100); + if( !(skill_get_castnodex(skill_id, skill_lv)&1) )// reduction from status point + time = (1 - sqrt( ((float)(status_get_dex(bl)*2 + status_get_int(bl)) / battle_config.vcast_stat_scale) )) * time; + // underflow checking/capping + time = max(time, 0) + (1 - (float)min(fixcast_r, 100) / 100) * max(fixed,0); + + return (int)time; +} +#endif + +/*========================================== + * Does delay reductions based on dex/agi, sc data, item bonuses, ... + *------------------------------------------*/ +int skill_delayfix (struct block_list *bl, uint16 skill_id, uint16 skill_lv) +{ + int delaynodex = skill_get_delaynodex(skill_id, skill_lv); + int time = skill_get_delay(skill_id, skill_lv); + struct map_session_data *sd; + struct status_change *sc = status_get_sc(bl); + + nullpo_ret(bl); + sd = BL_CAST(BL_PC, bl); + + if (skill_id == SA_ABRACADABRA || skill_id == WM_RANDOMIZESPELL) + return 0; //Will use picked skill's delay. + + if (bl->type&battle_config.no_skill_delay) + return battle_config.min_skill_delay_limit; + + if (time < 0) + time = -time + status_get_amotion(bl); // If set to <0, add to attack motion. + + // Delay reductions + switch (skill_id) { //Monk combo skills have their delay reduced by agi/dex. + case MO_TRIPLEATTACK: + case MO_CHAINCOMBO: + case MO_COMBOFINISH: + case CH_TIGERFIST: + case CH_CHAINCRUSH: + case SR_DRAGONCOMBO: + case SR_FALLENEMPIRE: + time -= 4*status_get_agi(bl) - 2*status_get_dex(bl); + break; + case HP_BASILICA: + if( sc && !sc->data[SC_BASILICA] ) + time = 0; // There is no Delay on Basilica creation, only on cancel + break; + default: + if (battle_config.delay_dependon_dex && !(delaynodex&1)) + { // if skill delay is allowed to be reduced by dex + int scale = battle_config.castrate_dex_scale - status_get_dex(bl); + if (scale > 0) + time = time * scale / battle_config.castrate_dex_scale; + else //To be capped later to minimum. + time = 0; + } + if (battle_config.delay_dependon_agi && !(delaynodex&1)) + { // if skill delay is allowed to be reduced by agi + int scale = battle_config.castrate_dex_scale - status_get_agi(bl); + if (scale > 0) + time = time * scale / battle_config.castrate_dex_scale; + else //To be capped later to minimum. + time = 0; + } + } + + if ( sc && sc->data[SC_SPIRIT] ) + { + switch (skill_id) { + case CR_SHIELDBOOMERANG: + if (sc->data[SC_SPIRIT]->val2 == SL_CRUSADER) + time /= 2; + break; + case AS_SONICBLOW: + if (!map_flag_gvg(bl->m) && !map[bl->m].flag.battleground && sc->data[SC_SPIRIT]->val2 == SL_ASSASIN) + time /= 2; + break; + } + } + + if (!(delaynodex&2)) + { + if (sc && sc->count) { + if (sc->data[SC_POEMBRAGI]) + time -= time * sc->data[SC_POEMBRAGI]->val3 / 100; + if (sc->data[SC_WIND_INSIGNIA] && sc->data[SC_WIND_INSIGNIA]->val1 == 3 && (skill_get_ele(skill_id, skill_lv) == ELE_WIND)) + time /= 2; // After Delay of Wind element spells reduced by 50%. + } + + } + + if( !(delaynodex&4) && sd && sd->delayrate != 100 ) + time = time * sd->delayrate / 100; + + if (battle_config.delay_rate != 100) + time = time * battle_config.delay_rate / 100; + + //min delay + time = max(time, status_get_amotion(bl)); // Delay can never be below amotion [Playtester] + time = max(time, battle_config.min_skill_delay_limit); + +// ShowInfo("Delay delayfix = %d\n",time); + return time; +} + +/*========================================= + * + *-----------------------------------------*/ +struct square { + int val1[5]; + int val2[5]; +}; + +static void skill_brandishspear_first (struct square *tc, uint8 dir, int16 x, int16 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; + } + +} + +static void skill_brandishspear_dir (struct square* tc, uint8 dir, int are) +{ + int c; + nullpo_retv(tc); + + for( c = 0; c < 5; c++ ) + { + switch( dir ) + { + case 0: tc->val2[c]+=are; break; + case 1: tc->val1[c]-=are; tc->val2[c]+=are; break; + case 2: tc->val1[c]-=are; break; + case 3: tc->val1[c]-=are; tc->val2[c]-=are; break; + case 4: tc->val2[c]-=are; break; + case 5: tc->val1[c]+=are; tc->val2[c]-=are; break; + case 6: tc->val1[c]+=are; break; + case 7: tc->val1[c]+=are; tc->val2[c]+=are; break; + } + } +} + +void skill_brandishspear(struct block_list* src, struct block_list* bl, uint16 skill_id, uint16 skill_lv, unsigned int tick, int flag) +{ + int c,n=4; + uint8 dir = map_calc_dir(src,bl->x,bl->y); + struct square tc; + int x=bl->x,y=bl->y; + skill_brandishspear_first(&tc,dir,x,y); + skill_brandishspear_dir(&tc,dir,4); + skill_area_temp[1] = bl->id; + + if(skill_lv > 9){ + for(c=1;c<4;c++){ + map_foreachincell(skill_area_sub, + bl->m,tc.val1[c],tc.val2[c],BL_CHAR, + src,skill_id,skill_lv,tick, flag|BCT_ENEMY|n, + skill_castend_damage_id); + } + } + if(skill_lv > 6){ + skill_brandishspear_dir(&tc,dir,-1); + n--; + }else{ + skill_brandishspear_dir(&tc,dir,-2); + n-=2; + } + + if(skill_lv > 3){ + for(c=0;c<5;c++){ + map_foreachincell(skill_area_sub, + bl->m,tc.val1[c],tc.val2[c],BL_CHAR, + src,skill_id,skill_lv,tick, flag|BCT_ENEMY|n, + skill_castend_damage_id); + if(skill_lv > 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_foreachincell(skill_area_sub, + bl->m,tc.val1[c%5],tc.val2[c%5],BL_CHAR, + src,skill_id,skill_lv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + } +} + +/*========================================== + * Weapon Repair [Celest/DracoRPG] + *------------------------------------------*/ +void skill_repairweapon (struct map_session_data *sd, int idx) { + int material; + int materials[4] = { 1002, 998, 999, 756 }; + struct item *item; + struct map_session_data *target_sd; + + nullpo_retv(sd); + + if ( !( target_sd = map_id2sd(sd->menuskill_val) ) ) //Failed.... + return; + + if( idx == 0xFFFF ) // No item selected ('Cancel' clicked) + return; + if( idx < 0 || idx >= MAX_INVENTORY ) + return; //Invalid index?? + + item = &target_sd->status.inventory[idx]; + if( item->nameid <= 0 || item->attribute == 0 ) + return; //Again invalid item.... + + if( sd != target_sd && !battle_check_range(&sd->bl,&target_sd->bl, skill_get_range2(&sd->bl, sd->menuskill_id,sd->menuskill_val2) ) ){ + clif_item_repaireffect(sd,idx,1); + return; + } + + if ( target_sd->inventory_data[idx]->type == IT_WEAPON ) + material = materials [ target_sd->inventory_data[idx]->wlv - 1 ]; // Lv1/2/3/4 weapons consume 1 Iron Ore/Iron/Steel/Rough Oridecon + else + material = materials [2]; // Armors consume 1 Steel + if ( pc_search_inventory(sd,material) < 0 ) { + clif_skill_fail(sd,sd->menuskill_id,USESKILL_FAIL_LEVEL,0); + return; + } + + clif_skill_nodamage(&sd->bl,&target_sd->bl,sd->menuskill_id,1,1); + + item->attribute = 0;/* clear broken state */ + + clif_equiplist(target_sd); + + pc_delitem(sd,pc_search_inventory(sd,material),1,0,0,LOG_TYPE_CONSUME); + + clif_item_repaireffect(sd,idx,0); + + if( sd != target_sd ) + clif_item_repaireffect(target_sd,idx,0); +} + +/*========================================== + * Item Appraisal + *------------------------------------------*/ +void skill_identify (struct map_session_data *sd, int idx) +{ + int flag=1; + + nullpo_retv(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); +} + +/*========================================== + * Weapon Refine [Celest] + *------------------------------------------*/ +void skill_weaponrefine (struct map_session_data *sd, int idx) +{ + nullpo_retv(sd); + + if (idx >= 0 && idx < MAX_INVENTORY) + { + int i = 0, ep = 0, per; + int material[5] = { 0, 1010, 1011, 984, 984 }; + struct item *item; + struct item_data *ditem = sd->inventory_data[idx]; + item = &sd->status.inventory[idx]; + + if(item->nameid > 0 && ditem->type == IT_WEAPON) + { + if( item->refine >= sd->menuskill_val + || item->refine >= 10 // if it's no longer refineable + || ditem->flag.no_refine // if the item isn't refinable + || (i = pc_search_inventory(sd, material [ditem->wlv])) < 0 ) + { + clif_skill_fail(sd,sd->menuskill_id,USESKILL_FAIL_LEVEL,0); + return; + } + + per = status_get_refine_chance(ditem->wlv, (int)item->refine); + per += (((signed int)sd->status.job_level)-50)/2; //Updated per the new kro descriptions. [Skotlex] + + pc_delitem(sd, i, 1, 0, 0, LOG_TYPE_OTHER); + if (per > rnd() % 100) { + log_pick_pc(sd, LOG_TYPE_OTHER, -1, item); + item->refine++; + log_pick_pc(sd, LOG_TYPE_OTHER, 1, item); + if(item->equip) { + ep = item->equip; + pc_unequipitem(sd,idx,3); + } + clif_refine(sd->fd,0,idx,item->refine); + clif_delitem(sd,idx,1,3); + clif_additem(sd,idx,1,0); + if (ep) + pc_equipitem(sd,idx,ep); + clif_misceffect(&sd->bl,3); + if(item->refine == 10 && + item->card[0] == CARD0_FORGE && + (int)MakeDWord(item->card[2],item->card[3]) == sd->status.char_id) + { // Fame point system [DracoRPG] + switch(ditem->wlv){ + case 1: + pc_addfame(sd,1); // Success to refine to +10 a lv1 weapon you forged = +1 fame point + break; + case 2: + pc_addfame(sd,25); // Success to refine to +10 a lv2 weapon you forged = +25 fame point + break; + case 3: + pc_addfame(sd,1000); // Success to refine to +10 a lv3 weapon you forged = +1000 fame point + break; + } + } + } else { + item->refine = 0; + if(item->equip) + pc_unequipitem(sd,idx,3); + clif_refine(sd->fd,1,idx,item->refine); + pc_delitem(sd,idx,1,0,2, LOG_TYPE_OTHER); + clif_misceffect(&sd->bl,2); + clif_emotion(&sd->bl, E_OMG); + } + } + } +} + +/*========================================== + * + *------------------------------------------*/ +int skill_autospell (struct map_session_data *sd, uint16 skill_id) +{ + uint16 skill_lv; + int maxlv=1,lv; + + nullpo_ret(sd); + + skill_lv = sd->menuskill_val; + lv=pc_checkskill(sd,skill_id); + + if(!skill_lv || !lv) return 0; // Player must learn the skill before doing auto-spell [Lance] + + if(skill_id==MG_NAPALMBEAT) maxlv=3; + else if(skill_id==MG_COLDBOLT || skill_id==MG_FIREBOLT || skill_id==MG_LIGHTNINGBOLT){ + if (sd->sc.data[SC_SPIRIT] && sd->sc.data[SC_SPIRIT]->val2 == SL_SAGE) + maxlv =10; //Soul Linker bonus. [Skotlex] + else if(skill_lv==2) maxlv=1; + else if(skill_lv==3) maxlv=2; + else if(skill_lv>=4) maxlv=3; + } + else if(skill_id==MG_SOULSTRIKE){ + if(skill_lv==5) maxlv=1; + else if(skill_lv==6) maxlv=2; + else if(skill_lv>=7) maxlv=3; + } + else if(skill_id==MG_FIREBALL){ + if(skill_lv==8) maxlv=1; + else if(skill_lv>=9) maxlv=2; + } + else if(skill_id==MG_FROSTDIVER) maxlv=1; + else return 0; + + if(maxlv > lv) + maxlv = lv; + + sc_start4(&sd->bl,SC_AUTOSPELL,100,skill_lv,skill_id,maxlv,0, + skill_get_time(SA_AUTOSPELL,skill_lv)); + return 0; +} + +/*========================================== + * Sitting skills functions. + *------------------------------------------*/ +static int skill_sit_count (struct block_list *bl, va_list ap) +{ + struct map_session_data *sd; + int type =va_arg(ap,int); + sd=(struct map_session_data*)bl; + + if(!pc_issit(sd)) + return 0; + + if(type&1 && pc_checkskill(sd,RG_GANGSTER) > 0) + return 1; + + if(type&2 && (pc_checkskill(sd,TK_HPTIME) > 0 || pc_checkskill(sd,TK_SPTIME) > 0)) + return 1; + + return 0; +} + +static int skill_sit_in (struct block_list *bl, va_list ap) +{ + struct map_session_data *sd; + int type =va_arg(ap,int); + + sd=(struct map_session_data*)bl; + + if(!pc_issit(sd)) + return 0; + + if(type&1 && pc_checkskill(sd,RG_GANGSTER) > 0) + sd->state.gangsterparadise=1; + + if(type&2 && (pc_checkskill(sd,TK_HPTIME) > 0 || pc_checkskill(sd,TK_SPTIME) > 0 )) + { + sd->state.rest=1; + status_calc_regen(bl, &sd->battle_status, &sd->regen); + status_calc_regen_rate(bl, &sd->regen, &sd->sc); + } + + return 0; +} + +static int skill_sit_out (struct block_list *bl, va_list ap) +{ + struct map_session_data *sd; + int type =va_arg(ap,int); + sd=(struct map_session_data*)bl; + if(sd->state.gangsterparadise && type&1) + sd->state.gangsterparadise=0; + if(sd->state.rest && type&2) { + sd->state.rest=0; + status_calc_regen(bl, &sd->battle_status, &sd->regen); + status_calc_regen_rate(bl, &sd->regen, &sd->sc); + } + return 0; +} + +int skill_sit (struct map_session_data *sd, int type) +{ + int flag = 0; + int range = 0, lv; + nullpo_ret(sd); + + + if((lv = pc_checkskill(sd,RG_GANGSTER)) > 0) { + flag|=1; + range = skill_get_splash(RG_GANGSTER, lv); + } + if((lv = pc_checkskill(sd,TK_HPTIME)) > 0) { + flag|=2; + range = skill_get_splash(TK_HPTIME, lv); + } + else if ((lv = pc_checkskill(sd,TK_SPTIME)) > 0) { + flag|=2; + range = skill_get_splash(TK_SPTIME, lv); + } + + if( type ) { + clif_status_load(&sd->bl,SI_SIT,1); + } else { + clif_status_load(&sd->bl,SI_SIT,0); + } + + if (!flag) return 0; + + if(type) { + if (map_foreachinrange(skill_sit_count,&sd->bl, range, BL_PC, flag) > 1) + map_foreachinrange(skill_sit_in,&sd->bl, range, BL_PC, flag); + } else { + if (map_foreachinrange(skill_sit_count,&sd->bl, range, BL_PC, flag) < 2) + map_foreachinrange(skill_sit_out,&sd->bl, range, BL_PC, flag); + } + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_frostjoke_scream (struct block_list *bl, va_list ap) +{ + struct block_list *src; + uint16 skill_id,skill_lv; + unsigned int tick; + + nullpo_ret(bl); + nullpo_ret(src=va_arg(ap,struct block_list*)); + + skill_id=va_arg(ap,int); + skill_lv=va_arg(ap,int); + if(!skill_lv) return 0; + tick=va_arg(ap,unsigned int); + + if (src == bl || status_isdead(bl)) + return 0; + if (bl->type == BL_PC) { + struct map_session_data *sd = (struct map_session_data *)bl; + if ( sd && sd->sc.option&(OPTION_INVISIBLE|OPTION_MADOGEAR) ) + return 0;//Frost Joke / Scream cannot target invisible or MADO Gear characters [Ind] + } + //It has been reported that Scream/Joke works the same regardless of woe-setting. [Skotlex] + if(battle_check_target(src,bl,BCT_ENEMY) > 0) + skill_additional_effect(src,bl,skill_id,skill_lv,BF_MISC,ATK_DEF,tick); + else if(battle_check_target(src,bl,BCT_PARTY) > 0 && rnd()%100 < 10) + skill_additional_effect(src,bl,skill_id,skill_lv,BF_MISC,ATK_DEF,tick); + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +static void skill_unitsetmapcell (struct skill_unit *src, uint16 skill_id, uint16 skill_lv, cell_t cell, bool flag) +{ + int range = skill_get_unit_range(skill_id,skill_lv); + int x,y; + + for( y = src->bl.y - range; y <= src->bl.y + range; ++y ) + for( x = src->bl.x - range; x <= src->bl.x + range; ++x ) + map_setcell(src->bl.m, x, y, cell, flag); +} + +/*========================================== + * + *------------------------------------------*/ +int skill_attack_area (struct block_list *bl, va_list ap) +{ + struct block_list *src,*dsrc; + int atk_type,skill_id,skill_lv,flag,type; + unsigned int tick; + + if(status_isdead(bl)) + return 0; + + atk_type = va_arg(ap,int); + src=va_arg(ap,struct block_list*); + dsrc=va_arg(ap,struct block_list*); + skill_id=va_arg(ap,int); + skill_lv=va_arg(ap,int); + tick=va_arg(ap,unsigned int); + flag=va_arg(ap,int); + type=va_arg(ap,int); + + + if (skill_area_temp[1] == bl->id) //This is the target of the skill, do a full attack and skip target checks. + return skill_attack(atk_type,src,dsrc,bl,skill_id,skill_lv,tick,flag); + + if(battle_check_target(dsrc,bl,type) <= 0 || + !status_check_skilluse(NULL, bl, skill_id, 2)) + return 0; + + + switch (skill_id) { + case WZ_FROSTNOVA: //Skills that don't require the animation to be removed + case NPC_ACIDBREATH: + case NPC_DARKNESSBREATH: + case NPC_FIREBREATH: + case NPC_ICEBREATH: + case NPC_THUNDERBREATH: + return skill_attack(atk_type,src,dsrc,bl,skill_id,skill_lv,tick,flag); + default: + //Area-splash, disable skill animation. + return skill_attack(atk_type,src,dsrc,bl,skill_id,skill_lv,tick,flag|SD_ANIMATION); + } +} +/*========================================== + * + *------------------------------------------*/ +int skill_clear_group (struct block_list *bl, int flag) +{ + struct unit_data *ud = unit_bl2ud(bl); + struct skill_unit_group *group[MAX_SKILLUNITGROUP]; + int i, count=0; + + nullpo_ret(bl); + if (!ud) return 0; + + //All groups to be deleted are first stored on an array since the array elements shift around when you delete them. [Skotlex] + for (i=0;i<MAX_SKILLUNITGROUP && ud->skillunit[i];i++) + { + switch (ud->skillunit[i]->skill_id) { + case SA_DELUGE: + case SA_VOLCANO: + case SA_VIOLENTGALE: + case SA_LANDPROTECTOR: + case NJ_SUITON: + case NJ_KAENSIN: + if (flag&1) + group[count++]= ud->skillunit[i]; + break; + case SO_WARMER: + if( flag&8 ) + group[count++]= ud->skillunit[i]; + break; + case SC_BLOODYLUST: + if (flag & 32) + group[count++] = ud->skillunit[i]; + break; + default: + if (flag&2 && skill_get_inf2(ud->skillunit[i]->skill_id)&INF2_TRAP) + group[count++]= ud->skillunit[i]; + break; + } + + } + for (i=0;i<count;i++) + skill_delunitgroup(group[i]); + return count; +} + +/*========================================== + * Returns the first element field found [Skotlex] + *------------------------------------------*/ +struct skill_unit_group *skill_locate_element_field(struct block_list *bl) +{ + struct unit_data *ud = unit_bl2ud(bl); + int i; + nullpo_ret(bl); + if (!ud) return NULL; + + for (i=0;i<MAX_SKILLUNITGROUP && ud->skillunit[i];i++) { + switch (ud->skillunit[i]->skill_id) { + case SA_DELUGE: + case SA_VOLCANO: + case SA_VIOLENTGALE: + case SA_LANDPROTECTOR: + case NJ_SUITON: + case SO_WARMER: + case SC_BLOODYLUST: + return ud->skillunit[i]; + } + } + return NULL; +} + +// for graffiti cleaner [Valaris] +int skill_graffitiremover (struct block_list *bl, va_list ap) +{ + struct skill_unit *unit=NULL; + + nullpo_ret(bl); + nullpo_ret(ap); + + if(bl->type!=BL_SKILL || (unit=(struct skill_unit *)bl) == NULL) + return 0; + + if((unit->group) && (unit->group->unit_id == UNT_GRAFFITI)) + skill_delunit(unit); + + return 0; +} + +int skill_greed (struct block_list *bl, va_list ap) +{ + struct block_list *src; + struct map_session_data *sd=NULL; + struct flooritem_data *fitem=NULL; + + nullpo_ret(bl); + nullpo_ret(src = va_arg(ap, struct block_list *)); + + if(src->type == BL_PC && (sd=(struct map_session_data *)src) && bl->type==BL_ITEM && (fitem=(struct flooritem_data *)bl)) + pc_takeitem(sd, fitem); + + return 0; +} +//For Ranger's Detonator [Jobbie/3CeAM] +int skill_detonator(struct block_list *bl, va_list ap) +{ + struct skill_unit *unit=NULL; + struct block_list *src; + int unit_id; + + nullpo_ret(bl); + nullpo_ret(ap); + src = va_arg(ap,struct block_list *); + + if( bl->type != BL_SKILL || (unit = (struct skill_unit *)bl) == NULL || !unit->group ) + return 0; + if( unit->group->src_id != src->id ) + return 0; + + unit_id = unit->group->unit_id; + switch( unit_id ) + { //List of Hunter and Ranger Traps that can be detonate. + case UNT_BLASTMINE: + case UNT_SANDMAN: + case UNT_CLAYMORETRAP: + case UNT_TALKIEBOX: + case UNT_CLUSTERBOMB: + case UNT_FIRINGTRAP: + case UNT_ICEBOUNDTRAP: + if( unit_id == UNT_TALKIEBOX ) + { + clif_talkiebox(bl,unit->group->valstr); + unit->group->val2 = -1; + } + else + map_foreachinrange(skill_trap_splash,bl,skill_get_splash(unit->group->skill_id,unit->group->skill_lv),unit->group->bl_flag,bl,unit->group->tick); + + clif_changetraplook(bl,unit_id == UNT_FIRINGTRAP ? UNT_DUMMYSKILL : UNT_USED_TRAPS); + unit->group->unit_id = UNT_USED_TRAPS; + unit->group->limit = DIFF_TICK(gettick(),unit->group->tick) + + (unit_id == UNT_TALKIEBOX ? 5000 : (unit_id == UNT_CLUSTERBOMB || unit_id == UNT_ICEBOUNDTRAP? 2500 : 1500) ); + break; + } + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +static int skill_cell_overlap(struct block_list *bl, va_list ap) +{ + uint16 skill_id; + int *alive; + struct skill_unit *unit; + + skill_id = va_arg(ap,int); + alive = va_arg(ap,int *); + unit = (struct skill_unit *)bl; + + if (unit == NULL || unit->group == NULL || (*alive) == 0) + return 0; + + switch (skill_id) { + case SA_LANDPROTECTOR: + if( unit->group->skill_id == SA_LANDPROTECTOR ) {//Check for offensive Land Protector to delete both. [Skotlex] + (*alive) = 0; + skill_delunit(unit); + return 1; + } + if( !(skill_get_inf2(unit->group->skill_id)&(INF2_SONG_DANCE|INF2_TRAP)) ) { //It deletes everything except songs/dances and traps + skill_delunit(unit); + return 1; + } + break; + case HW_GANBANTEIN: + case LG_EARTHDRIVE: + if( !(unit->group->state.song_dance&0x1) ) {// Don't touch song/dance. + skill_delunit(unit); + return 1; + } + break; + case SA_VOLCANO: + case SA_DELUGE: + case SA_VIOLENTGALE: +// The official implementation makes them fail to appear when casted on top of ANYTHING +// but I wonder if they didn't actually meant to fail when casted on top of each other? +// hence, I leave the alternate implementation here, commented. [Skotlex] + if (unit->range <= 0) + { + (*alive) = 0; + return 1; + } +/* + switch (unit->group->skill_id) + { //These cannot override each other. + case SA_VOLCANO: + case SA_DELUGE: + case SA_VIOLENTGALE: + (*alive) = 0; + return 1; + } +*/ + break; + case PF_FOGWALL: + switch(unit->group->skill_id) { + case SA_VOLCANO: //Can't be placed on top of these + case SA_VIOLENTGALE: + (*alive) = 0; + return 1; + case SA_DELUGE: + case NJ_SUITON: + //Cheap 'hack' to notify the calling function that duration should be doubled [Skotlex] + (*alive) = 2; + break; + } + break; + case HP_BASILICA: + if (unit->group->skill_id == HP_BASILICA) + { //Basilica can't be placed on top of itself to avoid map-cell stacking problems. [Skotlex] + (*alive) = 0; + return 1; + } + break; + case GN_CRAZYWEED_ATK: + switch(unit->group->unit_id){ //TODO: look for other ground skills that are affected. + case UNT_WALLOFTHORN: + case UNT_THORNS_TRAP: + case UNT_BLOODYLUST: + case UNT_CHAOSPANIC: + case UNT_MAELSTROM: + case UNT_FIREPILLAR_ACTIVE: + case UNT_LANDPROTECTOR: + case UNT_VOLCANO: + case UNT_DELUGE: + case UNT_VIOLENTGALE: + case UNT_SAFETYWALL: + case UNT_PNEUMA: + skill_delunit(unit); + return 1; + } + break; + } + + if (unit->group->skill_id == SA_LANDPROTECTOR && !(skill_get_inf2(skill_id)&(INF2_SONG_DANCE|INF2_TRAP))) { //It deletes everything except songs/dances/traps + (*alive) = 0; + return 1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_chastle_mob_changetarget(struct block_list *bl,va_list ap) +{ + struct mob_data* md; + struct unit_data*ud = unit_bl2ud(bl); + struct block_list *from_bl; + struct block_list *to_bl; + md = (struct mob_data*)bl; + from_bl = va_arg(ap,struct block_list *); + to_bl = va_arg(ap,struct block_list *); + + if(ud && ud->target == from_bl->id) + ud->target = to_bl->id; + + if(md->bl.type == BL_MOB && md->target_id == from_bl->id) + md->target_id = to_bl->id; + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +static int skill_trap_splash (struct block_list *bl, va_list ap) +{ + struct block_list *src; + int tick; + struct skill_unit *unit; + struct skill_unit_group *sg; + struct block_list *ss; + src = va_arg(ap,struct block_list *); + unit = (struct skill_unit *)src; + tick = va_arg(ap,int); + + if( !unit->alive || bl->prev == NULL ) + return 0; + + nullpo_ret(sg = unit->group); + nullpo_ret(ss = map_id2bl(sg->src_id)); + + if(battle_check_target(src,bl,sg->target_flag) <= 0) + return 0; + + switch(sg->unit_id){ + case UNT_SHOCKWAVE: + case UNT_SANDMAN: + case UNT_FLASHER: + skill_additional_effect(ss,bl,sg->skill_id,sg->skill_lv,BF_MISC,ATK_DEF,tick); + break; + case UNT_GROUNDDRIFT_WIND: + if(skill_attack(BF_WEAPON,ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1)) + sc_start(bl,SC_STUN,5,sg->skill_lv,skill_get_time2(sg->skill_id, sg->skill_lv)); + break; + case UNT_GROUNDDRIFT_DARK: + if(skill_attack(BF_WEAPON,ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1)) + sc_start(bl,SC_BLIND,5,sg->skill_lv,skill_get_time2(sg->skill_id, sg->skill_lv)); + break; + case UNT_GROUNDDRIFT_POISON: + if(skill_attack(BF_WEAPON,ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1)) + sc_start(bl,SC_POISON,5,sg->skill_lv,skill_get_time2(sg->skill_id, sg->skill_lv)); + break; + case UNT_GROUNDDRIFT_WATER: + if(skill_attack(BF_WEAPON,ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1)) + sc_start(bl,SC_FREEZE,5,sg->skill_lv,skill_get_time2(sg->skill_id, sg->skill_lv)); + break; + case UNT_GROUNDDRIFT_FIRE: + if(skill_attack(BF_WEAPON,ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1)) + skill_blown(src,bl,skill_get_blewcount(sg->skill_id,sg->skill_lv),-1,0); + break; + case UNT_ELECTRICSHOCKER: + clif_skill_damage(src,bl,tick,0,0,-30000,1,sg->skill_id,sg->skill_lv,5); + break; + case UNT_FIRINGTRAP: + case UNT_ICEBOUNDTRAP: + case UNT_CLUSTERBOMB: + if( ss != bl ) + skill_attack(BF_MISC,ss,src,bl,sg->skill_id,sg->skill_lv,tick,sg->val1|SD_LEVEL); + break; + case UNT_MAGENTATRAP: + case UNT_COBALTTRAP: + case UNT_MAIZETRAP: + case UNT_VERDURETRAP: + if( bl->type != BL_PC && !is_boss(bl) ) + sc_start2(bl,SC_ELEMENTALCHANGE,100,sg->skill_lv,skill_get_ele(sg->skill_id,sg->skill_lv),skill_get_time2(sg->skill_id,sg->skill_lv)); + break; + case UNT_REVERBERATION: + skill_addtimerskill(ss,tick+50,bl->id,0,0,WM_REVERBERATION_MELEE,sg->skill_lv,BF_WEAPON,0); // for proper skill delay animation when use with Dominion Impulse + skill_addtimerskill(ss,tick+250,bl->id,0,0,WM_REVERBERATION_MAGIC,sg->skill_lv,BF_MAGIC,0); + break; + default: + skill_attack(skill_get_type(sg->skill_id),ss,src,bl,sg->skill_id,sg->skill_lv,tick,0); + break; + } + return 1; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_enchant_elemental_end (struct block_list *bl, int type) +{ + struct status_change *sc; + const enum sc_type scs[] = { SC_ENCPOISON, SC_ASPERSIO, SC_FIREWEAPON, SC_WATERWEAPON, SC_WINDWEAPON, SC_EARTHWEAPON, SC_SHADOWWEAPON, SC_GHOSTWEAPON, SC_ENCHANTARMS, SC_EXEEDBREAK }; + int i; + nullpo_ret(bl); + nullpo_ret(sc= status_get_sc(bl)); + + if (!sc->count) return 0; + + for (i = 0; i < ARRAYLENGTH(scs); i++) + if (type != scs[i] && sc->data[scs[i]]) + status_change_end(bl, scs[i], INVALID_TIMER); + + return 0; +} + +bool skill_check_cloaking(struct block_list *bl, struct status_change_entry *sce) +{ + static int dx[] = { 0, 1, 0, -1, -1, 1, 1, -1}; + static int dy[] = {-1, 0, 1, 0, -1, -1, 1, 1}; + bool wall = true; + + if( (bl->type == BL_PC && battle_config.pc_cloak_check_type&1) + || (bl->type != BL_PC && battle_config.monster_cloak_check_type&1) ) + { //Check for walls. + int i; + ARR_FIND( 0, 8, i, map_getcell(bl->m, bl->x+dx[i], bl->y+dy[i], CELL_CHKNOPASS) != 0 ); + if( i == 8 ) + wall = false; + } + + if( sce ) + { + if( !wall ) + { + if( sce->val1 < 3 ) //End cloaking. + status_change_end(bl, SC_CLOAKING, INVALID_TIMER); + else + if( sce->val4&1 ) + { //Remove wall bonus + sce->val4&=~1; + status_calc_bl(bl,SCB_SPEED); + } + } + else + { + if( !(sce->val4&1) ) + { //Add wall speed bonus + sce->val4|=1; + status_calc_bl(bl,SCB_SPEED); + } + } + } + + return wall; +} +bool skill_check_camouflage(struct block_list *bl, struct status_change_entry *sce) +{ + static int dx[] = { 0, 1, 0, -1, -1, 1, 1, -1}; + static int dy[] = {-1, 0, 1, 0, -1, -1, 1, 1}; + bool wall = true; + + if( bl->type == BL_PC ) + { //Check for walls. + int i; + ARR_FIND( 0, 8, i, map_getcell(bl->m, bl->x+dx[i], bl->y+dy[i], CELL_CHKNOPASS) != 0 ); + if( i == 8 ) + wall = false; + } + + if( sce ) + { + if( !wall ) + { + if( sce->val1 < 3 ) //End camouflage. + status_change_end(bl, SC_CAMOUFLAGE, INVALID_TIMER); + else + if( sce->val3&1 ) + { //Remove wall bonus + sce->val3&=~1; + status_calc_bl(bl,SCB_SPEED); + } + } + } + + return wall; +} + +/*========================================== + * + *------------------------------------------*/ +struct skill_unit *skill_initunit (struct skill_unit_group *group, int idx, int x, int y, int val1, int val2) +{ + struct skill_unit *unit; + + nullpo_retr(NULL, group); + nullpo_retr(NULL, group->unit); // crash-protection against poor coding + nullpo_retr(NULL, unit=&group->unit[idx]); + + if(!unit->alive) + group->alive_count++; + + unit->bl.id=map_get_new_object_id(); + unit->bl.type=BL_SKILL; + unit->bl.m=group->map; + unit->bl.x=x; + unit->bl.y=y; + unit->group=group; + unit->alive=1; + unit->val1=val1; + unit->val2=val2; + + idb_put(skillunit_db, unit->bl.id, unit); + map_addiddb(&unit->bl); + map_addblock(&unit->bl); + + // perform oninit actions + switch (group->skill_id) { + case WZ_ICEWALL: + map_setgatcell(unit->bl.m,unit->bl.x,unit->bl.y,5); + clif_changemapcell(0,unit->bl.m,unit->bl.x,unit->bl.y,5,AREA); + skill_unitsetmapcell(unit,WZ_ICEWALL,group->skill_lv,CELL_ICEWALL,true); + map[unit->bl.m].icewall_num++; + break; + case SA_LANDPROTECTOR: + skill_unitsetmapcell(unit,SA_LANDPROTECTOR,group->skill_lv,CELL_LANDPROTECTOR,true); + break; + case HP_BASILICA: + skill_unitsetmapcell(unit,HP_BASILICA,group->skill_lv,CELL_BASILICA,true); + break; + case SC_MAELSTROM: + skill_unitsetmapcell(unit,SC_MAELSTROM,group->skill_lv,CELL_MAELSTROM,true); + break; + default: + if (group->state.song_dance&0x1) //Check for dissonance. + skill_dance_overlap(unit, 1); + break; + } + + clif_skill_setunit(unit); + + return unit; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_delunit (struct skill_unit* unit) +{ + struct skill_unit_group *group; + + nullpo_ret(unit); + if( !unit->alive ) + return 0; + unit->alive=0; + + nullpo_ret(group=unit->group); + + if( group->state.song_dance&0x1 ) //Cancel dissonance effect. + skill_dance_overlap(unit, 0); + + // invoke onout event + if( !unit->range ) + map_foreachincell(skill_unit_effect,unit->bl.m,unit->bl.x,unit->bl.y,group->bl_flag,&unit->bl,gettick(),4); + + // perform ondelete actions + switch (group->skill_id) { + case HT_ANKLESNARE: { + struct block_list* target = map_id2bl(group->val2); + if( target ) + status_change_end(target, SC_ANKLE, INVALID_TIMER); + } + break; + case WZ_ICEWALL: + map_setgatcell(unit->bl.m,unit->bl.x,unit->bl.y,unit->val2); + clif_changemapcell(0,unit->bl.m,unit->bl.x,unit->bl.y,unit->val2,ALL_SAMEMAP); // hack to avoid clientside cell bug + skill_unitsetmapcell(unit,WZ_ICEWALL,group->skill_lv,CELL_ICEWALL,false); + map[unit->bl.m].icewall_num--; + break; + case SA_LANDPROTECTOR: + skill_unitsetmapcell(unit,SA_LANDPROTECTOR,group->skill_lv,CELL_LANDPROTECTOR,false); + break; + case HP_BASILICA: + skill_unitsetmapcell(unit,HP_BASILICA,group->skill_lv,CELL_BASILICA,false); + break; + case RA_ELECTRICSHOCKER: { + struct block_list* target = map_id2bl(group->val2); + if( target ) + status_change_end(target, SC_ELECTRICSHOCKER, INVALID_TIMER); + } + break; + case SC_MAELSTROM: + skill_unitsetmapcell(unit,SC_MAELSTROM,group->skill_lv,CELL_MAELSTROM,false); + break; + case SC_MANHOLE: // Note : Removing the unit don't remove the status (official info) + if( group->val2 ) { // Someone Traped + struct status_change *tsc = status_get_sc( map_id2bl(group->val2)); + if( tsc && tsc->data[SC__MANHOLE] ) + tsc->data[SC__MANHOLE]->val4 = 0; // Remove the Unit ID + } + break; + } + + clif_skill_delunit(unit); + + unit->group=NULL; + map_delblock(&unit->bl); // don't free yet + map_deliddb(&unit->bl); + idb_remove(skillunit_db, unit->bl.id); + if(--group->alive_count==0) + skill_delunitgroup(group); + + return 0; +} +/*========================================== + * + *------------------------------------------*/ +static DBMap* group_db = NULL;// int group_id -> struct skill_unit_group* + +/// Returns the target skill_unit_group or NULL if not found. +struct skill_unit_group* skill_id2group(int group_id) +{ + return (struct skill_unit_group*)idb_get(group_db, group_id); +} + + +static int skill_unit_group_newid = MAX_SKILL_DB; + +/// Returns a new group_id that isn't being used in group_db. +/// Fatal error if nothing is available. +static int skill_get_new_group_id(void) +{ + if( skill_unit_group_newid >= MAX_SKILL_DB && skill_id2group(skill_unit_group_newid) == NULL ) + return skill_unit_group_newid++;// available + {// find next id + int base_id = skill_unit_group_newid; + while( base_id != ++skill_unit_group_newid ) + { + if( skill_unit_group_newid < MAX_SKILL_DB ) + skill_unit_group_newid = MAX_SKILL_DB; + if( skill_id2group(skill_unit_group_newid) == NULL ) + return skill_unit_group_newid++;// available + } + // full loop, nothing available + ShowFatalError("skill_get_new_group_id: All ids are taken. Exiting..."); + exit(1); + } +} + +struct skill_unit_group* skill_initunitgroup (struct block_list* src, int count, uint16 skill_id, uint16 skill_lv, int unit_id, int limit, int interval) +{ + struct unit_data* ud = unit_bl2ud( src ); + struct skill_unit_group* group; + int i; + + if(!(skill_id && skill_lv)) return 0; + + nullpo_retr(NULL, src); + nullpo_retr(NULL, ud); + + // find a free spot to store the new unit group + ARR_FIND( 0, MAX_SKILLUNITGROUP, i, ud->skillunit[i] == NULL ); + if(i == MAX_SKILLUNITGROUP) + { + // array is full, make room by discarding oldest group + int j=0; + unsigned maxdiff=0,x,tick=gettick(); + for(i=0;i<MAX_SKILLUNITGROUP && ud->skillunit[i];i++) + if((x=DIFF_TICK(tick,ud->skillunit[i]->tick))>maxdiff){ + maxdiff=x; + j=i; + } + skill_delunitgroup(ud->skillunit[j]); + //Since elements must have shifted, we use the last slot. + i = MAX_SKILLUNITGROUP-1; + } + + group = ers_alloc(skill_unit_ers, struct skill_unit_group); + group->src_id = src->id; + group->party_id = status_get_party_id(src); + group->guild_id = status_get_guild_id(src); + group->bg_id = bg_team_get_id(src); + group->group_id = skill_get_new_group_id(); + group->unit = (struct skill_unit *)aCalloc(count,sizeof(struct skill_unit)); + group->unit_count = count; + group->alive_count = 0; + group->val1 = 0; + group->val2 = 0; + group->val3 = 0; + group->skill_id = skill_id; + group->skill_lv = skill_lv; + group->unit_id = unit_id; + group->map = src->m; + group->limit = limit; + group->interval = interval; + group->tick = gettick(); + group->valstr = NULL; + + ud->skillunit[i] = group; + + if (skill_id == PR_SANCTUARY) //Sanctuary starts healing +1500ms after casted. [Skotlex] + group->tick += 1500; + + idb_put(group_db, group->group_id, group); + return group; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_delunitgroup_(struct skill_unit_group *group, const char* file, int line, const char* func) +{ + struct block_list* src; + struct unit_data *ud; + int i,j; + + if( group == NULL ) + { + ShowDebug("skill_delunitgroup: group is NULL (source=%s:%d, %s)! Please report this! (#3504)\n", file, line, func); + return 0; + } + + src=map_id2bl(group->src_id); + ud = unit_bl2ud(src); + if(!src || !ud) { + ShowError("skill_delunitgroup: Group's source not found! (src_id: %d skill_id: %d)\n", group->src_id, group->skill_id); + return 0; + } + + if( !status_isdead(src) && ((TBL_PC*)src)->state.warping && !((TBL_PC*)src)->state.changemap ) { + switch( group->skill_id ) { + 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: + skill_usave_add(((TBL_PC*)src), group->skill_id, group->skill_lv); + break; + } + } + + if (skill_get_unit_flag(group->skill_id)&(UF_DANCE|UF_SONG|UF_ENSEMBLE)) + { + struct status_change* sc = status_get_sc(src); + if (sc && sc->data[SC_DANCING]) + { + sc->data[SC_DANCING]->val2 = 0 ; //This prevents status_change_end attempting to redelete the group. [Skotlex] + status_change_end(src, SC_DANCING, INVALID_TIMER); + } + } + + // end Gospel's status change on 'src' + // (needs to be done when the group is deleted by other means than skill deactivation) + if (group->unit_id == UNT_GOSPEL) { + struct status_change *sc = status_get_sc(src); + if(sc && sc->data[SC_GOSPEL]) { + sc->data[SC_GOSPEL]->val3 = 0; //Remove reference to this group. [Skotlex] + status_change_end(src, SC_GOSPEL, INVALID_TIMER); + } + } + + switch( group->skill_id ) { + case SG_SUN_WARM: + case SG_MOON_WARM: + case SG_STAR_WARM: + { + struct status_change *sc = NULL; + if( (sc = status_get_sc(src)) != NULL && sc->data[SC_WARM] ) { + sc->data[SC_WARM]->val4 = 0; + status_change_end(src, SC_WARM, INVALID_TIMER); + } + } + break; + case NC_NEUTRALBARRIER: + { + struct status_change *sc = NULL; + if( (sc = status_get_sc(src)) != NULL && sc->data[SC_NEUTRALBARRIER_MASTER] ) { + sc->data[SC_NEUTRALBARRIER_MASTER]->val2 = 0; + status_change_end(src,SC_NEUTRALBARRIER_MASTER,INVALID_TIMER); + } + } + break; + case NC_STEALTHFIELD: + { + struct status_change *sc = NULL; + if( (sc = status_get_sc(src)) != NULL && sc->data[SC_STEALTHFIELD_MASTER] ) { + sc->data[SC_STEALTHFIELD_MASTER]->val2 = 0; + status_change_end(src,SC_STEALTHFIELD_MASTER,INVALID_TIMER); + } + } + break; + case LG_BANDING: + { + struct status_change *sc = NULL; + if( (sc = status_get_sc(src)) && sc->data[SC_BANDING] ) { + sc->data[SC_BANDING]->val4 = 0; + status_change_end(src,SC_BANDING,INVALID_TIMER); + } + } + break; + } + + if (src->type==BL_PC && group->state.ammo_consume) + battle_consume_ammo((TBL_PC*)src, group->skill_id, group->skill_lv); + + group->alive_count=0; + + // remove all unit cells + if(group->unit != NULL) + for( i = 0; i < group->unit_count; i++ ) + skill_delunit(&group->unit[i]); + + // clear Talkie-box string + if( group->valstr != NULL ) + { + aFree(group->valstr); + group->valstr = NULL; + } + + idb_remove(group_db, group->group_id); + map_freeblock(&group->unit->bl); // schedules deallocation of whole array (HACK) + group->unit=NULL; + group->group_id=0; + group->unit_count=0; + + // locate this group, swap with the last entry and delete it + ARR_FIND( 0, MAX_SKILLUNITGROUP, i, ud->skillunit[i] == group ); + ARR_FIND( i, MAX_SKILLUNITGROUP, j, ud->skillunit[j] == NULL ); j--; + if( i < MAX_SKILLUNITGROUP ) + { + ud->skillunit[i] = ud->skillunit[j]; + ud->skillunit[j] = NULL; + ers_free(skill_unit_ers, group); + } + else + ShowError("skill_delunitgroup: Group not found! (src_id: %d skill_id: %d)\n", group->src_id, group->skill_id); + + return 1; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_clear_unitgroup (struct block_list *src) +{ + struct unit_data *ud = unit_bl2ud(src); + + nullpo_ret(ud); + + while (ud->skillunit[0]) + skill_delunitgroup(ud->skillunit[0]); + + return 1; +} + +/*========================================== + * + *------------------------------------------*/ +struct skill_unit_group_tickset *skill_unitgrouptickset_search (struct block_list *bl, struct skill_unit_group *group, int tick) +{ + int i,j=-1,k,s,id; + struct unit_data *ud; + struct skill_unit_group_tickset *set; + + nullpo_ret(bl); + if (group->interval==-1) + return NULL; + + ud = unit_bl2ud(bl); + if (!ud) return NULL; + + set = ud->skillunittick; + + if (skill_get_unit_flag(group->skill_id)&UF_NOOVERLAP) + id = s = group->skill_id; + else + id = s = group->group_id; + + for (i=0; i<MAX_SKILLUNITGROUPTICKSET; i++) { + k = (i+s) % MAX_SKILLUNITGROUPTICKSET; + if (set[k].id == id) + return &set[k]; + else if (j==-1 && (DIFF_TICK(tick,set[k].tick)>0 || set[k].id==0)) + j=k; + } + + if (j == -1) { + ShowWarning ("skill_unitgrouptickset_search: tickset is full\n"); + j = id % MAX_SKILLUNITGROUPTICKSET; + } + + set[j].id = id; + set[j].tick = tick; + return &set[j]; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_unit_timer_sub_onplace (struct block_list* bl, va_list ap) +{ + struct skill_unit* unit = va_arg(ap,struct skill_unit *); + struct skill_unit_group* group = unit->group; + unsigned int tick = va_arg(ap,unsigned int); + + if( !unit->alive || bl->prev == NULL ) + return 0; + + nullpo_ret(group); + + if( !(skill_get_inf2(group->skill_id)&(INF2_SONG_DANCE|INF2_TRAP|INF2_NOLP)) && map_getcell(bl->m, bl->x, bl->y, CELL_CHKLANDPROTECTOR) ) + return 0; //AoE skills are ineffective. [Skotlex] + + if( battle_check_target(&unit->bl,bl,group->target_flag) <= 0 ) + return 0; + + skill_unit_onplace_timer(unit,bl,tick); + + return 1; +} + +/** + * @see DBApply + */ +static int skill_unit_timer_sub(DBKey key, DBData *data, va_list ap) +{ + struct skill_unit* unit = db_data2ptr(data); + struct skill_unit_group* group = unit->group; + unsigned int tick = va_arg(ap,unsigned int); + bool dissonance; + struct block_list* bl = &unit->bl; + + if( !unit->alive ) + return 0; + + nullpo_ret(group); + + // check for expiration + if( !group->state.guildaura && (DIFF_TICK(tick,group->tick) >= group->limit || DIFF_TICK(tick,group->tick) >= unit->limit) ) + {// skill unit expired (inlined from skill_unit_onlimit()) + switch( group->unit_id ) + { + case UNT_BLASTMINE: +#ifdef RENEWAL + case UNT_CLAYMORETRAP: +#endif + case UNT_GROUNDDRIFT_WIND: + case UNT_GROUNDDRIFT_DARK: + case UNT_GROUNDDRIFT_POISON: + case UNT_GROUNDDRIFT_WATER: + case UNT_GROUNDDRIFT_FIRE: + group->unit_id = UNT_USED_TRAPS; + //clif_changetraplook(bl, UNT_FIREPILLAR_ACTIVE); + group->limit=DIFF_TICK(tick+1500,group->tick); + unit->limit=DIFF_TICK(tick+1500,group->tick); + break; + + case UNT_ANKLESNARE: + case UNT_ELECTRICSHOCKER: + if( group->val2 > 0 ) { + // Used Trap don't returns back to item + skill_delunit(unit); + break; + } + case UNT_SKIDTRAP: + case UNT_LANDMINE: + case UNT_SHOCKWAVE: + case UNT_SANDMAN: + case UNT_FLASHER: + case UNT_FREEZINGTRAP: +#ifndef RENEWAL + case UNT_CLAYMORETRAP: +#endif + case UNT_TALKIEBOX: + case UNT_CLUSTERBOMB: + case UNT_MAGENTATRAP: + case UNT_COBALTTRAP: + case UNT_MAIZETRAP: + case UNT_VERDURETRAP: + case UNT_FIRINGTRAP: + case UNT_ICEBOUNDTRAP: + + { + struct block_list* src; + if( unit->val1 > 0 && (src = map_id2bl(group->src_id)) != NULL && src->type == BL_PC ) + { // revert unit back into a trap + struct item item_tmp; + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid = group->item_id?group->item_id:ITEMID_TRAP; + item_tmp.identify = 1; + map_addflooritem(&item_tmp,1,bl->m,bl->x,bl->y,0,0,0,0); + } + skill_delunit(unit); + } + break; + + case UNT_WARP_ACTIVE: + // warp portal opens (morph to a UNT_WARP_WAITING cell) + group->unit_id = skill_get_unit_id(group->skill_id, 1); // UNT_WARP_WAITING + clif_changelook(&unit->bl, LOOK_BASE, group->unit_id); + // restart timers + group->limit = skill_get_time(group->skill_id,group->skill_lv); + unit->limit = skill_get_time(group->skill_id,group->skill_lv); + // apply effect to all units standing on it + map_foreachincell(skill_unit_effect,unit->bl.m,unit->bl.x,unit->bl.y,group->bl_flag,&unit->bl,gettick(),1); + break; + + case UNT_CALLFAMILY: + { + struct map_session_data *sd = NULL; + if(group->val1) { + sd = map_charid2sd(group->val1); + group->val1 = 0; + if (sd && !map[sd->bl.m].flag.nowarp) + pc_setpos(sd,map_id2index(unit->bl.m),unit->bl.x,unit->bl.y,CLR_TELEPORT); + } + if(group->val2) { + sd = map_charid2sd(group->val2); + group->val2 = 0; + if (sd && !map[sd->bl.m].flag.nowarp) + pc_setpos(sd,map_id2index(unit->bl.m),unit->bl.x,unit->bl.y,CLR_TELEPORT); + } + skill_delunit(unit); + } + break; + + case UNT_REVERBERATION: + if( unit->val1 <= 0 ) { // If it was deactivated. + skill_delunit(unit); + break; + } + clif_changetraplook(bl,UNT_USED_TRAPS); + map_foreachinrange(skill_trap_splash, bl, skill_get_splash(group->skill_id, group->skill_lv), group->bl_flag, bl, tick); + group->limit = DIFF_TICK(tick,group->tick)+1000; + unit->limit = DIFF_TICK(tick,group->tick)+1000; + group->unit_id = UNT_USED_TRAPS; + break; + + case UNT_FEINTBOMB: { + struct block_list *src = map_id2bl(group->src_id); + if( src ) + map_foreachinrange(skill_area_sub, &group->unit->bl, unit->range, splash_target(src), src, SC_FEINTBOMB, group->skill_lv, tick, BCT_ENEMY|SD_ANIMATION|1, skill_castend_damage_id); + skill_delunit(unit); + break; + } + + case UNT_BANDING: + { + struct block_list *src = map_id2bl(group->src_id); + struct status_change *sc; + if( !src || (sc = status_get_sc(src)) == NULL || !sc->data[SC_BANDING] ) + { + skill_delunit(unit); + break; + } + // This unit isn't removed while SC_BANDING is active. + group->limit = DIFF_TICK(tick+group->interval,group->tick); + unit->limit = DIFF_TICK(tick+group->interval,group->tick); + } + break; + + default: + skill_delunit(unit); + } + } + else + {// skill unit is still active + switch( group->unit_id ) + { + case UNT_ICEWALL: + // icewall loses 50 hp every second + unit->val1 -= SKILLUNITTIMER_INTERVAL/20; // trap's hp + if( unit->val1 <= 0 && unit->limit + group->tick > tick + 700 ) + unit->limit = DIFF_TICK(tick+700,group->tick); + break; + case UNT_BLASTMINE: + case UNT_SKIDTRAP: + case UNT_LANDMINE: + case UNT_SHOCKWAVE: + case UNT_SANDMAN: + case UNT_FLASHER: + case UNT_CLAYMORETRAP: + case UNT_FREEZINGTRAP: + case UNT_TALKIEBOX: + case UNT_ANKLESNARE: + if( unit->val1 <= 0 ) { + if( group->unit_id == UNT_ANKLESNARE && group->val2 > 0 ) + skill_delunit(unit); + else { + clif_changetraplook(bl, group->unit_id==UNT_LANDMINE?UNT_FIREPILLAR_ACTIVE:UNT_USED_TRAPS); + group->limit = DIFF_TICK(tick, group->tick) + 1500; + group->unit_id = UNT_USED_TRAPS; + } + } + break; + case UNT_REVERBERATION: + if( unit->val1 <= 0 ){ + clif_changetraplook(bl,UNT_USED_TRAPS); + map_foreachinrange(skill_trap_splash, bl, skill_get_splash(group->skill_id, group->skill_lv), group->bl_flag, bl, tick); + group->limit = DIFF_TICK(tick,group->tick)+1000; + unit->limit = DIFF_TICK(tick,group->tick)+1000; + group->unit_id = UNT_USED_TRAPS; + } + break; + case UNT_WALLOFTHORN: + if( unit->val1 <= 0 ) { + group->unit_id = UNT_USED_TRAPS; + group->limit = DIFF_TICK(tick, group->tick) + 1500; + } + break; + } + } + + //Don't continue if unit or even group is expired and has been deleted. + if( !group || !unit->alive ) + return 0; + + dissonance = skill_dance_switch(unit, 0); + + if( unit->range >= 0 && group->interval != -1 ) + { + if( battle_config.skill_wall_check ) + map_foreachinshootrange(skill_unit_timer_sub_onplace, bl, unit->range, group->bl_flag, bl,tick); + else + map_foreachinrange(skill_unit_timer_sub_onplace, bl, unit->range, group->bl_flag, bl,tick); + + if(unit->range == -1) //Unit disabled, but it should not be deleted yet. + group->unit_id = UNT_USED_TRAPS; + + if( group->unit_id == UNT_TATAMIGAESHI ) + { + unit->range = -1; //Disable processed cell. + if (--group->val1 <= 0) // number of live cells + { //All tiles were processed, disable skill. + group->target_flag=BCT_NOONE; + group->bl_flag= BL_NUL; + } + } + } + + if( dissonance ) skill_dance_switch(unit, 1); + + return 0; +} +/*========================================== + * Executes on all skill units every SKILLUNITTIMER_INTERVAL miliseconds. + *------------------------------------------*/ +int skill_unit_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + map_freeblock_lock(); + + skillunit_db->foreach(skillunit_db, skill_unit_timer_sub, tick); + + map_freeblock_unlock(); + + return 0; +} + +static int skill_unit_temp[20]; // temporary storage for tracking skill unit skill ids as players move in/out of them +/*========================================== + * + *------------------------------------------*/ +int skill_unit_move_sub (struct block_list* bl, va_list ap) +{ + struct skill_unit* unit = (struct skill_unit *)bl; + struct skill_unit_group* group = unit->group; + + struct block_list* target = va_arg(ap,struct block_list*); + unsigned int tick = va_arg(ap,unsigned int); + int flag = va_arg(ap,int); + + bool dissonance; + uint16 skill_id; + int i; + + nullpo_ret(group); + + if( !unit->alive || target->prev == NULL ) + return 0; + + if( flag&1 && ( unit->group->skill_id == PF_SPIDERWEB || unit->group->skill_id == GN_THORNS_TRAP ) ) + return 0; // Fiberlock is never supposed to trigger on skill_unit_move. [Inkfish] + + dissonance = skill_dance_switch(unit, 0); + + //Necessary in case the group is deleted after calling on_place/on_out [Skotlex] + skill_id = unit->group->skill_id; + + if( unit->group->interval != -1 && !(skill_get_unit_flag(skill_id)&UF_DUALMODE) && skill_id != BD_LULLABY ) //Lullaby is the exception, bugreport:411 + { //Non-dualmode unit skills with a timer don't trigger when walking, so just return + if( dissonance ) skill_dance_switch(unit, 1); + return 0; + } + + //Target-type check. + if( !(group->bl_flag&target->type && battle_check_target(&unit->bl,target,group->target_flag) > 0) ) + { + if( group->src_id == target->id && group->state.song_dance&0x2 ) + { //Ensemble check to see if they went out/in of the area [Skotlex] + if( flag&1 ) + { + if( flag&2 ) + { //Clear this skill id. + ARR_FIND( 0, ARRAYLENGTH(skill_unit_temp), i, skill_unit_temp[i] == skill_id ); + if( i < ARRAYLENGTH(skill_unit_temp) ) + skill_unit_temp[i] = 0; + } + } + else + { + if( flag&2 ) + { //Store this skill id. + ARR_FIND( 0, ARRAYLENGTH(skill_unit_temp), i, skill_unit_temp[i] == 0 ); + if( i < ARRAYLENGTH(skill_unit_temp) ) + skill_unit_temp[i] = skill_id; + else + ShowError("skill_unit_move_sub: Reached limit of unit objects per cell!\n"); + } + + } + + if( flag&4 ) + skill_unit_onleft(skill_id,target,tick); + } + + if( dissonance ) skill_dance_switch(unit, 1); + + return 0; + } + else + { + if( flag&1 ) + { + int result = skill_unit_onplace(unit,target,tick); + if( flag&2 && result ) + { //Clear skill ids we have stored in onout. + ARR_FIND( 0, ARRAYLENGTH(skill_unit_temp), i, skill_unit_temp[i] == result ); + if( i < ARRAYLENGTH(skill_unit_temp) ) + skill_unit_temp[i] = 0; + } + } + else + { + int result = skill_unit_onout(unit,target,tick); + if( flag&2 && result ) + { //Store this unit id. + ARR_FIND( 0, ARRAYLENGTH(skill_unit_temp), i, skill_unit_temp[i] == 0 ); + if( i < ARRAYLENGTH(skill_unit_temp) ) + skill_unit_temp[i] = skill_id; + else + ShowError("skill_unit_move_sub: Reached limit of unit objects per cell!\n"); + } + } + + //TODO: Normally, this is dangerous since the unit and group could be freed + //inside the onout/onplace functions. Currently it is safe because we know song/dance + //cells do not get deleted within them. [Skotlex] + if( dissonance ) skill_dance_switch(unit, 1); + + if( flag&4 ) + skill_unit_onleft(skill_id,target,tick); + + return 1; + } +} + +/*========================================== + * Invoked when a char has moved and unit cells must be invoked (onplace, onout, onleft) + * Flag values: + * flag&1: invoke skill_unit_onplace (otherwise invoke skill_unit_onout) + * flag&2: this function is being invoked twice as a bl moves, store in memory the affected + * units to figure out when they have left a group. + * flag&4: Force a onleft event (triggered when the bl is killed, for example) + *------------------------------------------*/ +int skill_unit_move (struct block_list *bl, unsigned int tick, int flag) +{ + nullpo_ret(bl); + + if( bl->prev == NULL ) + return 0; + + if( flag&2 && !(flag&1) ) + { //Onout, clear data + memset(skill_unit_temp, 0, sizeof(skill_unit_temp)); + } + + map_foreachincell(skill_unit_move_sub,bl->m,bl->x,bl->y,BL_SKILL,bl,tick,flag); + + if( flag&2 && flag&1 ) + { //Onplace, check any skill units you have left. + int i; + for( i = 0; i < ARRAYLENGTH(skill_unit_temp); i++ ) + if( skill_unit_temp[i] ) + skill_unit_onleft(skill_unit_temp[i], bl, tick); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_unit_move_unit_group (struct skill_unit_group *group, int16 m, int16 dx, int16 dy) +{ + int i,j; + unsigned int tick = gettick(); + int *m_flag; + struct skill_unit *unit1; + struct skill_unit *unit2; + + if (group == NULL) + return 0; + if (group->unit_count<=0) + return 0; + if (group->unit==NULL) + return 0; + + if (skill_get_unit_flag(group->skill_id)&UF_ENSEMBLE) + return 0; //Ensembles may not be moved around. + + if( group->unit_id == UNT_ICEWALL || group->unit_id == UNT_WALLOFTHORN ) + return 0; //Icewalls and Wall of Thorns don't get knocked back + + m_flag = (int *) aCalloc(group->unit_count, sizeof(int)); + // m_flag + // 0: Neither of the following (skill_unit_onplace & skill_unit_onout are needed) + // 1: Unit will move to a slot that had another unit of the same group (skill_unit_onplace not needed) + // 2: Another unit from same group will end up positioned on this unit (skill_unit_onout not needed) + // 3: Both 1+2. + for(i=0;i<group->unit_count;i++){ + unit1=&group->unit[i]; + if (!unit1->alive || unit1->bl.m!=m) + continue; + for(j=0;j<group->unit_count;j++){ + unit2=&group->unit[j]; + if (!unit2->alive) + continue; + if (unit1->bl.x+dx==unit2->bl.x && unit1->bl.y+dy==unit2->bl.y){ + m_flag[i] |= 0x1; + } + if (unit1->bl.x-dx==unit2->bl.x && unit1->bl.y-dy==unit2->bl.y){ + m_flag[i] |= 0x2; + } + } + } + j = 0; + for (i=0;i<group->unit_count;i++) { + unit1=&group->unit[i]; + if (!unit1->alive) + continue; + if (!(m_flag[i]&0x2)) { + if (group->state.song_dance&0x1) //Cancel dissonance effect. + skill_dance_overlap(unit1, 0); + map_foreachincell(skill_unit_effect,unit1->bl.m,unit1->bl.x,unit1->bl.y,group->bl_flag,&unit1->bl,tick,4); + } + //Move Cell using "smart" criteria (avoid useless moving around) + switch(m_flag[i]) + { + case 0: + //Cell moves independently, safely move it. + map_moveblock(&unit1->bl, unit1->bl.x+dx, unit1->bl.y+dy, tick); + break; + case 1: + //Cell moves unto another cell, look for a replacement cell that won't collide + //and has no cell moving into it (flag == 2) + for(;j<group->unit_count;j++) + { + if(m_flag[j]!=2 || !group->unit[j].alive) + continue; + //Move to where this cell would had moved. + unit2 = &group->unit[j]; + map_moveblock(&unit1->bl, unit2->bl.x+dx, unit2->bl.y+dy, tick); + j++; //Skip this cell as we have used it. + break; + } + break; + case 2: + case 3: + break; //Don't move the cell as a cell will end on this tile anyway. + } + if (!(m_flag[i]&0x2)) { //We only moved the cell in 0-1 + if (group->state.song_dance&0x1) //Check for dissonance effect. + skill_dance_overlap(unit1, 1); + clif_skill_setunit(unit1); + map_foreachincell(skill_unit_effect,unit1->bl.m,unit1->bl.x,unit1->bl.y,group->bl_flag,&unit1->bl,tick,1); + } + } + aFree(m_flag); + return 0; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_can_produce_mix (struct map_session_data *sd, int nameid, int trigger, int qty) +{ + int i,j; + + nullpo_ret(sd); + + if(nameid<=0) + return 0; + + for(i=0;i<MAX_SKILL_PRODUCE_DB;i++){ + if(skill_produce_db[i].nameid == nameid ){ + if((j=skill_produce_db[i].req_skill)>0 && + pc_checkskill(sd,j) < skill_produce_db[i].req_skill_lv) + continue; // must iterate again to check other skills that produce it. [malufett] + if( j > 0 && sd->menuskill_id > 0 && sd->menuskill_id != j ) + continue; // special case + break; + } + } + + if( i >= MAX_SKILL_PRODUCE_DB ) + return 0; + + if( pc_checkadditem(sd, nameid, qty) == ADDITEM_OVERAMOUNT ) + {// cannot carry the produced stuff + return 0; + } + + if(trigger>=0){ + if(trigger>20) { // Non-weapon, non-food item (itemlv must match) + if(skill_produce_db[i].itemlv!=trigger) + return 0; + } else if(trigger>10) { // Food (any item level between 10 and 20 will do) + if(skill_produce_db[i].itemlv<=10 || skill_produce_db[i].itemlv>20) + return 0; + } else { // Weapon (itemlv must be higher or equal) + if(skill_produce_db[i].itemlv>trigger) + return 0; + } + } + + for(j=0;j<MAX_PRODUCE_RESOURCE;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<qty*skill_produce_db[i].mat_amount[j]) + return 0; + } + } + return i+1; +} + +/*========================================== + * + *------------------------------------------*/ +int skill_produce_mix (struct map_session_data *sd, uint16 skill_id, int nameid, int slot1, int slot2, int slot3, int qty) +{ + int slot[3]; + int i,sc,ele,idx,equip,wlv,make_per = 0,flag = 0,skill_lv = 0; + int num = -1; // exclude the recipe + struct status_data *status; + struct item_data* data; + + nullpo_ret(sd); + status = status_get_status_data(&sd->bl); + + if( sd->skill_id_old == skill_id ) + skill_lv = sd->skill_lv_old; + + if( !(idx=skill_can_produce_mix(sd,nameid,-1, qty)) ) + return 0; + idx--; + + if (qty < 1) + qty = 1; + + if (!skill_id) //A skill can be specified for some override cases. + skill_id = skill_produce_db[idx].req_skill; + + if( skill_id == GC_RESEARCHNEWPOISON ) + skill_id = GC_CREATENEWPOISON; + + slot[0]=slot1; + slot[1]=slot2; + slot[2]=slot3; + + for(i=0,sc=0,ele=0;i<3;i++){ //Note that qty should always be one if you are using these! + int j; + if( slot[i]<=0 ) + continue; + j = pc_search_inventory(sd,slot[i]); + if(j < 0) + continue; + if(slot[i]==1000){ /* Star Crumb */ + pc_delitem(sd,j,1,1,0,LOG_TYPE_PRODUCE); + sc++; + } + if(slot[i]>=994 && slot[i]<=997 && ele==0){ /* Flame Heart . . . Great Nature */ + static const int ele_table[4]={3,1,4,2}; + pc_delitem(sd,j,1,1,0,LOG_TYPE_PRODUCE); + ele=ele_table[slot[i]-994]; + } + } + + if( skill_id == RK_RUNEMASTERY ) { + int temp_qty, skill_lv = pc_checkskill(sd,skill_id); + data = itemdb_search(nameid); + + if( skill_lv == 10 ) temp_qty = 1 + rnd()%3; + else if( skill_lv > 5 ) temp_qty = 1 + rnd()%2; + else temp_qty = 1; + + if (data->stack.inventory) { + for( i = 0; i < MAX_INVENTORY; i++ ) { + if( sd->status.inventory[i].nameid == nameid ) { + if( sd->status.inventory[i].amount >= data->stack.amount ) { + clif_msgtable(sd->fd,0x61b); + return 0; + } else { + /** + * the amount fits, say we got temp_qty 4 and 19 runes, we trim temp_qty to 1. + **/ + if( temp_qty + sd->status.inventory[i].amount >= data->stack.amount ) + temp_qty = data->stack.amount - sd->status.inventory[i].amount; + } + break; + } + } + } + qty = temp_qty; + } + + for(i=0;i<MAX_PRODUCE_RESOURCE;i++){ + int j,id,x; + if( (id=skill_produce_db[idx].mat_id[i]) <= 0 ) + continue; + num++; + x=( skill_id == RK_RUNEMASTERY ? 1 : qty)*skill_produce_db[idx].mat_amount[i]; + do{ + 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,0,LOG_TYPE_PRODUCE); + } else + ShowError("skill_produce_mix: material item error\n"); + + x-=y; + }while( j>=0 && x>0 ); + } + + if( (equip = (itemdb_isequip(nameid) && skill_id != GN_CHANGEMATERIAL && skill_id != GN_MAKEBOMB )) ) + wlv = itemdb_wlv(nameid); + if(!equip) { + switch(skill_id){ + case BS_IRON: + case BS_STEEL: + case BS_ENCHANTEDSTONE: + // Ores & Metals Refining - skill bonuses are straight from kRO website [DracoRPG] + i = pc_checkskill(sd,skill_id); + make_per = sd->status.job_level*20 + status->dex*10 + status->luk*10; //Base chance + switch(nameid){ + case 998: // Iron + make_per += 4000+i*500; // Temper Iron bonus: +26/+32/+38/+44/+50 + break; + case 999: // Steel + make_per += 3000+i*500; // Temper Steel bonus: +35/+40/+45/+50/+55 + break; + case 1000: //Star Crumb + make_per = 100000; // Star Crumbs are 100% success crafting rate? (made 1000% so it succeeds even after penalties) [Skotlex] + break; + default: // Enchanted Stones + make_per += 1000+i*500; // Enchantedstone Craft bonus: +15/+20/+25/+30/+35 + break; + } + break; + case ASC_CDP: + make_per = (2000 + 40*status->dex + 20*status->luk); + break; + case AL_HOLYWATER: + /** + * Arch Bishop + **/ + case AB_ANCILLA: + make_per = 100000; //100% success + break; + case AM_PHARMACY: // Potion Preparation - reviewed with the help of various Ragnainfo sources [DracoRPG] + case AM_TWILIGHT1: + case AM_TWILIGHT2: + case AM_TWILIGHT3: + make_per = pc_checkskill(sd,AM_LEARNINGPOTION)*50 + + pc_checkskill(sd,AM_PHARMACY)*300 + sd->status.job_level*20 + + (status->int_/2)*10 + status->dex*10+status->luk*10; + if(merc_is_hom_active(sd->hd)) {//Player got a homun + int skill; + if((skill=merc_hom_checkskill(sd->hd,HVAN_INSTRUCT)) > 0) //His homun is a vanil with instruction change + make_per += skill*100; //+1% bonus per level + } + switch(nameid){ + case 501: // Red Potion + case 503: // Yellow Potion + case 504: // White Potion + make_per += (1+rnd()%100)*10 + 2000; + break; + case 970: // Alcohol + make_per += (1+rnd()%100)*10 + 1000; + break; + case 7135: // Bottle Grenade + case 7136: // Acid Bottle + case 7137: // Plant Bottle + case 7138: // Marine Sphere Bottle + make_per += (1+rnd()%100)*10; + break; + case 546: // Condensed Yellow Potion + make_per -= (1+rnd()%50)*10; + break; + case 547: // Condensed White Potion + case 7139: // Glistening Coat + make_per -= (1+rnd()%100)*10; + break; + //Common items, recieve no bonus or penalty, listed just because they are commonly produced + case 505: // Blue Potion + case 545: // Condensed Red Potion + case 605: // Anodyne + case 606: // Aloevera + default: + break; + } + if(battle_config.pp_rate != 100) + make_per = make_per * battle_config.pp_rate / 100; + break; + case SA_CREATECON: // Elemental Converter Creation + make_per = 100000; // should be 100% success rate + break; + /** + * Rune Knight + **/ + case RK_RUNEMASTERY: + { + int A = 100 * (51 + 2 * pc_checkskill(sd, skill_id)); + int B = 100 * status->dex / 30 + 10 * (status->luk + sd->status.job_level); + int C = 100 * cap_value(sd->itemid,0,100); //itemid depend on makerune() + int D = 0; + switch (nameid) { //rune rank it_diff 9 craftable rune + case ITEMID_BERKANA: + D = -2000; + break; //Rank S + case ITEMID_NAUTHIZ: + case ITEMID_URUZ: + D = -1500; + break; //Rank A + case ITEMID_ISA: + case ITEMID_WYRD: + D = -1000; + break; //Rank B + case ITEMID_RAIDO: + case ITEMID_THURISAZ: + case ITEMID_HAGALAZ: + case ITEMID_OTHILA: + D = -500; + break; //Rank C + default: D = -1500; + break; //not specified =-15% + } + make_per = A + B + C + D; + break; + } + /** + * Guilotine Cross + **/ + case GC_CREATENEWPOISON: + make_per = 3000 + 500 * pc_checkskill(sd,GC_RESEARCHNEWPOISON); + qty = 1+rnd()%pc_checkskill(sd,GC_RESEARCHNEWPOISON); + break; + case GN_CHANGEMATERIAL: + for(i=0; i<MAX_SKILL_PRODUCE_DB; i++) + if( skill_changematerial_db[i].itemid == nameid ){ + make_per = skill_changematerial_db[i].rate * 10; + break; + } + break; + case GN_S_PHARMACY: + { + int difficulty = 0; + + difficulty = (620 - 20 * skill_lv);// (620 - 20 * Skill Level) + + make_per = status->int_ + status->dex/2 + status->luk + sd->status.job_level + (30+rnd()%120) + // (Caster?s INT) + (Caster?s DEX / 2) + (Caster?s LUK) + (Caster?s Job Level) + Random number between (30 ~ 150) + + (sd->status.base_level-100) + pc_checkskill(sd, AM_LEARNINGPOTION) + pc_checkskill(sd, CR_FULLPROTECTION)*(4+rnd()%6); // (Caster?s Base Level - 100) + (Potion Research x 5) + (Full Chemical Protection Skill Level) x (Random number between 4 ~ 10) + + switch(nameid){// difficulty factor + case 12422: case 12425: + case 12428: + difficulty += 10; + break; + case 6212: case 12426: + difficulty += 15; + break; + case 13264: case 12423: + case 12427: case 12436: + difficulty += 20; + break; + case 6210: case 6211: + case 12437: + difficulty += 30; + break; + case 12424: case 12475: + difficulty += 40; + break; + } + + if( make_per >= 400 && make_per > difficulty) + qty = 10; + else if( make_per >= 300 && make_per > difficulty) + qty = 7; + else if( make_per >= 100 && make_per > difficulty) + qty = 6; + else if( make_per >= 1 && make_per > difficulty) + qty = 5; + else + qty = 4; + make_per = 10000; + } + break; + case GN_MAKEBOMB: + case GN_MIX_COOKING: + { + int difficulty = 30 + rnd()%120; // Random number between (30 ~ 150) + + make_per = sd->status.job_level / 4 + status->luk / 2 + status->dex / 3; // (Caster?s Job Level / 4) + (Caster?s LUK / 2) + (Caster?s DEX / 3) + qty = ~(5 + rnd()%5) + 1; + + switch(nameid){// difficulty factor + case 13260: + difficulty += 5; + break; + case 13261: case 13262: + difficulty += 10; + break; + case 12429: case 12430: case 12431: + case 12432: case 12433: case 12434: + case 13263: + difficulty += 15; + break; + case 13264: + difficulty += 20; + break; + } + + if( make_per >= 30 && make_per > difficulty) + qty = 10 + rnd()%2; + else if( make_per >= 10 && make_per > difficulty) + qty = 10; + else if( make_per == 10 && make_per > difficulty) + qty = 8; + else if( (make_per >= 50 || make_per < 30) && make_per < difficulty) + ;// Food/Bomb creation fails. + else if( make_per >= 30 && make_per < difficulty) + qty = 5; + + if( qty < 0 || (skill_lv == 1 && make_per < difficulty)){ + qty = ~qty + 1; + make_per = 0; + }else + make_per = 10000; + qty = (skill_lv > 1 ? qty : 1); + } + break; + default: + if (sd->menuskill_id == AM_PHARMACY && + sd->menuskill_val > 10 && sd->menuskill_val <= 20) + { //Assume Cooking Dish + if (sd->menuskill_val >= 15) //Legendary Cooking Set. + make_per = 10000; //100% Success + else + make_per = 1200 * (sd->menuskill_val - 10) + + 20 * (sd->status.base_level + 1) + + 20 * (status->dex + 1) + + 100 * (rnd()%(30+5*(sd->cook_mastery/400) - (6+sd->cook_mastery/80)) + (6+sd->cook_mastery/80)) + - 400 * (skill_produce_db[idx].itemlv - 11 + 1) + - 10 * (100 - status->luk + 1) + - 500 * (num - 1) + - 100 * (rnd()%4 + 1); + break; + } + make_per = 5000; + break; + } + } else { // Weapon Forging - skill bonuses are straight from kRO website, other things from a jRO calculator [DracoRPG] + make_per = 5000 + sd->status.job_level*20 + status->dex*10 + status->luk*10; // Base + make_per += pc_checkskill(sd,skill_id)*500; // Smithing skills bonus: +5/+10/+15 + make_per += pc_checkskill(sd,BS_WEAPONRESEARCH)*100 +((wlv >= 3)? pc_checkskill(sd,BS_ORIDEOCON)*100:0); // Weaponry Research bonus: +1/+2/+3/+4/+5/+6/+7/+8/+9/+10, Oridecon Research bonus (custom): +1/+2/+3/+4/+5 + make_per -= (ele?2000:0) + sc*1500 + (wlv>1?wlv*1000:0); // Element Stone: -20%, Star Crumb: -15% each, Weapon level malus: -0/-20/-30 + if(pc_search_inventory(sd,989) > 0) make_per+= 1000; // Emperium Anvil: +10 + else if(pc_search_inventory(sd,988) > 0) make_per+= 500; // Golden Anvil: +5 + else if(pc_search_inventory(sd,987) > 0) make_per+= 300; // Oridecon Anvil: +3 + else if(pc_search_inventory(sd,986) > 0) make_per+= 0; // Anvil: +0? + if(battle_config.wp_rate != 100) + make_per = make_per * battle_config.wp_rate / 100; + } + + if (sd->class_&JOBL_BABY) //if it's a Baby Class + make_per = (make_per * 50) / 100; //Baby penalty is 50% (bugreport:4847) + + if(make_per < 1) make_per = 1; + + + if(rnd()%10000 < make_per || qty > 1){ //Success, or crafting multiple items. + 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]=CARD0_FORGE; + tmp_item.card[1]=((sc*5)<<8)+ele; + tmp_item.card[2]=GetWord(sd->status.char_id,0); // CharId + tmp_item.card[3]=GetWord(sd->status.char_id,1); + } else { + //Flag is only used on the end, so it can be used here. [Skotlex] + switch (skill_id) { + case BS_DAGGER: + case BS_SWORD: + case BS_TWOHANDSWORD: + case BS_AXE: + case BS_MACE: + case BS_KNUCKLE: + case BS_SPEAR: + flag = battle_config.produce_item_name_input&0x1; + break; + case AM_PHARMACY: + case AM_TWILIGHT1: + case AM_TWILIGHT2: + case AM_TWILIGHT3: + flag = battle_config.produce_item_name_input&0x2; + break; + case AL_HOLYWATER: + /** + * Arch Bishop + **/ + case AB_ANCILLA: + flag = battle_config.produce_item_name_input&0x8; + break; + case ASC_CDP: + flag = battle_config.produce_item_name_input&0x10; + break; + default: + flag = battle_config.produce_item_name_input&0x80; + break; + } + if (flag) { + tmp_item.card[0]=CARD0_CREATE; + tmp_item.card[1]=0; + tmp_item.card[2]=GetWord(sd->status.char_id,0); // CharId + tmp_item.card[3]=GetWord(sd->status.char_id,1); + } + } + +// if(log_config.produce > 0) +// log_produce(sd,nameid,slot1,slot2,slot3,1); +//TODO update PICKLOG + + if(equip){ + clif_produceeffect(sd,0,nameid); + clif_misceffect(&sd->bl,3); + if(itemdb_wlv(nameid) >= 3 && ((ele? 1 : 0) + sc) >= 3) // Fame point system [DracoRPG] + pc_addfame(sd,10); // Success to forge a lv3 weapon with 3 additional ingredients = +10 fame point + } else { + int fame = 0; + tmp_item.amount = 0; + + for (i=0; i< qty; i++) { //Apply quantity modifiers. + if( (skill_id == GN_MIX_COOKING || skill_id == GN_MAKEBOMB || skill_id == GN_S_PHARMACY) && make_per > 1){ + tmp_item.amount = qty; + break; + } + if (rnd()%10000 < make_per || qty == 1) { //Success + tmp_item.amount++; + if(nameid < 545 || nameid > 547) + continue; + if( skill_id != AM_PHARMACY && + skill_id != AM_TWILIGHT1 && + skill_id != AM_TWILIGHT2 && + skill_id != AM_TWILIGHT3 ) + continue; + //Add fame as needed. + switch(++sd->potion_success_counter) { + case 3: + fame+=1; // Success to prepare 3 Condensed Potions in a row + break; + case 5: + fame+=3; // Success to prepare 5 Condensed Potions in a row + break; + case 7: + fame+=10; // Success to prepare 7 Condensed Potions in a row + break; + case 10: + fame+=50; // Success to prepare 10 Condensed Potions in a row + sd->potion_success_counter = 0; + break; + } + } else //Failure + sd->potion_success_counter = 0; + } + + if (fame) + pc_addfame(sd,fame); + //Visual effects and the like. + switch (skill_id) { + case AM_PHARMACY: + case AM_TWILIGHT1: + case AM_TWILIGHT2: + case AM_TWILIGHT3: + case ASC_CDP: + clif_produceeffect(sd,2,nameid); + clif_misceffect(&sd->bl,5); + break; + case BS_IRON: + case BS_STEEL: + case BS_ENCHANTEDSTONE: + clif_produceeffect(sd,0,nameid); + clif_misceffect(&sd->bl,3); + break; + case RK_RUNEMASTERY: + case GC_CREATENEWPOISON: + clif_produceeffect(sd,2,nameid); + clif_misceffect(&sd->bl,5); + break; + default: //Those that don't require a skill? + if( skill_produce_db[idx].itemlv > 10 && skill_produce_db[idx].itemlv <= 20) + { //Cooking items. + clif_specialeffect(&sd->bl, 608, AREA); + if( sd->cook_mastery < 1999 ) + pc_setglobalreg(sd, "COOK_MASTERY",sd->cook_mastery + ( 1 << ( (skill_produce_db[idx].itemlv - 11) / 2 ) ) * 5); + } + break; + } + } + if ( skill_id == GN_CHANGEMATERIAL && tmp_item.amount) { //Success + int j, k = 0; + for(i=0; i<MAX_SKILL_PRODUCE_DB; i++) + if( skill_changematerial_db[i].itemid == nameid ){ + for(j=0; j<5; j++){ + if( rnd()%1000 < skill_changematerial_db[i].qty_rate[j] ){ + tmp_item.amount = qty * skill_changematerial_db[i].qty[j]; + if((flag = pc_additem(sd,&tmp_item,tmp_item.amount,LOG_TYPE_PRODUCE))) { + clif_additem(sd,0,0,flag); + map_addflooritem(&tmp_item,tmp_item.amount,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + k++; + } + } + break; + } + if( k ){ + clif_msg_skill(sd,skill_id,0x627); + return 1; + } + } else if (tmp_item.amount) { //Success + if((flag = pc_additem(sd,&tmp_item,tmp_item.amount,LOG_TYPE_PRODUCE))) { + clif_additem(sd,0,0,flag); + map_addflooritem(&tmp_item,tmp_item.amount,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + if( skill_id == GN_MIX_COOKING || skill_id == GN_MAKEBOMB || skill_id == GN_S_PHARMACY ) + clif_msg_skill(sd,skill_id,0x627); + return 1; + } + } + //Failure +// if(log_config.produce) +// log_produce(sd,nameid,slot1,slot2,slot3,0); +//TODO update PICKLOG + + if(equip){ + clif_produceeffect(sd,1,nameid); + clif_misceffect(&sd->bl,2); + } else { + switch (skill_id) { + case ASC_CDP: //25% Damage yourself, and display same effect as failed potion. + status_percent_damage(NULL, &sd->bl, -25, 0, true); + case AM_PHARMACY: + case AM_TWILIGHT1: + case AM_TWILIGHT2: + case AM_TWILIGHT3: + clif_produceeffect(sd,3,nameid); + clif_misceffect(&sd->bl,6); + sd->potion_success_counter = 0; // Fame point system [DracoRPG] + break; + case BS_IRON: + case BS_STEEL: + case BS_ENCHANTEDSTONE: + clif_produceeffect(sd,1,nameid); + clif_misceffect(&sd->bl,2); + break; + case RK_RUNEMASTERY: + case GC_CREATENEWPOISON: + clif_produceeffect(sd,3,nameid); + clif_misceffect(&sd->bl,6); + break; + case GN_MIX_COOKING: { + struct item tmp_item; + const int compensation[5] = {13265, 13266, 13267, 12435, 13268}; + int rate = rnd()%500; + memset(&tmp_item,0,sizeof(tmp_item)); + if( rate < 50) i = 4; + else if( rate < 100) i = 2+rnd()%1; + else if( rate < 250 ) i = 1; + else if( rate < 500 ) i = 0; + tmp_item.nameid = compensation[i]; + tmp_item.amount = qty; + tmp_item.identify = 1; + if( pc_additem(sd,&tmp_item,tmp_item.amount,LOG_TYPE_PRODUCE) ) { + clif_additem(sd,0,0,flag); + map_addflooritem(&tmp_item,tmp_item.amount,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + clif_msg_skill(sd,skill_id,0x628); + } + break; + case GN_MAKEBOMB: + case GN_S_PHARMACY: + case GN_CHANGEMATERIAL: + clif_msg_skill(sd,skill_id,0x628); + break; + default: + if( skill_produce_db[idx].itemlv > 10 && skill_produce_db[idx].itemlv <= 20 ) + { //Cooking items. + clif_specialeffect(&sd->bl, 609, AREA); + if( sd->cook_mastery > 0 ) + pc_setglobalreg(sd, "COOK_MASTERY", sd->cook_mastery - ( 1 << ((skill_produce_db[idx].itemlv - 11) / 2) ) - ( ( ( 1 << ((skill_produce_db[idx].itemlv - 11) / 2) ) >> 1 ) * 3 )); + } + } + } + return 0; +} + +int skill_arrow_create (struct map_session_data *sd, int nameid) +{ + int i,j,flag,index=-1; + struct item tmp_item; + + nullpo_ret(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,0,LOG_TYPE_PRODUCE); + for(i=0;i<MAX_ARROW_RESOURCE;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.produce_item_name_input&0x4) { + tmp_item.card[0]=CARD0_CREATE; + tmp_item.card[1]=0; + tmp_item.card[2]=GetWord(sd->status.char_id,0); // CharId + tmp_item.card[3]=GetWord(sd->status.char_id,1); + } + if(tmp_item.nameid <= 0 || tmp_item.amount <= 0) + continue; + if((flag = pc_additem(sd,&tmp_item,tmp_item.amount,LOG_TYPE_PRODUCE))) { + clif_additem(sd,0,0,flag); + map_addflooritem(&tmp_item,tmp_item.amount,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + } + + return 0; +} +int skill_poisoningweapon( struct map_session_data *sd, int nameid) { + sc_type type; + int chance, i; + nullpo_ret(sd); + if( nameid <= 0 || (i = pc_search_inventory(sd,nameid)) < 0 || pc_delitem(sd,i,1,0,0,LOG_TYPE_CONSUME) ) { + clif_skill_fail(sd,GC_POISONINGWEAPON,USESKILL_FAIL_LEVEL,0); + return 0; + } + switch( nameid ) + { // t_lv used to take duration from skill_get_time2 + case PO_PARALYSE: type = SC_PARALYSE; break; + case PO_PYREXIA: type = SC_PYREXIA; break; + case PO_DEATHHURT: type = SC_DEATHHURT; break; + case PO_LEECHESEND: type = SC_LEECHESEND; break; + case PO_VENOMBLEED: type = SC_VENOMBLEED; break; + case PO_TOXIN: type = SC_TOXIN; break; + case PO_MAGICMUSHROOM: type = SC_MAGICMUSHROOM; break; + case PO_OBLIVIONCURSE: type = SC_OBLIVIONCURSE; break; + default: + clif_skill_fail(sd,GC_POISONINGWEAPON,USESKILL_FAIL_LEVEL,0); + return 0; + } + + chance = 2 + 2 * sd->menuskill_val; // 2 + 2 * skill_lv + sc_start4(&sd->bl, SC_POISONINGWEAPON, 100, pc_checkskill(sd, GC_RESEARCHNEWPOISON), //in Aegis it store the level of GC_RESEARCHNEWPOISON in val1 + type, chance, 0, skill_get_time(GC_POISONINGWEAPON, sd->menuskill_val)); + + return 0; +} + +static void skill_toggle_magicpower(struct block_list *bl, uint16 skill_id) +{ + struct status_change *sc = status_get_sc(bl); + + // non-offensive and non-magic skills do not affect the status + if (skill_get_nk(skill_id)&NK_NO_DAMAGE || !(skill_get_type(skill_id)&BF_MAGIC)) + return; + + if (sc && sc->count && sc->data[SC_MAGICPOWER]) + { + if (sc->data[SC_MAGICPOWER]->val4) + { + status_change_end(bl, SC_MAGICPOWER, INVALID_TIMER); + } + else + { + sc->data[SC_MAGICPOWER]->val4 = 1; + status_calc_bl(bl, status_sc2scb_flag(SC_MAGICPOWER)); +#ifndef RENEWAL + if(bl->type == BL_PC){// update current display. + clif_updatestatus(((TBL_PC *)bl),SP_MATK1); + clif_updatestatus(((TBL_PC *)bl),SP_MATK2); + } +#endif + } + } +} + + +int skill_magicdecoy(struct map_session_data *sd, int nameid) { + int x, y, i, class_, skill; + struct mob_data *md; + nullpo_ret(sd); + skill = sd->menuskill_val; + + if( nameid <= 0 || !itemdb_is_element(nameid) || (i = pc_search_inventory(sd,nameid)) < 0 || !skill || pc_delitem(sd,i,1,0,0,LOG_TYPE_CONSUME) ) + { + clif_skill_fail(sd,NC_MAGICDECOY,USESKILL_FAIL_LEVEL,0); + return 0; + } + + // Spawn Position + pc_delitem(sd,i,1,0,0,LOG_TYPE_CONSUME); + x = sd->sc.comet_x; + y = sd->sc.comet_y; + sd->sc.comet_x = sd->sc.comet_y = 0; + sd->menuskill_val = 0; + + class_ = (nameid == 990 || nameid == 991) ? 2043 + nameid - 990 : (nameid == 992) ? 2046 : 2045; + + + md = mob_once_spawn_sub(&sd->bl, sd->bl.m, x, y, sd->status.name, class_, "", SZ_SMALL, AI_NONE); + if( md ) { + md->master_id = sd->bl.id; + md->special_state.ai = AI_FLORA; + if( md->deletetimer != INVALID_TIMER ) + delete_timer(md->deletetimer, mob_timer_delete); + md->deletetimer = add_timer (gettick() + skill_get_time(NC_MAGICDECOY,skill), mob_timer_delete, md->bl.id, 0); + mob_spawn(md); + md->status.matk_min = md->status.matk_max = 250 + (50 * skill); + } + + return 0; +} + +// Warlock Spellbooks. [LimitLine/3CeAM] +int skill_spellbook (struct map_session_data *sd, int nameid) { + int i, max_preserve, skill_id, point; + struct status_change *sc; + + nullpo_ret(sd); + + sc = status_get_sc(&sd->bl); + status_change_end(&sd->bl, SC_STOP, INVALID_TIMER); + + for(i=SC_SPELLBOOK1; i <= SC_MAXSPELLBOOK; i++) if( sc && !sc->data[i] ) break; + if( i > SC_MAXSPELLBOOK ) + { + clif_skill_fail(sd, WL_READING_SB, USESKILL_FAIL_SPELLBOOK_READING, 0); + return 0; + } + + ARR_FIND(0,MAX_SKILL_SPELLBOOK_DB,i,skill_spellbook_db[i].nameid == nameid); // Search for information of this item + if( i == MAX_SKILL_SPELLBOOK_DB ) return 0; + + if( !pc_checkskill(sd, (skill_id = skill_spellbook_db[i].skill_id)) ) + { // User don't know the skill + sc_start(&sd->bl, SC_SLEEP, 100, 1, skill_get_time(WL_READING_SB, pc_checkskill(sd,WL_READING_SB))); + clif_skill_fail(sd, WL_READING_SB, USESKILL_FAIL_SPELLBOOK_DIFFICULT_SLEEP, 0); + return 0; + } + + max_preserve = 4 * pc_checkskill(sd, WL_FREEZE_SP) + status_get_int(&sd->bl) / 10 + sd->status.base_level / 10; + point = skill_spellbook_db[i].point; + + if( sc && sc->data[SC_READING_SB] ){ + if( (sc->data[SC_READING_SB]->val2 + point) > max_preserve ) + { + clif_skill_fail(sd, WL_READING_SB, USESKILL_FAIL_SPELLBOOK_PRESERVATION_POINT, 0); + return 0; + } + for(i = SC_MAXSPELLBOOK; i >= SC_SPELLBOOK1; i--){ // This is how official saves spellbook. [malufett] + if( !sc->data[i] ){ + sc->data[SC_READING_SB]->val2 += point; // increase points + sc_start4(&sd->bl, (sc_type)i, 100, skill_id, pc_checkskill(sd,skill_id), point, 0, INVALID_TIMER); + break; + } + } + }else{ + sc_start2(&sd->bl, SC_READING_SB, 100, 0, point, INVALID_TIMER); + sc_start4(&sd->bl, SC_MAXSPELLBOOK, 100, skill_id, pc_checkskill(sd,skill_id), point, 0, INVALID_TIMER); + } + + return 1; +} +int skill_select_menu(struct map_session_data *sd,uint16 skill_id) { + int id, lv, prob, aslvl = 0; + nullpo_ret(sd); + + if (sd->sc.data[SC_STOP]) { + aslvl = sd->sc.data[SC_STOP]->val1; + status_change_end(&sd->bl,SC_STOP,INVALID_TIMER); + } + + if( skill_id >= GS_GLITTERING || skill_get_type(skill_id) != BF_MAGIC || + (id = sd->status.skill[skill_id].id) == 0 || sd->status.skill[skill_id].flag != SKILL_FLAG_PLAGIARIZED ) { + clif_skill_fail(sd,SC_AUTOSHADOWSPELL,0,0); + return 0; + } + + lv = (aslvl + 1) / 2; // The level the skill will be autocasted + lv = min(lv,sd->status.skill[skill_id].lv); + prob = (aslvl == 10) ? 15 : (32 - 2 * aslvl); // Probability at level 10 was increased to 15. + sc_start4(&sd->bl,SC__AUTOSHADOWSPELL,100,id,lv,prob,0,skill_get_time(SC_AUTOSHADOWSPELL,aslvl)); + return 0; +} +int skill_elementalanalysis(struct map_session_data* sd, int n, uint16 skill_lv, unsigned short* item_list) { + int i; + + nullpo_ret(sd); + nullpo_ret(item_list); + + if( n <= 0 ) + return 1; + + for( i = 0; i < n; i++ ) { + int nameid, add_amount, del_amount, idx, product, flag; + struct item tmp_item; + + idx = item_list[i*2+0]-2; + del_amount = item_list[i*2+1]; + + if( skill_lv == 2 ) + del_amount -= (del_amount % 10); + add_amount = (skill_lv == 1) ? del_amount * (5 + rnd()%5) : del_amount / 10 ; + + if( (nameid = sd->status.inventory[idx].nameid) <= 0 || del_amount > sd->status.inventory[idx].amount ) { + clif_skill_fail(sd,SO_EL_ANALYSIS,USESKILL_FAIL_LEVEL,0); + return 1; + } + + switch( nameid ) { + // Level 1 + case 994: product = 990; break; // Flame Heart -> Red Blood. + case 995: product = 991; break; // Mystic Frozen -> Crystal Blue. + case 996: product = 992; break; // Rough Wind -> Wind of Verdure. + case 997: product = 993; break; // Great Nature -> Green Live. + // Level 2 + case 990: product = 994; break; // Red Blood -> Flame Heart. + case 991: product = 995; break; // Crystal Blue -> Mystic Frozen. + case 992: product = 996; break; // Wind of Verdure -> Rough Wind. + case 993: product = 997; break; // Green Live -> Great Nature. + default: + clif_skill_fail(sd,SO_EL_ANALYSIS,USESKILL_FAIL_LEVEL,0); + return 1; + } + + if( pc_delitem(sd,idx,del_amount,0,1,LOG_TYPE_CONSUME) ) { + clif_skill_fail(sd,SO_EL_ANALYSIS,USESKILL_FAIL_LEVEL,0); + return 1; + } + + if( skill_lv == 2 && rnd()%100 < 25 ) { // At level 2 have a fail chance. You loose your items if it fails. + clif_skill_fail(sd,SO_EL_ANALYSIS,USESKILL_FAIL_LEVEL,0); + return 1; + } + + + memset(&tmp_item,0,sizeof(tmp_item)); + tmp_item.nameid = product; + tmp_item.amount = add_amount; + tmp_item.identify = 1; + + if( tmp_item.amount ) { + if( (flag = pc_additem(sd,&tmp_item,tmp_item.amount,LOG_TYPE_CONSUME)) ) { + clif_additem(sd,0,0,flag); + map_addflooritem(&tmp_item,tmp_item.amount,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + } + + } + + return 0; +} + +int skill_changematerial(struct map_session_data *sd, int n, unsigned short *item_list) { + int i, j, k, c, p = 0, nameid, amount; + + nullpo_ret(sd); + nullpo_ret(item_list); + + // Search for objects that can be created. + for( i = 0; i < MAX_SKILL_PRODUCE_DB; i++ ) { + if( skill_produce_db[i].itemlv == 26 ) { + p = 0; + do { + c = 0; + // Verification of overlap between the objects required and the list submitted. + for( j = 0; j < MAX_PRODUCE_RESOURCE; j++ ) { + if( skill_produce_db[i].mat_id[j] > 0 ) { + for( k = 0; k < n; k++ ) { + int idx = item_list[k*2+0]-2; + nameid = sd->status.inventory[idx].nameid; + amount = item_list[k*2+1]; + if( nameid > 0 && sd->status.inventory[idx].identify == 0 ){ + clif_msg_skill(sd,GN_CHANGEMATERIAL,0x62D); + return 0; + } + if( nameid == skill_produce_db[i].mat_id[j] && (amount-p*skill_produce_db[i].mat_amount[j]) >= skill_produce_db[i].mat_amount[j] + && (amount-p*skill_produce_db[i].mat_amount[j])%skill_produce_db[i].mat_amount[j] == 0 ) // must be in exact amount + c++; // match + } + } + else + break; // No more items required + } + p++; + } while(n == j && c == n); + p--; + if ( p > 0 ) { + skill_produce_mix(sd,GN_CHANGEMATERIAL,skill_produce_db[i].nameid,0,0,0,p); + return 1; + } + } + } + + if( p == 0) + clif_msg_skill(sd,GN_CHANGEMATERIAL,0x623); + + return 0; +} +/** + * for Royal Guard's LG_TRAMPLE + **/ +static int skill_destroy_trap( struct block_list *bl, va_list ap ) { + struct skill_unit *su = (struct skill_unit *)bl; + struct skill_unit_group *sg; + unsigned int tick; + + nullpo_ret(su); + tick = va_arg(ap, unsigned int); + + if (su->alive && (sg = su->group) && skill_get_inf2(sg->skill_id)&INF2_TRAP) { + switch( sg->unit_id ) { + case UNT_LANDMINE: + case UNT_CLAYMORETRAP: + case UNT_BLASTMINE: + case UNT_SHOCKWAVE: + case UNT_SANDMAN: + case UNT_FLASHER: + case UNT_FREEZINGTRAP: + case UNT_CLUSTERBOMB: + case UNT_FIRINGTRAP: + case UNT_ICEBOUNDTRAP: + map_foreachinrange(skill_trap_splash,&su->bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, &su->bl,tick); + break; + } + // Traps aren't recovered. + skill_delunit(su); + } + return 0; +} +/*========================================== + * + *------------------------------------------*/ +int skill_blockpc_end(int tid, unsigned int tick, int id, intptr_t data) +{ + struct map_session_data *sd = map_id2sd(id); + struct skill_cd * cd = NULL; + + if (data <= 0 || data >= MAX_SKILL) + return 0; + if (!sd) return 0; + if (sd->blockskill[data] != (0x1|(tid&0xFE))) return 0; + + if( ( cd = idb_get(skillcd_db,sd->status.char_id) ) ) { + int i,cursor; + ARR_FIND( 0, cd->cursor+1, cursor, cd->skidx[cursor] == data ); + cd->duration[cursor] = 0; + cd->skidx[cursor] = 0; + cd->nameid[cursor] = 0; + // compact the cool down list + for( i = 0, cursor = 0; i < cd->cursor; i++ ) { + if( cd->duration[i] == 0 ) + continue; + if( cursor != i ) { + cd->duration[cursor] = cd->duration[i]; + cd->skidx[cursor] = cd->skidx[i]; + cd->nameid[cursor] = cd->nameid[i]; + } + cursor++; + } + if( cursor == 0 ) + idb_remove(skillcd_db,sd->status.char_id); + else + cd->cursor = cursor; + } + + sd->blockskill[data] = 0; + return 1; +} + +/** + * flags a singular skill as being blocked from persistent usage. + * @param sd the player the skill delay affects + * @param skill_id the skill which should be delayed + * @param tick the length of time the delay should last + * @param load whether this assignment is being loaded upon player login + * @return 0 if successful, -1 otherwise + */ +int skill_blockpc_start_(struct map_session_data *sd, uint16 skill_id, int tick, bool load) +{ + int oskill_id = skill_id; + struct skill_cd* cd = NULL; + uint16 idx = skill_get_index(skill_id); + + nullpo_retr (-1, sd); + + if (idx == 0) + return -1; + + if (tick < 1) { + sd->blockskill[idx] = 0; + return -1; + } + + if( battle_config.display_status_timers ) + clif_skill_cooldown(sd, idx, tick); + + if( !load ) + {// not being loaded initially so ensure the skill delay is recorded + if( !(cd = idb_get(skillcd_db,sd->status.char_id)) ) + {// create a new skill cooldown object for map storage + CREATE( cd, struct skill_cd, 1 ); + idb_put( skillcd_db, sd->status.char_id, cd ); + } + + // record the skill duration in the database map + cd->duration[cd->cursor] = tick; + cd->skidx[cd->cursor] = idx; + cd->nameid[cd->cursor] = oskill_id; + cd->cursor++; + } + + sd->blockskill[idx] = 0x1|(0xFE&add_timer(gettick()+tick,skill_blockpc_end,sd->bl.id,idx)); + return 0; +} + +int skill_blockhomun_end(int tid, unsigned int tick, int id, intptr_t data) //[orn] +{ + struct homun_data *hd = (TBL_HOM*) map_id2bl(id); + if (data <= 0 || data >= MAX_SKILL) + return 0; + if (hd) hd->blockskill[data] = 0; + + return 1; +} + +int skill_blockhomun_start(struct homun_data *hd, uint16 skill_id, int tick) //[orn] +{ + uint16 idx = skill_get_index(skill_id); + nullpo_retr (-1, hd); + + + if (idx == 0) + return -1; + + if (tick < 1) { + hd->blockskill[idx] = 0; + return -1; + } + hd->blockskill[idx] = 1; + return add_timer(gettick() + tick, skill_blockhomun_end, hd->bl.id, idx); +} + +int skill_blockmerc_end(int tid, unsigned int tick, int id, intptr_t data) //[orn] +{ + struct mercenary_data *md = (TBL_MER*)map_id2bl(id); + if( data <= 0 || data >= MAX_SKILL ) + return 0; + if( md ) md->blockskill[data] = 0; + + return 1; +} + +int skill_blockmerc_start(struct mercenary_data *md, uint16 skill_id, int tick) +{ + uint16 idx = skill_get_index(skill_id); + nullpo_retr (-1, md); + + if (idx == 0) + return -1; + if( tick < 1 ) + { + md->blockskill[idx] = 0; + return -1; + } + md->blockskill[idx] = 1; + return add_timer(gettick() + tick, skill_blockmerc_end, md->bl.id, idx); +} +/** + * Adds a new skill unit entry for this player to recast after map load + **/ +void skill_usave_add(struct map_session_data * sd, uint16 skill_id, uint16 skill_lv) { + struct skill_usave * sus = NULL; + + if( idb_exists(skillusave_db,sd->status.char_id) ) { + idb_remove(skillusave_db,sd->status.char_id); + } + + CREATE( sus, struct skill_usave, 1 ); + idb_put( skillusave_db, sd->status.char_id, sus ); + + sus->skill_id = skill_id; + sus->skill_lv = skill_lv; + + return; +} +void skill_usave_trigger(struct map_session_data *sd) { + struct skill_usave * sus = NULL; + + if( ! (sus = idb_get(skillusave_db,sd->status.char_id)) ) { + return; + } + + skill_unitsetting(&sd->bl,sus->skill_id,sus->skill_lv,sd->bl.x,sd->bl.y,0); + + idb_remove(skillusave_db,sd->status.char_id); + + return; +} +/* + * + */ +int skill_split_str (char *str, char **val, int num) +{ + int i; + + for( i = 0; i < num && str; i++ ) + { + val[i] = str; + str = strchr(str,','); + if( str ) + *str++=0; + } + + return i; +} +/* + * + */ +int skill_split_atoi (char *str, int *val) +{ + int i, j, diff, step = 1; + + for (i=0; i<MAX_SKILL_LEVEL; i++) { + if (!str) break; + val[i] = atoi(str); + str = strchr(str,':'); + if (str) + *str++=0; + } + if(i==0) //No data found. + return 0; + if(i==1) + { //Single value, have the whole range have the same value. + for (; i < MAX_SKILL_LEVEL; i++) + val[i] = val[i-1]; + return i; + } + //Check for linear change with increasing steps until we reach half of the data acquired. + for (step = 1; step <= i/2; step++) + { + diff = val[i-1] - val[i-step-1]; + for(j = i-1; j >= step; j--) + if ((val[j]-val[j-step]) != diff) + break; + + if (j>=step) //No match, try next step. + continue; + + for(; i < MAX_SKILL_LEVEL; i++) + { //Apply linear increase + val[i] = val[i-step]+diff; + if (val[i] < 1 && val[i-1] >=0) //Check if we have switched from + to -, cap the decrease to 0 in said cases. + { val[i] = 1; diff = 0; step = 1; } + } + return i; + } + //Okay.. we can't figure this one out, just fill out the stuff with the previous value. + for (;i<MAX_SKILL_LEVEL; i++) + val[i] = val[i-1]; + return i; +} + +/* + * + */ +void skill_init_unit_layout (void) +{ + int i,j,size,pos = 0; + + memset(skill_unit_layout,0,sizeof(skill_unit_layout)); + + // standard square layouts go first + for (i=0; i<=MAX_SQUARE_LAYOUT; i++) { + size = i*2+1; + skill_unit_layout[i].count = size*size; + for (j=0; j<size*size; j++) { + skill_unit_layout[i].dx[j] = (j%size-i); + skill_unit_layout[i].dy[j] = (j/size-i); + } + } + + // afterwards add special ones + pos = i; + for (i=0;i<MAX_SKILL_DB;i++) { + if (!skill_db[i].unit_id[0] || skill_db[i].unit_layout_type[0] != -1) + continue; + if( i >= HM_SKILLRANGEMIN && i <= EL_SKILLRANGEMAX ) { + int skill = i; + + if( i >= EL_SKILLRANGEMIN && i <= EL_SKILLRANGEMAX ) { + skill -= EL_SKILLRANGEMIN; + skill += EL_SKILLBASE; + } + if( skill == EL_FIRE_MANTLE ) { + static const int dx[] = {-1, 0, 1, 1, 1, 0,-1,-1}; + static const int dy[] = { 1, 1, 1, 0,-1,-1,-1, 0}; + skill_unit_layout[pos].count = 8; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + } else { + switch (i) { + case MG_FIREWALL: + case WZ_ICEWALL: + case WL_EARTHSTRAIN://Warlock + // these will be handled later + break; + case PR_SANCTUARY: + case NPC_EVILLAND: { + 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}; + skill_unit_layout[pos].count = 21; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + 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}; + skill_unit_layout[pos].count = 33; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + break; + case MH_POISON_MIST: + case AS_VENOMDUST: { + static const int dx[] = {-1, 0, 0, 0, 1}; + static const int dy[] = { 0,-1, 0, 1, 0}; + skill_unit_layout[pos].count = 5; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + break; + case CR_GRANDCROSS: + case NPC_GRANDDARKNESS: { + 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}; + skill_unit_layout[pos].count = 29; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + break; + case PF_FOGWALL: { + static const int dx[] = { + -2,-1, 0, 1, 2,-2,-1, 0, 1, 2,-2,-1, 0, 1, 2}; + static const int dy[] = { + -1,-1,-1,-1,-1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1}; + skill_unit_layout[pos].count = 15; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + break; + case PA_GOSPEL: { + 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}; + skill_unit_layout[pos].count = 33; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + break; + case NJ_KAENSIN: { + static const int dx[] = {-2,-1, 0, 1, 2,-2,-1, 0, 1, 2,-2,-1, 1, 2,-2,-1, 0, 1, 2,-2,-1, 0, 1, 2}; + static const int dy[] = { 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0,-1,-1,-1,-1,-1,-2,-2,-2,-2,-2}; + skill_unit_layout[pos].count = 24; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + break; + case NJ_TATAMIGAESHI: { + //Level 1 (count 4, cross of 3x3) + static const int dx1[] = {-1, 1, 0, 0}; + static const int dy1[] = { 0, 0,-1, 1}; + //Level 2-3 (count 8, cross of 5x5) + static const int dx2[] = {-2,-1, 1, 2, 0, 0, 0, 0}; + static const int dy2[] = { 0, 0, 0, 0,-2,-1, 1, 2}; + //Level 4-5 (count 12, cross of 7x7 + static const int dx3[] = {-3,-2,-1, 1, 2, 3, 0, 0, 0, 0, 0, 0}; + static const int dy3[] = { 0, 0, 0, 0, 0, 0,-3,-2,-1, 1, 2, 3}; + //lv1 + j = 0; + skill_unit_layout[pos].count = 4; + memcpy(skill_unit_layout[pos].dx,dx1,sizeof(dx1)); + memcpy(skill_unit_layout[pos].dy,dy1,sizeof(dy1)); + skill_db[i].unit_layout_type[j] = pos; + //lv2/3 + j++; + pos++; + skill_unit_layout[pos].count = 8; + memcpy(skill_unit_layout[pos].dx,dx2,sizeof(dx2)); + memcpy(skill_unit_layout[pos].dy,dy2,sizeof(dy2)); + skill_db[i].unit_layout_type[j] = pos; + skill_db[i].unit_layout_type[++j] = pos; + //lv4/5 + j++; + pos++; + skill_unit_layout[pos].count = 12; + memcpy(skill_unit_layout[pos].dx,dx3,sizeof(dx3)); + memcpy(skill_unit_layout[pos].dy,dy3,sizeof(dy3)); + skill_db[i].unit_layout_type[j] = pos; + skill_db[i].unit_layout_type[++j] = pos; + //Fill in the rest using lv 5. + for (;j<MAX_SKILL_LEVEL;j++) + skill_db[i].unit_layout_type[j] = pos; + //Skip, this way the check below will fail and continue to the next skill. + pos++; + } + break; + case GN_WALLOFTHORN: { + static const int dx[] = {-1,-2,-2,-2,-2,-2,-1, 0, 1, 2, 2, 2, 2, 2, 1, 0}; + static const int dy[] = { 2, 2, 1, 0,-1,-2,-2,-2,-2,-2,-1, 0, 1, 2, 2, 2}; + skill_unit_layout[pos].count = 16; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + break; + default: + ShowError("unknown unit layout at skill %d\n",i); + break; + } + } + if (!skill_unit_layout[pos].count) + continue; + for (j=0;j<MAX_SKILL_LEVEL;j++) + skill_db[i].unit_layout_type[j] = pos; + pos++; + } + + // firewall and icewall have 8 layouts (direction-dependent) + firewall_unit_pos = pos; + for (i=0;i<8;i++) { + if (i&1) { + skill_unit_layout[pos].count = 5; + if (i&0x2) { + int dx[] = {-1,-1, 0, 0, 1}; + int dy[] = { 1, 0, 0,-1,-1}; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } else { + int dx[] = { 1, 1 ,0, 0,-1}; + int dy[] = { 1, 0, 0,-1,-1}; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + } else { + skill_unit_layout[pos].count = 3; + if (i%4==0) { + int dx[] = {-1, 0, 1}; + int dy[] = { 0, 0, 0}; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } else { + int dx[] = { 0, 0, 0}; + int dy[] = {-1, 0, 1}; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + } + pos++; + } + icewall_unit_pos = pos; + for (i=0;i<8;i++) { + skill_unit_layout[pos].count = 5; + if (i&1) { + if (i&0x2) { + int dx[] = {-2,-1, 0, 1, 2}; + int dy[] = { 2, 1, 0,-1,-2}; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } else { + int dx[] = { 2, 1 ,0,-1,-2}; + int dy[] = { 2, 1, 0,-1,-2}; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + } else { + if (i%4==0) { + int dx[] = {-2,-1, 0, 1, 2}; + int dy[] = { 0, 0, 0, 0, 0}; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } else { + int dx[] = { 0, 0, 0, 0, 0}; + int dy[] = {-2,-1, 0, 1, 2}; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + } + pos++; + } + earthstrain_unit_pos = pos; + for( i = 0; i < 8; i++ ) + { // For each Direction + skill_unit_layout[pos].count = 15; + switch( i ) + { + case 0: case 1: case 3: case 4: case 5: case 7: + { + int dx[] = {-7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7}; + int dy[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + break; + case 2: + case 6: + { + int dx[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + int dy[] = {-7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7}; + memcpy(skill_unit_layout[pos].dx,dx,sizeof(dx)); + memcpy(skill_unit_layout[pos].dy,dy,sizeof(dy)); + } + break; + } + pos++; + } + +} + +int skill_block_check(struct block_list *bl, sc_type type , uint16 skill_id) { + int inf = 0; + struct status_change *sc = status_get_sc(bl); + + if( !sc || !bl || !skill_id ) + return 0; // Can do it + + switch(type){ + case SC_STASIS: + inf = skill_get_inf2(skill_id); + if( inf == INF2_SONG_DANCE || /*skill_get_inf2(skill_id) == INF2_CHORUS_SKILL ||*/ inf == INF2_SPIRIT_SKILL ) + return 1; // Can't do it. + switch( skill_id ) + { + case NV_FIRSTAID: case TF_HIDING: case AS_CLOAKING: case WZ_SIGHTRASHER: + case RG_STRIPWEAPON: case RG_STRIPSHIELD: case RG_STRIPARMOR: case WZ_METEOR: + case RG_STRIPHELM: case SC_STRIPACCESSARY: case ST_FULLSTRIP: case WZ_SIGHTBLASTER: + case ST_CHASEWALK: case SC_ENERVATION: case SC_GROOMY: case WZ_ICEWALL: + case SC_IGNORANCE: case SC_LAZINESS: case SC_UNLUCKY: case WZ_STORMGUST: + case SC_WEAKNESS: case AL_RUWACH: case AL_PNEUMA: case WZ_JUPITEL: + case AL_HEAL: case AL_BLESSING: case AL_INCAGI: case WZ_VERMILION: + case AL_TELEPORT: case AL_WARP: case AL_HOLYWATER: case WZ_EARTHSPIKE: + case AL_HOLYLIGHT: case PR_IMPOSITIO: case PR_ASPERSIO: case WZ_HEAVENDRIVE: + case PR_SANCTUARY: case PR_STRECOVERY: case PR_MAGNIFICAT: case WZ_QUAGMIRE: + case ALL_RESURRECTION: case PR_LEXDIVINA: case PR_LEXAETERNA: case HW_GRAVITATION: + case PR_MAGNUS: case PR_TURNUNDEAD: case MG_SRECOVERY: case HW_MAGICPOWER: + case MG_SIGHT: case MG_NAPALMBEAT: case MG_SAFETYWALL: case HW_GANBANTEIN: + case MG_SOULSTRIKE: case MG_COLDBOLT: case MG_FROSTDIVER: case WL_DRAINLIFE: + case MG_STONECURSE: case MG_FIREBALL: case MG_FIREWALL: case WL_SOULEXPANSION: + case MG_FIREBOLT: case MG_LIGHTNINGBOLT: case MG_THUNDERSTORM: case MG_ENERGYCOAT: + case WL_WHITEIMPRISON: case WL_SUMMONFB: case WL_SUMMONBL: case WL_SUMMONWB: + case WL_SUMMONSTONE: case WL_SIENNAEXECRATE: case WL_RELEASE: case WL_EARTHSTRAIN: + case WL_RECOGNIZEDSPELL: case WL_READING_SB: case SA_MAGICROD: case SA_SPELLBREAKER: + case SA_DISPELL: case SA_FLAMELAUNCHER: case SA_FROSTWEAPON: case SA_LIGHTNINGLOADER: + case SA_SEISMICWEAPON: case SA_VOLCANO: case SA_DELUGE: case SA_VIOLENTGALE: + case SA_LANDPROTECTOR: case PF_HPCONVERSION: case PF_SOULCHANGE: case PF_SPIDERWEB: + case PF_FOGWALL: case TK_RUN: case TK_HIGHJUMP: case TK_SEVENWIND: + case SL_KAAHI: case SL_KAUPE: case SL_KAITE: + + // Skills that need to be confirmed. + case SO_FIREWALK: case SO_ELECTRICWALK: case SO_SPELLFIST: case SO_EARTHGRAVE: + case SO_DIAMONDDUST: case SO_POISON_BUSTER: case SO_PSYCHIC_WAVE: case SO_CLOUD_KILL: + case SO_STRIKING: case SO_WARMER: case SO_VACUUM_EXTREME: case SO_VARETYR_SPEAR: + case SO_ARRULLO: + return 1; // Can't do it. + } + break; + case SC_KAGEHUMI: + switch(skill_id){ + case TF_HIDING: case AS_CLOAKING: case GC_CLOAKINGEXCEED: case SC_SHADOWFORM: + case MI_HARMONIZE: case CG_MARIONETTE: case AL_TELEPORT: case TF_BACKSLIDING: + case RA_CAMOUFLAGE: case ST_CHASEWALK: case GD_EMERGENCYCALL: + return 1; // needs more info + } + break; + } + + return 0; +} + +int skill_get_elemental_type( uint16 skill_id , uint16 skill_lv ) { + int type = 0; + + switch( skill_id ) { + case SO_SUMMON_AGNI: type = 2114; break; + case SO_SUMMON_AQUA: type = 2117; break; + case SO_SUMMON_VENTUS: type = 2120; break; + case SO_SUMMON_TERA: type = 2123; break; + } + + type += skill_lv - 1; + + return type; +} + +/** + * reload stored skill cooldowns when a player logs in. + * @param sd the affected player structure + */ +void skill_cooldown_load(struct map_session_data * sd) +{ + int i; + struct skill_cd* cd = NULL; + + // always check to make sure the session properly exists + nullpo_retv(sd); + + if( !(cd = idb_get(skillcd_db, sd->status.char_id)) ) + {// no skill cooldown is associated with this character + return; + } + + // process each individual cooldown associated with the character + for( i = 0; i < cd->cursor; i++ ) + { + // block the skill from usage but ensure it is not recorded (load = true) + skill_blockpc_start_( sd, cd->nameid[i], cd->duration[i], true ); + } +} + +/*========================================== + * sub-function of DB reading. + * skill_db.txt + *------------------------------------------*/ + +static bool skill_parse_row_skilldb(char* split[], int columns, int current) +{// id,range,hit,inf,element,nk,splash,max,list_num,castcancel,cast_defence_rate,inf2,maxcount,skill_type,blow_count,name,description + uint16 skill_id = atoi(split[0]); + uint16 idx; + if( (skill_id >= GD_SKILLRANGEMIN && skill_id <= GD_SKILLRANGEMAX) + || (skill_id >= HM_SKILLRANGEMIN && skill_id <= HM_SKILLRANGEMAX) + || (skill_id >= MC_SKILLRANGEMIN && skill_id <= MC_SKILLRANGEMAX) + || (skill_id >= EL_SKILLRANGEMIN && skill_id <= EL_SKILLRANGEMAX) ) { + ShowWarning("skill_parse_row_skilldb: Skill id %d is forbidden (interferes with guild/homun/mercenary skill mapping)!\n", skill_id); + return false; + } + + idx = skill_get_index(skill_id); + if( !idx ) // invalid skill id + return false; + + skill_split_atoi(split[1],skill_db[idx].range); + skill_db[idx].hit = atoi(split[2]); + skill_db[idx].inf = atoi(split[3]); + skill_split_atoi(split[4],skill_db[idx].element); + skill_db[idx].nk = (int)strtol(split[5], NULL, 0); + skill_split_atoi(split[6],skill_db[idx].splash); + skill_db[idx].max = atoi(split[7]); + skill_split_atoi(split[8],skill_db[idx].num); + + if( strcmpi(split[9],"yes") == 0 ) + skill_db[idx].castcancel = 1; + else + skill_db[idx].castcancel = 0; + skill_db[idx].cast_def_rate = atoi(split[10]); + skill_db[idx].inf2 = (int)strtol(split[11], NULL, 0); + skill_split_atoi(split[12],skill_db[idx].maxcount); + if( strcmpi(split[13],"weapon") == 0 ) + skill_db[idx].skill_type = BF_WEAPON; + else if( strcmpi(split[13],"magic") == 0 ) + skill_db[idx].skill_type = BF_MAGIC; + else if( strcmpi(split[13],"misc") == 0 ) + skill_db[idx].skill_type = BF_MISC; + else + skill_db[idx].skill_type = 0; + skill_split_atoi(split[14],skill_db[idx].blewcount); + safestrncpy(skill_db[idx].name, trim(split[15]), sizeof(skill_db[idx].name)); + safestrncpy(skill_db[idx].desc, trim(split[16]), sizeof(skill_db[idx].desc)); + strdb_iput(skilldb_name2id, skill_db[idx].name, skill_id); + + return true; +} + +static bool skill_parse_row_requiredb(char* split[], int columns, int current) +{// skill_id,HPCost,MaxHPTrigger,SPCost,HPRateCost,SPRateCost,ZenyCost,RequiredWeapons,RequiredAmmoTypes,RequiredAmmoAmount,RequiredState,SpiritSphereCost,RequiredItemID1,RequiredItemAmount1,RequiredItemID2,RequiredItemAmount2,RequiredItemID3,RequiredItemAmount3,RequiredItemID4,RequiredItemAmount4,RequiredItemID5,RequiredItemAmount5,RequiredItemID6,RequiredItemAmount6,RequiredItemID7,RequiredItemAmount7,RequiredItemID8,RequiredItemAmount8,RequiredItemID9,RequiredItemAmount9,RequiredItemID10,RequiredItemAmount10 + char* p; + int j; + + uint16 skill_id = atoi(split[0]); + uint16 idx = skill_get_index(skill_id); + if( !idx ) // invalid skill id + return false; + + skill_split_atoi(split[1],skill_db[idx].hp); + skill_split_atoi(split[2],skill_db[idx].mhp); + skill_split_atoi(split[3],skill_db[idx].sp); + skill_split_atoi(split[4],skill_db[idx].hp_rate); + skill_split_atoi(split[5],skill_db[idx].sp_rate); + skill_split_atoi(split[6],skill_db[idx].zeny); + + //Wich weapon type are required, see doc/item_db for types + p = split[7]; + for( j = 0; j < 32; j++ ) + { + int l = atoi(p); + if( l == 99 ) // Any weapon + { + skill_db[idx].weapon = 0; + break; + } + else + skill_db[idx].weapon |= 1<<l; + p = strchr(p,':'); + if(!p) + break; + p++; + } + + //FIXME: document this + p = split[8]; + for( j = 0; j < 32; j++ ) + { + int l = atoi(p); + if( l == 99 ) // Any ammo type + { + skill_db[idx].ammo = 0xFFFFFFFF; + break; + } + else if( l ) // 0 stands for no requirement + skill_db[idx].ammo |= 1<<l; + p = strchr(p,':'); + if( !p ) + break; + p++; + } + skill_split_atoi(split[9],skill_db[idx].ammo_qty); + + if( strcmpi(split[10],"hiding")==0 ) skill_db[idx].state = ST_HIDING; + else if( strcmpi(split[10],"cloaking")==0 ) skill_db[idx].state = ST_CLOAKING; + else if( strcmpi(split[10],"hidden")==0 ) skill_db[idx].state = ST_HIDDEN; + else if( strcmpi(split[10],"riding")==0 ) skill_db[idx].state = ST_RIDING; + else if( strcmpi(split[10],"falcon")==0 ) skill_db[idx].state = ST_FALCON; + else if( strcmpi(split[10],"cart")==0 ) skill_db[idx].state = ST_CART; + else if( strcmpi(split[10],"shield")==0 ) skill_db[idx].state = ST_SHIELD; + else if( strcmpi(split[10],"sight")==0 ) skill_db[idx].state = ST_SIGHT; + else if( strcmpi(split[10],"explosionspirits")==0 ) skill_db[idx].state = ST_EXPLOSIONSPIRITS; + else if( strcmpi(split[10],"cartboost")==0 ) skill_db[idx].state = ST_CARTBOOST; + else if( strcmpi(split[10],"recover_weight_rate")==0 ) skill_db[idx].state = ST_RECOV_WEIGHT_RATE; + else if( strcmpi(split[10],"move_enable")==0 ) skill_db[idx].state = ST_MOVE_ENABLE; + else if( strcmpi(split[10],"water")==0 ) skill_db[idx].state = ST_WATER; + /** + * New States + **/ + else if( strcmpi(split[10],"dragon")==0 ) skill_db[idx].state = ST_RIDINGDRAGON; + else if( strcmpi(split[10],"warg")==0 ) skill_db[idx].state = ST_WUG; + else if( strcmpi(split[10],"ridingwarg")==0 ) skill_db[idx].state = ST_RIDINGWUG; + else if( strcmpi(split[10],"mado")==0 ) skill_db[idx].state = ST_MADO; + else if( strcmpi(split[10],"elementalspirit")==0 ) skill_db[idx].state = ST_ELEMENTALSPIRIT; + else if (strcmpi(split[10], "poisonweapon") == 0) skill_db[idx].state = ST_POISONINGWEAPON; + else if (strcmpi(split[10], "rollingcutter") == 0) skill_db[idx].state = ST_ROLLINGCUTTER; + else if (strcmpi(split[10], "mh_fighting") == 0) skill_db[idx].state = ST_MH_FIGHTING; + else if (strcmpi(split[10], "mh_grappling") == 0) skill_db[idx].state = ST_MH_GRAPPLING; + + /** + * Unknown or no state + **/ + else skill_db[idx].state = ST_NONE; + + skill_split_atoi(split[11],skill_db[idx].spiritball); + for( j = 0; j < MAX_SKILL_ITEM_REQUIRE; j++ ) { + skill_db[idx].itemid[j] = atoi(split[12+ 2*j]); + skill_db[idx].amount[j] = atoi(split[13+ 2*j]); + } + + return true; +} + +static bool skill_parse_row_castdb(char* split[], int columns, int current) +{// skill_id,CastingTime,AfterCastActDelay,AfterCastWalkDelay,Duration1,Duration2 + uint16 skill_id = atoi(split[0]); + uint16 idx = skill_get_index(skill_id); + if( !idx ) // invalid skill id + return false; + + skill_split_atoi(split[1],skill_db[idx].cast); + skill_split_atoi(split[2],skill_db[idx].delay); + skill_split_atoi(split[3],skill_db[idx].walkdelay); + skill_split_atoi(split[4],skill_db[idx].upkeep_time); + skill_split_atoi(split[5],skill_db[idx].upkeep_time2); + skill_split_atoi(split[6],skill_db[idx].cooldown); +#ifdef RENEWAL_CAST + skill_split_atoi(split[7],skill_db[idx].fixed_cast); +#endif + return true; +} + +static bool skill_parse_row_castnodexdb(char* split[], int columns, int current) +{// Skill id,Cast,Delay (optional) + uint16 skill_id = atoi(split[0]); + uint16 idx = skill_get_index(skill_id); + if( !idx ) // invalid skill id + return false; + + skill_split_atoi(split[1],skill_db[idx].castnodex); + if( split[2] ) // optional column + skill_split_atoi(split[2],skill_db[idx].delaynodex); + + return true; +} + +static bool skill_parse_row_nocastdb(char* split[], int columns, int current) +{// skill_id,Flag + uint16 skill_id = atoi(split[0]); + uint16 idx = skill_get_index(skill_id); + if( !idx ) // invalid skill id + return false; + + skill_db[idx].nocast |= atoi(split[1]); + + return true; +} + +static bool skill_parse_row_unitdb(char* split[], int columns, int current) +{// ID,unit ID,unit ID 2,layout,range,interval,target,flag + uint16 skill_id = atoi(split[0]); + uint16 idx = skill_get_index(skill_id); + if( !idx ) // invalid skill id + return false; + + skill_db[idx].unit_id[0] = strtol(split[1],NULL,16); + skill_db[idx].unit_id[1] = strtol(split[2],NULL,16); + skill_split_atoi(split[3],skill_db[idx].unit_layout_type); + skill_split_atoi(split[4],skill_db[idx].unit_range); + skill_db[idx].unit_interval = atoi(split[5]); + + if( strcmpi(split[6],"noenemy")==0 ) skill_db[idx].unit_target = BCT_NOENEMY; + else if( strcmpi(split[6],"friend")==0 ) skill_db[idx].unit_target = BCT_NOENEMY; + else if( strcmpi(split[6],"party")==0 ) skill_db[idx].unit_target = BCT_PARTY; + else if( strcmpi(split[6],"ally")==0 ) skill_db[idx].unit_target = BCT_PARTY|BCT_GUILD; + else if( strcmpi(split[6],"guild")==0 ) skill_db[idx].unit_target = BCT_GUILD; + else if( strcmpi(split[6],"all")==0 ) skill_db[idx].unit_target = BCT_ALL; + else if( strcmpi(split[6],"enemy")==0 ) skill_db[idx].unit_target = BCT_ENEMY; + else if( strcmpi(split[6],"self")==0 ) skill_db[idx].unit_target = BCT_SELF; + else if( strcmpi(split[6],"noone")==0 ) skill_db[idx].unit_target = BCT_NOONE; + else skill_db[idx].unit_target = strtol(split[6],NULL,16); + + skill_db[idx].unit_flag = strtol(split[7],NULL,16); + + if (skill_db[idx].unit_flag&UF_DEFNOTENEMY && battle_config.defnotenemy) + skill_db[idx].unit_target = BCT_NOENEMY; + + //By default, target just characters. + skill_db[idx].unit_target |= BL_CHAR; + if (skill_db[idx].unit_flag&UF_NOPC) + skill_db[idx].unit_target &= ~BL_PC; + if (skill_db[idx].unit_flag&UF_NOMOB) + skill_db[idx].unit_target &= ~BL_MOB; + if (skill_db[idx].unit_flag&UF_SKILL) + skill_db[idx].unit_target |= BL_SKILL; + + return true; +} + +static bool skill_parse_row_producedb(char* split[], int columns, int current) +{// ProduceItemID,ItemLV,RequireSkill,Requireskill_lv,MaterialID1,MaterialAmount1,...... + int x,y; + + int i = atoi(split[0]); + if( !i ) + return false; + + skill_produce_db[current].nameid = i; + skill_produce_db[current].itemlv = atoi(split[1]); + skill_produce_db[current].req_skill = atoi(split[2]); + skill_produce_db[current].req_skill_lv = atoi(split[3]); + + for( x = 4, y = 0; x+1 < columns && split[x] && split[x+1] && y < MAX_PRODUCE_RESOURCE; x += 2, y++ ) + { + skill_produce_db[current].mat_id[y] = atoi(split[x]); + skill_produce_db[current].mat_amount[y] = atoi(split[x+1]); + } + + return true; +} + +static bool skill_parse_row_createarrowdb(char* split[], int columns, int current) +{// SourceID,MakeID1,MakeAmount1,...,MakeID5,MakeAmount5 + int x,y; + + int i = atoi(split[0]); + if( !i ) + return false; + + skill_arrow_db[current].nameid = i; + + for( x = 1, y = 0; x+1 < columns && split[x] && split[x+1] && y < MAX_ARROW_RESOURCE; x += 2, y++ ) + { + skill_arrow_db[current].cre_id[y] = atoi(split[x]); + skill_arrow_db[current].cre_amount[y] = atoi(split[x+1]); + } + + return true; +} +static bool skill_parse_row_spellbookdb(char* split[], int columns, int current) +{// skill_id,PreservePoints + + uint16 skill_id = atoi(split[0]); + int points = atoi(split[1]); + int nameid = atoi(split[2]); + + if( !skill_get_index(skill_id) || !skill_get_max(skill_id) ) + ShowError("spellbook_db: Invalid skill ID %d\n", skill_id); + if ( !skill_get_inf(skill_id) ) + ShowError("spellbook_db: Passive skills cannot be memorized (%d/%s)\n", skill_id, skill_get_name(skill_id)); + if( points < 1 ) + ShowError("spellbook_db: PreservePoints have to be 1 or above! (%d/%s)\n", skill_id, skill_get_name(skill_id)); + else + { + skill_spellbook_db[current].skill_id = skill_id; + skill_spellbook_db[current].point = points; + skill_spellbook_db[current].nameid = nameid; + + return true; + } + + return false; +} +static bool skill_parse_row_improvisedb(char* split[], int columns, int current) +{// SkillID,Rate + uint16 skill_id = atoi(split[0]); + short j = atoi(split[1]); + + if( !skill_get_index(skill_id) || !skill_get_max(skill_id) ) { + ShowError("skill_improvise_db: Invalid skill ID %d\n", skill_id); + return false; + } + if ( !skill_get_inf(skill_id) ) { + ShowError("skill_improvise_db: Passive skills cannot be casted (%d/%s)\n", skill_id, skill_get_name(skill_id)); + return false; + } + if( j < 1 ) { + ShowError("skill_improvise_db: Chances have to be 1 or above! (%d/%s)\n", skill_id, skill_get_name(skill_id)); + return false; + } + if( current >= MAX_SKILL_IMPROVISE_DB ) { + ShowError("skill_improvise_db: Maximum amount of entries reached (%d), increase MAX_SKILL_IMPROVISE_DB\n",MAX_SKILL_IMPROVISE_DB); + } + skill_improvise_db[current].skill_id = skill_id; + skill_improvise_db[current].per = j; // Still need confirm it. + + return true; +} +static bool skill_parse_row_magicmushroomdb(char* split[], int column, int current) +{// SkillID + uint16 skill_id = atoi(split[0]); + + if( !skill_get_index(skill_id) || !skill_get_max(skill_id) ) + { + ShowError("magicmushroom_db: Invalid skill ID %d\n", skill_id); + return false; + } + if ( !skill_get_inf(skill_id) ) + { + ShowError("magicmushroom_db: Passive skills cannot be casted (%d/%s)\n", skill_id, skill_get_name(skill_id)); + return false; + } + + skill_magicmushroom_db[current].skill_id = skill_id; + + return true; +} + +static bool skill_parse_row_reproducedb(char* split[], int column, int current) { + uint16 skill_id = atoi(split[0]); + uint16 idx = skill_get_index(skill_id); + if( !idx ) + return false; + + skill_reproduce_db[idx] = true; + + return true; +} + + +static bool skill_parse_row_abradb(char* split[], int columns, int current) +{// skill_id,DummyName,RequiredHocusPocusLevel,Rate + uint16 skill_id = atoi(split[0]); + if( !skill_get_index(skill_id) || !skill_get_max(skill_id) ) + { + ShowError("abra_db: Invalid skill ID %d\n", skill_id); + return false; + } + if ( !skill_get_inf(skill_id) ) + { + ShowError("abra_db: Passive skills cannot be casted (%d/%s)\n", skill_id, skill_get_name(skill_id)); + return false; + } + + skill_abra_db[current].skill_id = skill_id; + skill_abra_db[current].req_lv = atoi(split[2]); + skill_abra_db[current].per = atoi(split[3]); + + return true; +} + +static bool skill_parse_row_changematerialdb(char* split[], int columns, int current) +{// ProductID,BaseRate,MakeAmount1,MakeAmountRate1...,MakeAmount5,MakeAmountRate5 + uint16 skill_id = atoi(split[0]); + short j = atoi(split[1]); + int x,y; + + for(x=0; x<MAX_SKILL_PRODUCE_DB; x++){ + if( skill_produce_db[x].nameid == skill_id ) + if( skill_produce_db[x].req_skill == GN_CHANGEMATERIAL ) + break; + } + + if( x >= MAX_SKILL_PRODUCE_DB ){ + ShowError("changematerial_db: Not supported item ID(%d) for Change Material. \n", skill_id); + return false; + } + + if( current >= MAX_SKILL_PRODUCE_DB ) { + ShowError("skill_changematerial_db: Maximum amount of entries reached (%d), increase MAX_SKILL_PRODUCE_DB\n",MAX_SKILL_PRODUCE_DB); + } + + skill_changematerial_db[current].itemid = skill_id; + skill_changematerial_db[current].rate = j; + + for( x = 2, y = 0; x+1 < columns && split[x] && split[x+1] && y < 5; x += 2, y++ ) + { + skill_changematerial_db[current].qty[y] = atoi(split[x]); + skill_changematerial_db[current].qty_rate[y] = atoi(split[x+1]); + } + + return true; +} + +/*=============================== + * DB reading. + * skill_db.txt + * skill_require_db.txt + * skill_cast_db.txt + * skill_castnodex_db.txt + * skill_nocast_db.txt + * skill_unit_db.txt + * produce_db.txt + * create_arrow_db.txt + * abra_db.txt + *------------------------------*/ +static void skill_readdb(void) +{ + // init skill db structures + db_clear(skilldb_name2id); + memset(skill_db,0,sizeof(skill_db)); + memset(skill_produce_db,0,sizeof(skill_produce_db)); + memset(skill_arrow_db,0,sizeof(skill_arrow_db)); + memset(skill_abra_db,0,sizeof(skill_abra_db)); + memset(skill_spellbook_db,0,sizeof(skill_spellbook_db)); + memset(skill_magicmushroom_db,0,sizeof(skill_magicmushroom_db)); + memset(skill_reproduce_db,0,sizeof(skill_reproduce_db)); + memset(skill_changematerial_db,0,sizeof(skill_changematerial_db)); + + // load skill databases + safestrncpy(skill_db[0].name, "UNKNOWN_SKILL", sizeof(skill_db[0].name)); + safestrncpy(skill_db[0].desc, "Unknown Skill", sizeof(skill_db[0].desc)); + + sv_readdb(db_path, DBPATH"skill_db.txt" , ',', 17, 17, MAX_SKILL_DB, skill_parse_row_skilldb); + sv_readdb(db_path, DBPATH"skill_require_db.txt" , ',', 32, 32, MAX_SKILL_DB, skill_parse_row_requiredb); +#ifdef RENEWAL_CAST + sv_readdb(db_path, "re/skill_cast_db.txt" , ',', 8, 8, MAX_SKILL_DB, skill_parse_row_castdb); +#else + sv_readdb(db_path, "pre-re/skill_cast_db.txt" , ',', 7, 7, MAX_SKILL_DB, skill_parse_row_castdb); +#endif + sv_readdb(db_path, DBPATH"skill_castnodex_db.txt", ',', 2, 3, MAX_SKILL_DB, skill_parse_row_castnodexdb); + sv_readdb(db_path, DBPATH"skill_unit_db.txt" , ',', 8, 8, MAX_SKILL_DB, skill_parse_row_unitdb); + + sv_readdb(db_path, DBPATH"skill_nocast_db.txt" , ',', 2, 2, MAX_SKILL_DB, skill_parse_row_nocastdb); + + skill_init_unit_layout(); + sv_readdb(db_path, "produce_db.txt" , ',', 4, 4+2*MAX_PRODUCE_RESOURCE, MAX_SKILL_PRODUCE_DB, skill_parse_row_producedb); + sv_readdb(db_path, "create_arrow_db.txt" , ',', 1+2, 1+2*MAX_ARROW_RESOURCE, MAX_SKILL_ARROW_DB, skill_parse_row_createarrowdb); + sv_readdb(db_path, "abra_db.txt" , ',', 4, 4, MAX_SKILL_ABRA_DB, skill_parse_row_abradb); + //Warlock + sv_readdb(db_path, "spellbook_db.txt" , ',', 3, 3, MAX_SKILL_SPELLBOOK_DB, skill_parse_row_spellbookdb); + //Guillotine Cross + sv_readdb(db_path, "magicmushroom_db.txt" , ',', 1, 1, MAX_SKILL_MAGICMUSHROOM_DB, skill_parse_row_magicmushroomdb); + sv_readdb(db_path, "skill_reproduce_db.txt", ',', 1, 1, MAX_SKILL_DB, skill_parse_row_reproducedb); + sv_readdb(db_path, "skill_improvise_db.txt" , ',', 2, 2, MAX_SKILL_IMPROVISE_DB, skill_parse_row_improvisedb); + sv_readdb(db_path, "skill_changematerial_db.txt" , ',', 4, 4+2*5, MAX_SKILL_PRODUCE_DB, skill_parse_row_changematerialdb); + +} + +void skill_reload (void) { + struct s_mapiterator *iter; + struct map_session_data *sd; + skill_readdb(); + /* lets update all players skill tree : so that if any skill modes were changed they're properly updated */ + iter = mapit_getallusers(); + for( sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC*)mapit_next(iter) ) + clif_skillinfoblock(sd); + mapit_free(iter); + +} + +/*========================================== + * + *------------------------------------------*/ +int do_init_skill (void) +{ + skilldb_name2id = strdb_alloc(DB_OPT_DUP_KEY|DB_OPT_RELEASE_DATA, 0); + skill_readdb(); + + group_db = idb_alloc(DB_OPT_BASE); + skillunit_db = idb_alloc(DB_OPT_BASE); + skillcd_db = idb_alloc(DB_OPT_RELEASE_DATA); + skillusave_db = idb_alloc(DB_OPT_RELEASE_DATA); + skill_unit_ers = ers_new(sizeof(struct skill_unit_group),"skill.c::skill_unit_ers",ERS_OPT_NONE); + skill_timer_ers = ers_new(sizeof(struct skill_timerskill),"skill.c::skill_timer_ers",ERS_OPT_NONE); + + 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_blockpc_end, "skill_blockpc_end"); + + add_timer_interval(gettick()+SKILLUNITTIMER_INTERVAL,skill_unit_timer,0,0,SKILLUNITTIMER_INTERVAL); + + return 0; +} + +int do_final_skill(void) +{ + db_destroy(skilldb_name2id); + db_destroy(group_db); + db_destroy(skillunit_db); + db_destroy(skillcd_db); + db_destroy(skillusave_db); + ers_destroy(skill_unit_ers); + ers_destroy(skill_timer_ers); + return 0; +} diff --git a/src/map/status.c b/src/map/status.c index 649cfa1ae..cc322b686 100644 --- a/src/map/status.c +++ b/src/map/status.c @@ -1,11294 +1,11290 @@ -// Copyright (c) Athena Dev Teams - Licensed under GNU GPL -// For more information, see LICENCE in the main folder - -#include "../common/cbasetypes.h" -#include "../common/timer.h" -#include "../common/nullpo.h" -#include "../common/random.h" -#include "../common/showmsg.h" -#include "../common/malloc.h" -#include "../common/utils.h" -#include "../common/ers.h" -#include "../common/strlib.h" - -#include "map.h" -#include "path.h" -#include "pc.h" -#include "pet.h" -#include "npc.h" -#include "mob.h" -#include "clif.h" -#include "guild.h" -#include "skill.h" -#include "itemdb.h" -#include "battle.h" -#include "chrif.h" -#include "skill.h" -#include "status.h" -#include "script.h" -#include "unit.h" -#include "homunculus.h" -#include "mercenary.h" -#include "elemental.h" -#include "vending.h" - -#include <time.h> -#include <stdio.h> -#include <stdlib.h> -#include <memory.h> -#include <string.h> -#include <math.h> - -//Regen related flags. -enum e_regen -{ - RGN_HP = 0x01, - RGN_SP = 0x02, - RGN_SHP = 0x04, - RGN_SSP = 0x08, -}; - -static int max_weight_base[CLASS_COUNT]; -static int hp_coefficient[CLASS_COUNT]; -static int hp_coefficient2[CLASS_COUNT]; -static int hp_sigma_val[CLASS_COUNT][MAX_LEVEL+1]; -static int sp_coefficient[CLASS_COUNT]; -#ifdef RENEWAL_ASPD -static int aspd_base[CLASS_COUNT][MAX_WEAPON_TYPE+1]; -#else -static int aspd_base[CLASS_COUNT][MAX_WEAPON_TYPE]; //[blackhole89] -#endif - -// bonus values and upgrade chances for refining equipment -static struct { - int chance[MAX_REFINE]; // success chance - int bonus[MAX_REFINE]; // cumulative fixed bonus damage - int randombonus_max[MAX_REFINE]; // cumulative maximum random bonus damage -} refine_info[REFINE_TYPE_MAX]; - -static int atkmods[3][MAX_WEAPON_TYPE]; //ATK weapon modification for size (size_fix.txt) -static char job_bonus[CLASS_COUNT][MAX_LEVEL]; - -static struct eri *sc_data_ers; //For sc_data entries -static struct status_data dummy_status; - -int current_equip_item_index; //Contains inventory index of an equipped item. To pass it into the EQUP_SCRIPT [Lupus] -int current_equip_card_id; //To prevent card-stacking (from jA) [Skotlex] -//we need it for new cards 15 Feb 2005, to check if the combo cards are insrerted into the CURRENT weapon only -//to avoid cards exploits - -static sc_type SkillStatusChangeTable[MAX_SKILL]; // skill -> status -static int StatusIconChangeTable[SC_MAX]; // status -> "icon" (icon is a bit of a misnomer, since there exist values with no icon associated) -static unsigned int StatusChangeFlagTable[SC_MAX]; // status -> flags -static int StatusSkillChangeTable[SC_MAX]; // status -> skill -static int StatusRelevantBLTypes[SI_MAX]; // "icon" -> enum bl_type (for clif_status_change to identify for which bl types to send packets) -static unsigned int StatusChangeStateTable[SC_MAX]; // status -> flags - - -/** - * Returns the status change associated with a skill. - * @param skill The skill to look up - * @return The status registered for this skill - **/ -sc_type status_skill2sc(int skill) -{ - int idx = skill_get_index(skill); - if( idx == 0 ) { - ShowError("status_skill2sc: Unsupported skill id %d\n", skill); - return SC_NONE; - } - return SkillStatusChangeTable[idx]; -} - -/** - * Returns the FIRST skill (in order of definition in initChangeTables) to use a given status change. - * Utilized for various duration lookups. Use with caution! - * @param sc The status to look up - * @return A skill associated with the status - **/ -int status_sc2skill(sc_type sc) -{ - if( sc < 0 || sc >= SC_MAX ) { - ShowError("status_sc2skill: Unsupported status change id %d\n", sc); - return 0; - } - - return StatusSkillChangeTable[sc]; -} - -/** - * Returns the status calculation flag associated with a given status change. - * @param sc The status to look up - * @return The scb_flag registered for this status (see enum scb_flag) - **/ -unsigned int status_sc2scb_flag(sc_type sc) -{ - if( sc < 0 || sc >= SC_MAX ) { - ShowError("status_sc2scb_flag: Unsupported status change id %d\n", sc); - return SCB_NONE; - } - - return StatusChangeFlagTable[sc]; -} - -/** - * Returns the bl types which require a status change packet to be sent for a given client status identifier. - * @param type The client-side status identifier to look up (see enum si_type) - * @return The bl types relevant to the type (see enum bl_type) - **/ -int status_type2relevant_bl_types(int type) -{ - if( type < 0 || type >= SI_MAX ) { - ShowError("status_type2relevant_bl_types: Unsupported type %d\n", type); - return SI_BLANK; - } - - return StatusRelevantBLTypes[type]; -} - -#define add_sc(skill,sc) set_sc(skill,sc,SI_BLANK,SCB_NONE) -// indicates that the status displays a visual effect for the affected unit, and should be sent to the client for all supported units -#define set_sc_with_vfx(skill, sc, icon, flag) set_sc((skill), (sc), (icon), (flag)); if((icon) < SI_MAX) StatusRelevantBLTypes[(icon)] |= BL_SCEFFECT - -static void set_sc(uint16 skill_id, sc_type sc, int icon, unsigned int flag) -{ - uint16 idx = skill_get_index(skill_id); - if( idx == 0 ) { - ShowError("set_sc: Unsupported skill id %d\n", skill_id); - return; - } - if( sc < 0 || sc >= SC_MAX ) { - ShowError("set_sc: Unsupported status change id %d\n", sc); - return; - } - - if( StatusSkillChangeTable[sc] == 0 ) - StatusSkillChangeTable[sc] = skill_id; - if( StatusIconChangeTable[sc] == SI_BLANK ) - StatusIconChangeTable[sc] = icon; - StatusChangeFlagTable[sc] |= flag; - - if( SkillStatusChangeTable[idx] == SC_NONE ) - SkillStatusChangeTable[idx] = sc; -} - -void initChangeTables(void) { - int i; - - for (i = 0; i < SC_MAX; i++) - StatusIconChangeTable[i] = SI_BLANK; - - for (i = 0; i < MAX_SKILL; i++) - SkillStatusChangeTable[i] = SC_NONE; - - for (i = 0; i < SI_MAX; i++) - StatusRelevantBLTypes[i] = BL_PC; - - memset(StatusSkillChangeTable, 0, sizeof(StatusSkillChangeTable)); - memset(StatusChangeFlagTable, 0, sizeof(StatusChangeFlagTable)); - memset(StatusChangeStateTable, 0, sizeof(StatusChangeStateTable)); - - - //First we define the skill for common ailments. These are used in skill_additional_effect through sc cards. [Skotlex] - set_sc( NPC_PETRIFYATTACK , SC_STONE , SI_BLANK , SCB_DEF_ELE|SCB_DEF|SCB_MDEF ); - set_sc( NPC_WIDEFREEZE , SC_FREEZE , SI_BLANK , SCB_DEF_ELE|SCB_DEF|SCB_MDEF ); - set_sc( NPC_STUNATTACK , SC_STUN , SI_BLANK , SCB_NONE ); - set_sc( NPC_SLEEPATTACK , SC_SLEEP , SI_BLANK , SCB_NONE ); - set_sc( NPC_POISON , SC_POISON , SI_BLANK , SCB_DEF2|SCB_REGEN ); - set_sc( NPC_CURSEATTACK , SC_CURSE , SI_BLANK , SCB_LUK|SCB_BATK|SCB_WATK|SCB_SPEED ); - set_sc( NPC_SILENCEATTACK , SC_SILENCE , SI_BLANK , SCB_NONE ); - set_sc( NPC_WIDECONFUSE , SC_CONFUSION , SI_BLANK , SCB_NONE ); - set_sc( NPC_BLINDATTACK , SC_BLIND , SI_BLANK , SCB_HIT|SCB_FLEE ); - set_sc( NPC_BLEEDING , SC_BLEEDING , SI_BLEEDING , SCB_REGEN ); - set_sc( NPC_POISON , SC_DPOISON , SI_BLANK , SCB_DEF2|SCB_REGEN ); - - //The main status definitions - add_sc( SM_BASH , SC_STUN ); - set_sc( SM_PROVOKE , SC_PROVOKE , SI_PROVOKE , SCB_DEF|SCB_DEF2|SCB_BATK|SCB_WATK ); - add_sc( SM_MAGNUM , SC_WATK_ELEMENT ); - set_sc( SM_ENDURE , SC_ENDURE , SI_ENDURE , SCB_MDEF|SCB_DSPD ); - add_sc( MG_SIGHT , SC_SIGHT ); - add_sc( MG_SAFETYWALL , SC_SAFETYWALL ); - add_sc( MG_FROSTDIVER , SC_FREEZE ); - add_sc( MG_STONECURSE , SC_STONE ); - add_sc( AL_RUWACH , SC_RUWACH ); - add_sc( AL_PNEUMA , SC_PNEUMA ); - set_sc( AL_INCAGI , SC_INCREASEAGI , SI_INCREASEAGI , SCB_AGI|SCB_SPEED ); - set_sc( AL_DECAGI , SC_DECREASEAGI , SI_DECREASEAGI , SCB_AGI|SCB_SPEED ); - set_sc( AL_CRUCIS , SC_SIGNUMCRUCIS , SI_SIGNUMCRUCIS , SCB_DEF ); - set_sc( AL_ANGELUS , SC_ANGELUS , SI_ANGELUS , SCB_DEF2 ); - set_sc( AL_BLESSING , SC_BLESSING , SI_BLESSING , SCB_STR|SCB_INT|SCB_DEX ); - set_sc( AC_CONCENTRATION , SC_CONCENTRATE , SI_CONCENTRATE , SCB_AGI|SCB_DEX ); - set_sc( TF_HIDING , SC_HIDING , SI_HIDING , SCB_SPEED ); - add_sc( TF_POISON , SC_POISON ); - set_sc( KN_TWOHANDQUICKEN , SC_TWOHANDQUICKEN , SI_TWOHANDQUICKEN , SCB_ASPD ); - add_sc( KN_AUTOCOUNTER , SC_AUTOCOUNTER ); - set_sc( PR_IMPOSITIO , SC_IMPOSITIO , SI_IMPOSITIO , SCB_WATK ); - set_sc( PR_SUFFRAGIUM , SC_SUFFRAGIUM , SI_SUFFRAGIUM , SCB_NONE ); - set_sc( PR_ASPERSIO , SC_ASPERSIO , SI_ASPERSIO , SCB_ATK_ELE ); - set_sc( PR_BENEDICTIO , SC_BENEDICTIO , SI_BENEDICTIO , SCB_DEF_ELE ); - set_sc( PR_SLOWPOISON , SC_SLOWPOISON , SI_SLOWPOISON , SCB_REGEN ); - set_sc( PR_KYRIE , SC_KYRIE , SI_KYRIE , SCB_NONE ); - set_sc( PR_MAGNIFICAT , SC_MAGNIFICAT , SI_MAGNIFICAT , SCB_REGEN ); - set_sc( PR_GLORIA , SC_GLORIA , SI_GLORIA , SCB_LUK ); - add_sc( PR_LEXDIVINA , SC_SILENCE ); - set_sc( PR_LEXAETERNA , SC_AETERNA , SI_AETERNA , SCB_NONE ); - add_sc( WZ_METEOR , SC_STUN ); - add_sc( WZ_VERMILION , SC_BLIND ); - add_sc( WZ_FROSTNOVA , SC_FREEZE ); - add_sc( WZ_STORMGUST , SC_FREEZE ); - set_sc( WZ_QUAGMIRE , SC_QUAGMIRE , SI_QUAGMIRE , SCB_AGI|SCB_DEX|SCB_ASPD|SCB_SPEED ); - set_sc( BS_ADRENALINE , SC_ADRENALINE , SI_ADRENALINE , SCB_ASPD ); - set_sc( BS_WEAPONPERFECT , SC_WEAPONPERFECTION, SI_WEAPONPERFECTION, SCB_NONE ); - set_sc( BS_OVERTHRUST , SC_OVERTHRUST , SI_OVERTHRUST , SCB_NONE ); - set_sc( BS_MAXIMIZE , SC_MAXIMIZEPOWER , SI_MAXIMIZEPOWER , SCB_REGEN ); - add_sc( HT_LANDMINE , SC_STUN ); - add_sc( HT_ANKLESNARE , SC_ANKLE ); - add_sc( HT_SANDMAN , SC_SLEEP ); - add_sc( HT_FLASHER , SC_BLIND ); - add_sc( HT_FREEZINGTRAP , SC_FREEZE ); - set_sc( AS_CLOAKING , SC_CLOAKING , SI_CLOAKING , SCB_CRI|SCB_SPEED ); - add_sc( AS_SONICBLOW , SC_STUN ); - set_sc( AS_ENCHANTPOISON , SC_ENCPOISON , SI_ENCPOISON , SCB_ATK_ELE ); - set_sc( AS_POISONREACT , SC_POISONREACT , SI_POISONREACT , SCB_NONE ); - add_sc( AS_VENOMDUST , SC_POISON ); - add_sc( AS_SPLASHER , SC_SPLASHER ); - set_sc( NV_TRICKDEAD , SC_TRICKDEAD , SI_TRICKDEAD , SCB_REGEN ); - set_sc( SM_AUTOBERSERK , SC_AUTOBERSERK , SI_AUTOBERSERK , SCB_NONE ); - add_sc( TF_SPRINKLESAND , SC_BLIND ); - add_sc( TF_THROWSTONE , SC_STUN ); - set_sc( MC_LOUD , SC_LOUD , SI_LOUD , SCB_STR ); - set_sc( MG_ENERGYCOAT , SC_ENERGYCOAT , SI_ENERGYCOAT , SCB_NONE ); - set_sc( NPC_EMOTION , SC_MODECHANGE , SI_BLANK , SCB_MODE ); - add_sc( NPC_EMOTION_ON , SC_MODECHANGE ); - set_sc( NPC_ATTRICHANGE , SC_ELEMENTALCHANGE , SI_ARMOR_PROPERTY , SCB_DEF_ELE ); - add_sc( NPC_CHANGEWATER , SC_ELEMENTALCHANGE ); - add_sc( NPC_CHANGEGROUND , SC_ELEMENTALCHANGE ); - add_sc( NPC_CHANGEFIRE , SC_ELEMENTALCHANGE ); - add_sc( NPC_CHANGEWIND , SC_ELEMENTALCHANGE ); - add_sc( NPC_CHANGEPOISON , SC_ELEMENTALCHANGE ); - add_sc( NPC_CHANGEHOLY , SC_ELEMENTALCHANGE ); - add_sc( NPC_CHANGEDARKNESS , SC_ELEMENTALCHANGE ); - add_sc( NPC_CHANGETELEKINESIS, SC_ELEMENTALCHANGE ); - add_sc( NPC_POISON , SC_POISON ); - add_sc( NPC_BLINDATTACK , SC_BLIND ); - add_sc( NPC_SILENCEATTACK , SC_SILENCE ); - add_sc( NPC_STUNATTACK , SC_STUN ); - add_sc( NPC_PETRIFYATTACK , SC_STONE ); - add_sc( NPC_CURSEATTACK , SC_CURSE ); - add_sc( NPC_SLEEPATTACK , SC_SLEEP ); - add_sc( NPC_MAGICALATTACK , SC_MAGICALATTACK ); - set_sc( NPC_KEEPING , SC_KEEPING , SI_BLANK , SCB_DEF ); - add_sc( NPC_DARKBLESSING , SC_COMA ); - set_sc( NPC_BARRIER , SC_BARRIER , SI_BLANK , SCB_MDEF|SCB_DEF ); - add_sc( NPC_DEFENDER , SC_ARMOR ); - add_sc( NPC_LICK , SC_STUN ); - set_sc( NPC_HALLUCINATION , SC_HALLUCINATION , SI_HALLUCINATION , SCB_NONE ); - add_sc( NPC_REBIRTH , SC_REBIRTH ); - add_sc( RG_RAID , SC_STUN ); -#ifdef RENEWAL - add_sc( RG_RAID , SC_RAID ); - add_sc( RG_BACKSTAP , SC_STUN ); -#endif - set_sc( RG_STRIPWEAPON , SC_STRIPWEAPON , SI_STRIPWEAPON , SCB_WATK ); - set_sc( RG_STRIPSHIELD , SC_STRIPSHIELD , SI_STRIPSHIELD , SCB_DEF ); - set_sc( RG_STRIPARMOR , SC_STRIPARMOR , SI_STRIPARMOR , SCB_VIT ); - set_sc( RG_STRIPHELM , SC_STRIPHELM , SI_STRIPHELM , SCB_INT ); - add_sc( AM_ACIDTERROR , SC_BLEEDING ); - set_sc( AM_CP_WEAPON , SC_CP_WEAPON , SI_CP_WEAPON , SCB_NONE ); - set_sc( AM_CP_SHIELD , SC_CP_SHIELD , SI_CP_SHIELD , SCB_NONE ); - set_sc( AM_CP_ARMOR , SC_CP_ARMOR , SI_CP_ARMOR , SCB_NONE ); - set_sc( AM_CP_HELM , SC_CP_HELM , SI_CP_HELM , SCB_NONE ); - set_sc( CR_AUTOGUARD , SC_AUTOGUARD , SI_AUTOGUARD , SCB_NONE ); - add_sc( CR_SHIELDCHARGE , SC_STUN ); - set_sc( CR_REFLECTSHIELD , SC_REFLECTSHIELD , SI_REFLECTSHIELD , SCB_NONE ); - add_sc( CR_HOLYCROSS , SC_BLIND ); - add_sc( CR_GRANDCROSS , SC_BLIND ); - add_sc( CR_DEVOTION , SC_DEVOTION ); - set_sc( CR_PROVIDENCE , SC_PROVIDENCE , SI_PROVIDENCE , SCB_ALL ); - set_sc( CR_DEFENDER , SC_DEFENDER , SI_DEFENDER , SCB_SPEED|SCB_ASPD ); - set_sc( CR_SPEARQUICKEN , SC_SPEARQUICKEN , SI_SPEARQUICKEN , SCB_ASPD|SCB_CRI|SCB_FLEE ); - set_sc( MO_STEELBODY , SC_STEELBODY , SI_STEELBODY , SCB_DEF|SCB_MDEF|SCB_ASPD|SCB_SPEED ); - add_sc( MO_BLADESTOP , SC_BLADESTOP_WAIT ); - add_sc( MO_BLADESTOP , SC_BLADESTOP ); - set_sc( MO_EXPLOSIONSPIRITS , SC_EXPLOSIONSPIRITS, SI_EXPLOSIONSPIRITS, SCB_CRI|SCB_REGEN ); - set_sc( MO_EXTREMITYFIST , SC_EXTREMITYFIST , SI_BLANK , SCB_REGEN ); -#ifdef RENEWAL - set_sc( MO_EXTREMITYFIST , SC_EXTREMITYFIST2 , SI_EXTREMITYFIST , SCB_NONE ); -#endif - add_sc( SA_MAGICROD , SC_MAGICROD ); - set_sc( SA_AUTOSPELL , SC_AUTOSPELL , SI_AUTOSPELL , SCB_NONE ); - set_sc( SA_FLAMELAUNCHER , SC_FIREWEAPON , SI_FIREWEAPON , SCB_ATK_ELE ); - set_sc( SA_FROSTWEAPON , SC_WATERWEAPON , SI_WATERWEAPON , SCB_ATK_ELE ); - set_sc( SA_LIGHTNINGLOADER , SC_WINDWEAPON , SI_WINDWEAPON , SCB_ATK_ELE ); - set_sc( SA_SEISMICWEAPON , SC_EARTHWEAPON , SI_EARTHWEAPON , SCB_ATK_ELE ); - set_sc( SA_VOLCANO , SC_VOLCANO , SI_LANDENDOW , SCB_WATK ); - set_sc( SA_DELUGE , SC_DELUGE , SI_LANDENDOW , SCB_MAXHP ); - set_sc( SA_VIOLENTGALE , SC_VIOLENTGALE , SI_LANDENDOW , SCB_FLEE ); - add_sc( SA_REVERSEORCISH , SC_ORCISH ); - add_sc( SA_COMA , SC_COMA ); - set_sc( BD_ENCORE , SC_DANCING , SI_BLANK , SCB_SPEED|SCB_REGEN ); - add_sc( BD_RICHMANKIM , SC_RICHMANKIM ); - set_sc( BD_ETERNALCHAOS , SC_ETERNALCHAOS , SI_BLANK , SCB_DEF2 ); - set_sc( BD_DRUMBATTLEFIELD , SC_DRUMBATTLE , SI_BLANK , SCB_WATK|SCB_DEF ); - set_sc( BD_RINGNIBELUNGEN , SC_NIBELUNGEN , SI_BLANK , SCB_WATK ); - add_sc( BD_ROKISWEIL , SC_ROKISWEIL ); - add_sc( BD_INTOABYSS , SC_INTOABYSS ); - set_sc( BD_SIEGFRIED , SC_SIEGFRIED , SI_BLANK , SCB_ALL ); - add_sc( BA_FROSTJOKER , SC_FREEZE ); - set_sc( BA_WHISTLE , SC_WHISTLE , SI_BLANK , SCB_FLEE|SCB_FLEE2 ); - set_sc( BA_ASSASSINCROSS , SC_ASSNCROS , SI_BLANK , SCB_ASPD ); - add_sc( BA_POEMBRAGI , SC_POEMBRAGI ); - set_sc( BA_APPLEIDUN , SC_APPLEIDUN , SI_BLANK , SCB_MAXHP ); - add_sc( DC_SCREAM , SC_STUN ); - set_sc( DC_HUMMING , SC_HUMMING , SI_BLANK , SCB_HIT ); - set_sc( DC_DONTFORGETME , SC_DONTFORGETME , SI_BLANK , SCB_SPEED|SCB_ASPD ); - set_sc( DC_FORTUNEKISS , SC_FORTUNE , SI_BLANK , SCB_CRI ); - set_sc( DC_SERVICEFORYOU , SC_SERVICE4U , SI_BLANK , SCB_ALL ); - add_sc( NPC_DARKCROSS , SC_BLIND ); - add_sc( NPC_GRANDDARKNESS , SC_BLIND ); - set_sc( NPC_STOP , SC_STOP , SI_STOP , SCB_NONE ); - set_sc( NPC_WEAPONBRAKER , SC_BROKENWEAPON , SI_BROKENWEAPON , SCB_NONE ); - set_sc( NPC_ARMORBRAKE , SC_BROKENARMOR , SI_BROKENARMOR , SCB_NONE ); - set_sc( NPC_CHANGEUNDEAD , SC_CHANGEUNDEAD , SI_UNDEAD , SCB_DEF_ELE ); - set_sc( NPC_POWERUP , SC_INCHITRATE , SI_BLANK , SCB_HIT ); - set_sc( NPC_AGIUP , SC_INCFLEERATE , SI_BLANK , SCB_FLEE ); - add_sc( NPC_INVISIBLE , SC_CLOAKING ); - set_sc( LK_AURABLADE , SC_AURABLADE , SI_AURABLADE , SCB_NONE ); - set_sc( LK_PARRYING , SC_PARRYING , SI_PARRYING , SCB_NONE ); - set_sc( LK_CONCENTRATION , SC_CONCENTRATION , SI_CONCENTRATION , SCB_BATK|SCB_WATK|SCB_HIT|SCB_DEF|SCB_DEF2|SCB_MDEF|SCB_DSPD ); - set_sc( LK_TENSIONRELAX , SC_TENSIONRELAX , SI_TENSIONRELAX , SCB_REGEN ); - set_sc( LK_BERSERK , SC_BERSERK , SI_BERSERK , SCB_DEF|SCB_DEF2|SCB_MDEF|SCB_MDEF2|SCB_FLEE|SCB_SPEED|SCB_ASPD|SCB_MAXHP|SCB_REGEN ); - set_sc( HP_ASSUMPTIO , SC_ASSUMPTIO , SI_ASSUMPTIO , SCB_NONE ); - add_sc( HP_BASILICA , SC_BASILICA ); - set_sc( HW_MAGICPOWER , SC_MAGICPOWER , SI_MAGICPOWER , SCB_MATK ); - add_sc( PA_SACRIFICE , SC_SACRIFICE ); - set_sc( PA_GOSPEL , SC_GOSPEL , SI_BLANK , SCB_SPEED|SCB_ASPD ); - add_sc( PA_GOSPEL , SC_SCRESIST ); - add_sc( CH_TIGERFIST , SC_STOP ); - set_sc( ASC_EDP , SC_EDP , SI_EDP , SCB_NONE ); - set_sc( SN_SIGHT , SC_TRUESIGHT , SI_TRUESIGHT , SCB_STR|SCB_AGI|SCB_VIT|SCB_INT|SCB_DEX|SCB_LUK|SCB_CRI|SCB_HIT ); - set_sc( SN_WINDWALK , SC_WINDWALK , SI_WINDWALK , SCB_FLEE|SCB_SPEED ); - set_sc( WS_MELTDOWN , SC_MELTDOWN , SI_MELTDOWN , SCB_NONE ); - set_sc( WS_CARTBOOST , SC_CARTBOOST , SI_CARTBOOST , SCB_SPEED ); - set_sc( ST_CHASEWALK , SC_CHASEWALK , SI_BLANK , SCB_SPEED ); - set_sc( ST_REJECTSWORD , SC_REJECTSWORD , SI_REJECTSWORD , SCB_NONE ); - add_sc( ST_REJECTSWORD , SC_AUTOCOUNTER ); - set_sc( CG_MARIONETTE , SC_MARIONETTE , SI_MARIONETTE , SCB_STR|SCB_AGI|SCB_VIT|SCB_INT|SCB_DEX|SCB_LUK ); - set_sc( CG_MARIONETTE , SC_MARIONETTE2 , SI_MARIONETTE2 , SCB_STR|SCB_AGI|SCB_VIT|SCB_INT|SCB_DEX|SCB_LUK ); - add_sc( LK_SPIRALPIERCE , SC_STOP ); - add_sc( LK_HEADCRUSH , SC_BLEEDING ); - set_sc( LK_JOINTBEAT , SC_JOINTBEAT , SI_JOINTBEAT , SCB_BATK|SCB_DEF2|SCB_SPEED|SCB_ASPD ); - add_sc( HW_NAPALMVULCAN , SC_CURSE ); - set_sc( PF_MINDBREAKER , SC_MINDBREAKER , SI_BLANK , SCB_MATK|SCB_MDEF2 ); - add_sc( PF_MEMORIZE , SC_MEMORIZE ); - add_sc( PF_FOGWALL , SC_FOGWALL ); - set_sc( PF_SPIDERWEB , SC_SPIDERWEB , SI_BLANK , SCB_FLEE ); - set_sc( WE_BABY , SC_BABY , SI_BABY , SCB_NONE ); - set_sc( TK_RUN , SC_RUN , SI_RUN , SCB_SPEED|SCB_DSPD ); - set_sc( TK_RUN , SC_SPURT , SI_SPURT , SCB_STR ); - set_sc( TK_READYSTORM , SC_READYSTORM , SI_READYSTORM , SCB_NONE ); - set_sc( TK_READYDOWN , SC_READYDOWN , SI_READYDOWN , SCB_NONE ); - add_sc( TK_DOWNKICK , SC_STUN ); - set_sc( TK_READYTURN , SC_READYTURN , SI_READYTURN , SCB_NONE ); - set_sc( TK_READYCOUNTER , SC_READYCOUNTER , SI_READYCOUNTER , SCB_NONE ); - set_sc( TK_DODGE , SC_DODGE , SI_DODGE , SCB_NONE ); - set_sc( TK_SPTIME , SC_EARTHSCROLL , SI_EARTHSCROLL , SCB_NONE ); - add_sc( TK_SEVENWIND , SC_SEVENWIND ); - set_sc( TK_SEVENWIND , SC_GHOSTWEAPON , SI_GHOSTWEAPON , SCB_ATK_ELE ); - set_sc( TK_SEVENWIND , SC_SHADOWWEAPON , SI_SHADOWWEAPON , SCB_ATK_ELE ); - set_sc( SG_SUN_WARM , SC_WARM , SI_WARM , SCB_NONE ); - add_sc( SG_MOON_WARM , SC_WARM ); - add_sc( SG_STAR_WARM , SC_WARM ); - set_sc( SG_SUN_COMFORT , SC_SUN_COMFORT , SI_SUN_COMFORT , SCB_DEF2 ); - set_sc( SG_MOON_COMFORT , SC_MOON_COMFORT , SI_MOON_COMFORT , SCB_FLEE ); - set_sc( SG_STAR_COMFORT , SC_STAR_COMFORT , SI_STAR_COMFORT , SCB_ASPD ); - add_sc( SG_FRIEND , SC_SKILLRATE_UP ); - set_sc( SG_KNOWLEDGE , SC_KNOWLEDGE , SI_BLANK , SCB_ALL ); - set_sc( SG_FUSION , SC_FUSION , SI_BLANK , SCB_SPEED ); - set_sc( BS_ADRENALINE2 , SC_ADRENALINE2 , SI_ADRENALINE2 , SCB_ASPD ); - set_sc( SL_KAIZEL , SC_KAIZEL , SI_KAIZEL , SCB_NONE ); - set_sc( SL_KAAHI , SC_KAAHI , SI_KAAHI , SCB_NONE ); - set_sc( SL_KAUPE , SC_KAUPE , SI_KAUPE , SCB_NONE ); - set_sc( SL_KAITE , SC_KAITE , SI_KAITE , SCB_NONE ); - add_sc( SL_STUN , SC_STUN ); - set_sc( SL_SWOO , SC_SWOO , SI_BLANK , SCB_SPEED ); - set_sc( SL_SKE , SC_SKE , SI_BLANK , SCB_BATK|SCB_WATK|SCB_DEF|SCB_DEF2 ); - set_sc( SL_SKA , SC_SKA , SI_BLANK , SCB_DEF|SCB_MDEF|SCB_ASPD ); - set_sc( SL_SMA , SC_SMA , SI_SMA , SCB_NONE ); - set_sc( SM_SELFPROVOKE , SC_PROVOKE , SI_PROVOKE , SCB_DEF|SCB_DEF2|SCB_BATK|SCB_WATK ); - set_sc( ST_PRESERVE , SC_PRESERVE , SI_PRESERVE , SCB_NONE ); - set_sc( PF_DOUBLECASTING , SC_DOUBLECAST , SI_DOUBLECAST , SCB_NONE ); - set_sc( HW_GRAVITATION , SC_GRAVITATION , SI_BLANK , SCB_ASPD ); - add_sc( WS_CARTTERMINATION , SC_STUN ); - set_sc( WS_OVERTHRUSTMAX , SC_MAXOVERTHRUST , SI_MAXOVERTHRUST , SCB_NONE ); - set_sc( CG_LONGINGFREEDOM , SC_LONGING , SI_BLANK , SCB_SPEED|SCB_ASPD ); - add_sc( CG_HERMODE , SC_HERMODE ); - set_sc( ITEM_ENCHANTARMS , SC_ENCHANTARMS , SI_BLANK , SCB_ATK_ELE ); - set_sc( SL_HIGH , SC_SPIRIT , SI_SPIRIT , SCB_ALL ); - set_sc( KN_ONEHAND , SC_ONEHAND , SI_ONEHAND , SCB_ASPD ); - set_sc( GS_FLING , SC_FLING , SI_BLANK , SCB_DEF|SCB_DEF2 ); - add_sc( GS_CRACKER , SC_STUN ); - add_sc( GS_DISARM , SC_STRIPWEAPON ); - add_sc( GS_PIERCINGSHOT , SC_BLEEDING ); - set_sc( GS_MADNESSCANCEL , SC_MADNESSCANCEL , SI_MADNESSCANCEL , SCB_BATK|SCB_ASPD ); - set_sc( GS_ADJUSTMENT , SC_ADJUSTMENT , SI_ADJUSTMENT , SCB_HIT|SCB_FLEE ); - set_sc( GS_INCREASING , SC_INCREASING , SI_ACCURACY , SCB_AGI|SCB_DEX|SCB_HIT ); - set_sc( GS_GATLINGFEVER , SC_GATLINGFEVER , SI_GATLINGFEVER , SCB_BATK|SCB_FLEE|SCB_SPEED|SCB_ASPD ); - set_sc( NJ_TATAMIGAESHI , SC_TATAMIGAESHI , SI_BLANK , SCB_NONE ); - set_sc( NJ_SUITON , SC_SUITON , SI_BLANK , SCB_AGI|SCB_SPEED ); - add_sc( NJ_HYOUSYOURAKU , SC_FREEZE ); - set_sc( NJ_NEN , SC_NEN , SI_NEN , SCB_STR|SCB_INT ); - set_sc( NJ_UTSUSEMI , SC_UTSUSEMI , SI_UTSUSEMI , SCB_NONE ); - set_sc( NJ_BUNSINJYUTSU , SC_BUNSINJYUTSU , SI_BUNSINJYUTSU , SCB_DYE ); - - add_sc( NPC_ICEBREATH , SC_FREEZE ); - add_sc( NPC_ACIDBREATH , SC_POISON ); - add_sc( NPC_HELLJUDGEMENT , SC_CURSE ); - add_sc( NPC_WIDESILENCE , SC_SILENCE ); - add_sc( NPC_WIDEFREEZE , SC_FREEZE ); - add_sc( NPC_WIDEBLEEDING , SC_BLEEDING ); - add_sc( NPC_WIDESTONE , SC_STONE ); - add_sc( NPC_WIDECONFUSE , SC_CONFUSION ); - add_sc( NPC_WIDESLEEP , SC_SLEEP ); - add_sc( NPC_WIDESIGHT , SC_SIGHT ); - add_sc( NPC_EVILLAND , SC_BLIND ); - add_sc( NPC_MAGICMIRROR , SC_MAGICMIRROR ); - set_sc( NPC_SLOWCAST , SC_SLOWCAST , SI_SLOWCAST , SCB_NONE ); - set_sc( NPC_CRITICALWOUND , SC_CRITICALWOUND , SI_CRITICALWOUND , SCB_NONE ); - set_sc( NPC_STONESKIN , SC_ARMORCHANGE , SI_BLANK , SCB_DEF|SCB_MDEF ); - add_sc( NPC_ANTIMAGIC , SC_ARMORCHANGE ); - add_sc( NPC_WIDECURSE , SC_CURSE ); - add_sc( NPC_WIDESTUN , SC_STUN ); - - set_sc( NPC_HELLPOWER , SC_HELLPOWER , SI_HELLPOWER , SCB_NONE ); - set_sc( NPC_WIDEHELLDIGNITY , SC_HELLPOWER , SI_HELLPOWER , SCB_NONE ); - set_sc( NPC_INVINCIBLE , SC_INVINCIBLE , SI_INVINCIBLE , SCB_SPEED ); - set_sc( NPC_INVINCIBLEOFF , SC_INVINCIBLEOFF , SI_BLANK , SCB_SPEED ); - - set_sc( CASH_BLESSING , SC_BLESSING , SI_BLESSING , SCB_STR|SCB_INT|SCB_DEX ); - set_sc( CASH_INCAGI , SC_INCREASEAGI , SI_INCREASEAGI , SCB_AGI|SCB_SPEED ); - set_sc( CASH_ASSUMPTIO , SC_ASSUMPTIO , SI_ASSUMPTIO , SCB_NONE ); - - set_sc( ALL_PARTYFLEE , SC_PARTYFLEE , SI_PARTYFLEE , SCB_NONE ); - set_sc( ALL_ODINS_POWER , SC_ODINS_POWER , SI_ODINS_POWER , SCB_MATK|SCB_BATK|SCB_MDEF|SCB_DEF ); - - set_sc( CR_SHRINK , SC_SHRINK , SI_SHRINK , SCB_NONE ); - set_sc( RG_CLOSECONFINE , SC_CLOSECONFINE2 , SI_CLOSECONFINE2 , SCB_NONE ); - set_sc( RG_CLOSECONFINE , SC_CLOSECONFINE , SI_CLOSECONFINE , SCB_FLEE ); - set_sc( WZ_SIGHTBLASTER , SC_SIGHTBLASTER , SI_SIGHTBLASTER , SCB_NONE ); - set_sc( DC_WINKCHARM , SC_WINKCHARM , SI_WINKCHARM , SCB_NONE ); - add_sc( MO_BALKYOUNG , SC_STUN ); - add_sc( SA_ELEMENTWATER , SC_ELEMENTALCHANGE ); - add_sc( SA_ELEMENTFIRE , SC_ELEMENTALCHANGE ); - add_sc( SA_ELEMENTGROUND , SC_ELEMENTALCHANGE ); - add_sc( SA_ELEMENTWIND , SC_ELEMENTALCHANGE ); - - set_sc( HLIF_AVOID , SC_AVOID , SI_BLANK , SCB_SPEED ); - set_sc( HLIF_CHANGE , SC_CHANGE , SI_BLANK , SCB_VIT|SCB_INT ); - set_sc( HFLI_FLEET , SC_FLEET , SI_BLANK , SCB_ASPD|SCB_BATK|SCB_WATK ); - set_sc( HFLI_SPEED , SC_SPEED , SI_BLANK , SCB_FLEE ); - set_sc( HAMI_DEFENCE , SC_DEFENCE , SI_BLANK , SCB_DEF ); - set_sc( HAMI_BLOODLUST , SC_BLOODLUST , SI_BLANK , SCB_BATK|SCB_WATK ); - - // Homunculus S - add_sc(MH_STAHL_HORN, SC_STUN); - set_sc(MH_ANGRIFFS_MODUS, SC_ANGRIFFS_MODUS, SI_ANGRIFFS_MODUS, SCB_BATK | SCB_DEF | SCB_FLEE | SCB_MAXHP); - set_sc(MH_GOLDENE_FERSE, SC_GOLDENE_FERSE, SI_GOLDENE_FERSE, SCB_ASPD|SCB_MAXHP); - add_sc( MH_STEINWAND, SC_SAFETYWALL ); - add_sc(MH_ERASER_CUTTER, SC_ERASER_CUTTER); - set_sc(MH_OVERED_BOOST, SC_OVERED_BOOST, SI_BLANK, SCB_FLEE|SCB_ASPD); - add_sc(MH_LIGHT_OF_REGENE, SC_LIGHT_OF_REGENE); - set_sc(MH_VOLCANIC_ASH, SC_ASH, SI_VOLCANIC_ASH, SCB_DEF|SCB_DEF2|SCB_HIT|SCB_BATK|SCB_FLEE); - set_sc(MH_GRANITIC_ARMOR, SC_GRANITIC_ARMOR, SI_GRANITIC_ARMOR, SCB_NONE); - set_sc(MH_MAGMA_FLOW, SC_MAGMA_FLOW, SI_MAGMA_FLOW, SCB_NONE); - set_sc(MH_PYROCLASTIC, SC_PYROCLASTIC, SI_PYROCLASTIC, SCB_BATK|SCB_ATK_ELE); - add_sc(MH_LAVA_SLIDE, SC_BURNING); - set_sc(MH_NEEDLE_OF_PARALYZE, SC_PARALYSIS, SI_NEEDLE_OF_PARALYZE, SCB_DEF2); - add_sc(MH_POISON_MIST, SC_BLIND); - set_sc(MH_PAIN_KILLER, SC_PAIN_KILLER, SI_PAIN_KILLER, SCB_ASPD); - - add_sc(MH_STYLE_CHANGE, SC_STYLE_CHANGE); - set_sc( MH_TINDER_BREAKER , SC_CLOSECONFINE2 , SI_CLOSECONFINE2 , SCB_NONE ); - set_sc( MH_TINDER_BREAKER , SC_CLOSECONFINE , SI_CLOSECONFINE , SCB_FLEE ); - - - add_sc( MER_CRASH , SC_STUN ); - set_sc( MER_PROVOKE , SC_PROVOKE , SI_PROVOKE , SCB_DEF|SCB_DEF2|SCB_BATK|SCB_WATK ); - add_sc( MS_MAGNUM , SC_WATK_ELEMENT ); - add_sc( MER_SIGHT , SC_SIGHT ); - set_sc( MER_DECAGI , SC_DECREASEAGI , SI_DECREASEAGI , SCB_AGI|SCB_SPEED ); - set_sc( MER_MAGNIFICAT , SC_MAGNIFICAT , SI_MAGNIFICAT , SCB_REGEN ); - add_sc( MER_LEXDIVINA , SC_SILENCE ); - add_sc( MA_LANDMINE , SC_STUN ); - add_sc( MA_SANDMAN , SC_SLEEP ); - add_sc( MA_FREEZINGTRAP , SC_FREEZE ); - set_sc( MER_AUTOBERSERK , SC_AUTOBERSERK , SI_AUTOBERSERK , SCB_NONE ); - set_sc( ML_AUTOGUARD , SC_AUTOGUARD , SI_AUTOGUARD , SCB_NONE ); - set_sc( MS_REFLECTSHIELD , SC_REFLECTSHIELD , SI_REFLECTSHIELD , SCB_NONE ); - set_sc( ML_DEFENDER , SC_DEFENDER , SI_DEFENDER , SCB_SPEED|SCB_ASPD ); - set_sc( MS_PARRYING , SC_PARRYING , SI_PARRYING , SCB_NONE ); - set_sc( MS_BERSERK , SC_BERSERK , SI_BERSERK , SCB_DEF|SCB_DEF2|SCB_MDEF|SCB_MDEF2|SCB_FLEE|SCB_SPEED|SCB_ASPD|SCB_MAXHP|SCB_REGEN ); - add_sc( ML_SPIRALPIERCE , SC_STOP ); - set_sc( MER_QUICKEN , SC_MERC_QUICKEN , SI_BLANK , SCB_ASPD ); - add_sc( ML_DEVOTION , SC_DEVOTION ); - set_sc( MER_KYRIE , SC_KYRIE , SI_KYRIE , SCB_NONE ); - set_sc( MER_BLESSING , SC_BLESSING , SI_BLESSING , SCB_STR|SCB_INT|SCB_DEX ); - set_sc( MER_INCAGI , SC_INCREASEAGI , SI_INCREASEAGI , SCB_AGI|SCB_SPEED ); - - set_sc( GD_LEADERSHIP , SC_LEADERSHIP , SI_BLANK , SCB_STR ); - set_sc( GD_GLORYWOUNDS , SC_GLORYWOUNDS , SI_BLANK , SCB_VIT ); - set_sc( GD_SOULCOLD , SC_SOULCOLD , SI_BLANK , SCB_AGI ); - set_sc( GD_HAWKEYES , SC_HAWKEYES , SI_BLANK , SCB_DEX ); - - set_sc( GD_BATTLEORDER , SC_BATTLEORDERS , SI_BLANK , SCB_STR|SCB_INT|SCB_DEX ); - set_sc( GD_REGENERATION , SC_REGENERATION , SI_BLANK , SCB_REGEN ); - - /** - * Rune Knight - **/ - set_sc( RK_ENCHANTBLADE , SC_ENCHANTBLADE , SI_ENCHANTBLADE , SCB_NONE ); - set_sc( RK_DRAGONHOWLING , SC_FEAR , SI_BLANK , SCB_FLEE|SCB_HIT ); - set_sc( RK_DEATHBOUND , SC_DEATHBOUND , SI_DEATHBOUND , SCB_NONE ); - set_sc( RK_WINDCUTTER , SC_FEAR , SI_BLANK , SCB_FLEE|SCB_HIT ); - add_sc( RK_DRAGONBREATH , SC_BURNING ); - set_sc( RK_MILLENNIUMSHIELD , SC_MILLENNIUMSHIELD , SI_REUSE_MILLENNIUMSHIELD , SCB_NONE ); - set_sc( RK_REFRESH , SC_REFRESH , SI_REFRESH , SCB_NONE ); - set_sc( RK_GIANTGROWTH , SC_GIANTGROWTH , SI_GIANTGROWTH , SCB_STR ); - set_sc( RK_STONEHARDSKIN , SC_STONEHARDSKIN , SI_STONEHARDSKIN , SCB_NONE ); - set_sc( RK_VITALITYACTIVATION, SC_VITALITYACTIVATION, SI_VITALITYACTIVATION, SCB_REGEN ); - set_sc( RK_FIGHTINGSPIRIT , SC_FIGHTINGSPIRIT , SI_FIGHTINGSPIRIT , SCB_WATK|SCB_ASPD ); - set_sc( RK_ABUNDANCE , SC_ABUNDANCE , SI_ABUNDANCE , SCB_NONE ); - set_sc( RK_CRUSHSTRIKE , SC_CRUSHSTRIKE , SI_CRUSHSTRIKE , SCB_NONE ); - /** - * GC Guillotine Cross - **/ - set_sc_with_vfx( GC_VENOMIMPRESS , SC_VENOMIMPRESS , SI_VENOMIMPRESS , SCB_NONE ); - set_sc( GC_POISONINGWEAPON , SC_POISONINGWEAPON , SI_POISONINGWEAPON , SCB_NONE ); - set_sc( GC_WEAPONBLOCKING , SC_WEAPONBLOCKING , SI_WEAPONBLOCKING , SCB_NONE ); - set_sc( GC_CLOAKINGEXCEED , SC_CLOAKINGEXCEED , SI_CLOAKINGEXCEED , SCB_SPEED ); - set_sc( GC_HALLUCINATIONWALK , SC_HALLUCINATIONWALK, SI_HALLUCINATIONWALK, SCB_FLEE ); - set_sc( GC_ROLLINGCUTTER , SC_ROLLINGCUTTER , SI_ROLLINGCUTTER , SCB_NONE ); - /** - * Arch Bishop - **/ - set_sc( AB_ADORAMUS , SC_ADORAMUS , SI_ADORAMUS , SCB_AGI|SCB_SPEED ); - add_sc( AB_CLEMENTIA , SC_BLESSING ); - add_sc( AB_CANTO , SC_INCREASEAGI ); - set_sc( AB_EPICLESIS , SC_EPICLESIS , SI_EPICLESIS , SCB_MAXHP ); - add_sc( AB_PRAEFATIO , SC_KYRIE ); - set_sc_with_vfx( AB_ORATIO , SC_ORATIO , SI_ORATIO , SCB_NONE ); - set_sc( AB_LAUDAAGNUS , SC_LAUDAAGNUS , SI_LAUDAAGNUS , SCB_VIT ); - set_sc( AB_LAUDARAMUS , SC_LAUDARAMUS , SI_LAUDARAMUS , SCB_LUK ); - set_sc( AB_RENOVATIO , SC_RENOVATIO , SI_RENOVATIO , SCB_REGEN ); - set_sc( AB_EXPIATIO , SC_EXPIATIO , SI_EXPIATIO , SCB_ATK_ELE ); - set_sc( AB_DUPLELIGHT , SC_DUPLELIGHT , SI_DUPLELIGHT , SCB_NONE ); - set_sc( AB_SECRAMENT , SC_SECRAMENT , SI_SECRAMENT , SCB_NONE ); - /** - * Warlock - **/ - add_sc( WL_WHITEIMPRISON , SC_WHITEIMPRISON ); - set_sc_with_vfx( WL_FROSTMISTY , SC_FREEZING , SI_FROSTMISTY , SCB_ASPD|SCB_SPEED|SCB_DEF|SCB_DEF2 ); - set_sc( WL_MARSHOFABYSS , SC_MARSHOFABYSS , SI_MARSHOFABYSS , SCB_SPEED|SCB_FLEE|SCB_DEF|SCB_MDEF ); - set_sc(WL_RECOGNIZEDSPELL , SC_RECOGNIZEDSPELL , SI_RECOGNIZEDSPELL , SCB_MATK); - set_sc( WL_STASIS , SC_STASIS , SI_STASIS , SCB_NONE ); - /** - * Ranger - **/ - set_sc( RA_FEARBREEZE , SC_FEARBREEZE , SI_FEARBREEZE , SCB_NONE ); - set_sc( RA_ELECTRICSHOCKER , SC_ELECTRICSHOCKER , SI_ELECTRICSHOCKER , SCB_NONE ); - set_sc( RA_WUGDASH , SC_WUGDASH , SI_WUGDASH , SCB_SPEED ); - set_sc( RA_CAMOUFLAGE , SC_CAMOUFLAGE , SI_CAMOUFLAGE , SCB_SPEED ); - add_sc( RA_MAGENTATRAP , SC_ELEMENTALCHANGE ); - add_sc( RA_COBALTTRAP , SC_ELEMENTALCHANGE ); - add_sc( RA_MAIZETRAP , SC_ELEMENTALCHANGE ); - add_sc( RA_VERDURETRAP , SC_ELEMENTALCHANGE ); - add_sc( RA_FIRINGTRAP , SC_BURNING ); - set_sc_with_vfx( RA_ICEBOUNDTRAP , SC_FREEZING , SI_FROSTMISTY , SCB_NONE ); - /** - * Mechanic - **/ - set_sc( NC_ACCELERATION , SC_ACCELERATION , SI_ACCELERATION , SCB_SPEED ); - set_sc( NC_HOVERING , SC_HOVERING , SI_HOVERING , SCB_SPEED ); - set_sc( NC_SHAPESHIFT , SC_SHAPESHIFT , SI_SHAPESHIFT , SCB_DEF_ELE ); - set_sc( NC_INFRAREDSCAN , SC_INFRAREDSCAN , SI_INFRAREDSCAN , SCB_FLEE ); - set_sc( NC_ANALYZE , SC_ANALYZE , SI_ANALYZE , SCB_DEF|SCB_DEF2|SCB_MDEF|SCB_MDEF2 ); - set_sc( NC_MAGNETICFIELD , SC_MAGNETICFIELD , SI_MAGNETICFIELD , SCB_NONE ); - set_sc( NC_NEUTRALBARRIER , SC_NEUTRALBARRIER , SI_NEUTRALBARRIER , SCB_NONE ); - set_sc( NC_STEALTHFIELD , SC_STEALTHFIELD , SI_STEALTHFIELD , SCB_NONE ); - /** - * Royal Guard - **/ - set_sc( LG_REFLECTDAMAGE , SC_REFLECTDAMAGE , SI_LG_REFLECTDAMAGE, SCB_NONE ); - set_sc( LG_FORCEOFVANGUARD , SC_FORCEOFVANGUARD , SI_FORCEOFVANGUARD , SCB_MAXHP|SCB_DEF ); - set_sc( LG_EXEEDBREAK , SC_EXEEDBREAK , SI_EXEEDBREAK , SCB_NONE ); - set_sc( LG_PRESTIGE , SC_PRESTIGE , SI_PRESTIGE , SCB_DEF ); - set_sc( LG_BANDING , SC_BANDING , SI_BANDING , SCB_DEF2|SCB_WATK );// Renewal: atk2 & def2 - set_sc( LG_PIETY , SC_BENEDICTIO , SI_BENEDICTIO , SCB_DEF_ELE ); - set_sc( LG_EARTHDRIVE , SC_EARTHDRIVE , SI_EARTHDRIVE , SCB_DEF|SCB_ASPD ); - set_sc( LG_INSPIRATION , SC_INSPIRATION , SI_INSPIRATION , SCB_MAXHP|SCB_WATK|SCB_HIT|SCB_VIT|SCB_AGI|SCB_STR|SCB_DEX|SCB_INT|SCB_LUK); - set_sc( LG_SHIELDSPELL , SC_SHIELDSPELL_DEF , SI_SHIELDSPELL_DEF , SCB_WATK ); - set_sc( LG_SHIELDSPELL , SC_SHIELDSPELL_REF , SI_SHIELDSPELL_REF , SCB_DEF ); - /** - * Shadow Chaser - **/ - set_sc( SC_REPRODUCE , SC__REPRODUCE , SI_REPRODUCE , SCB_NONE ); - set_sc( SC_AUTOSHADOWSPELL , SC__AUTOSHADOWSPELL, SI_AUTOSHADOWSPELL , SCB_NONE ); - set_sc( SC_SHADOWFORM , SC__SHADOWFORM , SI_SHADOWFORM , SCB_NONE ); - set_sc( SC_BODYPAINT , SC__BODYPAINT , SI_BODYPAINT , SCB_ASPD ); - set_sc( SC_INVISIBILITY , SC__INVISIBILITY , SI_INVISIBILITY , SCB_ASPD|SCB_CRI|SCB_ATK_ELE ); - set_sc( SC_DEADLYINFECT , SC__DEADLYINFECT , SI_DEADLYINFECT , SCB_NONE ); - set_sc( SC_ENERVATION , SC__ENERVATION , SI_ENERVATION , SCB_BATK ); - set_sc( SC_GROOMY , SC__GROOMY , SI_GROOMY , SCB_ASPD|SCB_HIT|SCB_SPEED ); - set_sc( SC_IGNORANCE , SC__IGNORANCE , SI_IGNORANCE , SCB_NONE ); - set_sc( SC_LAZINESS , SC__LAZINESS , SI_LAZINESS , SCB_FLEE ); - set_sc( SC_UNLUCKY , SC__UNLUCKY , SI_UNLUCKY , SCB_CRI|SCB_FLEE2 ); - set_sc( SC_WEAKNESS , SC__WEAKNESS , SI_WEAKNESS , SCB_FLEE2|SCB_MAXHP ); - set_sc( SC_STRIPACCESSARY , SC__STRIPACCESSORY , SI_STRIPACCESSARY , SCB_DEX|SCB_INT|SCB_LUK ); - set_sc_with_vfx( SC_MANHOLE , SC__MANHOLE , SI_MANHOLE , SCB_NONE ); - add_sc( SC_CHAOSPANIC , SC_CONFUSION ); - set_sc_with_vfx( SC_BLOODYLUST , SC__BLOODYLUST , SI_BLOODYLUST , SCB_DEF | SCB_DEF2 | SCB_MDEF | SCB_MDEF2 | SCB_FLEE | SCB_SPEED | SCB_ASPD | SCB_MAXHP | SCB_REGEN ); - /** - * Sura - **/ - add_sc( SR_DRAGONCOMBO , SC_STUN ); - add_sc( SR_EARTHSHAKER , SC_STUN ); - set_sc( SR_CRESCENTELBOW , SC_CRESCENTELBOW , SI_CRESCENTELBOW , SCB_NONE ); - set_sc_with_vfx( SR_CURSEDCIRCLE , SC_CURSEDCIRCLE_TARGET, SI_CURSEDCIRCLE_TARGET , SCB_NONE ); - set_sc( SR_LIGHTNINGWALK , SC_LIGHTNINGWALK , SI_LIGHTNINGWALK , SCB_NONE ); - set_sc( SR_RAISINGDRAGON , SC_RAISINGDRAGON , SI_RAISINGDRAGON , SCB_REGEN|SCB_MAXHP|SCB_MAXSP ); - set_sc( SR_GENTLETOUCH_ENERGYGAIN, SC_GT_ENERGYGAIN , SI_GENTLETOUCH_ENERGYGAIN, SCB_NONE ); - set_sc( SR_GENTLETOUCH_CHANGE , SC_GT_CHANGE , SI_GENTLETOUCH_CHANGE , SCB_ASPD|SCB_MDEF|SCB_MAXHP ); - set_sc( SR_GENTLETOUCH_REVITALIZE, SC_GT_REVITALIZE , SI_GENTLETOUCH_REVITALIZE, SCB_MAXHP|SCB_REGEN ); - /** - * Wanderer / Minstrel - **/ - set_sc( WA_SWING_DANCE , SC_SWINGDANCE , SI_SWINGDANCE , SCB_SPEED|SCB_ASPD ); - set_sc( WA_SYMPHONY_OF_LOVER , SC_SYMPHONYOFLOVER , SI_SYMPHONYOFLOVERS , SCB_MDEF ); - set_sc( WA_MOONLIT_SERENADE , SC_MOONLITSERENADE , SI_MOONLITSERENADE , SCB_MATK ); - set_sc( MI_RUSH_WINDMILL , SC_RUSHWINDMILL , SI_RUSHWINDMILL , SCB_BATK ); - set_sc( MI_ECHOSONG , SC_ECHOSONG , SI_ECHOSONG , SCB_DEF2 ); - set_sc( MI_HARMONIZE , SC_HARMONIZE , SI_HARMONIZE , SCB_STR|SCB_AGI|SCB_VIT|SCB_INT|SCB_DEX|SCB_LUK ); - set_sc_with_vfx( WM_POEMOFNETHERWORLD , SC_NETHERWORLD , SI_NETHERWORLD , SCB_NONE ); - set_sc_with_vfx( WM_VOICEOFSIREN , SC_VOICEOFSIREN , SI_VOICEOFSIREN , SCB_NONE ); - set_sc_with_vfx( WM_LULLABY_DEEPSLEEP , SC_DEEPSLEEP , SI_DEEPSLEEP , SCB_NONE ); - set_sc( WM_SIRCLEOFNATURE , SC_SIRCLEOFNATURE , SI_SIRCLEOFNATURE , SCB_NONE ); - set_sc( WM_GLOOMYDAY , SC_GLOOMYDAY , SI_GLOOMYDAY , SCB_FLEE|SCB_ASPD ); - set_sc( WM_SONG_OF_MANA , SC_SONGOFMANA , SI_SONGOFMANA , SCB_NONE ); - set_sc( WM_DANCE_WITH_WUG , SC_DANCEWITHWUG , SI_DANCEWITHWUG , SCB_ASPD ); - set_sc( WM_SATURDAY_NIGHT_FEVER , SC_SATURDAYNIGHTFEVER , SI_SATURDAYNIGHTFEVER , SCB_BATK|SCB_DEF|SCB_FLEE|SCB_REGEN ); - set_sc( WM_LERADS_DEW , SC_LERADSDEW , SI_LERADSDEW , SCB_MAXHP ); - set_sc( WM_MELODYOFSINK , SC_MELODYOFSINK , SI_MELODYOFSINK , SCB_BATK|SCB_MATK ); - set_sc( WM_BEYOND_OF_WARCRY , SC_BEYONDOFWARCRY , SI_WARCRYOFBEYOND , SCB_BATK|SCB_MATK ); - set_sc( WM_UNLIMITED_HUMMING_VOICE, SC_UNLIMITEDHUMMINGVOICE, SI_UNLIMITEDHUMMINGVOICE, SCB_NONE ); - /** - * Sorcerer - **/ - set_sc( SO_FIREWALK , SC_PROPERTYWALK , SI_PROPERTYWALK , SCB_NONE ); - set_sc( SO_ELECTRICWALK , SC_PROPERTYWALK , SI_PROPERTYWALK , SCB_NONE ); - set_sc( SO_SPELLFIST , SC_SPELLFIST , SI_SPELLFIST , SCB_NONE ); - set_sc_with_vfx( SO_DIAMONDDUST , SC_CRYSTALIZE , SI_COLD , SCB_NONE ); // it does show the snow icon on mobs but doesn't affect it. - add_sc( SO_CLOUD_KILL , SC_POISON ); - set_sc( SO_STRIKING , SC_STRIKING , SI_STRIKING , SCB_WATK|SCB_CRI ); - set_sc( SO_WARMER , SC_WARMER , SI_WARMER , SCB_NONE ); - set_sc( SO_VACUUM_EXTREME , SC_VACUUM_EXTREME , SI_VACUUM_EXTREME , SCB_NONE ); - set_sc( SO_ARRULLO , SC_DEEPSLEEP , SI_DEEPSLEEP , SCB_NONE ); - set_sc( SO_FIRE_INSIGNIA , SC_FIRE_INSIGNIA , SI_FIRE_INSIGNIA , SCB_MATK | SCB_BATK | SCB_WATK | SCB_ATK_ELE | SCB_REGEN ); - set_sc( SO_WATER_INSIGNIA , SC_WATER_INSIGNIA , SI_WATER_INSIGNIA , SCB_WATK | SCB_ATK_ELE | SCB_REGEN ); - set_sc( SO_WIND_INSIGNIA , SC_WIND_INSIGNIA , SI_WIND_INSIGNIA , SCB_WATK | SCB_ATK_ELE | SCB_REGEN ); - set_sc( SO_EARTH_INSIGNIA , SC_EARTH_INSIGNIA , SI_EARTH_INSIGNIA , SCB_MDEF|SCB_DEF|SCB_MAXHP|SCB_MAXSP|SCB_WATK | SCB_ATK_ELE | SCB_REGEN ); - /** - * Genetic - **/ - set_sc( GN_CARTBOOST , SC_GN_CARTBOOST, SI_CARTSBOOST , SCB_SPEED ); - set_sc( GN_THORNS_TRAP , SC_THORNSTRAP , SI_THORNTRAP , SCB_NONE ); - set_sc_with_vfx( GN_BLOOD_SUCKER , SC_BLOODSUCKER , SI_BLOODSUCKER , SCB_NONE ); - set_sc( GN_WALLOFTHORN , SC_STOP , SI_BLANK , SCB_NONE ); - set_sc( GN_FIRE_EXPANSION_SMOKE_POWDER, SC_SMOKEPOWDER , SI_FIRE_EXPANSION_SMOKE_POWDER, SCB_NONE ); - set_sc( GN_FIRE_EXPANSION_TEAR_GAS , SC_TEARGAS , SI_FIRE_EXPANSION_TEAR_GAS , SCB_NONE ); - set_sc( GN_MANDRAGORA , SC_MANDRAGORA , SI_MANDRAGORA , SCB_INT ); - - // Elemental Spirit summoner's 'side' status changes. - set_sc( EL_CIRCLE_OF_FIRE , SC_CIRCLE_OF_FIRE_OPTION, SI_CIRCLE_OF_FIRE_OPTION, SCB_NONE ); - set_sc( EL_FIRE_CLOAK , SC_FIRE_CLOAK_OPTION , SI_FIRE_CLOAK_OPTION , SCB_ALL ); - set_sc( EL_WATER_SCREEN , SC_WATER_SCREEN_OPTION , SI_WATER_SCREEN_OPTION , SCB_NONE ); - set_sc( EL_WATER_DROP , SC_WATER_DROP_OPTION , SI_WATER_DROP_OPTION , SCB_ALL ); - set_sc( EL_WATER_BARRIER , SC_WATER_BARRIER , SI_WATER_BARRIER , SCB_MDEF|SCB_WATK|SCB_MATK|SCB_FLEE ); - set_sc( EL_WIND_STEP , SC_WIND_STEP_OPTION , SI_WIND_STEP_OPTION , SCB_SPEED|SCB_FLEE ); - set_sc( EL_WIND_CURTAIN , SC_WIND_CURTAIN_OPTION , SI_WIND_CURTAIN_OPTION , SCB_ALL ); - set_sc( EL_ZEPHYR , SC_ZEPHYR , SI_ZEPHYR , SCB_FLEE ); - set_sc( EL_SOLID_SKIN , SC_SOLID_SKIN_OPTION , SI_SOLID_SKIN_OPTION , SCB_DEF|SCB_MAXHP ); - set_sc( EL_STONE_SHIELD , SC_STONE_SHIELD_OPTION , SI_STONE_SHIELD_OPTION , SCB_ALL ); - set_sc( EL_POWER_OF_GAIA , SC_POWER_OF_GAIA , SI_POWER_OF_GAIA , SCB_MAXHP|SCB_DEF|SCB_SPEED ); - set_sc( EL_PYROTECHNIC , SC_PYROTECHNIC_OPTION , SI_PYROTECHNIC_OPTION , SCB_WATK ); - set_sc( EL_HEATER , SC_HEATER_OPTION , SI_HEATER_OPTION , SCB_WATK ); - set_sc( EL_TROPIC , SC_TROPIC_OPTION , SI_TROPIC_OPTION , SCB_WATK ); - set_sc( EL_AQUAPLAY , SC_AQUAPLAY_OPTION , SI_AQUAPLAY_OPTION , SCB_MATK ); - set_sc( EL_COOLER , SC_COOLER_OPTION , SI_COOLER_OPTION , SCB_MATK ); - set_sc( EL_CHILLY_AIR , SC_CHILLY_AIR_OPTION , SI_CHILLY_AIR_OPTION , SCB_MATK ); - set_sc( EL_GUST , SC_GUST_OPTION , SI_GUST_OPTION , SCB_ASPD ); - set_sc( EL_BLAST , SC_BLAST_OPTION , SI_BLAST_OPTION , SCB_ASPD ); - set_sc( EL_WILD_STORM , SC_WILD_STORM_OPTION , SI_WILD_STORM_OPTION , SCB_ASPD ); - set_sc( EL_PETROLOGY , SC_PETROLOGY_OPTION , SI_PETROLOGY_OPTION , SCB_MAXHP ); - set_sc( EL_CURSED_SOIL , SC_CURSED_SOIL_OPTION , SI_CURSED_SOIL_OPTION , SCB_NONE ); - set_sc( EL_UPHEAVAL , SC_UPHEAVAL_OPTION , SI_UPHEAVAL_OPTION , SCB_NONE ); - set_sc( EL_TIDAL_WEAPON , SC_TIDAL_WEAPON_OPTION , SI_TIDAL_WEAPON_OPTION , SCB_ALL ); - set_sc( EL_ROCK_CRUSHER , SC_ROCK_CRUSHER , SI_ROCK_CRUSHER , SCB_DEF ); - set_sc( EL_ROCK_CRUSHER_ATK, SC_ROCK_CRUSHER_ATK , SI_ROCK_CRUSHER_ATK , SCB_SPEED ); - - add_sc( KO_YAMIKUMO , SC_HIDING ); - set_sc_with_vfx( KO_JYUMONJIKIRI , SC_JYUMONJIKIRI , SI_KO_JYUMONJIKIRI , SCB_NONE ); - add_sc( KO_MAKIBISHI , SC_STUN ); - set_sc( KO_MEIKYOUSISUI , SC_MEIKYOUSISUI , SI_MEIKYOUSISUI , SCB_NONE ); - set_sc( KO_KYOUGAKU , SC_KYOUGAKU , SI_KYOUGAKU , SCB_STR|SCB_AGI|SCB_VIT|SCB_INT|SCB_DEX|SCB_LUK ); - add_sc( KO_JYUSATSU , SC_CURSE ); - set_sc( KO_ZENKAI , SC_ZENKAI , SI_ZENKAI , SCB_NONE ); - set_sc( KO_IZAYOI , SC_IZAYOI , SI_IZAYOI , SCB_MATK ); - set_sc( KG_KYOMU , SC_KYOMU , SI_KYOMU , SCB_NONE ); - set_sc( KG_KAGEMUSYA , SC_KAGEMUSYA , SI_KAGEMUSYA , SCB_NONE ); - set_sc( KG_KAGEHUMI , SC_KAGEHUMI , SI_KG_KAGEHUMI , SCB_NONE ); - set_sc( OB_ZANGETSU , SC_ZANGETSU , SI_ZANGETSU , SCB_MATK|SCB_BATK ); - set_sc_with_vfx( OB_AKAITSUKI , SC_AKAITSUKI , SI_AKAITSUKI , SCB_NONE ); - set_sc( OB_OBOROGENSOU , SC_GENSOU , SI_GENSOU , SCB_NONE ); - - // Storing the target job rather than simply SC_SPIRIT simplifies code later on. - SkillStatusChangeTable[SL_ALCHEMIST] = (sc_type)MAPID_ALCHEMIST, - SkillStatusChangeTable[SL_MONK] = (sc_type)MAPID_MONK, - SkillStatusChangeTable[SL_STAR] = (sc_type)MAPID_STAR_GLADIATOR, - SkillStatusChangeTable[SL_SAGE] = (sc_type)MAPID_SAGE, - SkillStatusChangeTable[SL_CRUSADER] = (sc_type)MAPID_CRUSADER, - SkillStatusChangeTable[SL_SUPERNOVICE] = (sc_type)MAPID_SUPER_NOVICE, - SkillStatusChangeTable[SL_KNIGHT] = (sc_type)MAPID_KNIGHT, - SkillStatusChangeTable[SL_WIZARD] = (sc_type)MAPID_WIZARD, - SkillStatusChangeTable[SL_PRIEST] = (sc_type)MAPID_PRIEST, - SkillStatusChangeTable[SL_BARDDANCER] = (sc_type)MAPID_BARDDANCER, - SkillStatusChangeTable[SL_ROGUE] = (sc_type)MAPID_ROGUE, - SkillStatusChangeTable[SL_ASSASIN] = (sc_type)MAPID_ASSASSIN, - SkillStatusChangeTable[SL_BLACKSMITH] = (sc_type)MAPID_BLACKSMITH, - SkillStatusChangeTable[SL_HUNTER] = (sc_type)MAPID_HUNTER, - SkillStatusChangeTable[SL_SOULLINKER] = (sc_type)MAPID_SOUL_LINKER, - - //Status that don't have a skill associated. - StatusIconChangeTable[SC_WEIGHT50] = SI_WEIGHT50; - StatusIconChangeTable[SC_WEIGHT90] = SI_WEIGHT90; - StatusIconChangeTable[SC_ASPDPOTION0] = SI_ASPDPOTION0; - StatusIconChangeTable[SC_ASPDPOTION1] = SI_ASPDPOTION1; - StatusIconChangeTable[SC_ASPDPOTION2] = SI_ASPDPOTION2; - StatusIconChangeTable[SC_ASPDPOTION3] = SI_ASPDPOTIONINFINITY; - StatusIconChangeTable[SC_SPEEDUP0] = SI_MOVHASTE_HORSE; - StatusIconChangeTable[SC_SPEEDUP1] = SI_SPEEDPOTION1; - StatusIconChangeTable[SC_INCSTR] = SI_INCSTR; - StatusIconChangeTable[SC_MIRACLE] = SI_SPIRIT; - StatusIconChangeTable[SC_INTRAVISION] = SI_INTRAVISION; - StatusIconChangeTable[SC_STRFOOD] = SI_FOODSTR; - StatusIconChangeTable[SC_AGIFOOD] = SI_FOODAGI; - StatusIconChangeTable[SC_VITFOOD] = SI_FOODVIT; - StatusIconChangeTable[SC_INTFOOD] = SI_FOODINT; - StatusIconChangeTable[SC_DEXFOOD] = SI_FOODDEX; - StatusIconChangeTable[SC_LUKFOOD] = SI_FOODLUK; - StatusIconChangeTable[SC_FLEEFOOD]= SI_FOODFLEE; - StatusIconChangeTable[SC_HITFOOD] = SI_FOODHIT; - StatusIconChangeTable[SC_MANU_ATK] = SI_MANU_ATK; - StatusIconChangeTable[SC_MANU_DEF] = SI_MANU_DEF; - StatusIconChangeTable[SC_SPL_ATK] = SI_SPL_ATK; - StatusIconChangeTable[SC_SPL_DEF] = SI_SPL_DEF; - StatusIconChangeTable[SC_MANU_MATK] = SI_MANU_MATK; - StatusIconChangeTable[SC_SPL_MATK] = SI_SPL_MATK; - StatusIconChangeTable[SC_ATKPOTION] = SI_PLUSATTACKPOWER; - StatusIconChangeTable[SC_MATKPOTION] = SI_PLUSMAGICPOWER; - //Cash Items - StatusIconChangeTable[SC_FOOD_STR_CASH] = SI_FOOD_STR_CASH; - StatusIconChangeTable[SC_FOOD_AGI_CASH] = SI_FOOD_AGI_CASH; - StatusIconChangeTable[SC_FOOD_VIT_CASH] = SI_FOOD_VIT_CASH; - StatusIconChangeTable[SC_FOOD_DEX_CASH] = SI_FOOD_DEX_CASH; - StatusIconChangeTable[SC_FOOD_INT_CASH] = SI_FOOD_INT_CASH; - StatusIconChangeTable[SC_FOOD_LUK_CASH] = SI_FOOD_LUK_CASH; - StatusIconChangeTable[SC_EXPBOOST] = SI_EXPBOOST; - StatusIconChangeTable[SC_ITEMBOOST] = SI_ITEMBOOST; - StatusIconChangeTable[SC_JEXPBOOST] = SI_CASH_PLUSONLYJOBEXP; - StatusIconChangeTable[SC_LIFEINSURANCE] = SI_LIFEINSURANCE; - StatusIconChangeTable[SC_BOSSMAPINFO] = SI_BOSSMAPINFO; - StatusIconChangeTable[SC_DEF_RATE] = SI_DEF_RATE; - StatusIconChangeTable[SC_MDEF_RATE] = SI_MDEF_RATE; - StatusIconChangeTable[SC_INCCRI] = SI_INCCRI; - StatusIconChangeTable[SC_INCFLEE2] = SI_PLUSAVOIDVALUE; - StatusIconChangeTable[SC_INCHEALRATE] = SI_INCHEALRATE; - StatusIconChangeTable[SC_S_LIFEPOTION] = SI_S_LIFEPOTION; - StatusIconChangeTable[SC_L_LIFEPOTION] = SI_L_LIFEPOTION; - StatusIconChangeTable[SC_SPCOST_RATE] = SI_ATKER_BLOOD; - StatusIconChangeTable[SC_COMMONSC_RESIST] = SI_TARGET_BLOOD; - // Mercenary Bonus Effects - StatusIconChangeTable[SC_MERC_FLEEUP] = SI_MERC_FLEEUP; - StatusIconChangeTable[SC_MERC_ATKUP] = SI_MERC_ATKUP; - StatusIconChangeTable[SC_MERC_HPUP] = SI_MERC_HPUP; - StatusIconChangeTable[SC_MERC_SPUP] = SI_MERC_SPUP; - StatusIconChangeTable[SC_MERC_HITUP] = SI_MERC_HITUP; - // Warlock Spheres - StatusIconChangeTable[SC_SPHERE_1] = SI_SPHERE_1; - StatusIconChangeTable[SC_SPHERE_2] = SI_SPHERE_2; - StatusIconChangeTable[SC_SPHERE_3] = SI_SPHERE_3; - StatusIconChangeTable[SC_SPHERE_4] = SI_SPHERE_4; - StatusIconChangeTable[SC_SPHERE_5] = SI_SPHERE_5; - // Warlock Preserved spells - StatusIconChangeTable[SC_SPELLBOOK1] = SI_SPELLBOOK1; - StatusIconChangeTable[SC_SPELLBOOK2] = SI_SPELLBOOK2; - StatusIconChangeTable[SC_SPELLBOOK3] = SI_SPELLBOOK3; - StatusIconChangeTable[SC_SPELLBOOK4] = SI_SPELLBOOK4; - StatusIconChangeTable[SC_SPELLBOOK5] = SI_SPELLBOOK5; - StatusIconChangeTable[SC_SPELLBOOK6] = SI_SPELLBOOK6; - StatusIconChangeTable[SC_MAXSPELLBOOK] = SI_SPELLBOOK7; - - StatusIconChangeTable[SC_NEUTRALBARRIER_MASTER] = SI_NEUTRALBARRIER_MASTER; - StatusIconChangeTable[SC_STEALTHFIELD_MASTER] = SI_STEALTHFIELD_MASTER; - StatusIconChangeTable[SC_OVERHEAT] = SI_OVERHEAT; - StatusIconChangeTable[SC_OVERHEAT_LIMITPOINT] = SI_OVERHEAT_LIMITPOINT; - - StatusIconChangeTable[SC_HALLUCINATIONWALK_POSTDELAY] = SI_HALLUCINATIONWALK_POSTDELAY; - StatusIconChangeTable[SC_TOXIN] = SI_TOXIN; - StatusIconChangeTable[SC_PARALYSE] = SI_PARALYSE; - StatusIconChangeTable[SC_VENOMBLEED] = SI_VENOMBLEED; - StatusIconChangeTable[SC_MAGICMUSHROOM] = SI_MAGICMUSHROOM; - StatusIconChangeTable[SC_DEATHHURT] = SI_DEATHHURT; - StatusIconChangeTable[SC_PYREXIA] = SI_PYREXIA; - StatusIconChangeTable[SC_OBLIVIONCURSE] = SI_OBLIVIONCURSE; - StatusIconChangeTable[SC_LEECHESEND] = SI_LEECHESEND; - - StatusIconChangeTable[SC_SHIELDSPELL_DEF] = SI_SHIELDSPELL_DEF; - StatusIconChangeTable[SC_SHIELDSPELL_MDEF] = SI_SHIELDSPELL_MDEF; - StatusIconChangeTable[SC_SHIELDSPELL_REF] = SI_SHIELDSPELL_REF; - StatusIconChangeTable[SC_BANDING_DEFENCE] = SI_BANDING_DEFENCE; - - StatusIconChangeTable[SC_GLOOMYDAY_SK] = SI_GLOOMYDAY; - - StatusIconChangeTable[SC_CURSEDCIRCLE_ATKER] = SI_CURSEDCIRCLE_ATKER; - - StatusIconChangeTable[SC_STOMACHACHE] = SI_STOMACHACHE; - StatusIconChangeTable[SC_MYSTERIOUS_POWDER] = SI_MYSTERIOUS_POWDER; - StatusIconChangeTable[SC_MELON_BOMB] = SI_MELON_BOMB; - StatusIconChangeTable[SC_BANANA_BOMB] = SI_BANANA_BOMB; - StatusIconChangeTable[SC_BANANA_BOMB_SITDOWN] = SI_BANANA_BOMB_SITDOWN_POSTDELAY; - - //Genetics New Food Items Status Icons - StatusIconChangeTable[SC_SAVAGE_STEAK] = SI_SAVAGE_STEAK; - StatusIconChangeTable[SC_COCKTAIL_WARG_BLOOD] = SI_COCKTAIL_WARG_BLOOD; - StatusIconChangeTable[SC_MINOR_BBQ] = SI_MINOR_BBQ; - StatusIconChangeTable[SC_SIROMA_ICE_TEA] = SI_SIROMA_ICE_TEA; - StatusIconChangeTable[SC_DROCERA_HERB_STEAMED] = SI_DROCERA_HERB_STEAMED; - StatusIconChangeTable[SC_PUTTI_TAILS_NOODLES] = SI_PUTTI_TAILS_NOODLES; - - StatusIconChangeTable[SC_BOOST500] |= SI_BOOST500; - StatusIconChangeTable[SC_FULL_SWING_K] |= SI_FULL_SWING_K; - StatusIconChangeTable[SC_MANA_PLUS] |= SI_MANA_PLUS; - StatusIconChangeTable[SC_MUSTLE_M] |= SI_MUSTLE_M; - StatusIconChangeTable[SC_LIFE_FORCE_F] |= SI_LIFE_FORCE_F; - StatusIconChangeTable[SC_EXTRACT_WHITE_POTION_Z] |= SI_EXTRACT_WHITE_POTION_Z; - StatusIconChangeTable[SC_VITATA_500] |= SI_VITATA_500; - StatusIconChangeTable[SC_EXTRACT_SALAMINE_JUICE] |= SI_EXTRACT_SALAMINE_JUICE; - - // Elemental Spirit's 'side' status change icons. - StatusIconChangeTable[SC_CIRCLE_OF_FIRE] = SI_CIRCLE_OF_FIRE; - StatusIconChangeTable[SC_FIRE_CLOAK] = SI_FIRE_CLOAK; - StatusIconChangeTable[SC_WATER_SCREEN] = SI_WATER_SCREEN; - StatusIconChangeTable[SC_WATER_DROP] = SI_WATER_DROP; - StatusIconChangeTable[SC_WIND_STEP] = SI_WIND_STEP; - StatusIconChangeTable[SC_WIND_CURTAIN] = SI_WIND_CURTAIN; - StatusIconChangeTable[SC_SOLID_SKIN] = SI_SOLID_SKIN; - StatusIconChangeTable[SC_STONE_SHIELD] = SI_STONE_SHIELD; - StatusIconChangeTable[SC_PYROTECHNIC] = SI_PYROTECHNIC; - StatusIconChangeTable[SC_HEATER] = SI_HEATER; - StatusIconChangeTable[SC_TROPIC] = SI_TROPIC; - StatusIconChangeTable[SC_AQUAPLAY] = SI_AQUAPLAY; - StatusIconChangeTable[SC_COOLER] = SI_COOLER; - StatusIconChangeTable[SC_CHILLY_AIR] = SI_CHILLY_AIR; - StatusIconChangeTable[SC_GUST] = SI_GUST; - StatusIconChangeTable[SC_BLAST] = SI_BLAST; - StatusIconChangeTable[SC_WILD_STORM] = SI_WILD_STORM; - StatusIconChangeTable[SC_PETROLOGY] = SI_PETROLOGY; - StatusIconChangeTable[SC_CURSED_SOIL] = SI_CURSED_SOIL; - StatusIconChangeTable[SC_UPHEAVAL] = SI_UPHEAVAL; - StatusIconChangeTable[SC_PUSH_CART] = SI_ON_PUSH_CART; - - //Other SC which are not necessarily associated to skills. - StatusChangeFlagTable[SC_ASPDPOTION0] = SCB_ASPD; - StatusChangeFlagTable[SC_ASPDPOTION1] = SCB_ASPD; - StatusChangeFlagTable[SC_ASPDPOTION2] = SCB_ASPD; - StatusChangeFlagTable[SC_ASPDPOTION3] = SCB_ASPD; - StatusChangeFlagTable[SC_SPEEDUP0] = SCB_SPEED; - StatusChangeFlagTable[SC_SPEEDUP1] = SCB_SPEED; - StatusChangeFlagTable[SC_ATKPOTION] = SCB_BATK; - StatusChangeFlagTable[SC_MATKPOTION] = SCB_MATK; - StatusChangeFlagTable[SC_INCALLSTATUS] |= SCB_STR|SCB_AGI|SCB_VIT|SCB_INT|SCB_DEX|SCB_LUK; - StatusChangeFlagTable[SC_INCSTR] |= SCB_STR; - StatusChangeFlagTable[SC_INCAGI] |= SCB_AGI; - StatusChangeFlagTable[SC_INCVIT] |= SCB_VIT; - StatusChangeFlagTable[SC_INCINT] |= SCB_INT; - StatusChangeFlagTable[SC_INCDEX] |= SCB_DEX; - StatusChangeFlagTable[SC_INCLUK] |= SCB_LUK; - StatusChangeFlagTable[SC_INCHIT] |= SCB_HIT; - StatusChangeFlagTable[SC_INCHITRATE] |= SCB_HIT; - StatusChangeFlagTable[SC_INCFLEE] |= SCB_FLEE; - StatusChangeFlagTable[SC_INCFLEERATE] |= SCB_FLEE; - StatusChangeFlagTable[SC_INCCRI] |= SCB_CRI; - StatusChangeFlagTable[SC_INCASPDRATE] |= SCB_ASPD; - StatusChangeFlagTable[SC_INCFLEE2] |= SCB_FLEE2; - StatusChangeFlagTable[SC_INCMHPRATE] |= SCB_MAXHP; - StatusChangeFlagTable[SC_INCMSPRATE] |= SCB_MAXSP; - StatusChangeFlagTable[SC_INCMHP] |= SCB_MAXHP; - StatusChangeFlagTable[SC_INCMSP] |= SCB_MAXSP; - StatusChangeFlagTable[SC_INCATKRATE] |= SCB_BATK|SCB_WATK; - StatusChangeFlagTable[SC_INCMATKRATE] |= SCB_MATK; - StatusChangeFlagTable[SC_INCDEFRATE] |= SCB_DEF; - StatusChangeFlagTable[SC_STRFOOD] |= SCB_STR; - StatusChangeFlagTable[SC_AGIFOOD] |= SCB_AGI; - StatusChangeFlagTable[SC_VITFOOD] |= SCB_VIT; - StatusChangeFlagTable[SC_INTFOOD] |= SCB_INT; - StatusChangeFlagTable[SC_DEXFOOD] |= SCB_DEX; - StatusChangeFlagTable[SC_LUKFOOD] |= SCB_LUK; - StatusChangeFlagTable[SC_HITFOOD] |= SCB_HIT; - StatusChangeFlagTable[SC_FLEEFOOD] |= SCB_FLEE; - StatusChangeFlagTable[SC_BATKFOOD] |= SCB_BATK; - StatusChangeFlagTable[SC_WATKFOOD] |= SCB_WATK; - StatusChangeFlagTable[SC_MATKFOOD] |= SCB_MATK; - StatusChangeFlagTable[SC_ARMOR_ELEMENT] |= SCB_ALL; - StatusChangeFlagTable[SC_ARMOR_RESIST] |= SCB_ALL; - StatusChangeFlagTable[SC_SPCOST_RATE] |= SCB_ALL; - StatusChangeFlagTable[SC_WALKSPEED] |= SCB_SPEED; - StatusChangeFlagTable[SC_ITEMSCRIPT] |= SCB_ALL; - // Cash Items - StatusChangeFlagTable[SC_FOOD_STR_CASH] = SCB_STR; - StatusChangeFlagTable[SC_FOOD_AGI_CASH] = SCB_AGI; - StatusChangeFlagTable[SC_FOOD_VIT_CASH] = SCB_VIT; - StatusChangeFlagTable[SC_FOOD_DEX_CASH] = SCB_DEX; - StatusChangeFlagTable[SC_FOOD_INT_CASH] = SCB_INT; - StatusChangeFlagTable[SC_FOOD_LUK_CASH] = SCB_LUK; - // Mercenary Bonus Effects - StatusChangeFlagTable[SC_MERC_FLEEUP] |= SCB_FLEE; - StatusChangeFlagTable[SC_MERC_ATKUP] |= SCB_WATK; - StatusChangeFlagTable[SC_MERC_HPUP] |= SCB_MAXHP; - StatusChangeFlagTable[SC_MERC_SPUP] |= SCB_MAXSP; - StatusChangeFlagTable[SC_MERC_HITUP] |= SCB_HIT; - // Guillotine Cross Poison Effects - StatusChangeFlagTable[SC_PARALYSE] |= SCB_ASPD|SCB_FLEE|SCB_SPEED; - StatusChangeFlagTable[SC_DEATHHURT] |= SCB_REGEN; - StatusChangeFlagTable[SC_VENOMBLEED] |= SCB_MAXHP; - StatusChangeFlagTable[SC_OBLIVIONCURSE] |= SCB_REGEN; - - StatusChangeFlagTable[SC_SAVAGE_STEAK] |= SCB_STR; - StatusChangeFlagTable[SC_COCKTAIL_WARG_BLOOD] |= SCB_INT; - StatusChangeFlagTable[SC_MINOR_BBQ] |= SCB_VIT; - StatusChangeFlagTable[SC_SIROMA_ICE_TEA] |= SCB_DEX; - StatusChangeFlagTable[SC_DROCERA_HERB_STEAMED] |= SCB_AGI; - StatusChangeFlagTable[SC_PUTTI_TAILS_NOODLES] |= SCB_LUK; - StatusChangeFlagTable[SC_BOOST500] |= SCB_ASPD; - StatusChangeFlagTable[SC_FULL_SWING_K] |= SCB_BATK; - StatusChangeFlagTable[SC_MANA_PLUS] |= SCB_MATK; - StatusChangeFlagTable[SC_MUSTLE_M] |= SCB_MAXHP; - StatusChangeFlagTable[SC_LIFE_FORCE_F] |= SCB_MAXSP; - StatusChangeFlagTable[SC_EXTRACT_WHITE_POTION_Z] |= SCB_REGEN; - StatusChangeFlagTable[SC_VITATA_500] |= SCB_REGEN; - StatusChangeFlagTable[SC_EXTRACT_SALAMINE_JUICE] |= SCB_ASPD; - -#ifdef RENEWAL_EDP - // renewal EDP increases your weapon atk - StatusChangeFlagTable[SC_EDP] |= SCB_WATK; -#endif - - if( !battle_config.display_hallucination ) //Disable Hallucination. - StatusIconChangeTable[SC_HALLUCINATION] = SI_BLANK; - - /* StatusChangeState (SCS_) NOMOVE */ - StatusChangeStateTable[SC_ANKLE] |= SCS_NOMOVE; - StatusChangeStateTable[SC_AUTOCOUNTER] |= SCS_NOMOVE; - StatusChangeStateTable[SC_TRICKDEAD] |= SCS_NOMOVE; - StatusChangeStateTable[SC_BLADESTOP] |= SCS_NOMOVE; - StatusChangeStateTable[SC_BLADESTOP_WAIT] |= SCS_NOMOVE; - StatusChangeStateTable[SC_GOSPEL] |= SCS_NOMOVE|SCS_NOMOVECOND; - StatusChangeStateTable[SC_BASILICA] |= SCS_NOMOVE|SCS_NOMOVECOND; - StatusChangeStateTable[SC_STOP] |= SCS_NOMOVE; - StatusChangeStateTable[SC_CLOSECONFINE] |= SCS_NOMOVE; - StatusChangeStateTable[SC_CLOSECONFINE2] |= SCS_NOMOVE; - StatusChangeStateTable[SC_MADNESSCANCEL] |= SCS_NOMOVE; - StatusChangeStateTable[SC_GRAVITATION] |= SCS_NOMOVE|SCS_NOMOVECOND; - StatusChangeStateTable[SC_WHITEIMPRISON] |= SCS_NOMOVE; - StatusChangeStateTable[SC_ELECTRICSHOCKER] |= SCS_NOMOVE; - StatusChangeStateTable[SC_BITE] |= SCS_NOMOVE; - StatusChangeStateTable[SC_THORNSTRAP] |= SCS_NOMOVE; - StatusChangeStateTable[SC_MAGNETICFIELD] |= SCS_NOMOVE; - StatusChangeStateTable[SC__MANHOLE] |= SCS_NOMOVE; - StatusChangeStateTable[SC_CURSEDCIRCLE_ATKER] |= SCS_NOMOVE; - StatusChangeStateTable[SC_CURSEDCIRCLE_TARGET] |= SCS_NOMOVE; - StatusChangeStateTable[SC_CRYSTALIZE] |= SCS_NOMOVE|SCS_NOMOVECOND; - StatusChangeStateTable[SC_NETHERWORLD] |= SCS_NOMOVE; - StatusChangeStateTable[SC_CAMOUFLAGE] |= SCS_NOMOVE|SCS_NOMOVECOND; - StatusChangeStateTable[SC_MEIKYOUSISUI] |= SCS_NOMOVE; - StatusChangeStateTable[SC_KAGEHUMI] |= SCS_NOMOVE; - StatusChangeStateTable[SC_KYOUGAKU] |= SCS_NOMOVE; - - /* StatusChangeState (SCS_) NOPICKUPITEMS */ - StatusChangeStateTable[SC_HIDING] |= SCS_NOPICKITEM; - StatusChangeStateTable[SC_CLOAKING] |= SCS_NOPICKITEM; - StatusChangeStateTable[SC_TRICKDEAD] |= SCS_NOPICKITEM; - StatusChangeStateTable[SC_BLADESTOP] |= SCS_NOPICKITEM; - StatusChangeStateTable[SC_CLOAKINGEXCEED] |= SCS_NOPICKITEM; - StatusChangeStateTable[SC_NOCHAT] |= SCS_NOPICKITEM|SCS_NOPICKITEMCOND; - - /* StatusChangeState (SCS_) NODROPITEMS */ - StatusChangeStateTable[SC_AUTOCOUNTER] |= SCS_NODROPITEM; - StatusChangeStateTable[SC_BLADESTOP] |= SCS_NODROPITEM; - StatusChangeStateTable[SC_NOCHAT] |= SCS_NODROPITEM|SCS_NODROPITEMCOND; - - /* StatusChangeState (SCS_) NOCAST (skills) */ - StatusChangeStateTable[SC_SILENCE] |= SCS_NOCAST; - StatusChangeStateTable[SC_STEELBODY] |= SCS_NOCAST; - StatusChangeStateTable[SC_BERSERK] |= SCS_NOCAST; - StatusChangeStateTable[SC__BLOODYLUST] |= SCS_NOCAST; - StatusChangeStateTable[SC_OBLIVIONCURSE] |= SCS_NOCAST; - StatusChangeStateTable[SC_WHITEIMPRISON] |= SCS_NOCAST; - StatusChangeStateTable[SC__INVISIBILITY] |= SCS_NOCAST; - StatusChangeStateTable[SC_CRYSTALIZE] |= SCS_NOCAST|SCS_NOCASTCOND; - StatusChangeStateTable[SC__IGNORANCE] |= SCS_NOCAST; - StatusChangeStateTable[SC_DEEPSLEEP] |= SCS_NOCAST; - StatusChangeStateTable[SC_SATURDAYNIGHTFEVER] |= SCS_NOCAST; - StatusChangeStateTable[SC_CURSEDCIRCLE_TARGET] |= SCS_NOCAST; - StatusChangeStateTable[SC_SILENCE] |= SCS_NOCAST; - - //Homon S - StatusChangeStateTable[SC_PARALYSIS] |= SCS_NOMOVE; - -} - -static void initDummyData(void) -{ - memset(&dummy_status, 0, sizeof(dummy_status)); - dummy_status.hp = - dummy_status.max_hp = - dummy_status.max_sp = - dummy_status.str = - dummy_status.agi = - dummy_status.vit = - dummy_status.int_ = - dummy_status.dex = - dummy_status.luk = - dummy_status.hit = 1; - dummy_status.speed = 2000; - dummy_status.adelay = 4000; - dummy_status.amotion = 2000; - dummy_status.dmotion = 2000; - dummy_status.ele_lv = 1; //Min elemental level. - dummy_status.mode = MD_CANMOVE; -} - - -//For copying a status_data structure from b to a, without overwriting current Hp and Sp -static inline void status_cpy(struct status_data* a, const struct status_data* b) -{ - memcpy((void*)&a->max_hp, (const void*)&b->max_hp, sizeof(struct status_data)-(sizeof(a->hp)+sizeof(a->sp))); -} - -//Sets HP to given value. Flag is the flag passed to status_heal in case -//final value is higher than current (use 2 to make a healing effect display -//on players) It will always succeed (overrides Berserk block), but it can't kill. -int status_set_hp(struct block_list *bl, unsigned int hp, int flag) -{ - struct status_data *status; - if (hp < 1) return 0; - status = status_get_status_data(bl); - if (status == &dummy_status) - return 0; - - if (hp > status->max_hp) hp = status->max_hp; - if (hp == status->hp) return 0; - if (hp > status->hp) - return status_heal(bl, hp - status->hp, 0, 1|flag); - return status_zap(bl, status->hp - hp, 0); -} - -//Sets SP to given value. Flag is the flag passed to status_heal in case -//final value is higher than current (use 2 to make a healing effect display -//on players) -int status_set_sp(struct block_list *bl, unsigned int sp, int flag) -{ - struct status_data *status; - - status = status_get_status_data(bl); - if (status == &dummy_status) - return 0; - - if (sp > status->max_sp) sp = status->max_sp; - if (sp == status->sp) return 0; - if (sp > status->sp) - return status_heal(bl, 0, sp - status->sp, 1|flag); - return status_zap(bl, 0, status->sp - sp); -} - -int status_charge(struct block_list* bl, int hp, int sp) -{ - if(!(bl->type&BL_CONSUME)) - return hp+sp; //Assume all was charged so there are no 'not enough' fails. - return status_damage(NULL, bl, hp, sp, 0, 3); -} - -//Inflicts damage on the target with the according walkdelay. -//If flag&1, damage is passive and does not triggers cancelling status changes. -//If flag&2, fail if target does not has enough to substract. -//If flag&4, if killed, mob must not give exp/loot. -//flag will be set to &8 when damaging sp of a dead character -int status_damage(struct block_list *src,struct block_list *target,int hp, int sp, int walkdelay, int flag) -{ - struct status_data *status; - struct status_change *sc; - - if(sp && !(target->type&BL_CONSUME)) - sp = 0; //Not a valid SP target. - - if (hp < 0) { //Assume absorbed damage. - status_heal(target, -hp, 0, 1); - hp = 0; - } - - if (sp < 0) { - status_heal(target, 0, -sp, 1); - sp = 0; - } - - if (target->type == BL_SKILL) - return skill_unit_ondamaged((struct skill_unit *)target, src, hp, gettick()); - - status = status_get_status_data(target); - if( status == &dummy_status ) - return 0; - - if ((unsigned int)hp >= status->hp) { - if (flag&2) return 0; - hp = status->hp; - } - - if ((unsigned int)sp > status->sp) { - if (flag&2) return 0; - sp = status->sp; - } - - if (!hp && !sp) - return 0; - - if( !status->hp ) - flag |= 8; - -// Let through. battle.c/skill.c have the whole logic of when it's possible or -// not to hurt someone (and this check breaks pet catching) [Skotlex] -// if (!target->prev && !(flag&2)) -// return 0; //Cannot damage a bl not on a map, except when "charging" hp/sp - - sc = status_get_sc(target); - if( hp && battle_config.invincible_nodamage && src && sc && sc->data[SC_INVINCIBLE] && !sc->data[SC_INVINCIBLEOFF] ) - hp = 1; - - if( hp && !(flag&1) ) { - if( sc ) { - struct status_change_entry *sce; - if (sc->data[SC_STONE] && sc->opt1 == OPT1_STONE) - status_change_end(target, SC_STONE, INVALID_TIMER); - status_change_end(target, SC_FREEZE, INVALID_TIMER); - status_change_end(target, SC_SLEEP, INVALID_TIMER); - status_change_end(target, SC_WINKCHARM, INVALID_TIMER); - status_change_end(target, SC_CONFUSION, INVALID_TIMER); - status_change_end(target, SC_TRICKDEAD, INVALID_TIMER); - status_change_end(target, SC_HIDING, INVALID_TIMER); - status_change_end(target, SC_CLOAKING, INVALID_TIMER); - status_change_end(target, SC_CHASEWALK, INVALID_TIMER); - status_change_end(target, SC_CAMOUFLAGE, INVALID_TIMER); - status_change_end(target, SC__INVISIBILITY, INVALID_TIMER); - status_change_end(target, SC_DEEPSLEEP, INVALID_TIMER); - if ((sce=sc->data[SC_ENDURE]) && !sce->val4) { - //Endure count is only reduced by non-players on non-gvg maps. - //val4 signals infinite endure. [Skotlex] - if (src && src->type != BL_PC && !map_flag_gvg(target->m) && !map[target->m].flag.battleground && --(sce->val2) < 0) - status_change_end(target, SC_ENDURE, INVALID_TIMER); - } - if ((sce=sc->data[SC_GRAVITATION]) && sce->val3 == BCT_SELF) { - struct skill_unit_group* sg = skill_id2group(sce->val4); - if (sg) { - skill_delunitgroup(sg); - sce->val4 = 0; - status_change_end(target, SC_GRAVITATION, INVALID_TIMER); - } - } - if(sc->data[SC_DANCING] && (unsigned int)hp > status->max_hp>>2) - status_change_end(target, SC_DANCING, INVALID_TIMER); - if(sc->data[SC_CLOAKINGEXCEED] && --(sc->data[SC_CLOAKINGEXCEED]->val2) <= 0) - status_change_end(target, SC_CLOAKINGEXCEED, INVALID_TIMER); - if(sc->data[SC_KAGEMUSYA] && --(sc->data[SC_KAGEMUSYA]->val3) <= 0) - status_change_end(target, SC_KAGEMUSYA, INVALID_TIMER); - } - unit_skillcastcancel(target, 2); - } - - status->hp-= hp; - status->sp-= sp; - - if (sc && hp && status->hp) { - if (sc->data[SC_AUTOBERSERK] && - (!sc->data[SC_PROVOKE] || !sc->data[SC_PROVOKE]->val2) && - status->hp < status->max_hp>>2) - sc_start4(target,SC_PROVOKE,100,10,1,0,0,0); - if (sc->data[SC_BERSERK] && status->hp <= 100) - status_change_end(target, SC_BERSERK, INVALID_TIMER); - if( sc->data[SC_RAISINGDRAGON] && status->hp <= 1000 ) - status_change_end(target, SC_RAISINGDRAGON, INVALID_TIMER); - if (sc->data[SC_SATURDAYNIGHTFEVER] && status->hp <= 100) - status_change_end(target, SC_SATURDAYNIGHTFEVER, INVALID_TIMER); - if (sc->data[SC__BLOODYLUST] && status->hp <= 100) - status_change_end(target, SC__BLOODYLUST, INVALID_TIMER); - } - - switch (target->type) { - case BL_PC: pc_damage((TBL_PC*)target,src,hp,sp); break; - case BL_MOB: mob_damage((TBL_MOB*)target, src, hp); break; - case BL_HOM: merc_damage((TBL_HOM*)target); break; - case BL_MER: mercenary_heal((TBL_MER*)target,hp,sp); break; - case BL_ELEM: elemental_heal((TBL_ELEM*)target,hp,sp); break; - } - - if( src && target->type == BL_PC && ((TBL_PC*)target)->disguise ) {// stop walking when attacked in disguise to prevent walk-delay bug - unit_stop_walking( target, 1 ); - } - - if( status->hp || (flag&8) ) - { //Still lives or has been dead before this damage. - if (walkdelay) - unit_set_walkdelay(target, gettick(), walkdelay, 0); - return hp+sp; - } - - status->hp = 1; //To let the dead function cast skills and all that. - //NOTE: These dead functions should return: [Skotlex] - //0: Death cancelled, auto-revived. - //Non-zero: Standard death. Clear status, cancel move/attack, etc - //&2: Also remove object from map. - //&4: Also delete object from memory. - switch (target->type) { - case BL_PC: flag = pc_dead((TBL_PC*)target,src); break; - case BL_MOB: flag = mob_dead((TBL_MOB*)target, src, flag&4?3:0); break; - case BL_HOM: flag = merc_hom_dead((TBL_HOM*)target); break; - case BL_MER: flag = mercenary_dead((TBL_MER*)target); break; - case BL_ELEM: flag = elemental_dead((TBL_ELEM*)target); break; - default: //Unhandled case, do nothing to object. - flag = 0; - break; - } - - if(!flag) //Death cancelled. - return hp+sp; - - //Normal death - status->hp = 0; - if (battle_config.clear_unit_ondeath && - battle_config.clear_unit_ondeath&target->type) - skill_clear_unitgroup(target); - - if(target->type&BL_REGEN) - { //Reset regen ticks. - struct regen_data *regen = status_get_regen_data(target); - if (regen) { - memset(®en->tick, 0, sizeof(regen->tick)); - if (regen->sregen) - memset(®en->sregen->tick, 0, sizeof(regen->sregen->tick)); - if (regen->ssregen) - memset(®en->ssregen->tick, 0, sizeof(regen->ssregen->tick)); - } - } - - if( sc && sc->data[SC_KAIZEL] && !map_flag_gvg(target->m) ) - { //flag&8 = disable Kaizel - int time = skill_get_time2(SL_KAIZEL,sc->data[SC_KAIZEL]->val1); - //Look for Osiris Card's bonus effect on the character and revive 100% or revive normally - if ( target->type == BL_PC && BL_CAST(BL_PC,target)->special_state.restart_full_recover ) - status_revive(target, 100, 100); - else - status_revive(target, sc->data[SC_KAIZEL]->val2, 0); - status_change_clear(target,0); - clif_skill_nodamage(target,target,ALL_RESURRECTION,1,1); - sc_start(target,status_skill2sc(PR_KYRIE),100,10,time); - - if( target->type == BL_MOB ) - ((TBL_MOB*)target)->state.rebirth = 1; - - return hp+sp; - } - if(target->type == BL_PC){ - TBL_PC *sd = BL_CAST(BL_PC,target); - TBL_HOM *hd = sd->hd; - if(hd && hd->sc.data[SC_LIGHT_OF_REGENE]){ - clif_skillcasting(&hd->bl, hd->bl.id, target->id, 0,0, MH_LIGHT_OF_REGENE, skill_get_ele(MH_LIGHT_OF_REGENE, 1), 10); //just to display usage - clif_skill_nodamage(&sd->bl, target, ALL_RESURRECTION, 1, status_revive(&sd->bl,10*hd->sc.data[SC_LIGHT_OF_REGENE]->val1,0)); - status_change_end(&sd->hd->bl,SC_LIGHT_OF_REGENE,INVALID_TIMER); - return hp + sp; - } - } - if (target->type == BL_MOB && sc && sc->data[SC_REBIRTH] && !((TBL_MOB*) target)->state.rebirth) {// Ensure the monster has not already rebirthed before doing so. - status_revive(target, sc->data[SC_REBIRTH]->val2, 0); - status_change_clear(target,0); - ((TBL_MOB*)target)->state.rebirth = 1; - - return hp+sp; - } - - status_change_clear(target,0); - - if(flag&4) //Delete from memory. (also invokes map removal code) - unit_free(target,CLR_DEAD); - else - if(flag&2) //remove from map - unit_remove_map(target,CLR_DEAD); - else - { //Some death states that would normally be handled by unit_remove_map - unit_stop_attack(target); - unit_stop_walking(target,1); - unit_skillcastcancel(target,0); - clif_clearunit_area(target,CLR_DEAD); - skill_unit_move(target,gettick(),4); - skill_cleartimerskill(target); - } - - return hp+sp; -} - -//Heals a character. If flag&1, this is forced healing (otherwise stuff like Berserk can block it) -//If flag&2, when the player is healed, show the HP/SP heal effect. -int status_heal(struct block_list *bl,int hp,int sp, int flag) -{ - struct status_data *status; - struct status_change *sc; - - status = status_get_status_data(bl); - - if (status == &dummy_status || !status->hp) - return 0; - - sc = status_get_sc(bl); - if (sc && !sc->count) - sc = NULL; - - if (hp < 0) { - if (hp == INT_MIN) hp++; //-INT_MIN == INT_MIN in some architectures! - status_damage(NULL, bl, -hp, 0, 0, 1); - hp = 0; - } - - if(hp) { - if( sc && (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) ) { - if( flag&1 ) - flag &= ~2; - else - hp = 0; - } - - if((unsigned int)hp > status->max_hp - status->hp) - hp = status->max_hp - status->hp; - } - - if(sp < 0) { - if (sp==INT_MIN) sp++; - status_damage(NULL, bl, 0, -sp, 0, 1); - sp = 0; - } - - if(sp) { - if((unsigned int)sp > status->max_sp - status->sp) - sp = status->max_sp - status->sp; - } - - if(!sp && !hp) return 0; - - status->hp+= hp; - status->sp+= sp; - - if(hp && sc && - sc->data[SC_AUTOBERSERK] && - sc->data[SC_PROVOKE] && - sc->data[SC_PROVOKE]->val2==1 && - status->hp>=status->max_hp>>2 - ) //End auto berserk. - status_change_end(bl, SC_PROVOKE, INVALID_TIMER); - - // send hp update to client - switch(bl->type) { - case BL_PC: pc_heal((TBL_PC*)bl,hp,sp,flag&2?1:0); break; - case BL_MOB: mob_heal((TBL_MOB*)bl,hp); break; - case BL_HOM: merc_hom_heal((TBL_HOM*)bl); break; - case BL_MER: mercenary_heal((TBL_MER*)bl,hp,sp); break; - case BL_ELEM: elemental_heal((TBL_ELEM*)bl,hp,sp); break; - } - - return hp+sp; -} - -//Does percentual non-flinching damage/heal. If mob is killed this way, -//no exp/drops will be awarded if there is no src (or src is target) -//If rates are > 0, percent is of current HP/SP -//If rates are < 0, percent is of max HP/SP -//If !flag, this is heal, otherwise it is damage. -//Furthermore, if flag==2, then the target must not die from the substraction. -int status_percent_change(struct block_list *src,struct block_list *target,signed char hp_rate, signed char sp_rate, int flag) -{ - struct status_data *status; - unsigned int hp =0, sp = 0; - - status = status_get_status_data(target); - - - //It's safe now [MarkZD] - if (hp_rate > 99) - hp = status->hp; - else if (hp_rate > 0) - hp = status->hp>10000? - hp_rate*(status->hp/100): - ((int64)hp_rate*status->hp)/100; - else if (hp_rate < -99) - hp = status->max_hp; - else if (hp_rate < 0) - hp = status->max_hp>10000? - (-hp_rate)*(status->max_hp/100): - ((int64)-hp_rate*status->max_hp)/100; - if (hp_rate && !hp) - hp = 1; - - if (flag == 2 && hp >= status->hp) - hp = status->hp-1; //Must not kill target. - - if (sp_rate > 99) - sp = status->sp; - else if (sp_rate > 0) - sp = ((int64)sp_rate*status->sp)/100; - else if (sp_rate < -99) - sp = status->max_sp; - else if (sp_rate < 0) - sp = ((int64)-sp_rate)*status->max_sp/100; - if (sp_rate && !sp) - sp = 1; - - //Ugly check in case damage dealt is too much for the received args of - //status_heal / status_damage. [Skotlex] - if (hp > INT_MAX) { - hp -= INT_MAX; - if (flag) - status_damage(src, target, INT_MAX, 0, 0, (!src||src==target?5:1)); - else - status_heal(target, INT_MAX, 0, 0); - } - if (sp > INT_MAX) { - sp -= INT_MAX; - if (flag) - status_damage(src, target, 0, INT_MAX, 0, (!src||src==target?5:1)); - else - status_heal(target, 0, INT_MAX, 0); - } - if (flag) - return status_damage(src, target, hp, sp, 0, (!src||src==target?5:1)); - return status_heal(target, hp, sp, 0); -} - -int status_revive(struct block_list *bl, unsigned char per_hp, unsigned char per_sp) -{ - struct status_data *status; - unsigned int hp, sp; - if (!status_isdead(bl)) return 0; - - status = status_get_status_data(bl); - if (status == &dummy_status) - return 0; //Invalid target. - - hp = (int64)status->max_hp * per_hp/100; - sp = (int64)status->max_sp * per_sp/100; - - if(hp > status->max_hp - status->hp) - hp = status->max_hp - status->hp; - else if (per_hp && !hp) - hp = 1; - - if(sp > status->max_sp - status->sp) - sp = status->max_sp - status->sp; - else if (per_sp && !sp) - sp = 1; - - status->hp += hp; - status->sp += sp; - - if (bl->prev) //Animation only if character is already on a map. - clif_resurrection(bl, 1); - switch (bl->type) { - case BL_PC: pc_revive((TBL_PC*)bl, hp, sp); break; - case BL_MOB: mob_revive((TBL_MOB*)bl, hp); break; - case BL_HOM: merc_hom_revive((TBL_HOM*)bl, hp, sp); break; - } - return 1; -} - -/*========================================== - * Checks whether the src can use the skill on the target, - * taking into account status/option of both source/target. [Skotlex] - * flag: - * 0 - Trying to use skill on target. - * 1 - Cast bar is done. - * 2 - Skill already pulled off, check is due to ground-based skills or splash-damage ones. - * src MAY be null to indicate we shouldn't check it, this is a ground-based skill attack. - * target MAY Be null, in which case the checks are only to see - * whether the source can cast or not the skill on the ground. - *------------------------------------------*/ -int status_check_skilluse(struct block_list *src, struct block_list *target, uint16 skill_id, int flag) -{ - struct status_data *status; - struct status_change *sc=NULL, *tsc; - int hide_flag; - - status = src?status_get_status_data(src):&dummy_status; - - if (src && src->type != BL_PC && status_isdead(src)) - return 0; - - if (!skill_id) { //Normal attack checks. - if (!(status->mode&MD_CANATTACK)) - return 0; //This mode is only needed for melee attacking. - //Dead state is not checked for skills as some skills can be used - //on dead characters, said checks are left to skill.c [Skotlex] - if (target && status_isdead(target)) - return 0; - if( src && (sc = status_get_sc(src)) && sc->data[SC_CRYSTALIZE] && src->type != BL_MOB) - return 0; - } - - switch( skill_id ) { - case PA_PRESSURE: - if( flag && target ) { - //Gloria Avoids pretty much everything.... - tsc = status_get_sc(target); - if(tsc && tsc->option&OPTION_HIDE) - return 0; - } - break; - case GN_WALLOFTHORN: - if( target && status_isdead(target) ) - return 0; - break; - case AL_TELEPORT: - //Should fail when used on top of Land Protector [Skotlex] - if (src && map_getcell(src->m, src->x, src->y, CELL_CHKLANDPROTECTOR) - && !(status->mode&MD_BOSS) - && (src->type != BL_PC || ((TBL_PC*)src)->skillitem != skill_id)) - return 0; - break; - default: - break; - } - - if ( src ) sc = status_get_sc(src); - - if( sc && sc->count ) { - - if (skill_id != RK_REFRESH && sc->opt1 >0 && (sc->opt1 != OPT1_CRYSTALIZE && src->type != BL_MOB) && sc->opt1 != OPT1_BURNING && skill_id != SR_GENTLETOUCH_CURE) { //Stuned/Frozen/etc - if (flag != 1) //Can't cast, casted stuff can't damage. - return 0; - if (!(skill_get_inf(skill_id)&INF_GROUND_SKILL)) - return 0; //Targetted spells can't come off. - } - - if ( - (sc->data[SC_TRICKDEAD] && skill_id != NV_TRICKDEAD) - || (sc->data[SC_AUTOCOUNTER] && !flag) - || (sc->data[SC_GOSPEL] && sc->data[SC_GOSPEL]->val4 == BCT_SELF && skill_id != PA_GOSPEL) - || (sc->data[SC_GRAVITATION] && sc->data[SC_GRAVITATION]->val3 == BCT_SELF && flag != 2) - ) - return 0; - - if (sc->data[SC_WINKCHARM] && target && !flag) { //Prevents skill usage - if( unit_bl2ud(src) && (unit_bl2ud(src))->walktimer == INVALID_TIMER ) - unit_walktobl(src, map_id2bl(sc->data[SC_WINKCHARM]->val2), 3, 1); - clif_emotion(src, E_LV); - return 0; - } - - if (sc->data[SC_BLADESTOP]) { - switch (sc->data[SC_BLADESTOP]->val1) - { - case 5: if (skill_id == MO_EXTREMITYFIST) break; - case 4: if (skill_id == MO_CHAINCOMBO) break; - case 3: if (skill_id == MO_INVESTIGATE) break; - case 2: if (skill_id == MO_FINGEROFFENSIVE) break; - default: return 0; - } - } - - if (sc->data[SC_DANCING] && flag!=2) { - if( src->type == BL_PC && skill_id >= WA_SWING_DANCE && skill_id <= WM_UNLIMITED_HUMMING_VOICE ) - { // Lvl 5 Lesson or higher allow you use 3rd job skills while dancing.v - if( pc_checkskill((TBL_PC*)src,WM_LESSON) < 5 ) - return 0; - } else if(sc->data[SC_LONGING]) { //Allow everything except dancing/re-dancing. [Skotlex] - if (skill_id == BD_ENCORE || - skill_get_inf2(skill_id)&(INF2_SONG_DANCE|INF2_ENSEMBLE_SKILL) - ) - return 0; - } else { - switch (skill_id) { - case BD_ADAPTATION: - case CG_LONGINGFREEDOM: - case BA_MUSICALSTRIKE: - case DC_THROWARROW: - break; - default: - return 0; - } - } - if ((sc->data[SC_DANCING]->val1&0xFFFF) == CG_HERMODE && skill_id == BD_ADAPTATION) - return 0; //Can't amp out of Wand of Hermode :/ [Skotlex] - } - - if (skill_id && //Do not block item-casted skills. - (src->type != BL_PC || ((TBL_PC*)src)->skillitem != skill_id) - ) { //Skills blocked through status changes... - if (!flag && ( //Blocked only from using the skill (stuff like autospell may still go through - sc->cant.cast || - (sc->data[SC_MARIONETTE] && skill_id != CG_MARIONETTE) || //Only skill you can use is marionette again to cancel it - (sc->data[SC_MARIONETTE2] && skill_id == CG_MARIONETTE) || //Cannot use marionette if you are being buffed by another - (sc->data[SC_STASIS] && skill_block_check(src, SC_STASIS, skill_id)) || - (sc->data[SC_KAGEHUMI] && skill_block_check(src, SC_KAGEHUMI, skill_id)) - )) - return 0; - - //Skill blocking. - if ( - (sc->data[SC_VOLCANO] && skill_id == WZ_ICEWALL) || - (sc->data[SC_ROKISWEIL] && skill_id != BD_ADAPTATION) || - (sc->data[SC_HERMODE] && skill_get_inf(skill_id) & INF_SUPPORT_SKILL) || - (sc->data[SC_NOCHAT] && sc->data[SC_NOCHAT]->val1&MANNER_NOSKILL) - ) - return 0; - - if( sc->data[SC__MANHOLE] || ((tsc = status_get_sc(target)) && tsc->data[SC__MANHOLE]) ) { - switch(skill_id) {//##TODO## make this a flag in skill_db? - // Skills that can be used even under Man Hole effects. - case SC_SHADOWFORM: - case SC_STRIPACCESSARY: - break; - default: - return 0; - } - } - - } - } - - if (sc && sc->option) - { - if (sc->option&OPTION_HIDE) - switch (skill_id) { //Usable skills while hiding. - case TF_HIDING: - case AS_GRIMTOOTH: - case RG_BACKSTAP: - case RG_RAID: - case NJ_SHADOWJUMP: - case NJ_KIRIKAGE: - case KO_YAMIKUMO: - break; - default: - //Non players can use all skills while hidden. - if (!skill_id || src->type == BL_PC) - return 0; - } - if (sc->option&OPTION_CHASEWALK && skill_id != ST_CHASEWALK) - return 0; - if(sc->option&OPTION_MOUNTING) - return 0;//New mounts can't attack nor use skills in the client; this check makes it cheat-safe [Ind] - } - - if (target == NULL || target == src) //No further checking needed. - return 1; - - tsc = status_get_sc(target); - - if(tsc && tsc->count) { - /* attacks in invincible are capped to 1 damage and handled in batte.c; allow spell break and eske for sealed shrine GDB when in INVINCIBLE state. */ - if( tsc->data[SC_INVINCIBLE] && !tsc->data[SC_INVINCIBLEOFF] && skill_id && !(skill_id&(SA_SPELLBREAKER|SL_SKE)) ) - return 0; - if(!skill_id && tsc->data[SC_TRICKDEAD]) - return 0; - if((skill_id == WZ_STORMGUST || skill_id == WZ_FROSTNOVA || skill_id == NJ_HYOUSYOURAKU) - && tsc->data[SC_FREEZE]) - return 0; - if(skill_id == PR_LEXAETERNA && (tsc->data[SC_FREEZE] || (tsc->data[SC_STONE] && tsc->opt1 == OPT1_STONE))) - return 0; - } - - //If targetting, cloak+hide protect you, otherwise only hiding does. - hide_flag = flag?OPTION_HIDE:(OPTION_HIDE|OPTION_CLOAK|OPTION_CHASEWALK); - - //You cannot hide from ground skills. - if( skill_get_ele(skill_id,1) == ELE_EARTH ) //TODO: Need Skill Lv here :/ - hide_flag &= ~OPTION_HIDE; - - switch( target->type ) { - case BL_PC: { - struct map_session_data *sd = (TBL_PC*) target; - bool is_boss = (status->mode&MD_BOSS); - bool is_detect = ((status->mode&MD_DETECTOR)?true:false);//god-knows-why gcc doesn't shut up until this happens - if (pc_isinvisible(sd)) - return 0; - if (tsc->option&hide_flag && !is_boss && - ((sd->special_state.perfect_hiding || !is_detect) || - (tsc->data[SC_CLOAKINGEXCEED] && is_detect))) - return 0; - if( tsc->data[SC_CAMOUFLAGE] && !(is_boss || is_detect) && !skill_id ) - return 0; - if( tsc->data[SC_STEALTHFIELD] && !is_boss ) - return 0; - } - break; - case BL_ITEM: //Allow targetting of items to pick'em up (or in the case of mobs, to loot them). - //TODO: Would be nice if this could be used to judge whether the player can or not pick up the item it targets. [Skotlex] - if (status->mode&MD_LOOTER) - return 1; - return 0; - case BL_HOM: - case BL_MER: - case BL_ELEM: - if( target->type == BL_HOM && skill_id && battle_config.hom_setting&0x1 && skill_get_inf(skill_id)&INF_SUPPORT_SKILL && battle_get_master(target) != src ) - return 0; // Can't use support skills on Homunculus (only Master/Self) - if( target->type == BL_MER && (skill_id == PR_ASPERSIO || (skill_id >= SA_FLAMELAUNCHER && skill_id <= SA_SEISMICWEAPON)) && battle_get_master(target) != src ) - return 0; // Can't use Weapon endow skills on Mercenary (only Master) - if( skill_id == AM_POTIONPITCHER && ( target->type == BL_MER || target->type == BL_ELEM) ) - return 0; // Can't use Potion Pitcher on Mercenaries - default: - //Check for chase-walk/hiding/cloaking opponents. - if( tsc ) { - if( tsc->option&hide_flag && !(status->mode&(MD_BOSS|MD_DETECTOR))) - return 0; - if( tsc->data[SC_STEALTHFIELD] && !(status->mode&MD_BOSS) ) - return 0; - } - } - return 1; -} - -//Checks whether the source can see and chase target. -int status_check_visibility(struct block_list *src, struct block_list *target) -{ - int view_range; - struct status_data* status = status_get_status_data(src); - struct status_change* tsc = status_get_sc(target); - switch (src->type) { - case BL_MOB: - view_range = ((TBL_MOB*)src)->min_chase; - break; - case BL_PET: - view_range = ((TBL_PET*)src)->db->range2; - break; - default: - view_range = AREA_SIZE; - } - - if (src->m != target->m || !check_distance_bl(src, target, view_range)) - return 0; - - if( tsc && tsc->data[SC_STEALTHFIELD] ) - return 0; - - switch (target->type) - { //Check for chase-walk/hiding/cloaking opponents. - case BL_PC: - if ( tsc->data[SC_CLOAKINGEXCEED] && !(status->mode&MD_BOSS) ) - return 0; - if( (tsc->option&(OPTION_HIDE|OPTION_CLOAK|OPTION_CHASEWALK) || tsc->data[SC__INVISIBILITY] || tsc->data[SC_CAMOUFLAGE]) && !(status->mode&MD_BOSS) && - ( ((TBL_PC*)target)->special_state.perfect_hiding || !(status->mode&MD_DETECTOR) ) ) - return 0; - break; - default: - if( tsc && (tsc->option&(OPTION_HIDE|OPTION_CLOAK|OPTION_CHASEWALK) || tsc->data[SC__INVISIBILITY] || tsc->data[SC_CAMOUFLAGE]) && !(status->mode&(MD_BOSS|MD_DETECTOR)) ) - return 0; - - } - - return 1; -} - -// Basic ASPD value -int status_base_amotion_pc(struct map_session_data* sd, struct status_data* status) -{ - int amotion; -#ifdef RENEWAL_ASPD - short mod = -1; - - switch( sd->weapontype2 ){ // adjustment for dual weilding - case W_DAGGER: mod = 0; break; // 0, 1, 1 - case W_1HSWORD: - case W_1HAXE: mod = 1; - if( (sd->class_&MAPID_THIRDMASK) == MAPID_GUILLOTINE_CROSS ) // 0, 2, 3 - mod = sd->weapontype2 / W_1HSWORD + W_1HSWORD / sd->weapontype2 ; - } - - amotion = ( sd->status.weapon < MAX_WEAPON_TYPE && mod < 0 ) - ? (aspd_base[pc_class2idx(sd->status.class_)][sd->status.weapon]) // single weapon - : ((aspd_base[pc_class2idx(sd->status.class_)][sd->weapontype2] // dual-wield - + aspd_base[pc_class2idx(sd->status.class_)][sd->weapontype2]) * 6 / 10 + 10 * mod - - aspd_base[pc_class2idx(sd->status.class_)][sd->weapontype2] - + aspd_base[pc_class2idx(sd->status.class_)][sd->weapontype1]); - - if ( sd->status.shield ) - amotion += ( 2000 - aspd_base[pc_class2idx(sd->status.class_)][W_FIST] ) + - ( aspd_base[pc_class2idx(sd->status.class_)][MAX_WEAPON_TYPE] - 2000 ); - -#else - // base weapon delay - amotion = (sd->status.weapon < MAX_WEAPON_TYPE) - ? (aspd_base[pc_class2idx(sd->status.class_)][sd->status.weapon]) // single weapon - : (aspd_base[pc_class2idx(sd->status.class_)][sd->weapontype1] + aspd_base[pc_class2idx(sd->status.class_)][sd->weapontype2])*7/10; // dual-wield - - // percentual delay reduction from stats - amotion -= amotion * (4*status->agi + status->dex)/1000; -#endif - // raw delay adjustment from bAspd bonus - amotion += sd->bonus.aspd_add; - - return amotion; -} - -static unsigned short status_base_atk(const struct block_list *bl, const struct status_data *status) -{ - int flag = 0, str, dex, -#ifdef RENEWAL - rstr, -#endif - dstr; - - - if(!(bl->type&battle_config.enable_baseatk)) - return 0; - - if (bl->type == BL_PC) - switch(((TBL_PC*)bl)->status.weapon){ - case W_BOW: - case W_MUSICAL: - case W_WHIP: - case W_REVOLVER: - case W_RIFLE: - case W_GATLING: - case W_SHOTGUN: - case W_GRENADE: - flag = 1; - } - if (flag) { -#ifdef RENEWAL - rstr = -#endif - str = status->dex; - dex = status->str; - } else { -#ifdef RENEWAL - rstr = -#endif - str = status->str; - dex = status->dex; - } - //Normally only players have base-atk, but homunc have a different batk - // equation, hinting that perhaps non-players should use this for batk. - // [Skotlex] - dstr = str/10; - str += dstr*dstr; - if (bl->type == BL_PC) -#ifdef RENEWAL - str = (rstr*10 + dex*10/5 + status->luk*10/3 + ((TBL_PC*)bl)->status.base_level*10/4)/10; -#else - str+= dex/5 + status->luk/5; -#endif - return cap_value(str, 0, USHRT_MAX); -} - -#ifndef RENEWAL -static inline unsigned short status_base_matk_min(const struct status_data* status){ return status->int_+(status->int_/7)*(status->int_/7); } -static inline unsigned short status_base_matk_max(const struct status_data* status){ return status->int_+(status->int_/5)*(status->int_/5); } -#else -unsigned short status_base_matk(const struct status_data* status, int level){ return status->int_+(status->int_/2)+(status->dex/5)+(status->luk/3)+(level/4); } -#endif - -//Fills in the misc data that can be calculated from the other status info (except for level) -void status_calc_misc(struct block_list *bl, struct status_data *status, int level) -{ - //Non players get the value set, players need to stack with previous bonuses. - if( bl->type != BL_PC ) - status->batk = - status->hit = status->flee = - status->def2 = status->mdef2 = - status->cri = status->flee2 = 0; - -#ifdef RENEWAL // renewal formulas - status->matk_min = status->matk_max = status_base_matk(status, level); - status->hit += level + status->dex + status->luk/3 + 175; //base level + ( every 1 dex = +1 hit ) + (every 3 luk = +1 hit) + 175 - status->flee += level + status->agi + status->luk/5 + 100; //base level + ( every 1 agi = +1 flee ) + (every 5 luk = +1 flee) + 100 - status->def2 += (int)(((float)level + status->vit)/2 + ((float)status->agi/5)); //base level + (every 2 vit = +1 def) + (every 5 agi = +1 def) - status->mdef2 += (int)(status->int_ + ((float)level/4) + ((float)status->dex/5) + ((float)status->vit/5)); //(every 4 base level = +1 mdef) + (every 1 int = +1 mdef) + (every 5 dex = +1 mdef) + (every 5 vit = +1 mdef) -#else - status->matk_min = status_base_matk_min(status); - status->matk_max = status_base_matk_max(status); - status->hit += level + status->dex; - status->flee += level + status->agi; - status->def2 += status->vit; - status->mdef2 += status->int_ + (status->vit>>1); -#endif - - if( bl->type&battle_config.enable_critical ) - status->cri += 10 + (status->luk*10/3); //(every 1 luk = +0.3 critical) - else - status->cri = 0; - - if (bl->type&battle_config.enable_perfect_flee) - status->flee2 += status->luk + 10; //(every 10 luk = +1 perfect flee) - else - status->flee2 = 0; - - if (status->batk) { - int temp = status->batk + status_base_atk(bl, status); - status->batk = cap_value(temp, 0, USHRT_MAX); - } else - status->batk = status_base_atk(bl, status); - if (status->cri) - switch (bl->type) { - case BL_MOB: - if(battle_config.mob_critical_rate != 100) - status->cri = status->cri*battle_config.mob_critical_rate/100; - if(!status->cri && battle_config.mob_critical_rate) - status->cri = 10; - break; - case BL_PC: - //Players don't have a critical adjustment setting as of yet. - break; - default: - if(battle_config.critical_rate != 100) - status->cri = status->cri*battle_config.critical_rate/100; - if (!status->cri && battle_config.critical_rate) - status->cri = 10; - } - if(bl->type&BL_REGEN) - status_calc_regen(bl, status, status_get_regen_data(bl)); -} - -//Skotlex: Calculates the initial status for the given mob -//first will only be false when the mob leveled up or got a GuardUp level. -int status_calc_mob_(struct mob_data* md, bool first) -{ - struct status_data *status; - struct block_list *mbl = NULL; - int flag=0; - - if(first) - { //Set basic level on respawn. - if (md->level > 0 && md->level <= MAX_LEVEL && md->level != md->db->lv) - ; - else - md->level = md->db->lv; - } - - //Check if we need custom base-status - if (battle_config.mobs_level_up && md->level > md->db->lv) - flag|=1; - - if (md->special_state.size) - flag|=2; - - if (md->guardian_data && md->guardian_data->guardup_lv) - flag|=4; - if (md->class_ == MOBID_EMPERIUM) - flag|=4; - - if (battle_config.slaves_inherit_speed && md->master_id) - flag|=8; - - if (md->master_id && md->special_state.ai>1) - flag|=16; - - if (!flag) - { //No special status required. - if (md->base_status) { - aFree(md->base_status); - md->base_status = NULL; - } - if(first) - memcpy(&md->status, &md->db->status, sizeof(struct status_data)); - return 0; - } - if (!md->base_status) - md->base_status = (struct status_data*)aCalloc(1, sizeof(struct status_data)); - - status = md->base_status; - memcpy(status, &md->db->status, sizeof(struct status_data)); - - if (flag&(8|16)) - mbl = map_id2bl(md->master_id); - - if (flag&8 && mbl) { - struct status_data *mstatus = status_get_base_status(mbl); - if (mstatus && - battle_config.slaves_inherit_speed&(mstatus->mode&MD_CANMOVE?1:2)) - status->speed = mstatus->speed; - if( status->speed < 2 ) /* minimum for the unit to function properly */ - status->speed = 2; - } - - if (flag&16 && mbl) - { //Max HP setting from Summon Flora/marine Sphere - struct unit_data *ud = unit_bl2ud(mbl); - //Remove special AI when this is used by regular mobs. - if (mbl->type == BL_MOB && !((TBL_MOB*)mbl)->special_state.ai) - md->special_state.ai = 0; - if (ud) - { // different levels of HP according to skill level - if (ud->skill_id == AM_SPHEREMINE) { - status->max_hp = 2000 + 400*ud->skill_lv; - } else if(ud->skill_id == KO_ZANZOU){ - status->max_hp = 3000 + 3000 * ud->skill_lv; - } else { //AM_CANNIBALIZE - status->max_hp = 1500 + 200*ud->skill_lv + 10*status_get_lv(mbl); - status->mode|= MD_CANATTACK|MD_AGGRESSIVE; - } - status->hp = status->max_hp; - } - } - - if (flag&1) - { // increase from mobs leveling up [Valaris] - int diff = md->level - md->db->lv; - status->str+= diff; - status->agi+= diff; - status->vit+= diff; - status->int_+= diff; - status->dex+= diff; - status->luk+= diff; - status->max_hp += diff*status->vit; - status->max_sp += diff*status->int_; - status->hp = status->max_hp; - status->sp = status->max_sp; - status->speed -= cap_value(diff, 0, status->speed - 10); - } - - - if (flag&2 && battle_config.mob_size_influence) - { // change for sized monsters [Valaris] - if (md->special_state.size==SZ_MEDIUM) { - status->max_hp>>=1; - status->max_sp>>=1; - if (!status->max_hp) status->max_hp = 1; - if (!status->max_sp) status->max_sp = 1; - status->hp=status->max_hp; - status->sp=status->max_sp; - status->str>>=1; - status->agi>>=1; - status->vit>>=1; - status->int_>>=1; - status->dex>>=1; - status->luk>>=1; - if (!status->str) status->str = 1; - if (!status->agi) status->agi = 1; - if (!status->vit) status->vit = 1; - if (!status->int_) status->int_ = 1; - if (!status->dex) status->dex = 1; - if (!status->luk) status->luk = 1; - } else if (md->special_state.size==SZ_BIG) { - status->max_hp<<=1; - status->max_sp<<=1; - status->hp=status->max_hp; - status->sp=status->max_sp; - status->str<<=1; - status->agi<<=1; - status->vit<<=1; - status->int_<<=1; - status->dex<<=1; - status->luk<<=1; - } - } - - status_calc_misc(&md->bl, status, md->level); - - if(flag&4) - { // Strengthen Guardians - custom value +10% / lv - struct guild_castle *gc; - gc=guild_mapname2gc(map[md->bl.m].name); - if (!gc) - ShowError("status_calc_mob: No castle set at map %s\n", map[md->bl.m].name); - else - if(gc->castle_id < 24 || md->class_ == MOBID_EMPERIUM) { -#ifdef RENEWAL - status->max_hp += 50 * gc->defense; - status->max_sp += 70 * gc->defense; -#else - status->max_hp += 1000 * gc->defense; - status->max_sp += 200 * gc->defense; -#endif - status->hp = status->max_hp; - status->sp = status->max_sp; - status->def += (gc->defense+2)/3; - status->mdef += (gc->defense+2)/3; - } - if(md->class_ != MOBID_EMPERIUM) { - status->batk += status->batk * 10*md->guardian_data->guardup_lv/100; - status->rhw.atk += status->rhw.atk * 10*md->guardian_data->guardup_lv/100; - status->rhw.atk2 += status->rhw.atk2 * 10*md->guardian_data->guardup_lv/100; - status->aspd_rate -= 100*md->guardian_data->guardup_lv; - } - } - - if( first ) //Initial battle status - memcpy(&md->status, status, sizeof(struct status_data)); - - return 1; -} - -//Skotlex: Calculates the stats of the given pet. -int status_calc_pet_(struct pet_data *pd, bool first) -{ - nullpo_ret(pd); - - if (first) { - memcpy(&pd->status, &pd->db->status, sizeof(struct status_data)); - pd->status.mode = MD_CANMOVE; // pets discard all modes, except walking - pd->status.speed = pd->petDB->speed; - - if(battle_config.pet_attack_support || battle_config.pet_damage_support) - {// attack support requires the pet to be able to attack - pd->status.mode|= MD_CANATTACK; - } - } - - if (battle_config.pet_lv_rate && pd->msd) - { - struct map_session_data *sd = pd->msd; - int lv; - - lv =sd->status.base_level*battle_config.pet_lv_rate/100; - if (lv < 0) - lv = 1; - if (lv != pd->pet.level || first) - { - struct status_data *bstat = &pd->db->status, *status = &pd->status; - pd->pet.level = lv; - if (!first) //Lv Up animation - clif_misceffect(&pd->bl, 0); - status->rhw.atk = (bstat->rhw.atk*lv)/pd->db->lv; - status->rhw.atk2 = (bstat->rhw.atk2*lv)/pd->db->lv; - status->str = (bstat->str*lv)/pd->db->lv; - status->agi = (bstat->agi*lv)/pd->db->lv; - status->vit = (bstat->vit*lv)/pd->db->lv; - status->int_ = (bstat->int_*lv)/pd->db->lv; - status->dex = (bstat->dex*lv)/pd->db->lv; - status->luk = (bstat->luk*lv)/pd->db->lv; - - status->rhw.atk = cap_value(status->rhw.atk, 1, battle_config.pet_max_atk1); - status->rhw.atk2 = cap_value(status->rhw.atk2, 2, battle_config.pet_max_atk2); - status->str = cap_value(status->str,1,battle_config.pet_max_stats); - status->agi = cap_value(status->agi,1,battle_config.pet_max_stats); - status->vit = cap_value(status->vit,1,battle_config.pet_max_stats); - status->int_= cap_value(status->int_,1,battle_config.pet_max_stats); - status->dex = cap_value(status->dex,1,battle_config.pet_max_stats); - status->luk = cap_value(status->luk,1,battle_config.pet_max_stats); - - status_calc_misc(&pd->bl, &pd->status, lv); - - if (!first) //Not done the first time because the pet is not visible yet - clif_send_petstatus(sd); - } - } else if (first) { - status_calc_misc(&pd->bl, &pd->status, pd->db->lv); - if (!battle_config.pet_lv_rate && pd->pet.level != pd->db->lv) - pd->pet.level = pd->db->lv; - } - - //Support rate modifier (1000 = 100%) - pd->rate_fix = 1000*(pd->pet.intimate - battle_config.pet_support_min_friendly)/(1000- battle_config.pet_support_min_friendly) +500; - if(battle_config.pet_support_rate != 100) - pd->rate_fix = pd->rate_fix*battle_config.pet_support_rate/100; - - return 1; -} - -/// Helper function for status_base_pc_maxhp(), used to pre-calculate the hp_sigma_val[] array -static void status_calc_sigma(void) -{ - int i,j; - - for(i = 0; i < CLASS_COUNT; i++) - { - unsigned int k = 0; - hp_sigma_val[i][0] = hp_sigma_val[i][1] = 0; - for(j = 2; j <= MAX_LEVEL; j++) - { - k += (hp_coefficient[i]*j + 50) / 100; - hp_sigma_val[i][j] = k; - if (k >= INT_MAX) - break; //Overflow protection. [Skotlex] - } - for(; j <= MAX_LEVEL; j++) - hp_sigma_val[i][j] = INT_MAX; - } -} - -/// Calculates base MaxHP value according to class and base level -/// The recursive equation used to calculate level bonus is (using integer operations) -/// f(0) = 35 | f(x+1) = f(x) + A + (x + B)*C/D -/// which reduces to something close to -/// f(x) = 35 + x*(A + B*C/D) + sum(i=2..x){ i*C/D } -static unsigned int status_base_pc_maxhp(struct map_session_data* sd, struct status_data* status) -{ - uint64 val = pc_class2idx(sd->status.class_); - val = 35 + sd->status.base_level*(int64)hp_coefficient2[val]/100 + hp_sigma_val[val][sd->status.base_level]; - - if((sd->class_&MAPID_UPPERMASK) == MAPID_NINJA || (sd->class_&MAPID_UPPERMASK) == MAPID_GUNSLINGER) - val += 100; //Since their HP can't be approximated well enough without this. - if((sd->class_&MAPID_UPPERMASK) == MAPID_TAEKWON && sd->status.base_level >= 90 && pc_famerank(sd->status.char_id, MAPID_TAEKWON)) - val *= 3; //Triple max HP for top ranking Taekwons over level 90. - if((sd->class_&MAPID_UPPERMASK) == MAPID_SUPER_NOVICE && sd->status.base_level >= 99) - val += 2000; //Supernovice lvl99 hp bonus. - - val += val * status->vit/100; // +1% per each point of VIT - - if (sd->class_&JOBL_UPPER) - val += val * 25/100; //Trans classes get a 25% hp bonus - else if (sd->class_&JOBL_BABY) - val -= val * 30/100; //Baby classes get a 30% hp penalty - return (unsigned int)val; -} - -static unsigned int status_base_pc_maxsp(struct map_session_data* sd, struct status_data *status) -{ - uint64 val; - - val = 10 + sd->status.base_level*(int64)sp_coefficient[pc_class2idx(sd->status.class_)]/100; - val += val * status->int_/100; - - if (sd->class_&JOBL_UPPER) - val += val * 25/100; - else if (sd->class_&JOBL_BABY) - val -= val * 30/100; - if ((sd->class_&MAPID_UPPERMASK) == MAPID_TAEKWON && sd->status.base_level >= 90 && pc_famerank(sd->status.char_id, MAPID_TAEKWON)) - val *= 3; //Triple max SP for top ranking Taekwons over level 90. - - return (unsigned int)val; -} - -//Calculates player data from scratch without counting SC adjustments. -//Should be invoked whenever players raise stats, learn passive skills or change equipment. -int status_calc_pc_(struct map_session_data* sd, bool first) -{ - static int calculating = 0; //Check for recursive call preemption. [Skotlex] - struct status_data *status; // pointer to the player's base status - const struct status_change *sc = &sd->sc; - struct s_skill b_skill[MAX_SKILL]; // previous skill tree - int b_weight, b_max_weight, b_cart_weight_max, // previous weight - i, index, skill,refinedef=0; - int64 i64; - - if (++calculating > 10) //Too many recursive calls! - return -1; - - // remember player-specific values that are currently being shown to the client (for refresh purposes) - memcpy(b_skill, &sd->status.skill, sizeof(b_skill)); - b_weight = sd->weight; - b_max_weight = sd->max_weight; - b_cart_weight_max = sd->cart_weight_max; - - pc_calc_skilltree(sd); // SkillTree calculation - - sd->max_weight = max_weight_base[pc_class2idx(sd->status.class_)]+sd->status.str*300; - - if(first) { - //Load Hp/SP from char-received data. - sd->battle_status.hp = sd->status.hp; - sd->battle_status.sp = sd->status.sp; - sd->regen.sregen = &sd->sregen; - sd->regen.ssregen = &sd->ssregen; - 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_weight=0; - 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++; - } - } - - status = &sd->base_status; - // these are not zeroed. [zzo] - sd->hprate=100; - sd->sprate=100; - sd->castrate=100; - sd->delayrate=100; - sd->dsprate=100; - sd->hprecov_rate = 100; - sd->sprecov_rate = 100; - sd->matk_rate = 100; - 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->regen.state.block = 0; - - // zeroed arrays, order follows the order in pc.h. - // add new arrays to the end of zeroed area in pc.h (see comments) and size here. [zzo] - memset (sd->param_bonus, 0, sizeof(sd->param_bonus) - + sizeof(sd->param_equip) - + sizeof(sd->subele) - + sizeof(sd->subrace) - + sizeof(sd->subrace2) - + sizeof(sd->subsize) - + sizeof(sd->reseff) - + sizeof(sd->weapon_coma_ele) - + sizeof(sd->weapon_coma_race) - + sizeof(sd->weapon_atk) - + sizeof(sd->weapon_atk_rate) - + sizeof(sd->arrow_addele) - + sizeof(sd->arrow_addrace) - + sizeof(sd->arrow_addsize) - + sizeof(sd->magic_addele) - + sizeof(sd->magic_addrace) - + sizeof(sd->magic_addsize) - + sizeof(sd->magic_atk_ele) - + sizeof(sd->critaddrace) - + sizeof(sd->expaddrace) - + sizeof(sd->ignore_mdef) - + sizeof(sd->ignore_def) - + sizeof(sd->itemgrouphealrate) - + sizeof(sd->sp_gain_race) - + sizeof(sd->sp_gain_race_attack) - + sizeof(sd->hp_gain_race_attack) - ); - - memset (&sd->right_weapon.overrefine, 0, sizeof(sd->right_weapon) - sizeof(sd->right_weapon.atkmods)); - memset (&sd->left_weapon.overrefine, 0, sizeof(sd->left_weapon) - sizeof(sd->left_weapon.atkmods)); - - if (sd->special_state.intravision && !sd->sc.data[SC_INTRAVISION]) //Clear intravision as long as nothing else is using it - clif_status_load(&sd->bl, SI_INTRAVISION, 0); - - memset(&sd->special_state,0,sizeof(sd->special_state)); - memset(&status->max_hp, 0, sizeof(struct status_data)-(sizeof(status->hp)+sizeof(status->sp))); - - //FIXME: Most of these stuff should be calculated once, but how do I fix the memset above to do that? [Skotlex] - status->speed = DEFAULT_WALK_SPEED; - //Give them all modes except these (useful for clones) - status->mode = MD_MASK&~(MD_BOSS|MD_PLANT|MD_DETECTOR|MD_ANGRY|MD_TARGETWEAK); - - status->size = (sd->class_&JOBL_BABY)?SZ_SMALL:SZ_MEDIUM; - if (battle_config.character_size && pc_isriding(sd)) { //[Lupus] - if (sd->class_&JOBL_BABY) { - if (battle_config.character_size&SZ_BIG) - status->size++; - } else - if(battle_config.character_size&SZ_MEDIUM) - status->size++; - } - status->aspd_rate = 1000; - status->ele_lv = 1; - status->race = RC_DEMIHUMAN; - - //zero up structures... - memset(&sd->autospell,0,sizeof(sd->autospell) - + sizeof(sd->autospell2) - + sizeof(sd->autospell3) - + sizeof(sd->addeff) - + sizeof(sd->addeff2) - + sizeof(sd->addeff3) - + sizeof(sd->skillatk) - + sizeof(sd->skillusesprate) - + sizeof(sd->skillusesp) - + sizeof(sd->skillheal) - + sizeof(sd->skillheal2) - + sizeof(sd->hp_loss) - + sizeof(sd->sp_loss) - + sizeof(sd->hp_regen) - + sizeof(sd->sp_regen) - + sizeof(sd->skillblown) - + sizeof(sd->skillcast) - + sizeof(sd->add_def) - + sizeof(sd->add_mdef) - + sizeof(sd->add_mdmg) - + sizeof(sd->add_drop) - + sizeof(sd->itemhealrate) - + sizeof(sd->subele2) - + sizeof(sd->skillcooldown) - + sizeof(sd->skillfixcast) - + sizeof(sd->skillvarcast) - ); - - memset (&sd->bonus, 0,sizeof(sd->bonus)); - - // Autobonus - pc_delautobonus(sd,sd->autobonus,ARRAYLENGTH(sd->autobonus),true); - pc_delautobonus(sd,sd->autobonus2,ARRAYLENGTH(sd->autobonus2),true); - pc_delautobonus(sd,sd->autobonus3,ARRAYLENGTH(sd->autobonus3),true); - - // Parse equipment. - for(i=0;i<EQI_MAX-1;i++) { - current_equip_item_index = index = sd->equip_index[i]; //We pass INDEX to current_equip_item_index - for EQUIP_SCRIPT (new cards solution) [Lupus] - if(index < 0) - continue; - if(i == EQI_HAND_R && sd->equip_index[EQI_HAND_L] == index) - continue; - if(i == EQI_HEAD_MID && sd->equip_index[EQI_HEAD_LOW] == index) - continue; - if(i == EQI_HEAD_TOP && (sd->equip_index[EQI_HEAD_MID] == index || sd->equip_index[EQI_HEAD_LOW] == index)) - continue; - if(i == EQI_COSTUME_MID && sd->equip_index[EQI_COSTUME_LOW] == index) - continue; - if(i == EQI_COSTUME_TOP && (sd->equip_index[EQI_COSTUME_MID] == index || sd->equip_index[EQI_COSTUME_LOW] == index)) - continue; - if(!sd->inventory_data[index]) - continue; - - status->def += sd->inventory_data[index]->def; - - if(first && sd->inventory_data[index]->equip_script) - { //Execute equip-script on login - run_script(sd->inventory_data[index]->equip_script,0,sd->bl.id,0); - if (!calculating) - return 1; - } - - // sanitize the refine level in case someone decreased the value inbetween - if (sd->status.inventory[index].refine > MAX_REFINE) - sd->status.inventory[index].refine = MAX_REFINE; - - if(sd->inventory_data[index]->type == IT_WEAPON) { - int r,wlv = sd->inventory_data[index]->wlv; - struct weapon_data *wd; - struct weapon_atk *wa; - if (wlv >= REFINE_TYPE_MAX) - wlv = REFINE_TYPE_MAX - 1; - if(i == EQI_HAND_L && sd->status.inventory[index].equip == EQP_HAND_L) { - wd = &sd->left_weapon; // Left-hand weapon - wa = &status->lhw; - } else { - wd = &sd->right_weapon; - wa = &status->rhw; - } - wa->atk += sd->inventory_data[index]->atk; - if ( (r = sd->status.inventory[index].refine) ) - wa->atk2 = refine_info[wlv].bonus[r-1] / 100; - -#ifdef RENEWAL - wa->matk += sd->inventory_data[index]->matk; - wa->wlv = wlv; - if( r ) // renewal magic attack refine bonus - wa->matk += refine_info[wlv].bonus[r-1] / 100; -#endif - - //Overrefine bonus. - if (r) - wd->overrefine = refine_info[wlv].randombonus_max[r-1] / 100; - - wa->range += sd->inventory_data[index]->range; - if(sd->inventory_data[index]->script) { - if (wd == &sd->left_weapon) { - sd->state.lr_flag = 1; - run_script(sd->inventory_data[index]->script,0,sd->bl.id,0); - sd->state.lr_flag = 0; - } else - run_script(sd->inventory_data[index]->script,0,sd->bl.id,0); - if (!calculating) //Abort, run_script retriggered this. [Skotlex] - return 1; - } - - if(sd->status.inventory[index].card[0]==CARD0_FORGE) - { // Forged weapon - wd->star += (sd->status.inventory[index].card[1]>>8); - if(wd->star >= 15) wd->star = 40; // 3 Star Crumbs now give +40 dmg - if(pc_famerank(MakeDWord(sd->status.inventory[index].card[2],sd->status.inventory[index].card[3]) ,MAPID_BLACKSMITH)) - wd->star += 10; - - if (!wa->ele) //Do not overwrite element from previous bonuses. - wa->ele = (sd->status.inventory[index].card[1]&0x0f); - } - } - else if(sd->inventory_data[index]->type == IT_ARMOR) { - int r; - if ( (r = sd->status.inventory[index].refine) ) - refinedef += refine_info[REFINE_TYPE_ARMOR].bonus[r-1]; - if(sd->inventory_data[index]->script) { - if( i == EQI_HAND_L ) //Shield - sd->state.lr_flag = 3; - run_script(sd->inventory_data[index]->script,0,sd->bl.id,0); - if( i == EQI_HAND_L ) //Shield - sd->state.lr_flag = 0; - if (!calculating) //Abort, run_script retriggered this. [Skotlex] - return 1; - } - } - } - - if(sd->equip_index[EQI_AMMO] >= 0){ - index = sd->equip_index[EQI_AMMO]; - if(sd->inventory_data[index]){ // Arrows - sd->bonus.arrow_atk += sd->inventory_data[index]->atk; - sd->state.lr_flag = 2; - if( !itemdb_is_GNthrowable(sd->inventory_data[index]->nameid) ) //don't run scripts on throwable items - run_script(sd->inventory_data[index]->script,0,sd->bl.id,0); - sd->state.lr_flag = 0; - if (!calculating) //Abort, run_script retriggered status_calc_pc. [Skotlex] - return 1; - } - } - - /* we've got combos to process */ - if( sd->combos.count ) { - for( i = 0; i < sd->combos.count; i++ ) { - run_script(sd->combos.bonus[i],0,sd->bl.id,0); - if (!calculating) //Abort, run_script retriggered this. - return 1; - } - } - - //Store equipment script bonuses - memcpy(sd->param_equip,sd->param_bonus,sizeof(sd->param_equip)); - memset(sd->param_bonus, 0, sizeof(sd->param_bonus)); - - status->def += (refinedef+50)/100; - - //Parse Cards - for(i=0;i<EQI_MAX-1;i++) { - current_equip_item_index = index = sd->equip_index[i]; //We pass INDEX to current_equip_item_index - for EQUIP_SCRIPT (new cards solution) [Lupus] - if(index < 0) - continue; - if(i == EQI_HAND_R && sd->equip_index[EQI_HAND_L] == index) - continue; - if(i == EQI_HEAD_MID && sd->equip_index[EQI_HEAD_LOW] == index) - continue; - if(i == EQI_HEAD_TOP && (sd->equip_index[EQI_HEAD_MID] == index || sd->equip_index[EQI_HEAD_LOW] == index)) - continue; - - if(sd->inventory_data[index]) { - int j,c; - struct item_data *data; - - //Card script execution. - if(itemdb_isspecial(sd->status.inventory[index].card[0])) - continue; - for(j=0;j<MAX_SLOTS;j++){ // Uses MAX_SLOTS to support Soul Bound system [Inkfish] - current_equip_card_id= c= sd->status.inventory[index].card[j]; - if(!c) - continue; - data = itemdb_exists(c); - if(!data) - continue; - if(first && data->equip_script) - { //Execute equip-script on login - run_script(data->equip_script,0,sd->bl.id,0); - if (!calculating) - return 1; - } - if(!data->script) - continue; - if(data->flag.no_equip) { //Card restriction checks. - if(map[sd->bl.m].flag.restricted && data->flag.no_equip&(8*map[sd->bl.m].zone)) - continue; - if(!map_flag_vs(sd->bl.m) && data->flag.no_equip&1) - continue; - if(map[sd->bl.m].flag.pvp && data->flag.no_equip&2) - continue; - if(map_flag_gvg(sd->bl.m) && data->flag.no_equip&4) - continue; - if(map[sd->bl.m].flag.battleground && data->flag.no_equip&8) - continue; - } - if(i == EQI_HAND_L && sd->status.inventory[index].equip == EQP_HAND_L) - { //Left hand status. - sd->state.lr_flag = 1; - run_script(data->script,0,sd->bl.id,0); - sd->state.lr_flag = 0; - } else - run_script(data->script,0,sd->bl.id,0); - if (!calculating) //Abort, run_script his function. [Skotlex] - return 1; - } - } - } - - if( sc->count && sc->data[SC_ITEMSCRIPT] ) - { - struct item_data *data = itemdb_exists(sc->data[SC_ITEMSCRIPT]->val1); - if( data && data->script ) - run_script(data->script,0,sd->bl.id,0); - } - - if( sd->pd ) - { // Pet Bonus - struct pet_data *pd = sd->pd; - if( pd && pd->petDB && pd->petDB->equip_script && pd->pet.intimate >= battle_config.pet_equip_min_friendly ) - run_script(pd->petDB->equip_script,0,sd->bl.id,0); - if( pd && pd->pet.intimate > 0 && (!battle_config.pet_equip_required || pd->pet.equip > 0) && pd->state.skillbonus == 1 && pd->bonus ) - pc_bonus(sd,pd->bonus->type, pd->bonus->val); - } - - //param_bonus now holds card bonuses. - if(status->rhw.range < 1) status->rhw.range = 1; - if(status->lhw.range < 1) status->lhw.range = 1; - if(status->rhw.range < status->lhw.range) - status->rhw.range = status->lhw.range; - - sd->bonus.double_rate += sd->bonus.double_add_rate; - sd->bonus.perfect_hit += sd->bonus.perfect_hit_add; - sd->bonus.splash_range += sd->bonus.splash_add_range; - - // Damage modifiers from weapon type - sd->right_weapon.atkmods[0] = atkmods[0][sd->weapontype1]; - sd->right_weapon.atkmods[1] = atkmods[1][sd->weapontype1]; - sd->right_weapon.atkmods[2] = atkmods[2][sd->weapontype1]; - sd->left_weapon.atkmods[0] = atkmods[0][sd->weapontype2]; - sd->left_weapon.atkmods[1] = atkmods[1][sd->weapontype2]; - sd->left_weapon.atkmods[2] = atkmods[2][sd->weapontype2]; - - if(pc_isriding(sd) && - (sd->status.weapon==W_1HSPEAR || sd->status.weapon==W_2HSPEAR)) - { //When Riding with spear, damage modifier to mid-class becomes - //same as versus large size. - sd->right_weapon.atkmods[1] = sd->right_weapon.atkmods[2]; - sd->left_weapon.atkmods[1] = sd->left_weapon.atkmods[2]; - } - -// ----- STATS CALCULATION ----- - - // Job bonuses - index = pc_class2idx(sd->status.class_); - for(i=0;i<(int)sd->status.job_level && i<MAX_LEVEL;i++){ - if(!job_bonus[index][i]) - continue; - switch(job_bonus[index][i]) { - case 1: status->str++; break; - case 2: status->agi++; break; - case 3: status->vit++; break; - case 4: status->int_++; break; - case 5: status->dex++; break; - case 6: status->luk++; break; - } - } - - // If a Super Novice has never died and is at least joblv 70, he gets all stats +10 - if((sd->class_&MAPID_UPPERMASK) == MAPID_SUPER_NOVICE && sd->die_counter == 0 && sd->status.job_level >= 70){ - status->str += 10; - status->agi += 10; - status->vit += 10; - status->int_+= 10; - status->dex += 10; - status->luk += 10; - } - - // Absolute modifiers from passive skills - if(pc_checkskill(sd,BS_HILTBINDING)>0) - status->str++; - if((skill=pc_checkskill(sd,SA_DRAGONOLOGY))>0) - status->int_ += (skill+1)/2; // +1 INT / 2 lv - if((skill=pc_checkskill(sd,AC_OWL))>0) - status->dex += skill; - if((skill = pc_checkskill(sd,RA_RESEARCHTRAP))>0) - status->int_ += skill; - - // Bonuses from cards and equipment as well as base stat, remember to avoid overflows. - i = status->str + sd->status.str + sd->param_bonus[0] + sd->param_equip[0]; - status->str = cap_value(i,0,USHRT_MAX); - i = status->agi + sd->status.agi + sd->param_bonus[1] + sd->param_equip[1]; - status->agi = cap_value(i,0,USHRT_MAX); - i = status->vit + sd->status.vit + sd->param_bonus[2] + sd->param_equip[2]; - status->vit = cap_value(i,0,USHRT_MAX); - i = status->int_+ sd->status.int_+ sd->param_bonus[3] + sd->param_equip[3]; - status->int_ = cap_value(i,0,USHRT_MAX); - i = status->dex + sd->status.dex + sd->param_bonus[4] + sd->param_equip[4]; - status->dex = cap_value(i,0,USHRT_MAX); - i = status->luk + sd->status.luk + sd->param_bonus[5] + sd->param_equip[5]; - status->luk = cap_value(i,0,USHRT_MAX); - -// ------ BASE ATTACK CALCULATION ------ - - // Base batk value is set on status_calc_misc - // weapon-type bonus (FIXME: Why is the weapon_atk bonus applied to base attack?) - if (sd->status.weapon < MAX_WEAPON_TYPE && sd->weapon_atk[sd->status.weapon]) - status->batk += sd->weapon_atk[sd->status.weapon]; - // Absolute modifiers from passive skills - if((skill=pc_checkskill(sd,BS_HILTBINDING))>0) - status->batk += 4; - -// ----- HP MAX CALCULATION ----- - - // Basic MaxHP value - //We hold the standard Max HP here to make it faster to recalculate on vit changes. - sd->status.max_hp = status_base_pc_maxhp(sd,status); - //This is done to handle underflows from negative Max HP bonuses - i64 = sd->status.max_hp + (int)status->max_hp; - status->max_hp = (unsigned int)cap_value(i64, 0, INT_MAX); - - // Absolute modifiers from passive skills - if((skill=pc_checkskill(sd,CR_TRUST))>0) - status->max_hp += skill*200; - - // Apply relative modifiers from equipment - if(sd->hprate < 0) - sd->hprate = 0; - if(sd->hprate!=100) - status->max_hp = (int64)status->max_hp * sd->hprate/100; - if(battle_config.hp_rate != 100) - status->max_hp = (int64)status->max_hp * battle_config.hp_rate/100; - - if(status->max_hp > (unsigned int)battle_config.max_hp) - status->max_hp = battle_config.max_hp; - else if(!status->max_hp) - status->max_hp = 1; - -// ----- SP MAX CALCULATION ----- - - // Basic MaxSP value - sd->status.max_sp = status_base_pc_maxsp(sd,status); - //This is done to handle underflows from negative Max SP bonuses - i64 = sd->status.max_sp + (int)status->max_sp; - status->max_sp = (unsigned int)cap_value(i64, 0, INT_MAX); - - // Absolute modifiers from passive skills - if((skill=pc_checkskill(sd,SL_KAINA))>0) - status->max_sp += 30*skill; - if((skill=pc_checkskill(sd,HP_MEDITATIO))>0) - status->max_sp += (int64)status->max_sp * skill/100; - if((skill=pc_checkskill(sd,HW_SOULDRAIN))>0) - status->max_sp += (int64)status->max_sp * 2*skill/100; - if( (skill = pc_checkskill(sd,RA_RESEARCHTRAP)) > 0 ) - status->max_sp += 200 + 20 * skill; - if( (skill = pc_checkskill(sd,WM_LESSON)) > 0 ) - status->max_sp += 30 * skill; - - - // Apply relative modifiers from equipment - if(sd->sprate < 0) - sd->sprate = 0; - if(sd->sprate!=100) - status->max_sp = (int64)status->max_sp * sd->sprate/100; - if(battle_config.sp_rate != 100) - status->max_sp = (int64)status->max_sp * battle_config.sp_rate/100; - - if(status->max_sp > (unsigned int)battle_config.max_sp) - status->max_sp = battle_config.max_sp; - else if(!status->max_sp) - status->max_sp = 1; - -// ----- RESPAWN HP/SP ----- -// - //Calc respawn hp and store it on base_status - if (sd->special_state.restart_full_recover) - { - status->hp = status->max_hp; - status->sp = status->max_sp; - } else { - if((sd->class_&MAPID_BASEMASK) == MAPID_NOVICE && !(sd->class_&JOBL_2) - && battle_config.restart_hp_rate < 50) - status->hp = status->max_hp>>1; - else - status->hp = (int64)status->max_hp * battle_config.restart_hp_rate/100; - if(!status->hp) - status->hp = 1; - - status->sp = (int64)status->max_sp * battle_config.restart_sp_rate /100; - - if( !status->sp ) /* the minimum for the respawn setting is SP:1 */ - status->sp = 1; - } - -// ----- MISC CALCULATION ----- - status_calc_misc(&sd->bl, status, sd->status.base_level); - - //Equipment modifiers for misc settings - if(sd->matk_rate < 0) - sd->matk_rate = 0; - - if(sd->matk_rate != 100){ - status->matk_max = status->matk_max * sd->matk_rate/100; - status->matk_min = status->matk_min * sd->matk_rate/100; - } - - if(sd->hit_rate < 0) - sd->hit_rate = 0; - if(sd->hit_rate != 100) - status->hit = status->hit * sd->hit_rate/100; - - if(sd->flee_rate < 0) - sd->flee_rate = 0; - if(sd->flee_rate != 100) - status->flee = status->flee * sd->flee_rate/100; - - if(sd->def2_rate < 0) - sd->def2_rate = 0; - if(sd->def2_rate != 100) - status->def2 = status->def2 * sd->def2_rate/100; - - if(sd->mdef2_rate < 0) - sd->mdef2_rate = 0; - if(sd->mdef2_rate != 100) - status->mdef2 = status->mdef2 * sd->mdef2_rate/100; - - if(sd->critical_rate < 0) - sd->critical_rate = 0; - if(sd->critical_rate != 100) - status->cri = status->cri * sd->critical_rate/100; - - if(sd->flee2_rate < 0) - sd->flee2_rate = 0; - if(sd->flee2_rate != 100) - status->flee2 = status->flee2 * sd->flee2_rate/100; - -// ----- HIT CALCULATION ----- - - // Absolute modifiers from passive skills - if((skill=pc_checkskill(sd,BS_WEAPONRESEARCH))>0) - status->hit += skill*2; - if((skill=pc_checkskill(sd,AC_VULTURE))>0){ -#ifndef RENEWAL - status->hit += skill; -#endif - if(sd->status.weapon == W_BOW) - status->rhw.range += skill; - } - if(sd->status.weapon >= W_REVOLVER && sd->status.weapon <= W_GRENADE) - { - if((skill=pc_checkskill(sd,GS_SINGLEACTION))>0) - status->hit += 2*skill; - if((skill=pc_checkskill(sd,GS_SNAKEEYE))>0) { - status->hit += skill; - status->rhw.range += skill; - } - } - -// ----- FLEE CALCULATION ----- - - // Absolute modifiers from passive skills - if((skill=pc_checkskill(sd,TF_MISS))>0) - status->flee += skill*(sd->class_&JOBL_2 && (sd->class_&MAPID_BASEMASK) == MAPID_THIEF? 4 : 3); - if((skill=pc_checkskill(sd,MO_DODGE))>0) - status->flee += (skill*3)>>1; -// ----- EQUIPMENT-DEF CALCULATION ----- - - // Apply relative modifiers from equipment - if(sd->def_rate < 0) - sd->def_rate = 0; - if(sd->def_rate != 100) { - i = status->def * sd->def_rate/100; - status->def = cap_value(i, DEFTYPE_MIN, DEFTYPE_MAX); - } - -#ifndef RENEWAL - if (!battle_config.weapon_defense_type && status->def > battle_config.max_def) - { - status->def2 += battle_config.over_def_bonus*(status->def -battle_config.max_def); - status->def = (unsigned char)battle_config.max_def; - } -#endif - -// ----- EQUIPMENT-MDEF CALCULATION ----- - - // Apply relative modifiers from equipment - if(sd->mdef_rate < 0) - sd->mdef_rate = 0; - if(sd->mdef_rate != 100) { - i = status->mdef * sd->mdef_rate/100; - status->mdef = cap_value(i, DEFTYPE_MIN, DEFTYPE_MAX); - } - -#ifndef RENEWAL - if (!battle_config.magic_defense_type && status->mdef > battle_config.max_def) - { - status->mdef2 += battle_config.over_def_bonus*(status->mdef -battle_config.max_def); - status->mdef = (signed char)battle_config.max_def; - } -#endif - -// ----- ASPD CALCULATION ----- -// Unlike other stats, ASPD rate modifiers from skills/SCs/items/etc are first all added together, then the final modifier is applied - - // Basic ASPD value - i = status_base_amotion_pc(sd,status); - status->amotion = cap_value(i,((sd->class_&JOBL_THIRD) ? battle_config.max_third_aspd : battle_config.max_aspd),2000); - - // Relative modifiers from passive skills -#ifndef RENEWAL_ASPD - if((skill=pc_checkskill(sd,SA_ADVANCEDBOOK))>0 && sd->status.weapon == W_BOOK) - status->aspd_rate -= 5*skill; - if((skill = pc_checkskill(sd,SG_DEVIL)) > 0 && !pc_nextjobexp(sd)) - status->aspd_rate -= 30*skill; - if((skill=pc_checkskill(sd,GS_SINGLEACTION))>0 && - (sd->status.weapon >= W_REVOLVER && sd->status.weapon <= W_GRENADE)) - status->aspd_rate -= ((skill+1)/2) * 10; - if(pc_isriding(sd)) - status->aspd_rate += 500-100*pc_checkskill(sd,KN_CAVALIERMASTERY); - else if(pc_isridingdragon(sd)) - status->aspd_rate += 250-50*pc_checkskill(sd,RK_DRAGONTRAINING); -#else // needs more info - if((skill=pc_checkskill(sd,SA_ADVANCEDBOOK))>0 && sd->status.weapon == W_BOOK) - status->aspd_rate += 5*skill; - if((skill = pc_checkskill(sd,SG_DEVIL)) > 0 && !pc_nextjobexp(sd)) - status->aspd_rate += 30*skill; - if((skill=pc_checkskill(sd,GS_SINGLEACTION))>0 && - (sd->status.weapon >= W_REVOLVER && sd->status.weapon <= W_GRENADE)) - status->aspd_rate += ((skill+1)/2) * 10; - if(pc_isriding(sd)) - status->aspd_rate -= 500-100*pc_checkskill(sd,KN_CAVALIERMASTERY); - else if(pc_isridingdragon(sd)) - status->aspd_rate -= 250-50*pc_checkskill(sd,RK_DRAGONTRAINING); -#endif - status->adelay = 2*status->amotion; - - -// ----- DMOTION ----- -// - i = 800-status->agi*4; - status->dmotion = cap_value(i, 400, 800); - if(battle_config.pc_damage_delay_rate != 100) - status->dmotion = status->dmotion*battle_config.pc_damage_delay_rate/100; - -// ----- MISC CALCULATIONS ----- - - // Weight - if((skill=pc_checkskill(sd,MC_INCCARRY))>0) - sd->max_weight += 2000*skill; - if(pc_isriding(sd) && pc_checkskill(sd,KN_RIDING)>0) - sd->max_weight += 10000; - else if(pc_isridingdragon(sd)) - sd->max_weight += 5000+2000*pc_checkskill(sd,RK_DRAGONTRAINING); - if(sc->data[SC_KNOWLEDGE]) - sd->max_weight += sd->max_weight*sc->data[SC_KNOWLEDGE]->val1/10; - if((skill=pc_checkskill(sd,ALL_INCCARRY))>0) - sd->max_weight += 2000*skill; - - sd->cart_weight_max = battle_config.max_cart_weight + (pc_checkskill(sd, GN_REMODELING_CART)*5000); - - if (pc_checkskill(sd,SM_MOVINGRECOVERY)>0) - sd->regen.state.walk = 1; - else - sd->regen.state.walk = 0; - - // Skill SP cost - if((skill=pc_checkskill(sd,HP_MANARECHARGE))>0 ) - sd->dsprate -= 4*skill; - - if(sc->data[SC_SERVICE4U]) - sd->dsprate -= sc->data[SC_SERVICE4U]->val3; - - if(sc->data[SC_SPCOST_RATE]) - sd->dsprate -= sc->data[SC_SPCOST_RATE]->val1; - - //Underflow protections. - if(sd->dsprate < 0) - sd->dsprate = 0; - if(sd->castrate < 0) - sd->castrate = 0; - if(sd->delayrate < 0) - sd->delayrate = 0; - if(sd->hprecov_rate < 0) - sd->hprecov_rate = 0; - if(sd->sprecov_rate < 0) - sd->sprecov_rate = 0; - - // Anti-element and anti-race - if((skill=pc_checkskill(sd,CR_TRUST))>0) - sd->subele[ELE_HOLY] += skill*5; - if((skill=pc_checkskill(sd,BS_SKINTEMPER))>0) { - sd->subele[ELE_NEUTRAL] += skill; - sd->subele[ELE_FIRE] += skill*4; - } - if((skill=pc_checkskill(sd,SA_DRAGONOLOGY))>0 ){ - skill = skill*4; - sd->right_weapon.addrace[RC_DRAGON]+=skill; - sd->left_weapon.addrace[RC_DRAGON]+=skill; - sd->magic_addrace[RC_DRAGON]+=skill; - sd->subrace[RC_DRAGON]+=skill; - } - - if(sc->count){ - if(sc->data[SC_CONCENTRATE]) { //Update the card-bonus data - sc->data[SC_CONCENTRATE]->val3 = sd->param_bonus[1]; //Agi - sc->data[SC_CONCENTRATE]->val4 = sd->param_bonus[4]; //Dex - } - if(sc->data[SC_SIEGFRIED]){ - i = sc->data[SC_SIEGFRIED]->val2; - sd->subele[ELE_WATER] += i; - sd->subele[ELE_EARTH] += i; - sd->subele[ELE_FIRE] += i; - sd->subele[ELE_WIND] += i; - sd->subele[ELE_POISON] += i; - sd->subele[ELE_HOLY] += i; - sd->subele[ELE_DARK] += i; - sd->subele[ELE_GHOST] += i; - sd->subele[ELE_UNDEAD] += i; - } - if(sc->data[SC_PROVIDENCE]){ - sd->subele[ELE_HOLY] += sc->data[SC_PROVIDENCE]->val2; - sd->subrace[RC_DEMON] += sc->data[SC_PROVIDENCE]->val2; - } - if(sc->data[SC_ARMOR_ELEMENT]) { //This status change should grant card-type elemental resist. - sd->subele[ELE_WATER] += sc->data[SC_ARMOR_ELEMENT]->val1; - sd->subele[ELE_EARTH] += sc->data[SC_ARMOR_ELEMENT]->val2; - sd->subele[ELE_FIRE] += sc->data[SC_ARMOR_ELEMENT]->val3; - sd->subele[ELE_WIND] += sc->data[SC_ARMOR_ELEMENT]->val4; - } - if(sc->data[SC_ARMOR_RESIST]) { // Undead Scroll - sd->subele[ELE_WATER] += sc->data[SC_ARMOR_RESIST]->val1; - sd->subele[ELE_EARTH] += sc->data[SC_ARMOR_RESIST]->val2; - sd->subele[ELE_FIRE] += sc->data[SC_ARMOR_RESIST]->val3; - sd->subele[ELE_WIND] += sc->data[SC_ARMOR_RESIST]->val4; - } - if( sc->data[SC_FIRE_CLOAK_OPTION] ) { - i = sc->data[SC_FIRE_CLOAK_OPTION]->val2; - sd->subele[ELE_FIRE] += i; - sd->subele[ELE_WATER] -= i; - } - if( sc->data[SC_WATER_DROP_OPTION] ) { - i = sc->data[SC_WATER_DROP_OPTION]->val2; - sd->subele[ELE_WATER] += i; - sd->subele[ELE_WIND] -= i; - } - if( sc->data[SC_WIND_CURTAIN_OPTION] ) { - i = sc->data[SC_WIND_CURTAIN_OPTION]->val2; - sd->subele[ELE_WIND] += i; - sd->subele[ELE_EARTH] -= i; - } - if( sc->data[SC_STONE_SHIELD_OPTION] ) { - i = sc->data[SC_STONE_SHIELD_OPTION]->val2; - sd->subele[ELE_EARTH] += i; - sd->subele[ELE_FIRE] -= i; - } - if( sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 3 ) - sd->magic_addele[ELE_FIRE] += 25; - if( sc->data[SC_WATER_INSIGNIA] && sc->data[SC_WATER_INSIGNIA]->val1 == 3 ) - sd->magic_addele[ELE_WATER] += 25; - if( sc->data[SC_WIND_INSIGNIA] && sc->data[SC_WIND_INSIGNIA]->val1 == 3 ) - sd->magic_addele[ELE_WIND] += 25; - if( sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 3 ) - sd->magic_addele[ELE_EARTH] += 25; - } - status_cpy(&sd->battle_status, status); - -// ----- CLIENT-SIDE REFRESH ----- - if(!sd->bl.prev) { - //Will update on LoadEndAck - calculating = 0; - return 0; - } - if(memcmp(b_skill,sd->status.skill,sizeof(sd->status.skill))) - clif_skillinfoblock(sd); - if(b_weight != sd->weight) - clif_updatestatus(sd,SP_WEIGHT); - if(b_max_weight != sd->max_weight) { - clif_updatestatus(sd,SP_MAXWEIGHT); - pc_updateweightstatus(sd); - } - if( b_cart_weight_max != sd->cart_weight_max ) { - clif_updatestatus(sd,SP_CARTINFO); - } - - calculating = 0; - - return 0; -} - -int status_calc_mercenary_(struct mercenary_data *md, bool first) -{ - struct status_data *status = &md->base_status; - struct s_mercenary *merc = &md->mercenary; - - if( first ) - { - memcpy(status, &md->db->status, sizeof(struct status_data)); - status->mode = MD_CANMOVE|MD_CANATTACK; - status->hp = status->max_hp; - status->sp = status->max_sp; - md->battle_status.hp = merc->hp; - md->battle_status.sp = merc->sp; - } - - status_calc_misc(&md->bl, status, md->db->lv); - status_cpy(&md->battle_status, status); - - return 0; -} - -int status_calc_homunculus_(struct homun_data *hd, bool first) -{ - struct status_data *status = &hd->base_status; - struct s_homunculus *hom = &hd->homunculus; - int skill; - int amotion; - - status->str = hom->str / 10; - status->agi = hom->agi / 10; - status->vit = hom->vit / 10; - status->dex = hom->dex / 10; - status->int_ = hom->int_ / 10; - status->luk = hom->luk / 10; - - if (first) { //[orn] - const struct s_homunculus_db *db = hd->homunculusDB; - status->def_ele = db->element; - status->ele_lv = 1; - status->race = db->race; - status->size = (hom->class_ == db->evo_class)?db->evo_size:db->base_size; - status->rhw.range = 1 + status->size; - status->mode = MD_CANMOVE|MD_CANATTACK; - status->speed = DEFAULT_WALK_SPEED; - if (battle_config.hom_setting&0x8 && hd->master) - status->speed = status_get_speed(&hd->master->bl); - - status->hp = 1; - status->sp = 1; - } - skill = hom->level/10 + status->vit/5; - status->def = cap_value(skill, 0, 99); - - skill = hom->level/10 + status->int_/5; - status->mdef = cap_value(skill, 0, 99); - - status->max_hp = hom->max_hp ; - status->max_sp = hom->max_sp ; - - merc_hom_calc_skilltree(hd, 0); - - if((skill=merc_hom_checkskill(hd,HAMI_SKIN)) > 0) - status->def += skill * 4; - - if((skill = merc_hom_checkskill(hd,HVAN_INSTRUCT)) > 0) - { - status->int_ += 1 +skill/2 +skill/4 +skill/5; - status->str += 1 +skill/3 +skill/3 +skill/4; - } - - if((skill=merc_hom_checkskill(hd,HAMI_SKIN)) > 0) - status->max_hp += skill * 2 * status->max_hp / 100; - - if((skill = merc_hom_checkskill(hd,HLIF_BRAIN)) > 0) - status->max_sp += (1 +skill/2 -skill/4 +skill/5) * status->max_sp / 100 ; - - if (first) { - hd->battle_status.hp = hom->hp ; - hd->battle_status.sp = hom->sp ; - } - - status->rhw.atk = status->dex; - status->rhw.atk2 = status->str + hom->level; - - status->aspd_rate = 1000; - - amotion = (1000 -4*status->agi -status->dex) * hd->homunculusDB->baseASPD/1000; - status->amotion = cap_value(amotion,battle_config.max_aspd,2000); - status->adelay = status->amotion; //It seems adelay = amotion for Homunculus. - - status_calc_misc(&hd->bl, status, hom->level); - -#ifdef RENEWAL - status->matk_max = status->matk_min; -#endif - - status_cpy(&hd->battle_status, status); - return 1; -} - -int status_calc_elemental_(struct elemental_data *ed, bool first) { - struct status_data *status = &ed->base_status; - struct s_elemental *ele = &ed->elemental; - struct map_session_data *sd = ed->master; - - if( !sd ) - return 0; - - if( first ) { - memcpy(status, &ed->db->status, sizeof(struct status_data)); - if( !ele->mode ) - status->mode = EL_MODE_PASSIVE; - else - status->mode = ele->mode; - - status_calc_misc(&ed->bl, status, 0); - - status->max_hp = ele->max_hp; - status->max_sp = ele->max_sp; - status->hp = ele->hp; - status->sp = ele->sp; - status->rhw.atk = ele->atk; - status->rhw.atk2 = ele->atk2; - - status->matk_min += ele->matk; - status->def += ele->def; - status->mdef += ele->mdef; - status->flee = ele->flee; - status->hit = ele->hit; - - memcpy(&ed->battle_status,status,sizeof(struct status_data)); - } else { - status_calc_misc(&ed->bl, status, 0); - status_cpy(&ed->battle_status, status); - } - - return 0; -} - -int status_calc_npc_(struct npc_data *nd, bool first) { - struct status_data *status = &nd->status; - - if (!nd) - return 0; - - if (first) { - status->hp = 1; - status->sp = 1; - status->max_hp = 1; - status->max_sp = 1; - - status->def_ele = ELE_NEUTRAL; - status->ele_lv = 1; - status->race = RC_DEMIHUMAN; - status->size = nd->size; - status->rhw.range = 1 + status->size; - status->mode = MD_CANMOVE|MD_CANATTACK; - status->speed = nd->speed; - } - - status->str = nd->stat_point; - status->agi = nd->stat_point; - status->vit = nd->stat_point; - status->int_= nd->stat_point; - status->dex = nd->stat_point; - status->luk = nd->stat_point; - - status_calc_misc(&nd->bl, status, nd->level); - status_cpy(&nd->status, status); - - return 0; -} - -static unsigned short status_calc_str(struct block_list *,struct status_change *,int); -static unsigned short status_calc_agi(struct block_list *,struct status_change *,int); -static unsigned short status_calc_vit(struct block_list *,struct status_change *,int); -static unsigned short status_calc_int(struct block_list *,struct status_change *,int); -static unsigned short status_calc_dex(struct block_list *,struct status_change *,int); -static unsigned short status_calc_luk(struct block_list *,struct status_change *,int); -static unsigned short status_calc_batk(struct block_list *,struct status_change *,int); -static unsigned short status_calc_watk(struct block_list *,struct status_change *,int); -static unsigned short status_calc_matk(struct block_list *,struct status_change *,int); -static signed short status_calc_hit(struct block_list *,struct status_change *,int); -static signed short status_calc_critical(struct block_list *,struct status_change *,int); -static signed short status_calc_flee(struct block_list *,struct status_change *,int); -static signed short status_calc_flee2(struct block_list *,struct status_change *,int); -static defType status_calc_def(struct block_list *bl, struct status_change *sc, int); -static signed short status_calc_def2(struct block_list *,struct status_change *,int); -static defType status_calc_mdef(struct block_list *bl, struct status_change *sc, int); -static signed short status_calc_mdef2(struct block_list *,struct status_change *,int); -static unsigned short status_calc_speed(struct block_list *,struct status_change *,int); -static short status_calc_aspd_rate(struct block_list *,struct status_change *,int); -static unsigned short status_calc_dmotion(struct block_list *bl, struct status_change *sc, int dmotion); -#ifdef RENEWAL_ASPD -static short status_calc_aspd(struct block_list *bl, struct status_change *sc, short flag); -#endif -static short status_calc_fix_aspd(struct block_list *bl, struct status_change *sc, int); -static unsigned int status_calc_maxhp(struct block_list *,struct status_change *, uint64); -static unsigned int status_calc_maxsp(struct block_list *,struct status_change *,unsigned int); -static unsigned char status_calc_element(struct block_list *bl, struct status_change *sc, int element); -static unsigned char status_calc_element_lv(struct block_list *bl, struct status_change *sc, int lv); -static unsigned short status_calc_mode(struct block_list *bl, struct status_change *sc, int mode); -#ifdef RENEWAL -static unsigned short status_calc_ematk(struct block_list *,struct status_change *,int); -#endif - -//Calculates base regen values. -void status_calc_regen(struct block_list *bl, struct status_data *status, struct regen_data *regen) -{ - struct map_session_data *sd; - int val, skill, reg_flag; - - if( !(bl->type&BL_REGEN) || !regen ) - return; - - sd = BL_CAST(BL_PC,bl); - val = 1 + (status->vit/5) + (status->max_hp/200); - - if( sd && sd->hprecov_rate != 100 ) - val = val*sd->hprecov_rate/100; - - reg_flag = bl->type == BL_PC ? 0 : 1; - - regen->hp = cap_value(val, reg_flag, SHRT_MAX); - - val = 1 + (status->int_/6) + (status->max_sp/100); - if( status->int_ >= 120 ) - val += ((status->int_-120)>>1) + 4; - - if( sd && sd->sprecov_rate != 100 ) - val = val*sd->sprecov_rate/100; - - regen->sp = cap_value(val, reg_flag, SHRT_MAX); - - if( sd ) - { - struct regen_data_sub *sregen; - if( (skill=pc_checkskill(sd,HP_MEDITATIO)) > 0 ) - { - val = regen->sp*(100+3*skill)/100; - regen->sp = cap_value(val, 1, SHRT_MAX); - } - //Only players have skill/sitting skill regen for now. - sregen = regen->sregen; - - val = 0; - if( (skill=pc_checkskill(sd,SM_RECOVERY)) > 0 ) - val += skill*5 + skill*status->max_hp/500; - sregen->hp = cap_value(val, 0, SHRT_MAX); - - val = 0; - if( (skill=pc_checkskill(sd,MG_SRECOVERY)) > 0 ) - val += skill*3 + skill*status->max_sp/500; - if( (skill=pc_checkskill(sd,NJ_NINPOU)) > 0 ) - val += skill*3 + skill*status->max_sp/500; - if( (skill=pc_checkskill(sd,WM_LESSON)) > 0 ) - val += 3 + 3 * skill; - - sregen->sp = cap_value(val, 0, SHRT_MAX); - - // Skill-related recovery (only when sit) - sregen = regen->ssregen; - - val = 0; - if( (skill=pc_checkskill(sd,MO_SPIRITSRECOVERY)) > 0 ) - val += skill*4 + skill*status->max_hp/500; - - if( (skill=pc_checkskill(sd,TK_HPTIME)) > 0 && sd->state.rest ) - val += skill*30 + skill*status->max_hp/500; - sregen->hp = cap_value(val, 0, SHRT_MAX); - - val = 0; - if( (skill=pc_checkskill(sd,TK_SPTIME)) > 0 && sd->state.rest ) - { - val += skill*3 + skill*status->max_sp/500; - if ((skill=pc_checkskill(sd,SL_KAINA)) > 0) //Power up Enjoyable Rest - val += (30+10*skill)*val/100; - } - if( (skill=pc_checkskill(sd,MO_SPIRITSRECOVERY)) > 0 ) - val += skill*2 + skill*status->max_sp/500; - sregen->sp = cap_value(val, 0, SHRT_MAX); - } - - if( bl->type == BL_HOM ) { - struct homun_data *hd = (TBL_HOM*)bl; - if( (skill = merc_hom_checkskill(hd,HAMI_SKIN)) > 0 ) { - val = regen->hp*(100+5*skill)/100; - regen->hp = cap_value(val, 1, SHRT_MAX); - } - if( (skill = merc_hom_checkskill(hd,HLIF_BRAIN)) > 0 ) { - val = regen->sp*(100+3*skill)/100; - regen->sp = cap_value(val, 1, SHRT_MAX); - } - } else if( bl->type == BL_MER ) { - val = (status->max_hp * status->vit / 10000 + 1) * 6; - regen->hp = cap_value(val, 1, SHRT_MAX); - - val = (status->max_sp * (status->int_ + 10) / 750) + 1; - regen->sp = cap_value(val, 1, SHRT_MAX); - } else if( bl->type == BL_ELEM ) { - val = (status->max_hp * status->vit / 10000 + 1) * 6; - regen->hp = cap_value(val, 1, SHRT_MAX); - - val = (status->max_sp * (status->int_ + 10) / 750) + 1; - regen->sp = cap_value(val, 1, SHRT_MAX); - } -} - -//Calculates SC related regen rates. -void status_calc_regen_rate(struct block_list *bl, struct regen_data *regen, struct status_change *sc) -{ - if (!(bl->type&BL_REGEN) || !regen) - return; - - regen->flag = RGN_HP|RGN_SP; - if(regen->sregen) - { - if (regen->sregen->hp) - regen->flag|=RGN_SHP; - - if (regen->sregen->sp) - regen->flag|=RGN_SSP; - regen->sregen->rate.hp = regen->sregen->rate.sp = 1; - } - if (regen->ssregen) - { - if (regen->ssregen->hp) - regen->flag|=RGN_SHP; - - if (regen->ssregen->sp) - regen->flag|=RGN_SSP; - regen->ssregen->rate.hp = regen->ssregen->rate.sp = 1; - } - regen->rate.hp = regen->rate.sp = 1; - - if (!sc || !sc->count) - return; - - if ( - (sc->data[SC_POISON] && !sc->data[SC_SLOWPOISON]) - || (sc->data[SC_DPOISON] && !sc->data[SC_SLOWPOISON]) - || sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST] - || sc->data[SC_TRICKDEAD] - || sc->data[SC_BLEEDING] - || sc->data[SC_MAGICMUSHROOM] - || sc->data[SC_RAISINGDRAGON] - || sc->data[SC_SATURDAYNIGHTFEVER] - ) //No regen - regen->flag = 0; - - if ( - sc->data[SC_DANCING] || sc->data[SC_OBLIVIONCURSE] || sc->data[SC_MAXIMIZEPOWER] - || ( - (bl->type == BL_PC && ((TBL_PC*)bl)->class_&MAPID_UPPERMASK) == MAPID_MONK && - (sc->data[SC_EXTREMITYFIST] || (sc->data[SC_EXPLOSIONSPIRITS] && (!sc->data[SC_SPIRIT] || sc->data[SC_SPIRIT]->val2 != SL_MONK))) - ) - ) //No natural SP regen - regen->flag &=~RGN_SP; - - if( - sc->data[SC_TENSIONRELAX] - ) { - regen->rate.hp += 2; - if (regen->sregen) - regen->sregen->rate.hp += 3; - } - if (sc->data[SC_MAGNIFICAT]) - { - regen->rate.hp += 1; - regen->rate.sp += 1; - } - if (sc->data[SC_REGENERATION]) - { - const struct status_change_entry *sce = sc->data[SC_REGENERATION]; - if (!sce->val4) - { - regen->rate.hp += sce->val2; - regen->rate.sp += sce->val3; - } else - regen->flag&=~sce->val4; //Remove regen as specified by val4 - } - if(sc->data[SC_GT_REVITALIZE]){ - regen->hp = cap_value(regen->hp*sc->data[SC_GT_REVITALIZE]->val3/100, 1, SHRT_MAX); - regen->state.walk= 1; - } - if ((sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 1) //if insignia lvl 1 - || (sc->data[SC_WATER_INSIGNIA] && sc->data[SC_WATER_INSIGNIA]->val1 == 1) - || (sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 1) - || (sc->data[SC_WIND_INSIGNIA] && sc->data[SC_WIND_INSIGNIA]->val1 == 1)) - regen->rate.hp *= 2; - -} -void status_calc_state( struct block_list *bl, struct status_change *sc, enum scs_flag flag, bool start ) { - - /* no sc at all, we can zero without any extra weight over our conciousness */ - if( !sc->count ) { - memset(&sc->cant, 0, sizeof (sc->cant)); - return; - } - - /* can move? */ - if( flag&SCS_NOMOVE ) { - if( !(flag&SCS_NOMOVECOND) ) { - sc->cant.move += ( start ? 1 : -1 ); - } else if( - (sc->data[SC_GOSPEL] && sc->data[SC_GOSPEL]->val4 == BCT_SELF) // cannot move while gospel is in effect - || (sc->data[SC_BASILICA] && sc->data[SC_BASILICA]->val4 == bl->id) // Basilica caster cannot move - || (sc->data[SC_GRAVITATION] && sc->data[SC_GRAVITATION]->val3 == BCT_SELF) - || (sc->data[SC_CRYSTALIZE] && bl->type != BL_MOB) - || (sc->data[SC_CAMOUFLAGE] && sc->data[SC_CAMOUFLAGE]->val1 < 3 - && !(sc->data[SC_CAMOUFLAGE]->val3&1)) - ) { - sc->cant.move += ( start ? 1 : -1 ); - } - } - - /* can't use skills */ - if( flag&SCS_NOCAST ) { - if( !(flag&SCS_NOCASTCOND) ) { - sc->cant.cast += ( start ? 1 : -1 ); - } else if( (sc->data[SC_CRYSTALIZE] && bl->type != BL_MOB) ){ - sc->cant.cast += ( start ? 1 : -1 ); - } - } - - /* player-only states */ - if( bl->type == BL_PC ) { - - /* can pick items? */ - if( flag&SCS_NOPICKITEM ) { - if( !(flag&SCS_NOPICKITEMCOND) ) { - sc->cant.pickup += ( start ? 1 : -1 ); - } else if( (sc->data[SC_NOCHAT] && sc->data[SC_NOCHAT]->val1&MANNER_NOITEM) ) { - sc->cant.pickup += ( start ? 1 : -1 ); - } - } - - /* can drop items? */ - if( flag&SCS_NODROPITEM ) { - if( !(flag&SCS_NODROPITEMCOND) ) { - sc->cant.drop += ( start ? 1 : -1 ); - } else if( (sc->data[SC_NOCHAT] && sc->data[SC_NOCHAT]->val1&MANNER_NOITEM) ) { - sc->cant.drop += ( start ? 1 : -1 ); - } - } - } - - return; -} -/// Recalculates parts of an object's battle status according to the specified flags. -/// @param flag bitfield of values from enum scb_flag -void status_calc_bl_main(struct block_list *bl, /*enum scb_flag*/int flag) -{ - const struct status_data *b_status = status_get_base_status(bl); - struct status_data *status = status_get_status_data(bl); - struct status_change *sc = status_get_sc(bl); - TBL_PC *sd = BL_CAST(BL_PC,bl); - int temp; - - if (!b_status || !status) - return; - - if((!(bl->type&BL_REGEN)) && (!sc || !sc->count)) { //No difference. - status_cpy(status, b_status); - return; - } - - if(flag&SCB_STR) { - status->str = status_calc_str(bl, sc, b_status->str); - flag|=SCB_BATK; - if( bl->type&BL_HOM ) - flag |= SCB_WATK; - } - - if(flag&SCB_AGI) { - status->agi = status_calc_agi(bl, sc, b_status->agi); - flag|=SCB_FLEE -#ifdef RENEWAL - |SCB_DEF2 -#endif - ; - if( bl->type&(BL_PC|BL_HOM) ) - flag |= SCB_ASPD|SCB_DSPD; - } - - if(flag&SCB_VIT) { - status->vit = status_calc_vit(bl, sc, b_status->vit); - flag|=SCB_DEF2|SCB_MDEF2; - if( bl->type&(BL_PC|BL_HOM|BL_MER|BL_ELEM) ) - flag |= SCB_MAXHP; - if( bl->type&BL_HOM ) - flag |= SCB_DEF; - } - - if(flag&SCB_INT) { - status->int_ = status_calc_int(bl, sc, b_status->int_); - flag|=SCB_MATK|SCB_MDEF2; - if( bl->type&(BL_PC|BL_HOM|BL_MER|BL_ELEM) ) - flag |= SCB_MAXSP; - if( bl->type&BL_HOM ) - flag |= SCB_MDEF; - } - - if(flag&SCB_DEX) { - status->dex = status_calc_dex(bl, sc, b_status->dex); - flag|=SCB_BATK|SCB_HIT -#ifdef RENEWAL - |SCB_MATK|SCB_MDEF2 -#endif - ; - if( bl->type&(BL_PC|BL_HOM) ) - flag |= SCB_ASPD; - if( bl->type&BL_HOM ) - flag |= SCB_WATK; - } - - if(flag&SCB_LUK) { - status->luk = status_calc_luk(bl, sc, b_status->luk); - flag|=SCB_BATK|SCB_CRI|SCB_FLEE2 -#ifdef RENEWAL - |SCB_MATK|SCB_HIT|SCB_FLEE -#endif - ; - } - - if(flag&SCB_BATK && b_status->batk) { - status->batk = status_base_atk(bl,status); - temp = b_status->batk - status_base_atk(bl,b_status); - if (temp) - { - temp += status->batk; - status->batk = cap_value(temp, 0, USHRT_MAX); - } - status->batk = status_calc_batk(bl, sc, status->batk); - } - - if(flag&SCB_WATK) { - - status->rhw.atk = status_calc_watk(bl, sc, b_status->rhw.atk); - if (!sd) //Should not affect weapon refine bonus - status->rhw.atk2 = status_calc_watk(bl, sc, b_status->rhw.atk2); - - if(b_status->lhw.atk) { - if (sd) { - sd->state.lr_flag = 1; - status->lhw.atk = status_calc_watk(bl, sc, b_status->lhw.atk); - sd->state.lr_flag = 0; - } else { - status->lhw.atk = status_calc_watk(bl, sc, b_status->lhw.atk); - status->lhw.atk2= status_calc_watk(bl, sc, b_status->lhw.atk2); - } - } - - if( bl->type&BL_HOM ) - { - status->rhw.atk += (status->dex - b_status->dex); - status->rhw.atk2 += (status->str - b_status->str); - if( status->rhw.atk2 < status->rhw.atk ) - status->rhw.atk2 = status->rhw.atk; - } - } - - if(flag&SCB_HIT) { - if (status->dex == b_status->dex -#ifdef RENEWAL - && status->luk == b_status->luk -#endif - ) - status->hit = status_calc_hit(bl, sc, b_status->hit); - else - status->hit = status_calc_hit(bl, sc, b_status->hit + (status->dex - b_status->dex) -#ifdef RENEWAL - + (status->luk/3 - b_status->luk/3) -#endif - ); - } - - if(flag&SCB_FLEE) { - if (status->agi == b_status->agi -#ifdef RENEWAL - && status->luk == b_status->luk -#endif - ) - status->flee = status_calc_flee(bl, sc, b_status->flee); - else - status->flee = status_calc_flee(bl, sc, b_status->flee +(status->agi - b_status->agi) -#ifdef RENEWAL - + (status->luk/5 - b_status->luk/5) -#endif - ); - } - - if(flag&SCB_DEF) - { - status->def = status_calc_def(bl, sc, b_status->def); - - if( bl->type&BL_HOM ) - status->def += (status->vit/5 - b_status->vit/5); - } - - if(flag&SCB_DEF2) { - if (status->vit == b_status->vit -#ifdef RENEWAL - && status->agi == b_status->agi -#endif - ) - status->def2 = status_calc_def2(bl, sc, b_status->def2); - else - status->def2 = status_calc_def2(bl, sc, b_status->def2 -#ifdef RENEWAL - + (int)( ((float)status->vit/2 + (float)b_status->vit/2) + ((float)status->agi/5 + (float)b_status->agi/5) ) -#else - + (status->vit - b_status->vit) -#endif - ); - } - - if(flag&SCB_MDEF) - { - status->mdef = status_calc_mdef(bl, sc, b_status->mdef); - - if( bl->type&BL_HOM ) - status->mdef += (status->int_/5 - b_status->int_/5); - } - - if(flag&SCB_MDEF2) { - if (status->int_ == b_status->int_ && status->vit == b_status->vit -#ifdef RENEWAL - && status->dex == b_status->dex -#endif - ) - status->mdef2 = status_calc_mdef2(bl, sc, b_status->mdef2); - else - status->mdef2 = status_calc_mdef2(bl, sc, b_status->mdef2 +(status->int_ - b_status->int_) -#ifdef RENEWAL - + (int)( ((float)status->dex/5 - (float)b_status->dex/5) + ((float)status->vit/5 + (float)b_status->vit/5) ) -#else - + ((status->vit - b_status->vit)>>1) -#endif - ); - } - - if(flag&SCB_SPEED) { - struct unit_data *ud = unit_bl2ud(bl); - status->speed = status_calc_speed(bl, sc, b_status->speed); - - //Re-walk to adjust speed (we do not check if walktimer != INVALID_TIMER - //because if you step on something while walking, the moment this - //piece of code triggers the walk-timer is set on INVALID_TIMER) [Skotlex] - if (ud) - ud->state.change_walk_target = ud->state.speed_changed = 1; - - if( bl->type&BL_PC && status->speed < battle_config.max_walk_speed ) - status->speed = battle_config.max_walk_speed; - - if( bl->type&BL_HOM && battle_config.hom_setting&0x8 && ((TBL_HOM*)bl)->master) - status->speed = status_get_speed(&((TBL_HOM*)bl)->master->bl); - - - } - - if(flag&SCB_CRI && b_status->cri) { - if (status->luk == b_status->luk) - status->cri = status_calc_critical(bl, sc, b_status->cri); - else - status->cri = status_calc_critical(bl, sc, b_status->cri + 3*(status->luk - b_status->luk)); - /** - * after status_calc_critical so the bonus is applied despite if you have or not a sc bugreport:5240 - **/ - if( bl->type == BL_PC && ((TBL_PC*)bl)->status.weapon == W_KATAR ) - status->cri <<= 1; - - } - - if(flag&SCB_FLEE2 && b_status->flee2) { - if (status->luk == b_status->luk) - status->flee2 = status_calc_flee2(bl, sc, b_status->flee2); - else - status->flee2 = status_calc_flee2(bl, sc, b_status->flee2 +(status->luk - b_status->luk)); - } - - if(flag&SCB_ATK_ELE) { - status->rhw.ele = status_calc_attack_element(bl, sc, b_status->rhw.ele); - if (sd) sd->state.lr_flag = 1; - status->lhw.ele = status_calc_attack_element(bl, sc, b_status->lhw.ele); - if (sd) sd->state.lr_flag = 0; - } - - if(flag&SCB_DEF_ELE) { - status->def_ele = status_calc_element(bl, sc, b_status->def_ele); - status->ele_lv = status_calc_element_lv(bl, sc, b_status->ele_lv); - } - - if(flag&SCB_MODE) - { - status->mode = status_calc_mode(bl, sc, b_status->mode); - //Since mode changed, reset their state. - if (!(status->mode&MD_CANATTACK)) - unit_stop_attack(bl); - if (!(status->mode&MD_CANMOVE)) - unit_stop_walking(bl,1); - } - -// No status changes alter these yet. -// if(flag&SCB_SIZE) -// if(flag&SCB_RACE) -// if(flag&SCB_RANGE) - - if(flag&SCB_MAXHP) { - if( bl->type&BL_PC ) - { - status->max_hp = status_base_pc_maxhp(sd,status); - status->max_hp += b_status->max_hp - sd->status.max_hp; - - status->max_hp = status_calc_maxhp(bl, sc, status->max_hp); - - if( status->max_hp > (unsigned int)battle_config.max_hp ) - status->max_hp = (unsigned int)battle_config.max_hp; - } - else - { - status->max_hp = status_calc_maxhp(bl, sc, b_status->max_hp); - } - - if( status->hp > status->max_hp ) //FIXME: Should perhaps a status_zap should be issued? - { - status->hp = status->max_hp; - if( sd ) clif_updatestatus(sd,SP_HP); - } - } - - if(flag&SCB_MAXSP) { - if( bl->type&BL_PC ) - { - status->max_sp = status_base_pc_maxsp(sd,status); - status->max_sp += b_status->max_sp - sd->status.max_sp; - - status->max_sp = status_calc_maxsp(&sd->bl, &sd->sc, status->max_sp); - - if( status->max_sp > (unsigned int)battle_config.max_sp ) - status->max_sp = (unsigned int)battle_config.max_sp; - } - else - { - status->max_sp = status_calc_maxsp(bl, sc, b_status->max_sp); - } - - if( status->sp > status->max_sp ) - { - status->sp = status->max_sp; - if( sd ) clif_updatestatus(sd,SP_SP); - } - } - - if(flag&SCB_MATK) { -#ifndef RENEWAL - status->matk_min = status_base_matk_min(status) + (sd?sd->bonus.ematk:0); - status->matk_max = status_base_matk_max(status) + (sd?sd->bonus.ematk:0); -#else - /** - * RE MATK Formula (from irowiki:http://irowiki.org/wiki/MATK) - * MATK = (sMATK + wMATK + eMATK) * Multiplicative Modifiers - **/ - status->matk_min = status->matk_max = status_base_matk(status, status_get_lv(bl)); - if( bl->type&BL_PC ){ - // Any +MATK you get from skills and cards, including cards in weapon, is added here. - if( sd->bonus.ematk > 0 ){ - status->matk_max += sd->bonus.ematk; - status->matk_min += sd->bonus.ematk; - } - status->matk_min = status_calc_ematk(bl, sc, status->matk_min); - status->matk_max = status_calc_ematk(bl, sc, status->matk_max); - //This is the only portion in MATK that varies depending on the weapon level and refinement rate. - if( status->rhw.matk > 0 ){ - int wMatk = status->rhw.matk; - int variance = wMatk * status->rhw.wlv / 10; - status->matk_min += wMatk - variance; - status->matk_max += wMatk + variance; - } - } -#endif - if (bl->type&BL_PC && sd->matk_rate != 100) { - status->matk_max = status->matk_max * sd->matk_rate/100; - status->matk_min = status->matk_min * sd->matk_rate/100; - } - - status->matk_min = status_calc_matk(bl, sc, status->matk_min); - status->matk_max = status_calc_matk(bl, sc, status->matk_max); - - if ((bl->type&BL_HOM && battle_config.hom_setting&0x20) //Hom Min Matk is always the same as Max Matk - || sc->data[SC_RECOGNIZEDSPELL]) - status->matk_min = status->matk_max; - -#ifdef RENEWAL - if( sd && sd->right_weapon.overrefine > 0){ - status->matk_min++; - status->matk_max += sd->right_weapon.overrefine - 1; - } -#endif - - } - - if(flag&SCB_ASPD) { - int amotion; - if( bl->type&BL_PC ) - { - amotion = status_base_amotion_pc(sd,status); -#ifndef RENEWAL_ASPD - status->aspd_rate = status_calc_aspd_rate(bl, sc, b_status->aspd_rate); - - if(status->aspd_rate != 1000) - amotion = amotion*status->aspd_rate/1000; -#else - // aspd = baseaspd + floor(sqrt((agi^2/2) + (dex^2/5))/4 + (potskillbonus*agi/200)) - amotion -= (int)(sqrt( (pow(status->agi, 2) / 2) + (pow(status->dex, 2) / 5) ) / 4 + (status_calc_aspd(bl, sc, 1) * status->agi / 200)) * 10; - - if( (status_calc_aspd(bl, sc, 2) + status->aspd_rate2) != 0 ) // RE ASPD percertage modifier - amotion -= ( amotion - ((sd->class_&JOBL_THIRD) ? battle_config.max_third_aspd : battle_config.max_aspd) ) - * (status_calc_aspd(bl, sc, 2) + status->aspd_rate2) / 100; - - if(status->aspd_rate != 1000) // absolute percentage modifier - amotion = ( 200 - (200-amotion/10) * status->aspd_rate / 1000 ) * 10; -#endif - amotion = status_calc_fix_aspd(bl, sc, amotion); - status->amotion = cap_value(amotion,((sd->class_&JOBL_THIRD) ? battle_config.max_third_aspd : battle_config.max_aspd),2000); - - status->adelay = 2*status->amotion; - } - else - if( bl->type&BL_HOM ) - { - amotion = (1000 -4*status->agi -status->dex) * ((TBL_HOM*)bl)->homunculusDB->baseASPD/1000; - status->aspd_rate = status_calc_aspd_rate(bl, sc, b_status->aspd_rate); - - if(status->aspd_rate != 1000) - amotion = amotion*status->aspd_rate/1000; - - amotion = status_calc_fix_aspd(bl, sc, amotion); - status->amotion = cap_value(amotion,battle_config.max_aspd,2000); - - status->adelay = status->amotion; - } - else // mercenary and mobs - { - amotion = b_status->amotion; - status->aspd_rate = status_calc_aspd_rate(bl, sc, b_status->aspd_rate); - - if(status->aspd_rate != 1000) - amotion = amotion*status->aspd_rate/1000; - - amotion = status_calc_fix_aspd(bl, sc, amotion); - status->amotion = cap_value(amotion, battle_config.monster_max_aspd, 2000); - - temp = b_status->adelay*status->aspd_rate/1000; - status->adelay = cap_value(temp, battle_config.monster_max_aspd*2, 4000); - } - } - - if(flag&SCB_DSPD) { - int dmotion; - if( bl->type&BL_PC ) - { - if (b_status->agi == status->agi) - status->dmotion = status_calc_dmotion(bl, sc, b_status->dmotion); - else { - dmotion = 800-status->agi*4; - status->dmotion = cap_value(dmotion, 400, 800); - if(battle_config.pc_damage_delay_rate != 100) - status->dmotion = status->dmotion*battle_config.pc_damage_delay_rate/100; - //It's safe to ignore b_status->dmotion since no bonus affects it. - status->dmotion = status_calc_dmotion(bl, sc, status->dmotion); - } - } - else - if( bl->type&BL_HOM ) - { - dmotion = 800-status->agi*4; - status->dmotion = cap_value(dmotion, 400, 800); - status->dmotion = status_calc_dmotion(bl, sc, b_status->dmotion); - } - else // mercenary and mobs - { - status->dmotion = status_calc_dmotion(bl, sc, b_status->dmotion); - } - } - - if(flag&(SCB_VIT|SCB_MAXHP|SCB_INT|SCB_MAXSP) && bl->type&BL_REGEN) - status_calc_regen(bl, status, status_get_regen_data(bl)); - - if(flag&SCB_REGEN && bl->type&BL_REGEN) - status_calc_regen_rate(bl, status_get_regen_data(bl), sc); -} -/// Recalculates parts of an object's base status and battle status according to the specified flags. -/// Also sends updates to the client wherever applicable. -/// @param flag bitfield of values from enum scb_flag -/// @param first if true, will cause status_calc_* functions to run their base status initialization code -void status_calc_bl_(struct block_list* bl, enum scb_flag flag, bool first) -{ - struct status_data b_status; // previous battle status - struct status_data* status; // pointer to current battle status - - // remember previous values - status = status_get_status_data(bl); - memcpy(&b_status, status, sizeof(struct status_data)); - - if( flag&SCB_BASE ) {// calculate the object's base status too - switch( bl->type ) { - case BL_PC: status_calc_pc_(BL_CAST(BL_PC,bl), first); break; - case BL_MOB: status_calc_mob_(BL_CAST(BL_MOB,bl), first); break; - case BL_PET: status_calc_pet_(BL_CAST(BL_PET,bl), first); break; - case BL_HOM: status_calc_homunculus_(BL_CAST(BL_HOM,bl), first); break; - case BL_MER: status_calc_mercenary_(BL_CAST(BL_MER,bl), first); break; - case BL_ELEM: status_calc_elemental_(BL_CAST(BL_ELEM,bl), first); break; - case BL_NPC: status_calc_npc_(BL_CAST(BL_NPC,bl), first); break; - } - } - - if( bl->type == BL_PET ) - return; // pets are not affected by statuses - - if( first && bl->type == BL_MOB ) - return; // assume there will be no statuses active - - status_calc_bl_main(bl, flag); - - if( first && bl->type == BL_HOM ) - return; // client update handled by caller - - // compare against new values and send client updates - if( bl->type == BL_PC ) - { - TBL_PC* sd = BL_CAST(BL_PC, bl); - if(b_status.str != status->str) - clif_updatestatus(sd,SP_STR); - if(b_status.agi != status->agi) - clif_updatestatus(sd,SP_AGI); - if(b_status.vit != status->vit) - clif_updatestatus(sd,SP_VIT); - if(b_status.int_ != status->int_) - clif_updatestatus(sd,SP_INT); - if(b_status.dex != status->dex) - clif_updatestatus(sd,SP_DEX); - if(b_status.luk != status->luk) - clif_updatestatus(sd,SP_LUK); - if(b_status.hit != status->hit) - clif_updatestatus(sd,SP_HIT); - if(b_status.flee != status->flee) - clif_updatestatus(sd,SP_FLEE1); - if(b_status.amotion != status->amotion) - clif_updatestatus(sd,SP_ASPD); - if(b_status.speed != status->speed) - clif_updatestatus(sd,SP_SPEED); - - if(b_status.batk != status->batk -#ifndef RENEWAL - || b_status.rhw.atk != status->rhw.atk || b_status.lhw.atk != status->lhw.atk -#endif - ) - clif_updatestatus(sd,SP_ATK1); - - if(b_status.def != status->def){ - clif_updatestatus(sd,SP_DEF1); -#ifdef RENEWAL - clif_updatestatus(sd,SP_DEF2); -#endif - } - - if(b_status.rhw.atk2 != status->rhw.atk2 || b_status.lhw.atk2 != status->lhw.atk2 -#ifdef RENEWAL - || b_status.rhw.atk != status->rhw.atk || b_status.lhw.atk != status->lhw.atk -#endif - ) - clif_updatestatus(sd,SP_ATK2); - - if(b_status.def2 != status->def2){ - clif_updatestatus(sd,SP_DEF2); -#ifdef RENEWAL - clif_updatestatus(sd,SP_DEF1); -#endif - } - if(b_status.flee2 != status->flee2) - clif_updatestatus(sd,SP_FLEE2); - if(b_status.cri != status->cri) - clif_updatestatus(sd,SP_CRITICAL); -#ifndef RENEWAL - if(b_status.matk_max != status->matk_max) - clif_updatestatus(sd,SP_MATK1); - if(b_status.matk_min != status->matk_min) - clif_updatestatus(sd,SP_MATK2); -#else - if(b_status.matk_max != status->matk_max || b_status.matk_min != status->matk_min){ - clif_updatestatus(sd,SP_MATK2); - clif_updatestatus(sd,SP_MATK1); - } -#endif - if(b_status.mdef != status->mdef){ - clif_updatestatus(sd,SP_MDEF1); -#ifdef RENEWAL - clif_updatestatus(sd,SP_MDEF2); -#endif - } - if(b_status.mdef2 != status->mdef2){ - clif_updatestatus(sd,SP_MDEF2); -#ifdef RENEWAL - clif_updatestatus(sd,SP_MDEF1); -#endif - } - if(b_status.rhw.range != status->rhw.range) - clif_updatestatus(sd,SP_ATTACKRANGE); - if(b_status.max_hp != status->max_hp) - clif_updatestatus(sd,SP_MAXHP); - if(b_status.max_sp != status->max_sp) - clif_updatestatus(sd,SP_MAXSP); - if(b_status.hp != status->hp) - clif_updatestatus(sd,SP_HP); - if(b_status.sp != status->sp) - clif_updatestatus(sd,SP_SP); - } else if( bl->type == BL_HOM ) { - TBL_HOM* hd = BL_CAST(BL_HOM, bl); - if( hd->master && memcmp(&b_status, status, sizeof(struct status_data)) != 0 ) - clif_hominfo(hd->master,hd,0); - } else if( bl->type == BL_MER ) { - TBL_MER* md = BL_CAST(BL_MER, bl); - if( b_status.rhw.atk != status->rhw.atk || b_status.rhw.atk2 != status->rhw.atk2 ) - clif_mercenary_updatestatus(md->master, SP_ATK1); - if( b_status.matk_max != status->matk_max ) - clif_mercenary_updatestatus(md->master, SP_MATK1); - if( b_status.hit != status->hit ) - clif_mercenary_updatestatus(md->master, SP_HIT); - if( b_status.cri != status->cri ) - clif_mercenary_updatestatus(md->master, SP_CRITICAL); - if( b_status.def != status->def ) - clif_mercenary_updatestatus(md->master, SP_DEF1); - if( b_status.mdef != status->mdef ) - clif_mercenary_updatestatus(md->master, SP_MDEF1); - if( b_status.flee != status->flee ) - clif_mercenary_updatestatus(md->master, SP_MERCFLEE); - if( b_status.amotion != status->amotion ) - clif_mercenary_updatestatus(md->master, SP_ASPD); - if( b_status.max_hp != status->max_hp ) - clif_mercenary_updatestatus(md->master, SP_MAXHP); - if( b_status.max_sp != status->max_sp ) - clif_mercenary_updatestatus(md->master, SP_MAXSP); - if( b_status.hp != status->hp ) - clif_mercenary_updatestatus(md->master, SP_HP); - if( b_status.sp != status->sp ) - clif_mercenary_updatestatus(md->master, SP_SP); - } else if( bl->type == BL_ELEM ) { - TBL_ELEM* ed = BL_CAST(BL_ELEM, bl); - if( b_status.max_hp != status->max_hp ) - clif_elemental_updatestatus(ed->master, SP_MAXHP); - if( b_status.max_sp != status->max_sp ) - clif_elemental_updatestatus(ed->master, SP_MAXSP); - if( b_status.hp != status->hp ) - clif_elemental_updatestatus(ed->master, SP_HP); - if( b_status.sp != status->sp ) - clif_mercenary_updatestatus(ed->master, SP_SP); - } -} - -/*========================================== - * Apply shared stat mods from status changes [DracoRPG] - *------------------------------------------*/ -static unsigned short status_calc_str(struct block_list *bl, struct status_change *sc, int str) -{ - if(!sc || !sc->count) - return cap_value(str,0,USHRT_MAX); - - if(sc->data[SC_HARMONIZE]) { - str -= sc->data[SC_HARMONIZE]->val2; - return (unsigned short)cap_value(str,0,USHRT_MAX); - } - if(sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_HIGH && str < 50) - return 50; - if(sc->data[SC_INCALLSTATUS]) - str += sc->data[SC_INCALLSTATUS]->val1; - if(sc->data[SC_INCSTR]) - str += sc->data[SC_INCSTR]->val1; - if(sc->data[SC_STRFOOD]) - str += sc->data[SC_STRFOOD]->val1; - if(sc->data[SC_FOOD_STR_CASH]) - str += sc->data[SC_FOOD_STR_CASH]->val1; - if(sc->data[SC_BATTLEORDERS]) - str += 5; - if(sc->data[SC_LEADERSHIP]) - str += sc->data[SC_LEADERSHIP]->val1; - if(sc->data[SC_LOUD]) - str += 4; - if(sc->data[SC_TRUESIGHT]) - str += 5; - if(sc->data[SC_SPURT]) - str += 10; - if(sc->data[SC_NEN]) - str += sc->data[SC_NEN]->val1; - if(sc->data[SC_BLESSING]){ - if(sc->data[SC_BLESSING]->val2) - str += sc->data[SC_BLESSING]->val2; - else - str >>= 1; - } - if(sc->data[SC_MARIONETTE]) - str -= ((sc->data[SC_MARIONETTE]->val3)>>16)&0xFF; - if(sc->data[SC_MARIONETTE2]) - str += ((sc->data[SC_MARIONETTE2]->val3)>>16)&0xFF; - if(sc->data[SC_GIANTGROWTH]) - str += 30; - if(sc->data[SC_SAVAGE_STEAK]) - str += sc->data[SC_SAVAGE_STEAK]->val1; - if(sc->data[SC_INSPIRATION]) - str += sc->data[SC_INSPIRATION]->val3; - if(sc->data[SC_STOMACHACHE]) - str -= sc->data[SC_STOMACHACHE]->val1; - if(sc->data[SC_KYOUGAKU]) - str -= sc->data[SC_KYOUGAKU]->val2; - - return (unsigned short)cap_value(str,0,USHRT_MAX); -} - -static unsigned short status_calc_agi(struct block_list *bl, struct status_change *sc, int agi) -{ - if(!sc || !sc->count) - return cap_value(agi,0,USHRT_MAX); - - if(sc->data[SC_HARMONIZE]) { - agi -= sc->data[SC_HARMONIZE]->val2; - return (unsigned short)cap_value(agi,0,USHRT_MAX); - } - if(sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_HIGH && agi < 50) - return 50; - if(sc->data[SC_CONCENTRATE] && !sc->data[SC_QUAGMIRE]) - agi += (agi-sc->data[SC_CONCENTRATE]->val3)*sc->data[SC_CONCENTRATE]->val2/100; - if(sc->data[SC_INCALLSTATUS]) - agi += sc->data[SC_INCALLSTATUS]->val1; - if(sc->data[SC_INCAGI]) - agi += sc->data[SC_INCAGI]->val1; - if(sc->data[SC_AGIFOOD]) - agi += sc->data[SC_AGIFOOD]->val1; - if(sc->data[SC_FOOD_AGI_CASH]) - agi += sc->data[SC_FOOD_AGI_CASH]->val1; - if(sc->data[SC_SOULCOLD]) - agi += sc->data[SC_SOULCOLD]->val1; - if(sc->data[SC_TRUESIGHT]) - agi += 5; - if(sc->data[SC_INCREASEAGI]) - agi += sc->data[SC_INCREASEAGI]->val2; - if(sc->data[SC_INCREASING]) - agi += 4; // added based on skill updates [Reddozen] - if(sc->data[SC_DECREASEAGI]) - agi -= sc->data[SC_DECREASEAGI]->val2; - if(sc->data[SC_QUAGMIRE]) - agi -= sc->data[SC_QUAGMIRE]->val2; - if(sc->data[SC_SUITON] && sc->data[SC_SUITON]->val3) - agi -= sc->data[SC_SUITON]->val2; - if(sc->data[SC_MARIONETTE]) - agi -= ((sc->data[SC_MARIONETTE]->val3)>>8)&0xFF; - if(sc->data[SC_MARIONETTE2]) - agi += ((sc->data[SC_MARIONETTE2]->val3)>>8)&0xFF; - if(sc->data[SC_ADORAMUS]) - agi -= sc->data[SC_ADORAMUS]->val2; - if(sc->data[SC_DROCERA_HERB_STEAMED]) - agi += sc->data[SC_DROCERA_HERB_STEAMED]->val1; - if(sc->data[SC_INSPIRATION]) - agi += sc->data[SC_INSPIRATION]->val3; - if(sc->data[SC_STOMACHACHE]) - agi -= sc->data[SC_STOMACHACHE]->val1; - if(sc->data[SC_KYOUGAKU]) - agi -= sc->data[SC_KYOUGAKU]->val2; - - return (unsigned short)cap_value(agi,0,USHRT_MAX); -} - -static unsigned short status_calc_vit(struct block_list *bl, struct status_change *sc, int vit) -{ - if(!sc || !sc->count) - return cap_value(vit,0,USHRT_MAX); - - if(sc->data[SC_HARMONIZE]) { - vit -= sc->data[SC_HARMONIZE]->val2; - return (unsigned short)cap_value(vit,0,USHRT_MAX); - } - if(sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_HIGH && vit < 50) - return 50; - if(sc->data[SC_INCALLSTATUS]) - vit += sc->data[SC_INCALLSTATUS]->val1; - if(sc->data[SC_INCVIT]) - vit += sc->data[SC_INCVIT]->val1; - if(sc->data[SC_VITFOOD]) - vit += sc->data[SC_VITFOOD]->val1; - if(sc->data[SC_FOOD_VIT_CASH]) - vit += sc->data[SC_FOOD_VIT_CASH]->val1; - if(sc->data[SC_CHANGE]) - vit += sc->data[SC_CHANGE]->val2; - if(sc->data[SC_GLORYWOUNDS]) - vit += sc->data[SC_GLORYWOUNDS]->val1; - if(sc->data[SC_TRUESIGHT]) - vit += 5; - if(sc->data[SC_MARIONETTE]) - vit -= sc->data[SC_MARIONETTE]->val3&0xFF; - if(sc->data[SC_MARIONETTE2]) - vit += sc->data[SC_MARIONETTE2]->val3&0xFF; - if(sc->data[SC_LAUDAAGNUS]) - vit += 4 + sc->data[SC_LAUDAAGNUS]->val1; - if(sc->data[SC_MINOR_BBQ]) - vit += sc->data[SC_MINOR_BBQ]->val1; - if(sc->data[SC_INSPIRATION]) - vit += sc->data[SC_INSPIRATION]->val3; - if(sc->data[SC_STOMACHACHE]) - vit -= sc->data[SC_STOMACHACHE]->val1; - if(sc->data[SC_KYOUGAKU]) - vit -= sc->data[SC_KYOUGAKU]->val2; - - if(sc->data[SC_STRIPARMOR]) - vit -= vit * sc->data[SC_STRIPARMOR]->val2/100; - - return (unsigned short)cap_value(vit,0,USHRT_MAX); -} - -static unsigned short status_calc_int(struct block_list *bl, struct status_change *sc, int int_) -{ - if(!sc || !sc->count) - return cap_value(int_,0,USHRT_MAX); - - if(sc->data[SC_HARMONIZE]) { - int_ -= sc->data[SC_HARMONIZE]->val2; - return (unsigned short)cap_value(int_,0,USHRT_MAX); - } - if(sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_HIGH && int_ < 50) - return 50; - if(sc->data[SC_INCALLSTATUS]) - int_ += sc->data[SC_INCALLSTATUS]->val1; - if(sc->data[SC_INCINT]) - int_ += sc->data[SC_INCINT]->val1; - if(sc->data[SC_INTFOOD]) - int_ += sc->data[SC_INTFOOD]->val1; - if(sc->data[SC_FOOD_INT_CASH]) - int_ += sc->data[SC_FOOD_INT_CASH]->val1; - if(sc->data[SC_CHANGE]) - int_ += sc->data[SC_CHANGE]->val3; - if(sc->data[SC_BATTLEORDERS]) - int_ += 5; - if(sc->data[SC_TRUESIGHT]) - int_ += 5; - if(sc->data[SC_BLESSING]){ - if (sc->data[SC_BLESSING]->val2) - int_ += sc->data[SC_BLESSING]->val2; - else - int_ >>= 1; - } - if(sc->data[SC_NEN]) - int_ += sc->data[SC_NEN]->val1; - if(sc->data[SC_MARIONETTE]) - int_ -= ((sc->data[SC_MARIONETTE]->val4)>>16)&0xFF; - if(sc->data[SC_MARIONETTE2]) - int_ += ((sc->data[SC_MARIONETTE2]->val4)>>16)&0xFF; - if(sc->data[SC_MANDRAGORA]) - int_ -= 5 + 5 * sc->data[SC_MANDRAGORA]->val1; - if(sc->data[SC_COCKTAIL_WARG_BLOOD]) - int_ += sc->data[SC_COCKTAIL_WARG_BLOOD]->val1; - if(sc->data[SC_INSPIRATION]) - int_ += sc->data[SC_INSPIRATION]->val3; - if(sc->data[SC_STOMACHACHE]) - int_ -= sc->data[SC_STOMACHACHE]->val1; - if(sc->data[SC_KYOUGAKU]) - int_ -= sc->data[SC_KYOUGAKU]->val2; - - if(sc->data[SC_STRIPHELM]) - int_ -= int_ * sc->data[SC_STRIPHELM]->val2/100; - if(sc->data[SC__STRIPACCESSORY]) - int_ -= int_ * sc->data[SC__STRIPACCESSORY]->val2 / 100; - - return (unsigned short)cap_value(int_,0,USHRT_MAX); -} - -static unsigned short status_calc_dex(struct block_list *bl, struct status_change *sc, int dex) -{ - if(!sc || !sc->count) - return cap_value(dex,0,USHRT_MAX); - - if(sc->data[SC_HARMONIZE]) { - dex -= sc->data[SC_HARMONIZE]->val2; - return (unsigned short)cap_value(dex,0,USHRT_MAX); - } - if(sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_HIGH && dex < 50) - return 50; - if(sc->data[SC_CONCENTRATE] && !sc->data[SC_QUAGMIRE]) - dex += (dex-sc->data[SC_CONCENTRATE]->val4)*sc->data[SC_CONCENTRATE]->val2/100; - if(sc->data[SC_INCALLSTATUS]) - dex += sc->data[SC_INCALLSTATUS]->val1; - if(sc->data[SC_INCDEX]) - dex += sc->data[SC_INCDEX]->val1; - if(sc->data[SC_DEXFOOD]) - dex += sc->data[SC_DEXFOOD]->val1; - if(sc->data[SC_FOOD_DEX_CASH]) - dex += sc->data[SC_FOOD_DEX_CASH]->val1; - if(sc->data[SC_BATTLEORDERS]) - dex += 5; - if(sc->data[SC_HAWKEYES]) - dex += sc->data[SC_HAWKEYES]->val1; - if(sc->data[SC_TRUESIGHT]) - dex += 5; - if(sc->data[SC_QUAGMIRE]) - dex -= sc->data[SC_QUAGMIRE]->val2; - if(sc->data[SC_BLESSING]){ - if (sc->data[SC_BLESSING]->val2) - dex += sc->data[SC_BLESSING]->val2; - else - dex >>= 1; - } - if(sc->data[SC_INCREASING]) - dex += 4; // added based on skill updates [Reddozen] - if(sc->data[SC_MARIONETTE]) - dex -= ((sc->data[SC_MARIONETTE]->val4)>>8)&0xFF; - if(sc->data[SC_MARIONETTE2]) - dex += ((sc->data[SC_MARIONETTE2]->val4)>>8)&0xFF; - if(sc->data[SC_SIROMA_ICE_TEA]) - dex += sc->data[SC_SIROMA_ICE_TEA]->val1; - if(sc->data[SC_INSPIRATION]) - dex += sc->data[SC_INSPIRATION]->val3; - if(sc->data[SC_STOMACHACHE]) - dex -= sc->data[SC_STOMACHACHE]->val1; - if(sc->data[SC_KYOUGAKU]) - dex -= sc->data[SC_KYOUGAKU]->val2; - - if(sc->data[SC__STRIPACCESSORY]) - dex -= dex * sc->data[SC__STRIPACCESSORY]->val2 / 100; - - return (unsigned short)cap_value(dex,0,USHRT_MAX); -} - -static unsigned short status_calc_luk(struct block_list *bl, struct status_change *sc, int luk) -{ - if(!sc || !sc->count) - return cap_value(luk,0,USHRT_MAX); - - if(sc->data[SC_HARMONIZE]) { - luk -= sc->data[SC_HARMONIZE]->val2; - return (unsigned short)cap_value(luk,0,USHRT_MAX); - } - if(sc->data[SC_CURSE]) - return 0; - if(sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_HIGH && luk < 50) - return 50; - if(sc->data[SC_INCALLSTATUS]) - luk += sc->data[SC_INCALLSTATUS]->val1; - if(sc->data[SC_INCLUK]) - luk += sc->data[SC_INCLUK]->val1; - if(sc->data[SC_LUKFOOD]) - luk += sc->data[SC_LUKFOOD]->val1; - if(sc->data[SC_FOOD_LUK_CASH]) - luk += sc->data[SC_FOOD_LUK_CASH]->val1; - if(sc->data[SC_TRUESIGHT]) - luk += 5; - if(sc->data[SC_GLORIA]) - luk += 30; - if(sc->data[SC_MARIONETTE]) - luk -= sc->data[SC_MARIONETTE]->val4&0xFF; - if(sc->data[SC_MARIONETTE2]) - luk += sc->data[SC_MARIONETTE2]->val4&0xFF; - if(sc->data[SC_PUTTI_TAILS_NOODLES]) - luk += sc->data[SC_PUTTI_TAILS_NOODLES]->val1; - if(sc->data[SC_INSPIRATION]) - luk += sc->data[SC_INSPIRATION]->val3; - if(sc->data[SC_STOMACHACHE]) - luk -= sc->data[SC_STOMACHACHE]->val1; - if(sc->data[SC_KYOUGAKU]) - luk -= sc->data[SC_KYOUGAKU]->val2; - if(sc->data[SC_LAUDARAMUS]) - luk += 4 + sc->data[SC_LAUDARAMUS]->val1; - - if(sc->data[SC__STRIPACCESSORY]) - luk -= luk * sc->data[SC__STRIPACCESSORY]->val2 / 100; - if(sc->data[SC_BANANA_BOMB]) - luk -= luk * sc->data[SC_BANANA_BOMB]->val1 / 100; - - return (unsigned short)cap_value(luk,0,USHRT_MAX); -} - -static unsigned short status_calc_batk(struct block_list *bl, struct status_change *sc, int batk) -{ - if(!sc || !sc->count) - return cap_value(batk,0,USHRT_MAX); - - if(sc->data[SC_ATKPOTION]) - batk += sc->data[SC_ATKPOTION]->val1; - if(sc->data[SC_BATKFOOD]) - batk += sc->data[SC_BATKFOOD]->val1; - if(sc->data[SC_GATLINGFEVER]) - batk += sc->data[SC_GATLINGFEVER]->val3; - if(sc->data[SC_MADNESSCANCEL]) - batk += 100; - if(sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 2) - batk += 50; - if(bl->type == BL_ELEM - && ((sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 1) - || (sc->data[SC_WATER_INSIGNIA] && sc->data[SC_WATER_INSIGNIA]->val1 == 1) - || (sc->data[SC_WIND_INSIGNIA] && sc->data[SC_WIND_INSIGNIA]->val1 == 1) - || (sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 1)) - ) - batk += batk / 5; - if(sc->data[SC_FULL_SWING_K]) - batk += sc->data[SC_FULL_SWING_K]->val1; - if(sc->data[SC_ODINS_POWER]) - batk += 70; - if(sc->data[SC_ASH] && (bl->type==BL_MOB)){ - if(status_get_element(bl) == ELE_WATER) //water type - batk /= 2; - } - if(sc->data[SC_PYROCLASTIC]) - batk += sc->data[SC_PYROCLASTIC]->val2; - if (sc->data[SC_ANGRIFFS_MODUS]) - batk += sc->data[SC_ANGRIFFS_MODUS]->val2; - - if(sc->data[SC_INCATKRATE]) - batk += batk * sc->data[SC_INCATKRATE]->val1/100; - if(sc->data[SC_PROVOKE]) - batk += batk * sc->data[SC_PROVOKE]->val3/100; - if(sc->data[SC_CONCENTRATION]) - batk += batk * sc->data[SC_CONCENTRATION]->val2/100; - if(sc->data[SC_SKE]) - batk += batk * 3; - if(sc->data[SC_BLOODLUST]) - batk += batk * sc->data[SC_BLOODLUST]->val2/100; - if(sc->data[SC_JOINTBEAT] && sc->data[SC_JOINTBEAT]->val2&BREAK_WAIST) - batk -= batk * 25/100; - if(sc->data[SC_CURSE]) - batk -= batk * 25/100; -//Curse shouldn't effect on this? <- Curse OR Bleeding?? -// if(sc->data[SC_BLEEDING]) -// batk -= batk * 25/100; - if(sc->data[SC_FLEET]) - batk += batk * sc->data[SC_FLEET]->val3/100; - if(sc->data[SC__ENERVATION]) - batk -= batk * sc->data[SC__ENERVATION]->val2 / 100; - if(sc->data[SC_RUSHWINDMILL]) - batk += batk * sc->data[SC_RUSHWINDMILL]->val2/100; - if(sc->data[SC_SATURDAYNIGHTFEVER]) - batk += 100 * sc->data[SC_SATURDAYNIGHTFEVER]->val1; - if(sc->data[SC_MELODYOFSINK]) - batk -= batk * sc->data[SC_MELODYOFSINK]->val3/100; - if(sc->data[SC_BEYONDOFWARCRY]) - batk += batk * sc->data[SC_BEYONDOFWARCRY]->val3/100; - if( sc->data[SC_ZANGETSU] ) - batk += batk * sc->data[SC_ZANGETSU]->val2 / 100; - - return (unsigned short)cap_value(batk,0,USHRT_MAX); -} - -static unsigned short status_calc_watk(struct block_list *bl, struct status_change *sc, int watk) -{ - if(!sc || !sc->count) - return cap_value(watk,0,USHRT_MAX); - - if(sc->data[SC_IMPOSITIO]) - watk += sc->data[SC_IMPOSITIO]->val2; - if(sc->data[SC_WATKFOOD]) - watk += sc->data[SC_WATKFOOD]->val1; - if(sc->data[SC_DRUMBATTLE]) - watk += sc->data[SC_DRUMBATTLE]->val2; - if(sc->data[SC_VOLCANO]) - watk += sc->data[SC_VOLCANO]->val2; - if(sc->data[SC_MERC_ATKUP]) - watk += sc->data[SC_MERC_ATKUP]->val2; - if(sc->data[SC_FIGHTINGSPIRIT]) - watk += sc->data[SC_FIGHTINGSPIRIT]->val1; - if(sc->data[SC_STRIKING]) - watk += sc->data[SC_STRIKING]->val2; - if(sc->data[SC_SHIELDSPELL_DEF] && sc->data[SC_SHIELDSPELL_DEF]->val1 == 3) - watk += sc->data[SC_SHIELDSPELL_DEF]->val2; - if(sc->data[SC_INSPIRATION]) - watk += sc->data[SC_INSPIRATION]->val2; - if( sc->data[SC_BANDING] && sc->data[SC_BANDING]->val2 > 0 ) - watk += (10 + 10 * sc->data[SC_BANDING]->val1) * (sc->data[SC_BANDING]->val2); - if( sc->data[SC_TROPIC_OPTION] ) - watk += sc->data[SC_TROPIC_OPTION]->val2; - if( sc->data[SC_HEATER_OPTION] ) - watk += sc->data[SC_HEATER_OPTION]->val2; - if( sc->data[SC_WATER_BARRIER] ) - watk -= sc->data[SC_WATER_BARRIER]->val3; - if( sc->data[SC_PYROTECHNIC_OPTION] ) - watk += sc->data[SC_PYROTECHNIC_OPTION]->val2; - if(sc->data[SC_NIBELUNGEN]) { - if (bl->type != BL_PC) - watk += sc->data[SC_NIBELUNGEN]->val2; - else { - #ifndef RENEWAL - TBL_PC *sd = (TBL_PC*)bl; - int index = sd->equip_index[sd->state.lr_flag?EQI_HAND_L:EQI_HAND_R]; - if(index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->wlv == 4) - #endif - watk += sc->data[SC_NIBELUNGEN]->val2; - } - } - - if(sc->data[SC_INCATKRATE]) - watk += watk * sc->data[SC_INCATKRATE]->val1/100; - if(sc->data[SC_PROVOKE]) - watk += watk * sc->data[SC_PROVOKE]->val3/100; - if(sc->data[SC_CONCENTRATION]) - watk += watk * sc->data[SC_CONCENTRATION]->val2/100; - if(sc->data[SC_SKE]) - watk += watk * 3; - if(sc->data[SC__ENERVATION]) - watk -= watk * sc->data[SC__ENERVATION]->val2 / 100; - if(sc->data[SC_FLEET]) - watk += watk * sc->data[SC_FLEET]->val3/100; - if(sc->data[SC_CURSE]) - watk -= watk * 25/100; - if(sc->data[SC_STRIPWEAPON]) - watk -= watk * sc->data[SC_STRIPWEAPON]->val2/100; - if(sc->data[SC__ENERVATION]) - watk -= watk * sc->data[SC__ENERVATION]->val2 / 100; - if((sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 2) - || (sc->data[SC_WATER_INSIGNIA] && sc->data[SC_WATER_INSIGNIA]->val1 == 2) - || (sc->data[SC_WIND_INSIGNIA] && sc->data[SC_WIND_INSIGNIA]->val1 == 2) - || (sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 2) - ) - watk += watk / 10; - if( sc && sc->data[SC_TIDAL_WEAPON] ) - watk += watk * sc->data[SC_TIDAL_WEAPON]->val2 / 100; - if(sc->data[SC_ANGRIFFS_MODUS]) - watk += watk * sc->data[SC_ANGRIFFS_MODUS]->val2/100; -#ifdef RENEWAL_EDP - if( sc->data[SC_EDP] ) - watk = watk * (100 + sc->data[SC_EDP]->val1 * 80) / 100; -#endif - - return (unsigned short)cap_value(watk,0,USHRT_MAX); -} -#ifdef RENEWAL -static unsigned short status_calc_ematk(struct block_list *bl, struct status_change *sc, int matk) -{ - - if (!sc || !sc->count) - return cap_value(matk,0,USHRT_MAX); - if (sc->data[SC_MATKPOTION]) - matk += sc->data[SC_MATKPOTION]->val1; - if (sc->data[SC_MATKFOOD]) - matk += sc->data[SC_MATKFOOD]->val1; - if(sc->data[SC_MANA_PLUS]) - matk += sc->data[SC_MANA_PLUS]->val1; - if(sc->data[SC_AQUAPLAY_OPTION]) - matk += sc->data[SC_AQUAPLAY_OPTION]->val2; - if(sc->data[SC_CHILLY_AIR_OPTION]) - matk += sc->data[SC_CHILLY_AIR_OPTION]->val2; - if(sc->data[SC_WATER_BARRIER]) - matk -= sc->data[SC_WATER_BARRIER]->val3; - if(sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 3) - matk += 50; - if(sc->data[SC_ODINS_POWER]) - matk += 40 + 30 * sc->data[SC_ODINS_POWER]->val1; //70 lvl1, 100lvl2 - if(sc->data[SC_IZAYOI]) - matk += 50 * sc->data[SC_IZAYOI]->val1; - return (unsigned short)cap_value(matk,0,USHRT_MAX); -} -#endif -static unsigned short status_calc_matk(struct block_list *bl, struct status_change *sc, int matk) -{ - if(!sc || !sc->count) - return cap_value(matk,0,USHRT_MAX); -#ifndef RENEWAL - // take note fixed value first before % modifiers - if (sc->data[SC_MATKPOTION]) - matk += sc->data[SC_MATKPOTION]->val1; - if (sc->data[SC_MATKFOOD]) - matk += sc->data[SC_MATKFOOD]->val1; - if (sc->data[SC_MANA_PLUS]) - matk += sc->data[SC_MANA_PLUS]->val1; - if (sc->data[SC_AQUAPLAY_OPTION]) - matk += sc->data[SC_AQUAPLAY_OPTION]->val2; - if (sc->data[SC_CHILLY_AIR_OPTION]) - matk += sc->data[SC_CHILLY_AIR_OPTION]->val2; - if (sc->data[SC_WATER_BARRIER]) - matk -= sc->data[SC_WATER_BARRIER]->val3; - if (sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 3) - matk += 50; - if (sc->data[SC_ODINS_POWER]) - matk += 40 + 30 * sc->data[SC_ODINS_POWER]->val1; //70 lvl1, 100lvl2 - if (sc->data[SC_IZAYOI]) - matk += 50 * sc->data[SC_IZAYOI]->val1; -#endif - if (sc->data[SC_MAGICPOWER]) - matk += matk * sc->data[SC_MAGICPOWER]->val3/100; - if (sc->data[SC_MINDBREAKER]) - matk += matk * sc->data[SC_MINDBREAKER]->val2/100; - if (sc->data[SC_INCMATKRATE]) - matk += matk * sc->data[SC_INCMATKRATE]->val1/100; - if (sc->data[SC_MOONLITSERENADE]) - matk += matk * sc->data[SC_MOONLITSERENADE]->val2/100; - if (sc->data[SC_MELODYOFSINK]) - matk += matk * sc->data[SC_MELODYOFSINK]->val3/100; - if (sc->data[SC_BEYONDOFWARCRY]) - matk -= matk * sc->data[SC_BEYONDOFWARCRY]->val3/100; - if( sc->data[SC_ZANGETSU] ) - matk += matk * sc->data[SC_ZANGETSU]->val2 / 100; - - return (unsigned short)cap_value(matk,0,USHRT_MAX); -} - -static signed short status_calc_critical(struct block_list *bl, struct status_change *sc, int critical) { - - if(!sc || !sc->count) - return cap_value(critical,10,SHRT_MAX); - - if (sc->data[SC_INCCRI]) - critical += sc->data[SC_INCCRI]->val2; - if (sc->data[SC_EXPLOSIONSPIRITS]) - critical += sc->data[SC_EXPLOSIONSPIRITS]->val2; - if (sc->data[SC_FORTUNE]) - critical += sc->data[SC_FORTUNE]->val2; - if (sc->data[SC_TRUESIGHT]) - critical += sc->data[SC_TRUESIGHT]->val2; - if(sc->data[SC_CLOAKING]) - critical += critical; - if(sc->data[SC_STRIKING]) - critical += sc->data[SC_STRIKING]->val1; -#ifdef RENEWAL - if (sc->data[SC_SPEARQUICKEN]) - critical += 3*sc->data[SC_SPEARQUICKEN]->val1*10; -#endif - - if(sc->data[SC__INVISIBILITY]) - critical += critical * sc->data[SC__INVISIBILITY]->val3 / 100; - if(sc->data[SC__UNLUCKY]) - critical -= critical * sc->data[SC__UNLUCKY]->val2 / 100; - - return (short)cap_value(critical,10,SHRT_MAX); -} - -static signed short status_calc_hit(struct block_list *bl, struct status_change *sc, int hit) -{ - - if(!sc || !sc->count) - return cap_value(hit,1,SHRT_MAX); - - if(sc->data[SC_INCHIT]) - hit += sc->data[SC_INCHIT]->val1; - if(sc->data[SC_HITFOOD]) - hit += sc->data[SC_HITFOOD]->val1; - if(sc->data[SC_TRUESIGHT]) - hit += sc->data[SC_TRUESIGHT]->val3; - if(sc->data[SC_HUMMING]) - hit += sc->data[SC_HUMMING]->val2; - if(sc->data[SC_CONCENTRATION]) - hit += sc->data[SC_CONCENTRATION]->val3; - if(sc->data[SC_INSPIRATION]) - hit += 5 * sc->data[SC_INSPIRATION]->val1; - if(sc->data[SC_ADJUSTMENT]) - hit -= 30; - if(sc->data[SC_INCREASING]) - hit += 20; // RockmanEXE; changed based on updated [Reddozen] - if(sc->data[SC_MERC_HITUP]) - hit += sc->data[SC_MERC_HITUP]->val2; - - if(sc->data[SC_INCHITRATE]) - hit += hit * sc->data[SC_INCHITRATE]->val1/100; - if(sc->data[SC_BLIND]) - hit -= hit * 25/100; - if(sc->data[SC__GROOMY]) - hit -= hit * sc->data[SC__GROOMY]->val3 / 100; - if(sc->data[SC_FEAR]) - hit -= hit * 20 / 100; - if (sc->data[SC_ASH]) - hit /= 2; - - return (short)cap_value(hit,1,SHRT_MAX); -} - -static signed short status_calc_flee(struct block_list *bl, struct status_change *sc, int flee) -{ - if( bl->type == BL_PC ) - { - if( map_flag_gvg(bl->m) ) - flee -= flee * battle_config.gvg_flee_penalty/100; - else if( map[bl->m].flag.battleground ) - flee -= flee * battle_config.bg_flee_penalty/100; - } - - if(!sc || !sc->count) - return cap_value(flee,1,SHRT_MAX); - - if(sc->data[SC_INCFLEE]) - flee += sc->data[SC_INCFLEE]->val1; - if(sc->data[SC_FLEEFOOD]) - flee += sc->data[SC_FLEEFOOD]->val1; - if(sc->data[SC_WHISTLE]) - flee += sc->data[SC_WHISTLE]->val2; - if(sc->data[SC_WINDWALK]) - flee += sc->data[SC_WINDWALK]->val2; - if(sc->data[SC_VIOLENTGALE]) - flee += sc->data[SC_VIOLENTGALE]->val2; - if(sc->data[SC_MOON_COMFORT]) //SG skill [Komurka] - flee += sc->data[SC_MOON_COMFORT]->val2; - if(sc->data[SC_CLOSECONFINE]) - flee += 10; - if (sc->data[SC_ANGRIFFS_MODUS]) - flee -= sc->data[SC_ANGRIFFS_MODUS]->val3; - if (sc->data[SC_OVERED_BOOST]) - flee = max(flee,sc->data[SC_OVERED_BOOST]->val2); - if(sc->data[SC_ADJUSTMENT]) - flee += 30; - if(sc->data[SC_SPEED]) - flee += 10 + sc->data[SC_SPEED]->val1 * 10; - if(sc->data[SC_GATLINGFEVER]) - flee -= sc->data[SC_GATLINGFEVER]->val4; - if(sc->data[SC_PARTYFLEE]) - flee += sc->data[SC_PARTYFLEE]->val1 * 10; - if(sc->data[SC_MERC_FLEEUP]) - flee += sc->data[SC_MERC_FLEEUP]->val2; - if( sc->data[SC_HALLUCINATIONWALK] ) - flee += sc->data[SC_HALLUCINATIONWALK]->val2; - if( sc->data[SC_WATER_BARRIER] ) - flee -= sc->data[SC_WATER_BARRIER]->val3; - if( sc->data[SC_MARSHOFABYSS] ) - flee -= (9 * sc->data[SC_MARSHOFABYSS]->val3 / 10 + sc->data[SC_MARSHOFABYSS]->val2 / 10) * (bl->type == BL_MOB ? 2 : 1); -#ifdef RENEWAL - if( sc->data[SC_SPEARQUICKEN] ) - flee += 2 * sc->data[SC_SPEARQUICKEN]->val1; -#endif - - if(sc->data[SC_INCFLEERATE]) - flee += flee * sc->data[SC_INCFLEERATE]->val1/100; - if(sc->data[SC_SPIDERWEB] && sc->data[SC_SPIDERWEB]->val1) - flee -= flee * 50/100; - if (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) - flee -= flee * 50/100; - if(sc->data[SC_BLIND]) - flee -= flee * 25/100; - if(sc->data[SC_FEAR]) - flee -= flee * 20 / 100; - if(sc->data[SC_PARALYSE]) - flee -= flee * 10 / 100; // 10% Flee reduction - if(sc->data[SC_INFRAREDSCAN]) - flee -= flee * 30 / 100; - if( sc->data[SC__LAZINESS] ) - flee -= flee * sc->data[SC__LAZINESS]->val3 / 100; - if( sc->data[SC_GLOOMYDAY] ) - flee -= flee * sc->data[SC_GLOOMYDAY]->val2 / 100; - if( sc->data[SC_SATURDAYNIGHTFEVER] ) - flee -= flee * (40 + 10 * sc->data[SC_SATURDAYNIGHTFEVER]->val1) / 100; - if( sc->data[SC_WIND_STEP_OPTION] ) - flee += flee * sc->data[SC_WIND_STEP_OPTION]->val2 / 100; - if( sc->data[SC_ZEPHYR] ) - flee += flee * sc->data[SC_ZEPHYR]->val2 / 100; - if(sc->data[SC_ASH] && (bl->type==BL_MOB)){ //mob - if(status_get_element(bl) == ELE_WATER) //water type - flee /= 2; - } - - return (short)cap_value(flee,1,SHRT_MAX); -} - -static signed short status_calc_flee2(struct block_list *bl, struct status_change *sc, int flee2) -{ - if(!sc || !sc->count) - return cap_value(flee2,10,SHRT_MAX); - - if(sc->data[SC_INCFLEE2]) - flee2 += sc->data[SC_INCFLEE2]->val2; - if(sc->data[SC_WHISTLE]) - flee2 += sc->data[SC_WHISTLE]->val3*10; - if(sc->data[SC__UNLUCKY]) - flee2 -= flee2 * sc->data[SC__UNLUCKY]->val2 / 100; - - return (short)cap_value(flee2,10,SHRT_MAX); -} -static defType status_calc_def(struct block_list *bl, struct status_change *sc, int def) { - - if(!sc || !sc->count) - return (defType)cap_value(def,DEFTYPE_MIN,DEFTYPE_MAX); - - if (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) - return 0; - if(sc->data[SC_SKA]) - return sc->data[SC_SKA]->val3; - if(sc->data[SC_BARRIER]) - return 100; - if(sc->data[SC_KEEPING]) - return 90; -#ifndef RENEWAL // does not provide 90 DEF in renewal mode - if(sc->data[SC_STEELBODY]) - return 90; -#endif - - if(sc->data[SC_ARMORCHANGE]) - def += sc->data[SC_ARMORCHANGE]->val2; - if(sc->data[SC_DRUMBATTLE]) - def += sc->data[SC_DRUMBATTLE]->val3; - if(sc->data[SC_DEFENCE]) //[orn] - def += sc->data[SC_DEFENCE]->val2 ; - if(sc->data[SC_INCDEFRATE]) - def += def * sc->data[SC_INCDEFRATE]->val1/100; - if(sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 2) - def += 50; - if(sc->data[SC_ODINS_POWER]) - def -= 20; - if( sc->data[SC_ANGRIFFS_MODUS] ) - def -= 30 + 20 * sc->data[SC_ANGRIFFS_MODUS]->val1; - if(sc->data[SC_STONEHARDSKIN])// Final DEF increase divided by 10 since were using classic (pre-renewal) mechanics. [Rytech] - def += sc->data[SC_STONEHARDSKIN]->val1; - if(sc->data[SC_STONE] && sc->opt1 == OPT1_STONE) - def >>=1; - if(sc->data[SC_FREEZE]) - def >>=1; - if(sc->data[SC_SIGNUMCRUCIS]) - def -= def * sc->data[SC_SIGNUMCRUCIS]->val2/100; - if(sc->data[SC_CONCENTRATION]) - def -= def * sc->data[SC_CONCENTRATION]->val4/100; - if(sc->data[SC_SKE]) - def >>=1; - if(sc->data[SC_PROVOKE] && bl->type != BL_PC) // Provoke doesn't alter player defense-> - def -= def * sc->data[SC_PROVOKE]->val4/100; - if(sc->data[SC_STRIPSHIELD]) - def -= def * sc->data[SC_STRIPSHIELD]->val2/100; - if (sc->data[SC_FLING]) - def -= def * (sc->data[SC_FLING]->val2)/100; - if( sc->data[SC_FREEZING] ) - def -= def * 10 / 100; - if( sc->data[SC_MARSHOFABYSS] ) - def -= def * ( 6 + 6 * sc->data[SC_MARSHOFABYSS]->val3/10 + (bl->type == BL_MOB ? 5 : 3) * sc->data[SC_MARSHOFABYSS]->val2/36 ) / 100; - if( sc->data[SC_ANALYZE] ) - def -= def * ( 14 * sc->data[SC_ANALYZE]->val1 ) / 100; - if( sc->data[SC_FORCEOFVANGUARD] ) - def += def * 2 * sc->data[SC_FORCEOFVANGUARD]->val1 / 100; - if(sc->data[SC_SATURDAYNIGHTFEVER]) - def -= def * (10 + 10 * sc->data[SC_SATURDAYNIGHTFEVER]->val1) / 100; - if(sc->data[SC_EARTHDRIVE]) - def -= def * 25 / 100; - if( sc->data[SC_ROCK_CRUSHER] ) - def -= def * sc->data[SC_ROCK_CRUSHER]->val2 / 100; - if( sc->data[SC_POWER_OF_GAIA] ) - def += def * sc->data[SC_POWER_OF_GAIA]->val2 / 100; - if( sc->data[SC_PRESTIGE] ) - def += def * sc->data[SC_PRESTIGE]->val1 / 100; - if(sc->data[SC_ASH] && (bl->type==BL_MOB)){ - if(status_get_race(bl)==RC_PLANT) - def /= 2; - } - - return (defType)cap_value(def,DEFTYPE_MIN,DEFTYPE_MAX);; -} - -static signed short status_calc_def2(struct block_list *bl, struct status_change *sc, int def2) -{ - if(!sc || !sc->count) -#ifdef RENEWAL - return (short)cap_value(def2,SHRT_MIN,SHRT_MAX); -#else - return (short)cap_value(def2,1,SHRT_MAX); -#endif - - if (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) - return 0; - if(sc->data[SC_ETERNALCHAOS]) - return 0; - if(sc->data[SC_SUN_COMFORT]) - def2 += sc->data[SC_SUN_COMFORT]->val2; - if( sc->data[SC_SHIELDSPELL_REF] && sc->data[SC_SHIELDSPELL_REF]->val1 == 1 ) - def2 += sc->data[SC_SHIELDSPELL_REF]->val2; - if( sc->data[SC_BANDING] && sc->data[SC_BANDING]->val2 > 0 ) - def2 += (5 + sc->data[SC_BANDING]->val1) * (sc->data[SC_BANDING]->val2); - - if(sc->data[SC_ANGELUS]) -#ifdef RENEWAL //in renewal only the VIT stat bonus is boosted by angelus - def2 += status_get_vit(bl) / 2 * sc->data[SC_ANGELUS]->val2/100; -#else - def2 += def2 * sc->data[SC_ANGELUS]->val2/100; -#endif - if(sc->data[SC_CONCENTRATION]) - def2 -= def2 * sc->data[SC_CONCENTRATION]->val4/100; - if(sc->data[SC_POISON]) - def2 -= def2 * 25/100; - if(sc->data[SC_DPOISON]) - def2 -= def2 * 25/100; - if(sc->data[SC_SKE]) - def2 -= def2 * 50/100; - if(sc->data[SC_PROVOKE]) - def2 -= def2 * sc->data[SC_PROVOKE]->val4/100; - if(sc->data[SC_JOINTBEAT]) - def2 -= def2 * ( sc->data[SC_JOINTBEAT]->val2&BREAK_SHOULDER ? 50 : 0 ) / 100 - + def2 * ( sc->data[SC_JOINTBEAT]->val2&BREAK_WAIST ? 25 : 0 ) / 100; - if(sc->data[SC_FLING]) - def2 -= def2 * (sc->data[SC_FLING]->val3)/100; - if( sc->data[SC_FREEZING] ) - def2 -= def2 * 3 / 10; - if(sc->data[SC_ANALYZE]) - def2 -= def2 * ( 14 * sc->data[SC_ANALYZE]->val1 ) / 100; - if( sc->data[SC_ECHOSONG] ) - def2 += def2 * sc->data[SC_ECHOSONG]->val2/100; - if( sc->data[SC_GT_REVITALIZE] && sc->data[SC_GT_REVITALIZE]->val4) - def2 += def2 * sc->data[SC_GT_REVITALIZE]->val4 / 100; - if(sc->data[SC_ASH] && (bl->type==BL_MOB)){ - if(status_get_race(bl)==RC_PLANT) - def2 /= 2; - } - if (sc->data[SC_PARALYSIS]) - def2 -= def2 * sc->data[SC_PARALYSIS]->val2 / 100; - -#ifdef RENEWAL - return (short)cap_value(def2,SHRT_MIN,SHRT_MAX); -#else - return (short)cap_value(def2,1,SHRT_MAX); -#endif -} - - -static defType status_calc_mdef(struct block_list *bl, struct status_change *sc, int mdef) { - - if(!sc || !sc->count) - return (defType)cap_value(mdef,DEFTYPE_MIN,DEFTYPE_MAX); - - if (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) - return 0; - if(sc->data[SC_BARRIER]) - return 100; - -#ifndef RENEWAL // no longer provides 90 MDEF in renewal mode - if(sc->data[SC_STEELBODY]) - return 90; -#endif - - if(sc->data[SC_ARMORCHANGE]) - mdef += sc->data[SC_ARMORCHANGE]->val3; - if(sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 3) - mdef += 50; - if(sc->data[SC_ENDURE])// It has been confirmed that eddga card grants 1 MDEF, not 0, not 10, but 1. - mdef += (sc->data[SC_ENDURE]->val4 == 0) ? sc->data[SC_ENDURE]->val1 : 1; - if(sc->data[SC_CONCENTRATION]) - mdef += 1; //Skill info says it adds a fixed 1 Mdef point. - if(sc->data[SC_STONEHARDSKIN])// Final MDEF increase divided by 10 since were using classic (pre-renewal) mechanics. [Rytech] - mdef += sc->data[SC_STONEHARDSKIN]->val1; - if(sc->data[SC_WATER_BARRIER]) - mdef += sc->data[SC_WATER_BARRIER]->val2; - if(sc->data[SC_STONE] && sc->opt1 == OPT1_STONE) - mdef += 25*mdef/100; - if(sc->data[SC_FREEZE]) - mdef += 25*mdef/100; - if( sc->data[SC_MARSHOFABYSS] ) - mdef -= mdef * ( 6 + 6 * sc->data[SC_MARSHOFABYSS]->val3/10 + (bl->type == BL_MOB ? 5 : 3) * sc->data[SC_MARSHOFABYSS]->val2/36 ) / 100; - if(sc->data[SC_ANALYZE]) - mdef -= mdef * ( 14 * sc->data[SC_ANALYZE]->val1 ) / 100; - if(sc->data[SC_SYMPHONYOFLOVER]) - mdef += mdef * sc->data[SC_SYMPHONYOFLOVER]->val2 / 100; - if(sc->data[SC_GT_CHANGE] && sc->data[SC_GT_CHANGE]->val4) - mdef -= mdef * sc->data[SC_GT_CHANGE]->val4 / 100; - if (sc->data[SC_ODINS_POWER]) - mdef -= 20 * sc->data[SC_ODINS_POWER]->val1; - - return (defType)cap_value(mdef,DEFTYPE_MIN,DEFTYPE_MAX); -} - -static signed short status_calc_mdef2(struct block_list *bl, struct status_change *sc, int mdef2) -{ - if(!sc || !sc->count) -#ifdef RENEWAL - return (short)cap_value(mdef2,SHRT_MIN,SHRT_MAX); -#else - return (short)cap_value(mdef2,1,SHRT_MAX); -#endif - - - if (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) - return 0; - if(sc->data[SC_SKA]) - return 90; - if(sc->data[SC_MINDBREAKER]) - mdef2 -= mdef2 * sc->data[SC_MINDBREAKER]->val3/100; - if(sc->data[SC_ANALYZE]) - mdef2 -= mdef2 * ( 14 * sc->data[SC_ANALYZE]->val1 ) / 100; - -#ifdef RENEWAL - return (short)cap_value(mdef2,SHRT_MIN,SHRT_MAX); -#else - return (short)cap_value(mdef2,1,SHRT_MAX); -#endif -} - -static unsigned short status_calc_speed(struct block_list *bl, struct status_change *sc, int speed) -{ - TBL_PC* sd = BL_CAST(BL_PC, bl); - int speed_rate; - - if( sc == NULL ) - return cap_value(speed,10,USHRT_MAX); - - if( sd && sd->ud.skilltimer != INVALID_TIMER && (pc_checkskill(sd,SA_FREECAST) > 0 || sd->ud.skill_id == LG_EXEEDBREAK) ) - { - if( sd->ud.skill_id == LG_EXEEDBREAK ) - speed_rate = 100 + 60 - (sd->ud.skill_lv * 10); - else - speed_rate = 175 - 5 * pc_checkskill(sd,SA_FREECAST); - } - else - { - speed_rate = 100; - - //GetMoveHasteValue2() - { - int val = 0; - - if( sc->data[SC_FUSION] ) - val = 25; - else if( sd ) { - if( pc_isriding(sd) || sd->sc.option&(OPTION_DRAGON|OPTION_MOUNTING) ) - val = 25;//Same bonus - else if( pc_isridingwug(sd) ) - val = 15 + 5 * pc_checkskill(sd, RA_WUGRIDER); - else if( pc_ismadogear(sd) ) { - val = (- 10 * (5 - pc_checkskill(sd,NC_MADOLICENCE))); - if( sc->data[SC_ACCELERATION] ) - val += 25; - } - } - - speed_rate -= val; - } - - //GetMoveSlowValue() - { - int val = 0; - - if( sd && sc->data[SC_HIDING] && pc_checkskill(sd,RG_TUNNELDRIVE) > 0 ) - val = 120 - 6 * pc_checkskill(sd,RG_TUNNELDRIVE); - else - if( sd && sc->data[SC_CHASEWALK] && sc->data[SC_CHASEWALK]->val3 < 0 ) - val = sc->data[SC_CHASEWALK]->val3; - else - { - // Longing for Freedom cancels song/dance penalty - if( sc->data[SC_LONGING] ) - val = max( val, 50 - 10 * sc->data[SC_LONGING]->val1 ); - else - if( sd && sc->data[SC_DANCING] ) - val = max( val, 500 - (40 + 10 * (sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_BARDDANCER)) * pc_checkskill(sd,(sd->status.sex?BA_MUSICALLESSON:DC_DANCINGLESSON)) ); - - if( sc->data[SC_DECREASEAGI] ) - val = max( val, 25 ); - if( sc->data[SC_QUAGMIRE] || sc->data[SC_HALLUCINATIONWALK_POSTDELAY] || (sc->data[SC_GLOOMYDAY] && sc->data[SC_GLOOMYDAY]->val4) ) - val = max( val, 50 ); - if( sc->data[SC_DONTFORGETME] ) - val = max( val, sc->data[SC_DONTFORGETME]->val3 ); - if( sc->data[SC_CURSE] ) - val = max( val, 300 ); - if( sc->data[SC_CHASEWALK] ) - val = max( val, sc->data[SC_CHASEWALK]->val3 ); - if( sc->data[SC_WEDDING] ) - val = max( val, 100 ); - if( sc->data[SC_JOINTBEAT] && sc->data[SC_JOINTBEAT]->val2&(BREAK_ANKLE|BREAK_KNEE) ) - val = max( val, (sc->data[SC_JOINTBEAT]->val2&BREAK_ANKLE ? 50 : 0) + (sc->data[SC_JOINTBEAT]->val2&BREAK_KNEE ? 30 : 0) ); - if( sc->data[SC_CLOAKING] && (sc->data[SC_CLOAKING]->val4&1) == 0 ) - val = max( val, sc->data[SC_CLOAKING]->val1 < 3 ? 300 : 30 - 3 * sc->data[SC_CLOAKING]->val1 ); - if( sc->data[SC_GOSPEL] && sc->data[SC_GOSPEL]->val4 == BCT_ENEMY ) - val = max( val, 75 ); - if( sc->data[SC_SLOWDOWN] ) // Slow Potion - val = max( val, 100 ); - if( sc->data[SC_GATLINGFEVER] ) - val = max( val, 100 ); - if( sc->data[SC_SUITON] ) - val = max( val, sc->data[SC_SUITON]->val3 ); - if( sc->data[SC_SWOO] ) - val = max( val, 300 ); - if( sc->data[SC_FREEZING] ) - val = max( val, 70 ); - if( sc->data[SC_MARSHOFABYSS] ) - val = max( val, 40 + 10 * sc->data[SC_MARSHOFABYSS]->val1 ); - if( sc->data[SC_CAMOUFLAGE] && (sc->data[SC_CAMOUFLAGE]->val3&1) == 0 ) - val = max( val, sc->data[SC_CAMOUFLAGE]->val1 < 3 ? 0 : 25 * (5 - sc->data[SC_CAMOUFLAGE]->val1) ); - if( sc->data[SC__GROOMY] ) - val = max( val, sc->data[SC__GROOMY]->val2); - if( sc->data[SC_STEALTHFIELD_MASTER] ) - val = max( val, 30 ); - if( sc->data[SC_BANDING_DEFENCE] ) - val = max( val, sc->data[SC_BANDING_DEFENCE]->val1 );//+90% walking speed. - if( sc->data[SC_ROCK_CRUSHER_ATK] ) - val = max( val, sc->data[SC_ROCK_CRUSHER_ATK]->val2 ); - if( sc->data[SC_POWER_OF_GAIA] ) - val = max( val, sc->data[SC_POWER_OF_GAIA]->val2 ); - if( sc->data[SC_MELON_BOMB] ) - val = max( val, sc->data[SC_MELON_BOMB]->val1 ); - - if( sd && sd->bonus.speed_rate + sd->bonus.speed_add_rate > 0 ) // permanent item-based speedup - val = max( val, sd->bonus.speed_rate + sd->bonus.speed_add_rate ); - } - - speed_rate += val; - } - - //GetMoveHasteValue1() - { - int val = 0; - - if( sc->data[SC_SPEEDUP1] ) //FIXME: used both by NPC_AGIUP and Speed Potion script - val = max( val, 50 ); - if( sc->data[SC_INCREASEAGI] ) - val = max( val, 25 ); - if( sc->data[SC_WINDWALK] ) - val = max( val, 2 * sc->data[SC_WINDWALK]->val1 ); - if( sc->data[SC_CARTBOOST] ) - val = max( val, 20 ); - if( sd && (sd->class_&MAPID_UPPERMASK) == MAPID_ASSASSIN && pc_checkskill(sd,TF_MISS) > 0 ) - val = max( val, 1 * pc_checkskill(sd,TF_MISS) ); - if( sc->data[SC_CLOAKING] && (sc->data[SC_CLOAKING]->val4&1) == 1 ) - val = max( val, sc->data[SC_CLOAKING]->val1 >= 10 ? 25 : 3 * sc->data[SC_CLOAKING]->val1 - 3 ); - if (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) - val = max( val, 25 ); - if( sc->data[SC_RUN] ) - val = max( val, 55 ); - if( sc->data[SC_AVOID] ) - val = max( val, 10 * sc->data[SC_AVOID]->val1 ); - if( sc->data[SC_INVINCIBLE] && !sc->data[SC_INVINCIBLEOFF] ) - val = max( val, 75 ); - if( sc->data[SC_CLOAKINGEXCEED] ) - val = max( val, sc->data[SC_CLOAKINGEXCEED]->val3); - if( sc->data[SC_HOVERING] ) - val = max( val, 10 ); - if( sc->data[SC_GN_CARTBOOST] ) - val = max( val, sc->data[SC_GN_CARTBOOST]->val2 ); - if( sc->data[SC_SWINGDANCE] ) - val = max( val, sc->data[SC_SWINGDANCE]->val2 ); - if( sc->data[SC_WIND_STEP_OPTION] ) - val = max( val, sc->data[SC_WIND_STEP_OPTION]->val2 ); - - //FIXME: official items use a single bonus for this [ultramage] - if( sc->data[SC_SPEEDUP0] ) // temporary item-based speedup - val = max( val, 25 ); - if( sd && sd->bonus.speed_rate + sd->bonus.speed_add_rate < 0 ) // permanent item-based speedup - val = max( val, -(sd->bonus.speed_rate + sd->bonus.speed_add_rate) ); - - speed_rate -= val; - } - - if( speed_rate < 40 ) - speed_rate = 40; - } - - //GetSpeed() - { - if( sd && pc_iscarton(sd) ) - speed += speed * (50 - 5 * pc_checkskill(sd,MC_PUSHCART)) / 100; - if( sc->data[SC_PARALYSE] ) - speed += speed * 50 / 100; - if( speed_rate != 100 ) - speed = speed * speed_rate / 100; - if( sc->data[SC_STEELBODY] ) - speed = 200; - if( sc->data[SC_DEFENDER] ) - speed = max(speed, 200); - if( sc->data[SC_WALKSPEED] && sc->data[SC_WALKSPEED]->val1 > 0 ) // ChangeSpeed - speed = speed * 100 / sc->data[SC_WALKSPEED]->val1; - } - - return (short)cap_value(speed,10,USHRT_MAX); -} - -#ifdef RENEWAL_ASPD -// flag&1 - fixed value [malufett] -// flag&2 - percentage value -static short status_calc_aspd(struct block_list *bl, struct status_change *sc, short flag) -{ - int i, pots = 0, skills1 = 0, skills2 = 0; - - if(!sc || !sc->count) - return 0; - - if(sc->data[i=SC_ASPDPOTION3] || - sc->data[i=SC_ASPDPOTION2] || - sc->data[i=SC_ASPDPOTION1] || - sc->data[i=SC_ASPDPOTION0]) - pots += sc->data[i]->val1; - - if( !sc->data[SC_QUAGMIRE] ){ - if(sc->data[SC_STAR_COMFORT]) - skills1 = 5; // needs more info - - if(sc->data[SC_TWOHANDQUICKEN] && skills1 < 7) - skills1 = 7; - - if(sc->data[SC_ONEHAND] && skills1 < 7) skills1 = 7; - - if(sc->data[SC_MERC_QUICKEN] && skills1 < 7) // needs more info - skills1 = 7; - - if(sc->data[SC_ADRENALINE2] && skills1 < 6) - skills1 = 6; - - if(sc->data[SC_ADRENALINE] && skills1 < 7) - skills1 = 7; - - if(sc->data[SC_SPEARQUICKEN] && skills1 < 7) - skills1 = 7; - - if(sc->data[SC_GATLINGFEVER] && skills1 < 9) // needs more info - skills1 = 9; - - if(sc->data[SC_FLEET] && skills1 < 5) - skills1 = 5; - - if(sc->data[SC_ASSNCROS] && - skills1 < 5+1*sc->data[SC_ASSNCROS]->val1) // needs more info - { - if (bl->type!=BL_PC) - skills1 = 4+1*sc->data[SC_ASSNCROS]->val1; - else - switch(((TBL_PC*)bl)->status.weapon) - { - case W_BOW: - case W_REVOLVER: - case W_RIFLE: - case W_GATLING: - case W_SHOTGUN: - case W_GRENADE: - break; - default: - skills1 = 5+1*sc->data[SC_ASSNCROS]->val1; - } - } - } - - if((sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) && skills1 < 15) - skills1 = 15; - else if(sc->data[SC_MADNESSCANCEL] && skills1 < 15) // needs more info - skills1 = 15; - - if(sc->data[SC_DONTFORGETME]) - skills2 -= sc->data[SC_DONTFORGETME]->val2; // needs more info - if(sc->data[SC_LONGING]) - skills2 -= sc->data[SC_LONGING]->val2; // needs more info - if(sc->data[SC_STEELBODY]) - skills2 -= 25; - if(sc->data[SC_SKA]) - skills2 -= 25; - if(sc->data[SC_DEFENDER]) - skills2 -= sc->data[SC_DEFENDER]->val4; // needs more info - if(sc->data[SC_GOSPEL] && sc->data[SC_GOSPEL]->val4 == BCT_ENEMY) // needs more info - skills2 -= 25; - if(sc->data[SC_GRAVITATION]) - skills2 -= sc->data[SC_GRAVITATION]->val2; // needs more info - if(sc->data[SC_JOINTBEAT]) { // needs more info - if( sc->data[SC_JOINTBEAT]->val2&BREAK_WRIST ) - skills2 -= 25; - if( sc->data[SC_JOINTBEAT]->val2&BREAK_KNEE ) - skills2 -= 10; - } - if( sc->data[SC_FREEZING] ) - skills2 -= 30; - if( sc->data[SC_HALLUCINATIONWALK_POSTDELAY] ) - skills2 -= 50; - if( sc->data[SC_PARALYSE] ) - skills2 -= 10; - if( sc->data[SC__BODYPAINT] ) - skills2 -= 2 + 5 * sc->data[SC__BODYPAINT]->val1; - if( sc->data[SC__INVISIBILITY] ) - skills2 -= sc->data[SC__INVISIBILITY]->val2 ; - if( sc->data[SC__GROOMY] ) - skills2 -= sc->data[SC__GROOMY]->val2; - if( sc->data[SC_SWINGDANCE] ) - skills2 += sc->data[SC_SWINGDANCE]->val2; - if( sc->data[SC_DANCEWITHWUG] ) - skills2 += sc->data[SC_DANCEWITHWUG]->val3; - if( sc->data[SC_GLOOMYDAY] ) - skills2 -= sc->data[SC_GLOOMYDAY]->val3; - if( sc->data[SC_EARTHDRIVE] ) - skills2 -= 25; - if( sc->data[SC_GT_CHANGE] ) - skills2 += sc->data[SC_GT_CHANGE]->val3; - if( sc->data[SC_MELON_BOMB] ) - skills2 -= sc->data[SC_MELON_BOMB]->val1; - if( sc->data[SC_BOOST500] ) - skills2 += sc->data[SC_BOOST500]->val1; - if( sc->data[SC_EXTRACT_SALAMINE_JUICE] ) - skills2 += sc->data[SC_EXTRACT_SALAMINE_JUICE]->val1; - if( sc->data[SC_INCASPDRATE] ) - skills2 += sc->data[SC_INCASPDRATE]->val1; - - return ( flag&1? (skills1 + pots) : skills2 ); -} -#endif - -static short status_calc_fix_aspd(struct block_list *bl, struct status_change *sc, int aspd) { - if (!sc || !sc->count) - return cap_value(aspd, 0, 2000); - - if (!sc->data[SC_QUAGMIRE]) { - if (sc->data[SC_OVERED_BOOST]) - aspd = 2000 - sc->data[SC_OVERED_BOOST]->val3*10; - } - - if ((sc->data[SC_GUST_OPTION] || sc->data[SC_BLAST_OPTION] - || sc->data[SC_WILD_STORM_OPTION])) - aspd -= 50; // +5 ASPD - if( sc && sc->data[SC_FIGHTINGSPIRIT] && sc->data[SC_FIGHTINGSPIRIT]->val2 ) - aspd -= (bl->type==BL_PC?pc_checkskill((TBL_PC *)bl, RK_RUNEMASTERY):10) / 10 * 40; - - return cap_value(aspd, 0, 2000); // will be recap for proper bl anyway -} - -/// Calculates an object's ASPD modifier (alters the base amotion value). -/// Note that the scale of aspd_rate is 1000 = 100%. -static short status_calc_aspd_rate(struct block_list *bl, struct status_change *sc, int aspd_rate) -{ - int i; - - if(!sc || !sc->count) - return cap_value(aspd_rate,0,SHRT_MAX); - - if( !sc->data[SC_QUAGMIRE] ){ - int max = 0; - if(sc->data[SC_STAR_COMFORT]) - max = sc->data[SC_STAR_COMFORT]->val2; - - if(sc->data[SC_TWOHANDQUICKEN] && - max < sc->data[SC_TWOHANDQUICKEN]->val2) - max = sc->data[SC_TWOHANDQUICKEN]->val2; - - if(sc->data[SC_ONEHAND] && - max < sc->data[SC_ONEHAND]->val2) - max = sc->data[SC_ONEHAND]->val2; - - if(sc->data[SC_MERC_QUICKEN] && - max < sc->data[SC_MERC_QUICKEN]->val2) - max = sc->data[SC_MERC_QUICKEN]->val2; - - if(sc->data[SC_ADRENALINE2] && - max < sc->data[SC_ADRENALINE2]->val3) - max = sc->data[SC_ADRENALINE2]->val3; - - if(sc->data[SC_ADRENALINE] && - max < sc->data[SC_ADRENALINE]->val3) - max = sc->data[SC_ADRENALINE]->val3; - - if(sc->data[SC_SPEARQUICKEN] && - max < sc->data[SC_SPEARQUICKEN]->val2) - max = sc->data[SC_SPEARQUICKEN]->val2; - - if(sc->data[SC_GATLINGFEVER] && - max < sc->data[SC_GATLINGFEVER]->val2) - max = sc->data[SC_GATLINGFEVER]->val2; - - if(sc->data[SC_FLEET] && - max < sc->data[SC_FLEET]->val2) - max = sc->data[SC_FLEET]->val2; - - if(sc->data[SC_ASSNCROS] && - max < sc->data[SC_ASSNCROS]->val2) - { - if (bl->type!=BL_PC) - max = sc->data[SC_ASSNCROS]->val2; - else - switch(((TBL_PC*)bl)->status.weapon) - { - case W_BOW: - case W_REVOLVER: - case W_RIFLE: - case W_GATLING: - case W_SHOTGUN: - case W_GRENADE: - break; - default: - max = sc->data[SC_ASSNCROS]->val2; - } - } - aspd_rate -= max; - - if((sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST])) - aspd_rate -= 300; - else if(sc->data[SC_MADNESSCANCEL]) - aspd_rate -= 200; - } - - if( sc->data[i=SC_ASPDPOTION3] || - sc->data[i=SC_ASPDPOTION2] || - sc->data[i=SC_ASPDPOTION1] || - sc->data[i=SC_ASPDPOTION0] ) - aspd_rate -= sc->data[i]->val2; - - if(sc->data[SC_DONTFORGETME]) - aspd_rate += 10 * sc->data[SC_DONTFORGETME]->val2; - if(sc->data[SC_LONGING]) - aspd_rate += sc->data[SC_LONGING]->val2; - if(sc->data[SC_STEELBODY]) - aspd_rate += 250; - if(sc->data[SC_SKA]) - aspd_rate += 250; - if(sc->data[SC_DEFENDER]) - aspd_rate += sc->data[SC_DEFENDER]->val4; - if(sc->data[SC_GOSPEL] && sc->data[SC_GOSPEL]->val4 == BCT_ENEMY) - aspd_rate += 250; - if(sc->data[SC_GRAVITATION]) - aspd_rate += sc->data[SC_GRAVITATION]->val2; - if(sc->data[SC_JOINTBEAT]) { - if( sc->data[SC_JOINTBEAT]->val2&BREAK_WRIST ) - aspd_rate += 250; - if( sc->data[SC_JOINTBEAT]->val2&BREAK_KNEE ) - aspd_rate += 100; - } - if( sc->data[SC_FREEZING] ) - aspd_rate += 300; - if( sc->data[SC_HALLUCINATIONWALK_POSTDELAY] ) - aspd_rate += 500; - if( sc->data[SC_FIGHTINGSPIRIT] && sc->data[SC_FIGHTINGSPIRIT]->val2 ) - aspd_rate -= sc->data[SC_FIGHTINGSPIRIT]->val2; - if( sc->data[SC_PARALYSE] ) - aspd_rate += 100; - if( sc->data[SC__BODYPAINT] ) - aspd_rate += 200 + 50 * sc->data[SC__BODYPAINT]->val1; - if( sc->data[SC__INVISIBILITY] ) - aspd_rate += sc->data[SC__INVISIBILITY]->val2 * 10 ; - if( sc->data[SC__GROOMY] ) - aspd_rate += sc->data[SC__GROOMY]->val2 * 10; - if( sc->data[SC_SWINGDANCE] ) - aspd_rate -= sc->data[SC_SWINGDANCE]->val2 * 10; - if( sc->data[SC_DANCEWITHWUG] ) - aspd_rate -= sc->data[SC_DANCEWITHWUG]->val3 * 10; - if( sc->data[SC_GLOOMYDAY] ) - aspd_rate += sc->data[SC_GLOOMYDAY]->val3 * 10; - if( sc->data[SC_EARTHDRIVE] ) - aspd_rate += 250; - if( sc->data[SC_GT_CHANGE] ) - aspd_rate -= sc->data[SC_GT_CHANGE]->val3 * 10; - if( sc->data[SC_MELON_BOMB] ) - aspd_rate += sc->data[SC_MELON_BOMB]->val1 * 10; - if( sc->data[SC_BOOST500] ) - aspd_rate -= sc->data[SC_BOOST500]->val1 *10; - if( sc->data[SC_EXTRACT_SALAMINE_JUICE] ) - aspd_rate -= sc->data[SC_EXTRACT_SALAMINE_JUICE]->val1 * 10; - if( sc->data[SC_INCASPDRATE] ) - aspd_rate -= sc->data[SC_INCASPDRATE]->val1 * 10; - if( sc->data[SC_PAIN_KILLER]) - aspd_rate += sc->data[SC_PAIN_KILLER]->val2 * 10; - if( sc->data[SC_GOLDENE_FERSE]) - aspd_rate -= sc->data[SC_GOLDENE_FERSE]->val3 * 10; - - return (short)cap_value(aspd_rate,0,SHRT_MAX); -} - -static unsigned short status_calc_dmotion(struct block_list *bl, struct status_change *sc, int dmotion) -{ - if( !sc || !sc->count || map_flag_gvg(bl->m) || map[bl->m].flag.battleground ) - return cap_value(dmotion,0,USHRT_MAX); - /** - * It has been confirmed on official servers that MvP mobs have no dmotion even without endure - **/ - if( sc->data[SC_ENDURE] || ( bl->type == BL_MOB && (((TBL_MOB*)bl)->status.mode&MD_BOSS) ) ) - return 0; - if( sc->data[SC_CONCENTRATION] ) - return 0; - if( sc->data[SC_RUN] || sc->data[SC_WUGDASH] ) - return 0; - - return (unsigned short)cap_value(dmotion,0,USHRT_MAX); -} - -static unsigned int status_calc_maxhp(struct block_list *bl, struct status_change *sc, uint64 maxhp) -{ - if(!sc || !sc->count) - return (unsigned int)cap_value(maxhp,1,UINT_MAX); - - if(sc->data[SC_INCMHPRATE]) - maxhp += maxhp * sc->data[SC_INCMHPRATE]->val1/100; - if(sc->data[SC_INCMHP]) - maxhp += (sc->data[SC_INCMHP]->val1); - if(sc->data[SC_APPLEIDUN]) - maxhp += maxhp * sc->data[SC_APPLEIDUN]->val2/100; - if(sc->data[SC_DELUGE]) - maxhp += maxhp * sc->data[SC_DELUGE]->val2/100; - if (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) - maxhp += maxhp * 2; - if(sc->data[SC_MARIONETTE]) - maxhp -= 1000; - if(sc->data[SC_SOLID_SKIN_OPTION]) - maxhp += 2000;// Fix amount. - if(sc->data[SC_POWER_OF_GAIA]) - maxhp += 3000; - if(sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 2) - maxhp += 500; - - if(sc->data[SC_MERC_HPUP]) - maxhp += maxhp * sc->data[SC_MERC_HPUP]->val2/100; - - if(sc->data[SC_EPICLESIS]) - maxhp += maxhp * 5 * sc->data[SC_EPICLESIS]->val1 / 100; - if(sc->data[SC_VENOMBLEED]) - maxhp -= maxhp * 15 / 100; - if(sc->data[SC__WEAKNESS]) - maxhp -= maxhp * sc->data[SC__WEAKNESS]->val2 / 100; - if(sc->data[SC_LERADSDEW]) - maxhp += maxhp * sc->data[SC_LERADSDEW]->val3 / 100; - if(sc->data[SC_FORCEOFVANGUARD]) - maxhp += maxhp * 3 * sc->data[SC_FORCEOFVANGUARD]->val1 / 100; - if(sc->data[SC_INSPIRATION]) //Custom value. - maxhp += maxhp * 3 * sc->data[SC_INSPIRATION]->val1 / 100; - if(sc->data[SC_RAISINGDRAGON]) - maxhp += maxhp * (2 + sc->data[SC_RAISINGDRAGON]->val1) / 100; - if(sc->data[SC_GT_CHANGE]) // Max HP decrease: [Skill Level x 4] % - maxhp -= maxhp * (4 * sc->data[SC_GT_CHANGE]->val1) / 100; - if(sc->data[SC_GT_REVITALIZE])// Max HP increase: [Skill Level x 2] % - maxhp += maxhp * (2 * sc->data[SC_GT_REVITALIZE]->val1) / 100; - if(sc->data[SC_MUSTLE_M]) - maxhp += maxhp * sc->data[SC_MUSTLE_M]->val1/100; - if(sc->data[SC_MYSTERIOUS_POWDER]) - maxhp -= sc->data[SC_MYSTERIOUS_POWDER]->val1 / 100; - if(sc->data[SC_PETROLOGY_OPTION]) - maxhp += maxhp * sc->data[SC_PETROLOGY_OPTION]->val2 / 100; - if (sc->data[SC_ANGRIFFS_MODUS]) - maxhp += maxhp * 5 * sc->data[SC_ANGRIFFS_MODUS]->val1 /100; - if (sc->data[SC_GOLDENE_FERSE]) - maxhp += maxhp * sc->data[SC_GOLDENE_FERSE]->val2 / 100; - - return (unsigned int)cap_value(maxhp,1,UINT_MAX); -} - -static unsigned int status_calc_maxsp(struct block_list *bl, struct status_change *sc, unsigned int maxsp) -{ - if(!sc || !sc->count) - return cap_value(maxsp,1,UINT_MAX); - - if(sc->data[SC_INCMSPRATE]) - maxsp += maxsp * sc->data[SC_INCMSPRATE]->val1/100; - if(sc->data[SC_INCMSP]) - maxsp += (sc->data[SC_INCMSP]->val1); - if(sc->data[SC_SERVICE4U]) - maxsp += maxsp * sc->data[SC_SERVICE4U]->val2/100; - if(sc->data[SC_MERC_SPUP]) - maxsp += maxsp * sc->data[SC_MERC_SPUP]->val2/100; - if(sc->data[SC_RAISINGDRAGON]) - maxsp += maxsp * (2 + sc->data[SC_RAISINGDRAGON]->val1) / 100; - if(sc->data[SC_LIFE_FORCE_F]) - maxsp += maxsp * sc->data[SC_LIFE_FORCE_F]->val1/100; - if(sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 3) - maxsp += 50; - - return cap_value(maxsp,1,UINT_MAX); -} - -static unsigned char status_calc_element(struct block_list *bl, struct status_change *sc, int element) -{ - if(!sc || !sc->count) - return element; - - if(sc->data[SC_FREEZE]) - return ELE_WATER; - if(sc->data[SC_STONE] && sc->opt1 == OPT1_STONE) - return ELE_EARTH; - if(sc->data[SC_BENEDICTIO]) - return ELE_HOLY; - if(sc->data[SC_CHANGEUNDEAD]) - return ELE_UNDEAD; - if(sc->data[SC_ELEMENTALCHANGE]) - return sc->data[SC_ELEMENTALCHANGE]->val2; - if(sc->data[SC_SHAPESHIFT]) - return sc->data[SC_SHAPESHIFT]->val2; - - return (unsigned char)cap_value(element,0,UCHAR_MAX); -} - -static unsigned char status_calc_element_lv(struct block_list *bl, struct status_change *sc, int lv) -{ - if(!sc || !sc->count) - return lv; - - if(sc->data[SC_FREEZE]) - return 1; - if(sc->data[SC_STONE] && sc->opt1 == OPT1_STONE) - return 1; - if(sc->data[SC_BENEDICTIO]) - return 1; - if(sc->data[SC_CHANGEUNDEAD]) - return 1; - if(sc->data[SC_ELEMENTALCHANGE]) - return sc->data[SC_ELEMENTALCHANGE]->val1; - if(sc->data[SC_SHAPESHIFT]) - return 1; - if(sc->data[SC__INVISIBILITY]) - return 1; - - return (unsigned char)cap_value(lv,1,4); -} - - -unsigned char status_calc_attack_element(struct block_list *bl, struct status_change *sc, int element) -{ - if(!sc || !sc->count) - return element; - if(sc->data[SC_ENCHANTARMS]) - return sc->data[SC_ENCHANTARMS]->val2; - if(sc->data[SC_WATERWEAPON] - || (sc->data[SC_WATER_INSIGNIA] && sc->data[SC_WATER_INSIGNIA]->val1 == 2) ) - return ELE_WATER; - if(sc->data[SC_EARTHWEAPON] - || (sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 2) ) - return ELE_EARTH; - if(sc->data[SC_FIREWEAPON] - || (sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 2) ) - return ELE_FIRE; - if(sc->data[SC_WINDWEAPON] - || (sc->data[SC_WIND_INSIGNIA] && sc->data[SC_WIND_INSIGNIA]->val1 == 2) ) - return ELE_WIND; - if(sc->data[SC_ENCPOISON]) - return ELE_POISON; - if(sc->data[SC_ASPERSIO]) - return ELE_HOLY; - if(sc->data[SC_SHADOWWEAPON]) - return ELE_DARK; - if(sc->data[SC_GHOSTWEAPON] || sc->data[SC__INVISIBILITY]) - return ELE_GHOST; - if(sc->data[SC_TIDAL_WEAPON_OPTION] || sc->data[SC_TIDAL_WEAPON] ) - return ELE_WATER; - if(sc->data[SC_PYROCLASTIC]) - return ELE_FIRE; - return (unsigned char)cap_value(element,0,UCHAR_MAX); -} - -static unsigned short status_calc_mode(struct block_list *bl, struct status_change *sc, int mode) -{ - if(!sc || !sc->count) - return mode; - if(sc->data[SC_MODECHANGE]) { - if (sc->data[SC_MODECHANGE]->val2) - mode = sc->data[SC_MODECHANGE]->val2; //Set mode - if (sc->data[SC_MODECHANGE]->val3) - mode|= sc->data[SC_MODECHANGE]->val3; //Add mode - if (sc->data[SC_MODECHANGE]->val4) - mode&=~sc->data[SC_MODECHANGE]->val4; //Del mode - } - return cap_value(mode,0,USHRT_MAX); -} - -const char* status_get_name(struct block_list *bl) { - nullpo_ret(bl); - switch (bl->type) { - case BL_PC: return ((TBL_PC *)bl)->fakename[0] != '\0' ? ((TBL_PC*)bl)->fakename : ((TBL_PC*)bl)->status.name; - case BL_MOB: return ((TBL_MOB*)bl)->name; - case BL_PET: return ((TBL_PET*)bl)->pet.name; - case BL_HOM: return ((TBL_HOM*)bl)->homunculus.name; - case BL_NPC: return ((TBL_NPC*)bl)->name; - } - return "Unknown"; -} - -/*========================================== - * Get the class of the current bl - * return - * 0 = fail - * class_id = success - *------------------------------------------*/ -int status_get_class(struct block_list *bl) { - nullpo_ret(bl); - switch( bl->type ) { - case BL_PC: return ((TBL_PC*)bl)->status.class_; - case BL_MOB: return ((TBL_MOB*)bl)->vd->class_; //Class used on all code should be the view class of the mob. - case BL_PET: return ((TBL_PET*)bl)->pet.class_; - case BL_HOM: return ((TBL_HOM*)bl)->homunculus.class_; - case BL_MER: return ((TBL_MER*)bl)->mercenary.class_; - case BL_NPC: return ((TBL_NPC*)bl)->class_; - case BL_ELEM: return ((TBL_ELEM*)bl)->elemental.class_; - } - return 0; -} -/*========================================== - * Get the base level of the current bl - * return - * 1 = fail - * level = success - *------------------------------------------*/ -int status_get_lv(struct block_list *bl) { - nullpo_ret(bl); - switch (bl->type) { - case BL_PC: return ((TBL_PC*)bl)->status.base_level; - case BL_MOB: return ((TBL_MOB*)bl)->level; - case BL_PET: return ((TBL_PET*)bl)->pet.level; - case BL_HOM: return ((TBL_HOM*)bl)->homunculus.level; - case BL_MER: return ((TBL_MER*)bl)->db->lv; - case BL_ELEM: return ((TBL_ELEM*)bl)->db->lv; - case BL_NPC: return ((TBL_NPC*)bl)->level; - } - return 1; -} - -struct regen_data *status_get_regen_data(struct block_list *bl) -{ - nullpo_retr(NULL, bl); - switch (bl->type) { - case BL_PC: return &((TBL_PC*)bl)->regen; - case BL_HOM: return &((TBL_HOM*)bl)->regen; - case BL_MER: return &((TBL_MER*)bl)->regen; - case BL_ELEM: return &((TBL_ELEM*)bl)->regen; - default: - return NULL; - } -} - -struct status_data *status_get_status_data(struct block_list *bl) -{ - nullpo_retr(&dummy_status, bl); - - switch (bl->type) { - case BL_PC: return &((TBL_PC*)bl)->battle_status; - case BL_MOB: return &((TBL_MOB*)bl)->status; - case BL_PET: return &((TBL_PET*)bl)->status; - case BL_HOM: return &((TBL_HOM*)bl)->battle_status; - case BL_MER: return &((TBL_MER*)bl)->battle_status; - case BL_ELEM: return &((TBL_ELEM*)bl)->battle_status; - case BL_NPC: return ((mobdb_checkid(((TBL_NPC*)bl)->class_) == 0) ? &((TBL_NPC*)bl)->status : &dummy_status); - default: - return &dummy_status; - } -} - -struct status_data *status_get_base_status(struct block_list *bl) -{ - nullpo_retr(NULL, bl); - switch (bl->type) { - case BL_PC: return &((TBL_PC*)bl)->base_status; - case BL_MOB: return ((TBL_MOB*)bl)->base_status ? ((TBL_MOB*)bl)->base_status : &((TBL_MOB*)bl)->db->status; - case BL_PET: return &((TBL_PET*)bl)->db->status; - case BL_HOM: return &((TBL_HOM*)bl)->base_status; - case BL_MER: return &((TBL_MER*)bl)->base_status; - case BL_ELEM: return &((TBL_ELEM*)bl)->base_status; - case BL_NPC: return ((mobdb_checkid(((TBL_NPC*)bl)->class_) == 0) ? &((TBL_NPC*)bl)->status : NULL); - default: - return NULL; - } -} -defType status_get_def(struct block_list *bl) { - struct unit_data *ud; - struct status_data *status = status_get_status_data(bl); - int def = status?status->def:0; - ud = unit_bl2ud(bl); - if (ud && ud->skilltimer != INVALID_TIMER) - def -= def * skill_get_castdef(ud->skill_id)/100; - - return cap_value(def, DEFTYPE_MIN, DEFTYPE_MAX); -} - -unsigned short status_get_speed(struct block_list *bl) -{ - if(bl->type==BL_NPC)//Only BL with speed data but no status_data [Skotlex] - return ((struct npc_data *)bl)->speed; - return status_get_status_data(bl)->speed; -} - -int status_get_party_id(struct block_list *bl) { - nullpo_ret(bl); - switch (bl->type) { - case BL_PC: - return ((TBL_PC*)bl)->status.party_id; - case BL_PET: - if (((TBL_PET*)bl)->msd) - return ((TBL_PET*)bl)->msd->status.party_id; - break; - case BL_MOB: { - struct mob_data *md=(TBL_MOB*)bl; - if( md->master_id > 0 ) { - struct map_session_data *msd; - if (md->special_state.ai && (msd = map_id2sd(md->master_id)) != NULL) - return msd->status.party_id; - return -md->master_id; - } - } - break; - case BL_HOM: - if (((TBL_HOM*)bl)->master) - return ((TBL_HOM*)bl)->master->status.party_id; - break; - case BL_MER: - if (((TBL_MER*)bl)->master) - return ((TBL_MER*)bl)->master->status.party_id; - break; - case BL_SKILL: - return ((TBL_SKILL*)bl)->group->party_id; - case BL_ELEM: - if (((TBL_ELEM*)bl)->master) - return ((TBL_ELEM*)bl)->master->status.party_id; - break; - } - return 0; -} - -int status_get_guild_id(struct block_list *bl) { - nullpo_ret(bl); - switch (bl->type) { - case BL_PC: - return ((TBL_PC*)bl)->status.guild_id; - case BL_PET: - if (((TBL_PET*)bl)->msd) - return ((TBL_PET*)bl)->msd->status.guild_id; - break; - case BL_MOB: { - struct map_session_data *msd; - struct mob_data *md = (struct mob_data *)bl; - if (md->guardian_data) //Guardian's guild [Skotlex] - return md->guardian_data->guild_id; - if (md->special_state.ai && (msd = map_id2sd(md->master_id)) != NULL) - return msd->status.guild_id; //Alchemist's mobs [Skotlex] - } - break; - case BL_HOM: - if (((TBL_HOM*)bl)->master) - return ((TBL_HOM*)bl)->master->status.guild_id; - break; - case BL_MER: - if (((TBL_MER*)bl)->master) - return ((TBL_MER*)bl)->master->status.guild_id; - break; - case BL_NPC: - if (((TBL_NPC*)bl)->subtype == SCRIPT) - return ((TBL_NPC*)bl)->u.scr.guild_id; - break; - case BL_SKILL: - return ((TBL_SKILL*)bl)->group->guild_id; - case BL_ELEM: - if (((TBL_ELEM*)bl)->master) - return ((TBL_ELEM*)bl)->master->status.guild_id; - break; - } - return 0; -} - -int status_get_emblem_id(struct block_list *bl) { - nullpo_ret(bl); - switch (bl->type) { - case BL_PC: - return ((TBL_PC*)bl)->guild_emblem_id; - case BL_PET: - if (((TBL_PET*)bl)->msd) - return ((TBL_PET*)bl)->msd->guild_emblem_id; - break; - case BL_MOB: { - struct map_session_data *msd; - struct mob_data *md = (struct mob_data *)bl; - if (md->guardian_data) //Guardian's guild [Skotlex] - return md->guardian_data->emblem_id; - if (md->special_state.ai && (msd = map_id2sd(md->master_id)) != NULL) - return msd->guild_emblem_id; //Alchemist's mobs [Skotlex] - } - break; - case BL_HOM: - if (((TBL_HOM*)bl)->master) - return ((TBL_HOM*)bl)->master->guild_emblem_id; - break; - case BL_MER: - if (((TBL_MER*)bl)->master) - return ((TBL_MER*)bl)->master->guild_emblem_id; - break; - case BL_NPC: - if (((TBL_NPC*)bl)->subtype == SCRIPT && ((TBL_NPC*)bl)->u.scr.guild_id > 0) { - struct guild *g = guild_search(((TBL_NPC*)bl)->u.scr.guild_id); - if (g) - return g->emblem_id; - } - break; - case BL_ELEM: - if (((TBL_ELEM*)bl)->master) - return ((TBL_ELEM*)bl)->master->guild_emblem_id; - break; - } - return 0; -} - -int status_get_mexp(struct block_list *bl) -{ - nullpo_ret(bl); - if(bl->type==BL_MOB) - return ((struct mob_data *)bl)->db->mexp; - if(bl->type==BL_PET) - return ((struct pet_data *)bl)->db->mexp; - return 0; -} -int status_get_race2(struct block_list *bl) -{ - nullpo_ret(bl); - if(bl->type == BL_MOB) - return ((struct mob_data *)bl)->db->race2; - if(bl->type==BL_PET) - return ((struct pet_data *)bl)->db->race2; - return 0; -} - -int status_isdead(struct block_list *bl) -{ - nullpo_ret(bl); - return status_get_status_data(bl)->hp == 0; -} - -int status_isimmune(struct block_list *bl) -{ - struct status_change *sc =status_get_sc(bl); - if (sc && sc->data[SC_HERMODE]) - return 100; - - if (bl->type == BL_PC && - ((TBL_PC*)bl)->special_state.no_magic_damage >= battle_config.gtb_sc_immunity) - return ((TBL_PC*)bl)->special_state.no_magic_damage; - return 0; -} - -struct view_data* status_get_viewdata(struct block_list *bl) -{ - nullpo_retr(NULL, bl); - switch (bl->type) { - case BL_PC: return &((TBL_PC*)bl)->vd; - case BL_MOB: return ((TBL_MOB*)bl)->vd; - case BL_PET: return &((TBL_PET*)bl)->vd; - case BL_NPC: return ((TBL_NPC*)bl)->vd; - case BL_HOM: return ((TBL_HOM*)bl)->vd; - case BL_MER: return ((TBL_MER*)bl)->vd; - case BL_ELEM: return ((TBL_ELEM*)bl)->vd; - } - return NULL; -} - -void status_set_viewdata(struct block_list *bl, int class_) -{ - struct view_data* vd; - nullpo_retv(bl); - if (mobdb_checkid(class_) || mob_is_clone(class_)) - vd = mob_get_viewdata(class_); - else if (npcdb_checkid(class_) || (bl->type == BL_NPC && class_ == WARP_CLASS)) - vd = npc_get_viewdata(class_); - else if (homdb_checkid(class_)) - vd = merc_get_hom_viewdata(class_); - else if (merc_class(class_)) - vd = merc_get_viewdata(class_); - else if (elemental_class(class_)) - vd = elemental_get_viewdata(class_); - else - vd = NULL; - - switch (bl->type) { - case BL_PC: - { - TBL_PC* sd = (TBL_PC*)bl; - if (pcdb_checkid(class_)) { - if (sd->sc.option&OPTION_WEDDING) - class_ = JOB_WEDDING; - else if (sd->sc.option&OPTION_SUMMER) - class_ = JOB_SUMMER; - else if (sd->sc.option&OPTION_XMAS) - class_ = JOB_XMAS; - else if (sd->sc.option&OPTION_RIDING) { - switch (class_) { //Adapt class to a Mounted one. - case JOB_KNIGHT: - class_ = JOB_KNIGHT2; - break; - case JOB_CRUSADER: - class_ = JOB_CRUSADER2; - break; - case JOB_LORD_KNIGHT: - class_ = JOB_LORD_KNIGHT2; - break; - case JOB_PALADIN: - class_ = JOB_PALADIN2; - break; - case JOB_BABY_KNIGHT: - class_ = JOB_BABY_KNIGHT2; - break; - case JOB_BABY_CRUSADER: - class_ = JOB_BABY_CRUSADER2; - break; - } - } - sd->vd.class_ = class_; - clif_get_weapon_view(sd, &sd->vd.weapon, &sd->vd.shield); - sd->vd.head_top = sd->status.head_top; - sd->vd.head_mid = sd->status.head_mid; - sd->vd.head_bottom = sd->status.head_bottom; - sd->vd.hair_style = cap_value(sd->status.hair,0,battle_config.max_hair_style); - sd->vd.hair_color = cap_value(sd->status.hair_color,0,battle_config.max_hair_color); - sd->vd.cloth_color = cap_value(sd->status.clothes_color,0,battle_config.max_cloth_color); - sd->vd.sex = sd->status.sex; - } else if (vd) - memcpy(&sd->vd, vd, sizeof(struct view_data)); - else - ShowError("status_set_viewdata (PC): No view data for class %d\n", class_); - } - break; - case BL_MOB: - { - TBL_MOB* md = (TBL_MOB*)bl; - if (vd) - md->vd = vd; - else - ShowError("status_set_viewdata (MOB): No view data for class %d\n", class_); - } - break; - case BL_PET: - { - TBL_PET* pd = (TBL_PET*)bl; - if (vd) { - memcpy(&pd->vd, vd, sizeof(struct view_data)); - if (!pcdb_checkid(vd->class_)) { - pd->vd.hair_style = battle_config.pet_hair_style; - if(pd->pet.equip) { - pd->vd.head_bottom = itemdb_viewid(pd->pet.equip); - if (!pd->vd.head_bottom) - pd->vd.head_bottom = pd->pet.equip; - } - } - } else - ShowError("status_set_viewdata (PET): No view data for class %d\n", class_); - } - break; - case BL_NPC: - { - TBL_NPC* nd = (TBL_NPC*)bl; - if (vd) - nd->vd = vd; - else - ShowError("status_set_viewdata (NPC): No view data for class %d\n", class_); - } - break; - case BL_HOM: //[blackhole89] - { - struct homun_data *hd = (struct homun_data*)bl; - if (vd) - hd->vd = vd; - else - ShowError("status_set_viewdata (HOMUNCULUS): No view data for class %d\n", class_); - } - break; - case BL_MER: - { - struct mercenary_data *md = (struct mercenary_data*)bl; - if (vd) - md->vd = vd; - else - ShowError("status_set_viewdata (MERCENARY): No view data for class %d\n", class_); - } - break; - case BL_ELEM: - { - struct elemental_data *ed = (struct elemental_data*)bl; - if (vd) - ed->vd = vd; - else - ShowError("status_set_viewdata (ELEMENTAL): No view data for class %d\n", class_); - } - break; - } - vd = status_get_viewdata(bl); - if (vd && vd->cloth_color && ( - (vd->class_==JOB_WEDDING && battle_config.wedding_ignorepalette) - || (vd->class_==JOB_XMAS && battle_config.xmas_ignorepalette) - || (vd->class_==JOB_SUMMER && battle_config.summer_ignorepalette) - )) - vd->cloth_color = 0; -} - -/// Returns the status_change data of bl or NULL if it doesn't exist. -struct status_change *status_get_sc(struct block_list *bl) { - if( bl ) - switch (bl->type) { - case BL_PC: return &((TBL_PC*)bl)->sc; - case BL_MOB: return &((TBL_MOB*)bl)->sc; - case BL_NPC: return &((TBL_NPC*)bl)->sc; - case BL_HOM: return &((TBL_HOM*)bl)->sc; - case BL_MER: return &((TBL_MER*)bl)->sc; - case BL_ELEM: return &((TBL_ELEM*)bl)->sc; - } - return NULL; -} - -void status_change_init(struct block_list *bl) -{ - struct status_change *sc = status_get_sc(bl); - nullpo_retv(sc); - memset(sc, 0, sizeof (struct status_change)); -} - -//Applies SC defense to a given status change. -//Returns the adjusted duration based on flag values. -//the flag values are the same as in status_change_start. -int status_get_sc_def(struct block_list *bl, enum sc_type type, int rate, int tick, int flag) -{ - int sc_def = 0, tick_def = 0; - struct status_data* status; - struct status_change* sc; - struct map_session_data *sd; - - nullpo_ret(bl); - - //Status that are blocked by Golden Thief Bug card or Wand of Hermod - if (status_isimmune(bl)) - switch (type) { - case SC_DECREASEAGI: - case SC_SILENCE: - case SC_COMA: - case SC_INCREASEAGI: - case SC_BLESSING: - case SC_SLOWPOISON: - case SC_IMPOSITIO: - case SC_AETERNA: - case SC_SUFFRAGIUM: - case SC_BENEDICTIO: - case SC_PROVIDENCE: - case SC_KYRIE: - case SC_ASSUMPTIO: - case SC_ANGELUS: - case SC_MAGNIFICAT: - case SC_GLORIA: - case SC_WINDWALK: - case SC_MAGICROD: - case SC_HALLUCINATION: - case SC_STONE: - case SC_QUAGMIRE: - case SC_SUITON: - case SC_SWINGDANCE: - case SC__ENERVATION: - case SC__GROOMY: - case SC__IGNORANCE: - case SC__LAZINESS: - case SC__UNLUCKY: - case SC__WEAKNESS: - case SC__BLOODYLUST: - return 0; - } - - sd = BL_CAST(BL_PC,bl); - status = status_get_status_data(bl); - sc = status_get_sc(bl); - if( sc && !sc->count ) - sc = NULL; - switch (type) { - case SC_STUN: - case SC_POISON: - if( sc && sc->data[SC__UNLUCKY] ) - return tick; - case SC_DPOISON: - case SC_SILENCE: - case SC_BLEEDING: - sc_def = 3 +status->vit; - break; - case SC_SLEEP: - sc_def = 3 +status->int_; - break; - case SC_DEEPSLEEP: - tick_def = status->int_ / 10 + status_get_lv(bl) * 65 / 1000; // Seems to be -1 sec every 10 int and -5% chance every 10 int. - sc_def = 5 * status->int_ /10; - break; - case SC_DECREASEAGI: - case SC_ADORAMUS://Arch Bishop - if (sd) tick>>=1; //Half duration for players. - case SC_STONE: - case SC_FREEZE: - sc_def = 3 +status->mdef; - break; - case SC_CURSE: - //Special property: inmunity when luk is greater than level or zero - if (status->luk > status_get_lv(bl) || status->luk == 0) - return 0; - else - sc_def = 3 +status->luk; - tick_def = status->vit; - break; - case SC_BLIND: - if( sc && sc->data[SC__UNLUCKY] ) - return tick; - sc_def = 3 +(status->vit + status->int_)/2; - break; - case SC_CONFUSION: - sc_def = 3 +(status->str + status->int_)/2; - break; - case SC_ANKLE: - if(status->mode&MD_BOSS) // Lasts 5 times less on bosses - tick /= 5; - sc_def = status->agi / 2; - break; - case SC_MAGICMIRROR: - case SC_ARMORCHANGE: - if (sd) //Duration greatly reduced for players. - tick /= 15; - //No defense against it (buff). - rate -= (status_get_lv(bl) / 5 + status->vit / 4 + status->agi / 10)*100; // Lineal Reduction of Rate - break; - case SC_MARSHOFABYSS: - //5 second (Fixed) + 25 second - {( INT + LUK ) / 20 second } - tick -= (status->int_ + status->luk) / 20 * 1000; - break; - case SC_STASIS: - //5 second (fixed) + { Stasis Skill level * 5 - (Target�s VIT + DEX) / 20 } - tick -= (status->vit + status->dex) / 20 * 1000; - break; - case SC_WHITEIMPRISON: - if( tick == 5000 ) // 100% on caster - break; - if( bl->type == BL_PC ) - tick -= (status_get_lv(bl) / 5 + status->vit / 4 + status->agi / 10)*100; - else - tick -= (status->vit + status->luk) / 20 * 1000; - break; - case SC_BURNING: - // From iROwiki : http://forums.irowiki.org/showpost.php?p=577240&postcount=583 - tick -= 50*status->luk + 60*status->int_ + 170*status->vit; - tick = max(tick,10000); // Minimum Duration 10s. - break; - case SC_FREEZING: - tick -= 1000 * ((status->vit + status->dex) / 20); - tick = max(tick,10000); // Minimum Duration 10s. - break; - case SC_OBLIVIONCURSE: // 100% - (100 - 0.8 x INT) - sc_def = 100 - ( 100 - status->int_* 8 / 10 ); - sc_def = max(sc_def, 5); // minimum of 5% - break; - case SC_BITE: // {(Base Success chance) - (Target's AGI / 4)} - rate -= status->agi*1000/4; - rate = max(rate,50000); // minimum of 50% - break; - case SC_ELECTRICSHOCKER: - if( bl->type == BL_MOB ) - tick -= 1000 * (status->agi/10); - break; - case SC_CRYSTALIZE: - tick -= (1000*(status->vit/10))+(status_get_lv(bl)/50); - break; - case SC_MANDRAGORA: - sc_def = (status->vit+status->luk)/5; - break; - case SC_KYOUGAKU: - tick -= 30*status->int_; - break; - case SC_PARALYSIS: - tick -= 50 * (status->vit + status->luk); //(1000/20); - break; - default: - //Effect that cannot be reduced? Likely a buff. - if (!(rnd()%10000 < rate)) - return 0; - return tick?tick:1; - } - - if (sd) { - - if (battle_config.pc_sc_def_rate != 100) - sc_def = sc_def*battle_config.pc_sc_def_rate/100; - - if (sc_def < battle_config.pc_max_sc_def) - sc_def += (battle_config.pc_max_sc_def - sc_def)* - status->luk/battle_config.pc_luk_sc_def; - else - sc_def = battle_config.pc_max_sc_def; - - if (tick_def) { - if (battle_config.pc_sc_def_rate != 100) - tick_def = tick_def*battle_config.pc_sc_def_rate/100; - } - - } else { - - if (battle_config.mob_sc_def_rate != 100) - sc_def = sc_def*battle_config.mob_sc_def_rate/100; - - if (sc_def < battle_config.mob_max_sc_def) - sc_def += (battle_config.mob_max_sc_def - sc_def)* - status->luk/battle_config.mob_luk_sc_def; - else - sc_def = battle_config.mob_max_sc_def; - - if (tick_def) { - if (battle_config.mob_sc_def_rate != 100) - tick_def = tick_def*battle_config.mob_sc_def_rate/100; - } - } - - if (sc) { - if (sc->data[SC_SCRESIST]) - sc_def += sc->data[SC_SCRESIST]->val1; //Status resist - else if (sc->data[SC_SIEGFRIED]) - sc_def += sc->data[SC_SIEGFRIED]->val3; //Status resistance. - } - - //When no tick def, reduction is the same for both. - if( !tick_def && type != SC_STONE ) //Recent tests show duration of petrify isn't reduced by MDEF. [Inkfish] - tick_def = sc_def; - - //Natural resistance - if (!(flag&8)) { - rate -= rate*sc_def/100; - - //Item resistance (only applies to rate%) - if(sd && SC_COMMON_MIN <= type && type <= SC_COMMON_MAX) - { - if( sd->reseff[type-SC_COMMON_MIN] > 0 ) - rate -= rate*sd->reseff[type-SC_COMMON_MIN]/10000; - if( sd->sc.data[SC_COMMONSC_RESIST] ) - rate -= rate*sd->sc.data[SC_COMMONSC_RESIST]->val1/100; - } - } - if (!(rnd()%10000 < rate)) - return 0; - - //Why would a status start with no duration? Presume it has - //duration defined elsewhere. - if (!tick) return 1; - - //Rate reduction - if (flag&2) - return tick; - - tick -= tick*tick_def/100; - // Changed to 5 seconds according to recent tests [Playtester] - if (type == SC_ANKLE && tick < 5000) - tick = 5000; - return tick<=0?0:tick; -} - -/*========================================== - * Starts a status change. - * 'type' = type, 'val1~4' depend on the type. - * 'rate' = base success rate. 10000 = 100% - * 'tick' is base duration - * 'flag': - * &1: Cannot be avoided (it has to start) - * &2: Tick should not be reduced (by vit, luk, lv, etc) - * &4: sc_data loaded, no value has to be altered. - * &8: rate should not be reduced - *------------------------------------------*/ -int status_change_start(struct block_list* bl,enum sc_type type,int rate,int val1,int val2,int val3,int val4,int tick,int flag) -{ - struct map_session_data *sd = NULL; - struct status_change* sc; - struct status_change_entry* sce; - struct status_data *status; - struct view_data *vd; - int opt_flag, calc_flag, undead_flag, val_flag = 0, tick_time = 0; - bool sc_isnew = true; - - nullpo_ret(bl); - sc = status_get_sc(bl); - status = status_get_status_data(bl); - - if( type <= SC_NONE || type >= SC_MAX ) - { - ShowError("status_change_start: invalid status change (%d)!\n", type); - return 0; - } - - if( !sc ) - return 0; //Unable to receive status changes - - if( status_isdead(bl) && type != SC_NOCHAT ) // SC_NOCHAT should work even on dead characters - return 0; - - if( bl->type == BL_MOB) - { - struct mob_data *md = BL_CAST(BL_MOB,bl); - if(md && (md->class_ == MOBID_EMPERIUM || mob_is_battleground(md)) && type != SC_SAFETYWALL && type != SC_PNEUMA) - return 0; //Emperium/BG Monsters can't be afflicted by status changes - // if(md && mob_is_gvg(md) && status_sc2scb_flag(type)&SCB_MAXHP) - // return 0; //prevent status addinh hp to gvg mob (like bloodylust=hp*3 etc... - } - - if( sc->data[SC_REFRESH] ) { - if( type >= SC_COMMON_MIN && type <= SC_COMMON_MAX) // Confirmed. - return 0; // Immune to status ailements - switch( type ) { - case SC_QUAGMIRE://Tester said it protects against this and decrease agi. - case SC_DECREASEAGI: - case SC_BURNING: - case SC_FREEZING: - //case SC_WHITEIMPRISON://Need confirm. Protected against this in the past. [Rytech] - case SC_MARSHOFABYSS: - case SC_TOXIN: - case SC_PARALYSE: - case SC_VENOMBLEED: - case SC_MAGICMUSHROOM: - case SC_DEATHHURT: - case SC_PYREXIA: - case SC_OBLIVIONCURSE: - case SC_LEECHESEND: - case SC_CRYSTALIZE: ////08/31/2011 - Class Balance Changes - case SC_DEEPSLEEP: - case SC_MANDRAGORA: - return 0; - } - } - else if( sc->data[SC_INSPIRATION] ) { - if( type >= SC_COMMON_MIN && type <= SC_COMMON_MAX ) - return 0; // Immune to status ailements - switch( type ) { - case SC_DEEPSLEEP: - case SC_SATURDAYNIGHTFEVER: - case SC_PYREXIA: - case SC_DEATHHURT: - case SC_MAGICMUSHROOM: - case SC_VENOMBLEED: - case SC_TOXIN: - case SC_OBLIVIONCURSE: - case SC_LEECHESEND: - case SC__ENERVATION: - case SC__GROOMY: - case SC__LAZINESS: - case SC__UNLUCKY: - case SC__WEAKNESS: - case SC__BODYPAINT: - case SC__IGNORANCE: - return 0; - } - } - - sd = BL_CAST(BL_PC, bl); - - //Adjust tick according to status resistances - if( !(flag&(1|4)) ) - { - tick = status_get_sc_def(bl, type, rate, tick, flag); - if( !tick ) return 0; - } - - undead_flag = battle_check_undead(status->race,status->def_ele); - //Check for inmunities / sc fails - switch (type) { - case SC_ANGRIFFS_MODUS: - case SC_GOLDENE_FERSE: - if ((type==SC_GOLDENE_FERSE && sc->data[SC_ANGRIFFS_MODUS]) - || (type==SC_ANGRIFFS_MODUS && sc->data[SC_GOLDENE_FERSE]) - ) - return 0; - case SC_STONE: - if(sc->data[SC_POWER_OF_GAIA]) - return 0; - case SC_FREEZE: - //Undead are immune to Freeze/Stone - if (undead_flag && !(flag&1)) - return 0; - case SC_DEEPSLEEP: - case SC_SLEEP: - case SC_STUN: - case SC_FREEZING: - case SC_CRYSTALIZE: - if (sc->opt1) - return 0; //Cannot override other opt1 status changes. [Skotlex] - if((type == SC_FREEZE || type == SC_FREEZING || type == SC_CRYSTALIZE) && sc->data[SC_WARMER]) - return 0; //Immune to Frozen and Freezing status if under Warmer status. [Jobbie] - break; - - //There all like berserk, do not everlap each other - case SC__BLOODYLUST: - if(!sd) return 0; //should only affect player - case SC_BERSERK: - if (((type == SC_BERSERK) && (sc->data[SC_SATURDAYNIGHTFEVER] || sc->data[SC__BLOODYLUST])) - || ((type == SC__BLOODYLUST) && (sc->data[SC_SATURDAYNIGHTFEVER] || sc->data[SC_BERSERK])) - ) - return 0; - break; - - case SC_BURNING: - if(sc->opt1 || sc->data[SC_FREEZING]) - return 0; - break; - - case SC_SIGNUMCRUCIS: - //Only affects demons and undead element (but not players) - if((!undead_flag && status->race!=RC_DEMON) || bl->type == BL_PC) - return 0; - break; - case SC_AETERNA: - if( (sc->data[SC_STONE] && sc->opt1 == OPT1_STONE) || sc->data[SC_FREEZE] ) - return 0; - break; - case SC_KYRIE: - if (bl->type == BL_MOB) - return 0; - break; - case SC_OVERTHRUST: - if (sc->data[SC_MAXOVERTHRUST]) - return 0; //Overthrust can't take effect if under Max Overthrust. [Skotlex] - case SC_MAXOVERTHRUST: - if( sc->option&OPTION_MADOGEAR ) - return 0;//Overthrust and Overthrust Max cannot be used on Mado Gear [Ind] - break; - case SC_ADRENALINE: - if(sd && !pc_check_weapontype(sd,skill_get_weapontype(BS_ADRENALINE))) - return 0; - if (sc->data[SC_QUAGMIRE] || - sc->data[SC_DECREASEAGI] || - sc->option&OPTION_MADOGEAR //Adrenaline doesn't affect Mado Gear [Ind] - ) - return 0; - break; - case SC_ADRENALINE2: - if(sd && !pc_check_weapontype(sd,skill_get_weapontype(BS_ADRENALINE2))) - return 0; - if (sc->data[SC_QUAGMIRE] || - sc->data[SC_DECREASEAGI] - ) - return 0; - break; - case SC_MAGNIFICAT: - if( sc->option&OPTION_MADOGEAR ) //Mado is immune to magnificat - return 0; - break; - case SC_ONEHAND: - case SC_MERC_QUICKEN: - case SC_TWOHANDQUICKEN: - if(sc->data[SC_DECREASEAGI]) - return 0; - - case SC_INCREASEAGI: - if(sd && pc_issit(sd)){ - pc_setstand(sd); - } - - case SC_CONCENTRATE: - case SC_SPEARQUICKEN: - case SC_TRUESIGHT: - case SC_WINDWALK: - case SC_CARTBOOST: - case SC_ASSNCROS: - if (sc->data[SC_QUAGMIRE]) - return 0; - if(sc->option&OPTION_MADOGEAR) - return 0;//Mado is immune to increase agi, wind walk, cart boost, etc (others above) [Ind] - break; - case SC_CLOAKING: - //Avoid cloaking with no wall and low skill level. [Skotlex] - //Due to the cloaking card, we have to check the wall versus to known - //skill level rather than the used one. [Skotlex] - //if (sd && val1 < 3 && skill_check_cloaking(bl,NULL)) - if( sd && pc_checkskill(sd, AS_CLOAKING) < 3 && !skill_check_cloaking(bl,NULL) ) - return 0; - break; - case SC_MODECHANGE: - { - int mode; - struct status_data *bstatus = status_get_base_status(bl); - if (!bstatus) return 0; - if (sc->data[type]) - { //Pile up with previous values. - if(!val2) val2 = sc->data[type]->val2; - val3 |= sc->data[type]->val3; - val4 |= sc->data[type]->val4; - } - mode = val2?val2:bstatus->mode; //Base mode - if (val4) mode&=~val4; //Del mode - if (val3) mode|= val3; //Add mode - if (mode == bstatus->mode) { //No change. - if (sc->data[type]) //Abort previous status - return status_change_end(bl, type, INVALID_TIMER); - return 0; - } - } - break; - //Strip skills, need to divest something or it fails. - case SC_STRIPWEAPON: - if (sd && !(flag&4)) { //apply sc anyway if loading saved sc_data - int i; - opt_flag = 0; //Reuse to check success condition. - if(sd->bonus.unstripable_equip&EQP_WEAPON) - return 0; - i = sd->equip_index[EQI_HAND_L]; - if (i>=0 && sd->inventory_data[i] && sd->inventory_data[i]->type == IT_WEAPON) { - opt_flag|=1; - pc_unequipitem(sd,i,3); //L-hand weapon - } - - i = sd->equip_index[EQI_HAND_R]; - if (i>=0 && sd->inventory_data[i] && sd->inventory_data[i]->type == IT_WEAPON) { - opt_flag|=2; - pc_unequipitem(sd,i,3); - } - if (!opt_flag) return 0; - } - if (tick == 1) return 1; //Minimal duration: Only strip without causing the SC - break; - case SC_STRIPSHIELD: - if( val2 == 1 ) val2 = 0; //GX effect. Do not take shield off.. - else - if (sd && !(flag&4)) { - int i; - if(sd->bonus.unstripable_equip&EQP_SHIELD) - return 0; - i = sd->equip_index[EQI_HAND_L]; - if ( i < 0 || !sd->inventory_data[i] || sd->inventory_data[i]->type != IT_ARMOR ) - return 0; - pc_unequipitem(sd,i,3); - } - if (tick == 1) return 1; //Minimal duration: Only strip without causing the SC - break; - case SC_STRIPARMOR: - if (sd && !(flag&4)) { - int i; - if(sd->bonus.unstripable_equip&EQP_ARMOR) - return 0; - i = sd->equip_index[EQI_ARMOR]; - if ( i < 0 || !sd->inventory_data[i] ) - return 0; - pc_unequipitem(sd,i,3); - } - if (tick == 1) return 1; //Minimal duration: Only strip without causing the SC - break; - case SC_STRIPHELM: - if (sd && !(flag&4)) { - int i; - if(sd->bonus.unstripable_equip&EQP_HELM) - return 0; - i = sd->equip_index[EQI_HEAD_TOP]; - if ( i < 0 || !sd->inventory_data[i] ) - return 0; - pc_unequipitem(sd,i,3); - } - if (tick == 1) return 1; //Minimal duration: Only strip without causing the SC - break; - case SC_MERC_FLEEUP: - case SC_MERC_ATKUP: - case SC_MERC_HPUP: - case SC_MERC_SPUP: - case SC_MERC_HITUP: - if( bl->type != BL_MER ) - return 0; // Stats only for Mercenaries - break; - case SC_STRFOOD: - if (sc->data[SC_FOOD_STR_CASH] && sc->data[SC_FOOD_STR_CASH]->val1 > val1) - return 0; - break; - case SC_AGIFOOD: - if (sc->data[SC_FOOD_AGI_CASH] && sc->data[SC_FOOD_AGI_CASH]->val1 > val1) - return 0; - break; - case SC_VITFOOD: - if (sc->data[SC_FOOD_VIT_CASH] && sc->data[SC_FOOD_VIT_CASH]->val1 > val1) - return 0; - break; - case SC_INTFOOD: - if (sc->data[SC_FOOD_INT_CASH] && sc->data[SC_FOOD_INT_CASH]->val1 > val1) - return 0; - break; - case SC_DEXFOOD: - if (sc->data[SC_FOOD_DEX_CASH] && sc->data[SC_FOOD_DEX_CASH]->val1 > val1) - return 0; - break; - case SC_LUKFOOD: - if (sc->data[SC_FOOD_LUK_CASH] && sc->data[SC_FOOD_LUK_CASH]->val1 > val1) - return 0; - break; - case SC_FOOD_STR_CASH: - if (sc->data[SC_STRFOOD] && sc->data[SC_STRFOOD]->val1 > val1) - return 0; - break; - case SC_FOOD_AGI_CASH: - if (sc->data[SC_AGIFOOD] && sc->data[SC_AGIFOOD]->val1 > val1) - return 0; - break; - case SC_FOOD_VIT_CASH: - if (sc->data[SC_VITFOOD] && sc->data[SC_VITFOOD]->val1 > val1) - return 0; - break; - case SC_FOOD_INT_CASH: - if (sc->data[SC_INTFOOD] && sc->data[SC_INTFOOD]->val1 > val1) - return 0; - break; - case SC_FOOD_DEX_CASH: - if (sc->data[SC_DEXFOOD] && sc->data[SC_DEXFOOD]->val1 > val1) - return 0; - break; - case SC_FOOD_LUK_CASH: - if (sc->data[SC_LUKFOOD] && sc->data[SC_LUKFOOD]->val1 > val1) - return 0; - break; - case SC_CAMOUFLAGE: - if( sd && pc_checkskill(sd, RA_CAMOUFLAGE) < 3 && !skill_check_camouflage(bl,NULL) ) - return 0; - break; - case SC__STRIPACCESSORY: - if( sd ) { - int i = -1; - if( !(sd->bonus.unstripable_equip&EQI_ACC_L) ) { - i = sd->equip_index[EQI_ACC_L]; - if( i >= 0 && sd->inventory_data[i] && sd->inventory_data[i]->type == IT_ARMOR ) - pc_unequipitem(sd,i,3); //L-Accessory - } if( !(sd->bonus.unstripable_equip&EQI_ACC_R) ) { - i = sd->equip_index[EQI_ACC_R]; - if( i >= 0 && sd->inventory_data[i] && sd->inventory_data[i]->type == IT_ARMOR ) - pc_unequipitem(sd,i,3); //R-Accessory - } - if( i < 0 ) - return 0; - } - if (tick == 1) return 1; //Minimal duration: Only strip without causing the SC - break; - case SC_TOXIN: - case SC_PARALYSE: - case SC_VENOMBLEED: - case SC_MAGICMUSHROOM: - case SC_DEATHHURT: - case SC_PYREXIA: - case SC_OBLIVIONCURSE: - case SC_LEECHESEND: - { // it doesn't stack or even renewed - int i = SC_TOXIN; - for(; i<= SC_LEECHESEND; i++) - if(sc->data[i]) return 0; - } - break; - case SC_SATURDAYNIGHTFEVER: - if (sc->data[SC_BERSERK] || sc->data[SC_INSPIRATION] || sc->data[SC__BLOODYLUST]) - return 0; - break; - } - - //Check for BOSS resistances - if(status->mode&MD_BOSS && !(flag&1)) { - if (type>=SC_COMMON_MIN && type <= SC_COMMON_MAX) - return 0; - switch (type) { - case SC_BLESSING: - case SC_DECREASEAGI: - case SC_PROVOKE: - case SC_COMA: - case SC_GRAVITATION: - case SC_SUITON: - case SC_RICHMANKIM: - case SC_ROKISWEIL: - case SC_FOGWALL: - case SC_FREEZING: - case SC_BURNING: - case SC_MARSHOFABYSS: - case SC_ADORAMUS: - case SC_PARALYSIS: - case SC_DEEPSLEEP: - case SC_CRYSTALIZE: - - // Exploit prevention - kRO Fix - case SC_PYREXIA: - case SC_DEATHHURT: - case SC_TOXIN: - case SC_PARALYSE: - case SC_VENOMBLEED: - case SC_MAGICMUSHROOM: - case SC_OBLIVIONCURSE: - case SC_LEECHESEND: - - // Ranger Effects - case SC_BITE: - case SC_ELECTRICSHOCKER: - case SC_MAGNETICFIELD: - - return 0; - } - } - - //Before overlapping fail, one must check for status cured. - switch (type) { - case SC_BLESSING: - //TO-DO Blessing and Agi up should do 1 damage against players on Undead Status, even on PvM - //but cannot be plagiarized (this requires aegis investigation on packets and official behavior) [Brainstorm] - if ((!undead_flag && status->race!=RC_DEMON) || bl->type == BL_PC) { - status_change_end(bl, SC_CURSE, INVALID_TIMER); - if (sc->data[SC_STONE] && sc->opt1 == OPT1_STONE) - status_change_end(bl, SC_STONE, INVALID_TIMER); - } - break; - case SC_INCREASEAGI: - status_change_end(bl, SC_DECREASEAGI, INVALID_TIMER); - break; - case SC_QUAGMIRE: - status_change_end(bl, SC_CONCENTRATE, INVALID_TIMER); - status_change_end(bl, SC_TRUESIGHT, INVALID_TIMER); - status_change_end(bl, SC_WINDWALK, INVALID_TIMER); - //Also blocks the ones below... - case SC_DECREASEAGI: - status_change_end(bl, SC_CARTBOOST, INVALID_TIMER); - //Also blocks the ones below... - case SC_DONTFORGETME: - status_change_end(bl, SC_INCREASEAGI, INVALID_TIMER); - status_change_end(bl, SC_ADRENALINE, INVALID_TIMER); - status_change_end(bl, SC_ADRENALINE2, INVALID_TIMER); - status_change_end(bl, SC_SPEARQUICKEN, INVALID_TIMER); - status_change_end(bl, SC_TWOHANDQUICKEN, INVALID_TIMER); - status_change_end(bl, SC_ONEHAND, INVALID_TIMER); - status_change_end(bl, SC_MERC_QUICKEN, INVALID_TIMER); - status_change_end(bl, SC_ACCELERATION, INVALID_TIMER); - break; - case SC_ONEHAND: - //Removes the Aspd potion effect, as reported by Vicious. [Skotlex] - status_change_end(bl, SC_ASPDPOTION0, INVALID_TIMER); - status_change_end(bl, SC_ASPDPOTION1, INVALID_TIMER); - status_change_end(bl, SC_ASPDPOTION2, INVALID_TIMER); - status_change_end(bl, SC_ASPDPOTION3, INVALID_TIMER); - break; - case SC_MAXOVERTHRUST: - //Cancels Normal Overthrust. [Skotlex] - status_change_end(bl, SC_OVERTHRUST, INVALID_TIMER); - break; - case SC_KYRIE: - //Cancels Assumptio - status_change_end(bl, SC_ASSUMPTIO, INVALID_TIMER); - break; - case SC_DELUGE: - if (sc->data[SC_FOGWALL] && sc->data[SC_BLIND]) - status_change_end(bl, SC_BLIND, INVALID_TIMER); - break; - case SC_SILENCE: - if (sc->data[SC_GOSPEL] && sc->data[SC_GOSPEL]->val4 == BCT_SELF) - status_change_end(bl, SC_GOSPEL, INVALID_TIMER); - break; - case SC_HIDING: - status_change_end(bl, SC_CLOSECONFINE, INVALID_TIMER); - status_change_end(bl, SC_CLOSECONFINE2, INVALID_TIMER); - break; - case SC__BLOODYLUST: - case SC_BERSERK: - if(battle_config.berserk_cancels_buffs) { - status_change_end(bl, SC_ONEHAND, INVALID_TIMER); - status_change_end(bl, SC_TWOHANDQUICKEN, INVALID_TIMER); - status_change_end(bl, SC_CONCENTRATION, INVALID_TIMER); - status_change_end(bl, SC_PARRYING, INVALID_TIMER); - status_change_end(bl, SC_AURABLADE, INVALID_TIMER); - status_change_end(bl, SC_MERC_QUICKEN, INVALID_TIMER); - } -#ifdef RENEWAL - else { - status_change_end(bl, SC_TWOHANDQUICKEN, INVALID_TIMER); - } -#endif - break; - case SC_ASSUMPTIO: - status_change_end(bl, SC_KYRIE, INVALID_TIMER); - status_change_end(bl, SC_KAITE, INVALID_TIMER); - break; - case SC_KAITE: - status_change_end(bl, SC_ASSUMPTIO, INVALID_TIMER); - break; - case SC_CARTBOOST: - if(sc->data[SC_DECREASEAGI]) - { //Cancel Decrease Agi, but take no further effect [Skotlex] - status_change_end(bl, SC_DECREASEAGI, INVALID_TIMER); - return 0; - } - break; - case SC_FUSION: - status_change_end(bl, SC_SPIRIT, INVALID_TIMER); - break; - case SC_ADJUSTMENT: - status_change_end(bl, SC_MADNESSCANCEL, INVALID_TIMER); - break; - case SC_MADNESSCANCEL: - status_change_end(bl, SC_ADJUSTMENT, INVALID_TIMER); - break; - //NPC_CHANGEUNDEAD will debuff Blessing and Agi Up - case SC_CHANGEUNDEAD: - status_change_end(bl, SC_BLESSING, INVALID_TIMER); - status_change_end(bl, SC_INCREASEAGI, INVALID_TIMER); - break; - case SC_STRFOOD: - status_change_end(bl, SC_FOOD_STR_CASH, INVALID_TIMER); - break; - case SC_AGIFOOD: - status_change_end(bl, SC_FOOD_AGI_CASH, INVALID_TIMER); - break; - case SC_VITFOOD: - status_change_end(bl, SC_FOOD_VIT_CASH, INVALID_TIMER); - break; - case SC_INTFOOD: - status_change_end(bl, SC_FOOD_INT_CASH, INVALID_TIMER); - break; - case SC_DEXFOOD: - status_change_end(bl, SC_FOOD_DEX_CASH, INVALID_TIMER); - break; - case SC_LUKFOOD: - status_change_end(bl, SC_FOOD_LUK_CASH, INVALID_TIMER); - break; - case SC_FOOD_STR_CASH: - status_change_end(bl, SC_STRFOOD, INVALID_TIMER); - break; - case SC_FOOD_AGI_CASH: - status_change_end(bl, SC_AGIFOOD, INVALID_TIMER); - break; - case SC_FOOD_VIT_CASH: - status_change_end(bl, SC_VITFOOD, INVALID_TIMER); - break; - case SC_FOOD_INT_CASH: - status_change_end(bl, SC_INTFOOD, INVALID_TIMER); - break; - case SC_FOOD_DEX_CASH: - status_change_end(bl, SC_DEXFOOD, INVALID_TIMER); - break; - case SC_FOOD_LUK_CASH: - status_change_end(bl, SC_LUKFOOD, INVALID_TIMER); - break; - case SC_FIGHTINGSPIRIT: - status_change_end(bl, type, INVALID_TIMER); // Remove previous one. - break; - case SC_MARSHOFABYSS: - status_change_end(bl, SC_INCAGI, INVALID_TIMER); - status_change_end(bl, SC_WINDWALK, INVALID_TIMER); - status_change_end(bl, SC_ASPDPOTION0, INVALID_TIMER); - status_change_end(bl, SC_ASPDPOTION1, INVALID_TIMER); - status_change_end(bl, SC_ASPDPOTION2, INVALID_TIMER); - status_change_end(bl, SC_ASPDPOTION3, INVALID_TIMER); - break; - case SC_SWINGDANCE: - case SC_SYMPHONYOFLOVER: - case SC_MOONLITSERENADE: - case SC_RUSHWINDMILL: - case SC_ECHOSONG: - case SC_HARMONIZE: //group A doesn't overlap - if (type != SC_SWINGDANCE) status_change_end(bl, SC_SWINGDANCE, INVALID_TIMER); - if (type != SC_SYMPHONYOFLOVER) status_change_end(bl, SC_SYMPHONYOFLOVER, INVALID_TIMER); - if (type != SC_MOONLITSERENADE) status_change_end(bl, SC_MOONLITSERENADE, INVALID_TIMER); - if (type != SC_RUSHWINDMILL) status_change_end(bl, SC_RUSHWINDMILL, INVALID_TIMER); - if (type != SC_ECHOSONG) status_change_end(bl, SC_ECHOSONG, INVALID_TIMER); - if (type != SC_HARMONIZE) status_change_end(bl, SC_HARMONIZE, INVALID_TIMER); - break; - case SC_VOICEOFSIREN: - case SC_DEEPSLEEP: - case SC_GLOOMYDAY: - case SC_SONGOFMANA: - case SC_DANCEWITHWUG: - case SC_SATURDAYNIGHTFEVER: - case SC_LERADSDEW: - case SC_MELODYOFSINK: - case SC_BEYONDOFWARCRY: - case SC_UNLIMITEDHUMMINGVOICE: //group B - if (type != SC_VOICEOFSIREN) status_change_end(bl, SC_VOICEOFSIREN, INVALID_TIMER); - if (type != SC_DEEPSLEEP) status_change_end(bl, SC_DEEPSLEEP, INVALID_TIMER); - if (type != SC_LERADSDEW) status_change_end(bl, SC_LERADSDEW, INVALID_TIMER); - if (type != SC_MELODYOFSINK) status_change_end(bl, SC_MELODYOFSINK, INVALID_TIMER); - if (type != SC_BEYONDOFWARCRY) status_change_end(bl, SC_BEYONDOFWARCRY, INVALID_TIMER); - if (type != SC_UNLIMITEDHUMMINGVOICE) status_change_end(bl, SC_UNLIMITEDHUMMINGVOICE, INVALID_TIMER); - if (type != SC_GLOOMYDAY) { - status_change_end(bl, SC_GLOOMYDAY, INVALID_TIMER); - status_change_end(bl, SC_GLOOMYDAY_SK, INVALID_TIMER); - } - if (type != SC_SONGOFMANA) status_change_end(bl, SC_SONGOFMANA, INVALID_TIMER); - if (type != SC_DANCEWITHWUG) status_change_end(bl, SC_DANCEWITHWUG, INVALID_TIMER); - if (type != SC_SATURDAYNIGHTFEVER) { - if (sc->data[SC_SATURDAYNIGHTFEVER]) { - sc->data[SC_SATURDAYNIGHTFEVER]->val2 = 0; //mark to not lose hp - status_change_end(bl, SC_SATURDAYNIGHTFEVER, INVALID_TIMER); - } - } - break; - case SC_REFLECTSHIELD: - status_change_end(bl, SC_REFLECTDAMAGE, INVALID_TIMER); - break; - case SC_REFLECTDAMAGE: - status_change_end(bl, SC_REFLECTSHIELD, INVALID_TIMER); - break; - case SC_SHIELDSPELL_DEF: - case SC_SHIELDSPELL_MDEF: - case SC_SHIELDSPELL_REF: - status_change_end(bl, SC_MAGNIFICAT, INVALID_TIMER); - if( type != SC_SHIELDSPELL_DEF ) - status_change_end(bl, SC_SHIELDSPELL_DEF, INVALID_TIMER); - if( type != SC_SHIELDSPELL_MDEF ) - status_change_end(bl, SC_SHIELDSPELL_MDEF, INVALID_TIMER); - if( type != SC_SHIELDSPELL_REF ) - status_change_end(bl, SC_SHIELDSPELL_REF, INVALID_TIMER); - break; - case SC_GT_ENERGYGAIN: - case SC_GT_CHANGE: - case SC_GT_REVITALIZE: - if( type != SC_GT_REVITALIZE ) - status_change_end(bl, SC_GT_REVITALIZE, INVALID_TIMER); - if( type != SC_GT_ENERGYGAIN ) - status_change_end(bl, SC_GT_ENERGYGAIN, INVALID_TIMER); - if( type != SC_GT_CHANGE ) - status_change_end(bl, SC_GT_CHANGE, INVALID_TIMER); - break; - case SC_INVINCIBLE: - status_change_end(bl, SC_INVINCIBLEOFF, INVALID_TIMER); - break; - case SC_INVINCIBLEOFF: - status_change_end(bl, SC_INVINCIBLE, INVALID_TIMER); - break; - case SC_MAGICPOWER: - status_change_end(bl, type, INVALID_TIMER); - break; - } - - //Check for overlapping fails - if( (sce = sc->data[type]) ) { - switch( type ) { - case SC_MERC_FLEEUP: - case SC_MERC_ATKUP: - case SC_MERC_HPUP: - case SC_MERC_SPUP: - case SC_MERC_HITUP: - if( sce->val1 > val1 ) - val1 = sce->val1; - break; - case SC_ADRENALINE: - case SC_ADRENALINE2: - case SC_WEAPONPERFECTION: - case SC_OVERTHRUST: - if (sce->val2 > val2) - return 0; - break; - case SC_S_LIFEPOTION: - case SC_L_LIFEPOTION: - case SC_BOSSMAPINFO: - case SC_STUN: - case SC_SLEEP: - case SC_POISON: - case SC_CURSE: - case SC_SILENCE: - case SC_CONFUSION: - case SC_BLIND: - case SC_BLEEDING: - case SC_DPOISON: - case SC_CLOSECONFINE2: //Can't be re-closed in. - case SC_MARIONETTE: - case SC_MARIONETTE2: - case SC_NOCHAT: - case SC_CHANGE: //Otherwise your Hp/Sp would get refilled while still within effect of the last invocation. - case SC__INVISIBILITY: - case SC__ENERVATION: - case SC__GROOMY: - case SC__IGNORANCE: - case SC__LAZINESS: - case SC__WEAKNESS: - case SC__UNLUCKY: - return 0; - case SC_COMBO: - case SC_DANCING: - case SC_DEVOTION: - case SC_ASPDPOTION0: - case SC_ASPDPOTION1: - case SC_ASPDPOTION2: - case SC_ASPDPOTION3: - case SC_ATKPOTION: - case SC_MATKPOTION: - case SC_ENCHANTARMS: - case SC_ARMOR_ELEMENT: - case SC_ARMOR_RESIST: - break; - case SC_GOSPEL: - //Must not override a casting gospel char. - if(sce->val4 == BCT_SELF) - return 0; - if(sce->val1 > val1) - return 1; - break; - case SC_ENDURE: - if(sce->val4 && !val4) - return 1; //Don't let you override infinite endure. - if(sce->val1 > val1) - return 1; - break; - case SC_KAAHI: - //Kaahi overwrites previous level regardless of existing level. - //Delete timer if it exists. - if (sce->val4 != INVALID_TIMER) { - delete_timer(sce->val4,kaahi_heal_timer); - sce->val4 = INVALID_TIMER; - } - break; - case SC_JAILED: - //When a player is already jailed, do not edit the jail data. - val2 = sce->val2; - val3 = sce->val3; - val4 = sce->val4; - break; - case SC_LERADSDEW: - if (sc && (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST])) - return 0; - case SC_SHAPESHIFT: - case SC_PROPERTYWALK: - break; - case SC_LEADERSHIP: - case SC_GLORYWOUNDS: - case SC_SOULCOLD: - case SC_HAWKEYES: - if( sce->val4 && !val4 )//you cannot override master guild aura - return 0; - break; - case SC_JOINTBEAT: - val2 |= sce->val2; // stackable ailments - default: - if(sce->val1 > val1) - return 1; //Return true to not mess up skill animations. [Skotlex] - } - } - - vd = status_get_viewdata(bl); - calc_flag = StatusChangeFlagTable[type]; - if(!(flag&4)) //&4 - Do not parse val settings when loading SCs - switch(type) - { - case SC_DECREASEAGI: - case SC_INCREASEAGI: - val2 = 2 + val1; //Agi change - break; - case SC_ENDURE: - val2 = 7; // Hit-count [Celest] - if( !(flag&1) && (bl->type&(BL_PC|BL_MER)) && !map_flag_gvg(bl->m) && !map[bl->m].flag.battleground && !val4 ) - { - struct map_session_data *tsd; - if( sd ) - { - int i; - for( i = 0; i < 5; i++ ) - { - if( sd->devotion[i] && (tsd = map_id2sd(sd->devotion[i])) ) - status_change_start(&tsd->bl, type, 10000, val1, val2, val3, val4, tick, 1); - } - } - else if( bl->type == BL_MER && ((TBL_MER*)bl)->devotion_flag && (tsd = ((TBL_MER*)bl)->master) ) - status_change_start(&tsd->bl, type, 10000, val1, val2, val3, val4, tick, 1); - } - //val4 signals infinite endure (if val4 == 2 it is infinite endure from Berserk) - if( val4 ) - tick = -1; - break; - case SC_AUTOBERSERK: - if (status->hp < status->max_hp>>2 && - (!sc->data[SC_PROVOKE] || sc->data[SC_PROVOKE]->val2==0)) - sc_start4(bl,SC_PROVOKE,100,10,1,0,0,60000); - tick = -1; - break; - case SC_SIGNUMCRUCIS: - val2 = 10 + 4*val1; //Def reduction - tick = -1; - clif_emotion(bl,E_SWT); - break; - case SC_MAXIMIZEPOWER: - tick_time = val2 = tick>0?tick:60000; - tick = -1; // duration sent to the client should be infinite - break; - case SC_EDP: // [Celest] - val2 = val1 + 2; //Chance to Poison enemies. -#ifndef RENEWAL_EDP - val3 = 50*(val1+1); //Damage increase (+50 +50*lv%) -#endif - if( sd )//[Ind] - iROwiki says each level increases its duration by 3 seconds - tick += pc_checkskill(sd,GC_RESEARCHNEWPOISON)*3000; - break; - case SC_POISONREACT: - val2=(val1+1)/2 + val1/10; // Number of counters [Skotlex] - val3=50; // + 5*val1; //Chance to counter. [Skotlex] - break; - case SC_MAGICROD: - val2 = val1*20; //SP gained - break; - case SC_KYRIE: - val2 = (int64)status->max_hp * (val1 * 2 + 10) / 100; //%Max HP to absorb - val3 = (val1 / 2 + 5); //Hits - break; - case SC_MAGICPOWER: - //val1: Skill lv - val2 = 1; //Lasts 1 invocation - val3 = 5*val1; //Matk% increase - val4 = 0; // 0 = ready to be used, 1 = activated and running - break; - case SC_SACRIFICE: - val2 = 5; //Lasts 5 hits - tick = -1; - break; - case SC_ENCPOISON: - val2= 250+50*val1; //Poisoning Chance (2.5+0.5%) in 1/10000 rate - case SC_ASPERSIO: - case SC_FIREWEAPON: - case SC_WATERWEAPON: - case SC_WINDWEAPON: - case SC_EARTHWEAPON: - case SC_SHADOWWEAPON: - case SC_GHOSTWEAPON: - skill_enchant_elemental_end(bl,type); - break; - case SC_ELEMENTALCHANGE: - // val1 : Element Lvl (if called by skill lvl 1, takes random value between 1 and 4) - // val2 : Element (When no element, random one is picked) - // val3 : 0 = called by skill 1 = called by script (fixed level) - if( !val2 ) val2 = rnd()%ELE_MAX; - - if( val1 == 1 && val3 == 0 ) - val1 = 1 + rnd()%4; - else if( val1 > 4 ) - val1 = 4; // Max Level - val3 = 0; // Not need to keep this info. - break; - case SC_PROVIDENCE: - val2=val1*5; //Race/Ele resist - break; - case SC_REFLECTSHIELD: - val2=10+val1*3; // %Dmg reflected - if( !(flag&1) && (bl->type&(BL_PC|BL_MER)) ) - { - struct map_session_data *tsd; - if( sd ) - { - int i; - for( i = 0; i < 5; i++ ) - { - if( sd->devotion[i] && (tsd = map_id2sd(sd->devotion[i])) ) - status_change_start(&tsd->bl, type, 10000, val1, val2, 0, 0, tick, 1); - } - } - else if( bl->type == BL_MER && ((TBL_MER*)bl)->devotion_flag && (tsd = ((TBL_MER*)bl)->master) ) - status_change_start(&tsd->bl, type, 10000, val1, val2, 0, 0, tick, 1); - } - break; - case SC_STRIPWEAPON: - if (!sd) //Watk reduction - val2 = 25; - break; - case SC_STRIPSHIELD: - if (!sd) //Def reduction - val2 = 15; - break; - case SC_STRIPARMOR: - if (!sd) //Vit reduction - val2 = 40; - break; - case SC_STRIPHELM: - if (!sd) //Int reduction - val2 = 40; - break; - case SC_AUTOSPELL: - //Val1 Skill LV of Autospell - //Val2 Skill ID to cast - //Val3 Max Lv to cast - val4 = 5 + val1*2; //Chance of casting - break; - case SC_VOLCANO: - val2 = val1*10; //Watk increase -#ifndef RENEWAL - if (status->def_ele != ELE_FIRE) - val2 = 0; -#endif - break; - case SC_VIOLENTGALE: - val2 = val1*3; //Flee increase - #ifndef RENEWAL - if (status->def_ele != ELE_WIND) - val2 = 0; - #endif - break; - case SC_DELUGE: - val2 = deluge_eff[val1-1]; //HP increase -#ifndef RENEWAL - if(status->def_ele != ELE_WATER) - val2 = 0; -#endif - break; - case SC_SUITON: - if (!val2 || (sd && (sd->class_&MAPID_UPPERMASK) == MAPID_NINJA)) { - //No penalties. - val2 = 0; //Agi penalty - val3 = 0; //Walk speed penalty - break; - } - val3 = 50; - val2 = 3*((val1+1)/3); - if (val1 > 4) val2--; - break; - case SC_ONEHAND: - case SC_TWOHANDQUICKEN: - val2 = 300; - if (val1 > 10) //For boss casted skills [Skotlex] - val2 += 20*(val1-10); - break; - case SC_MERC_QUICKEN: - val2 = 300; - break; -#ifndef RENEWAL - case SC_SPEARQUICKEN: - val2 = 200+10*val1; - break; -#endif - case SC_DANCING: - //val1 : Skill ID + LV - //val2 : Skill Group of the Dance. - //val3 : Brings the skill_lv (merged into val1 here) - //val4 : Partner - if (val1 == CG_MOONLIT) - clif_status_change(bl,SI_MOONLIT,1,tick,0, 0, 0); - val1|= (val3<<16); - val3 = tick/1000; //Tick duration - tick_time = 1000; // [GodLesZ] tick time - break; - case SC_LONGING: - val2 = 500-100*val1; //Aspd penalty. - break; - case SC_EXPLOSIONSPIRITS: - val2 = 75 + 25*val1; //Cri bonus - break; - - case SC_ASPDPOTION0: - case SC_ASPDPOTION1: - case SC_ASPDPOTION2: - case SC_ASPDPOTION3: - val2 = 50*(2+type-SC_ASPDPOTION0); - break; - - case SC_WEDDING: - case SC_XMAS: - case SC_SUMMER: - if (!vd) return 0; - //Store previous values as they could be removed. - val1 = vd->class_; - val2 = vd->weapon; - val3 = vd->shield; - val4 = vd->cloth_color; - unit_stop_attack(bl); - clif_changelook(bl,LOOK_WEAPON,0); - clif_changelook(bl,LOOK_SHIELD,0); - clif_changelook(bl,LOOK_BASE,type==SC_WEDDING?JOB_WEDDING:type==SC_XMAS?JOB_XMAS:JOB_SUMMER); - clif_changelook(bl,LOOK_CLOTHES_COLOR,vd->cloth_color); - break; - case SC_NOCHAT: - // [GodLesZ] FIXME: is this correct? a hardcoded interval of 60sec? what about configuration ?_? - tick = 60000; - val1 = battle_config.manner_system; //Mute filters. - if (sd) - { - clif_changestatus(sd,SP_MANNER,sd->status.manner); - clif_updatestatus(sd,SP_MANNER); - } - break; - - case SC_STONE: - val3 = tick/1000; //Petrified HP-damage iterations. - if(val3 < 1) val3 = 1; - tick = val4; //Petrifying time. - tick = max(tick, 1000); //Min time - calc_flag = 0; //Actual status changes take effect on petrified state. - break; - - case SC_DPOISON: - //Lose 10/15% of your life as long as it doesn't brings life below 25% - if (status->hp > status->max_hp>>2) { - int diff = status->max_hp*(bl->type==BL_PC?10:15)/100; - if (status->hp - diff < status->max_hp>>2) - diff = status->hp - (status->max_hp>>2); - if( val2 && bl->type == BL_MOB ) { - struct block_list* src = map_id2bl(val2); - if( src ) - mob_log_damage((TBL_MOB*)bl,src,diff); - } - status_zap(bl, diff, 0); - } - // fall through - case SC_POISON: - val3 = tick/1000; //Damage iterations - if(val3 < 1) val3 = 1; - tick_time = 1000; // [GodLesZ] tick time - //val4: HP damage - if (bl->type == BL_PC) - val4 = (type == SC_DPOISON) ? 3 + status->max_hp/50 : 3 + status->max_hp*3/200; - else - val4 = (type == SC_DPOISON) ? 3 + status->max_hp/100 : 3 + status->max_hp/200; - - break; - case SC_CONFUSION: - clif_emotion(bl,E_WHAT); - break; - case SC_BLEEDING: - val4 = tick/10000; - if (!val4) val4 = 1; - tick_time = 10000; // [GodLesZ] tick time - break; - case SC_S_LIFEPOTION: - case SC_L_LIFEPOTION: - if( val1 == 0 ) return 0; - // val1 = heal percent/amout - // val2 = seconds between heals - // val4 = total of heals - if( val2 < 1 ) val2 = 1; - if( (val4 = tick/(val2 * 1000)) < 1 ) - val4 = 1; - tick_time = val2 * 1000; // [GodLesZ] tick time - break; - case SC_BOSSMAPINFO: - if( sd != NULL ) - { - struct mob_data *boss_md = map_getmob_boss(bl->m); // Search for Boss on this Map - if( boss_md == NULL || boss_md->bl.prev == NULL ) - { // No MVP on this map - MVP is dead - clif_bossmapinfo(sd->fd, boss_md, 1); - return 0; // No need to start SC - } - val1 = boss_md->bl.id; - if( (val4 = tick/1000) < 1 ) - val4 = 1; - tick_time = 1000; // [GodLesZ] tick time - } - break; - case SC_HIDING: - val2 = tick/1000; - tick_time = 1000; // [GodLesZ] tick time - val3 = 0; // unused, previously speed adjustment - val4 = val1+3; //Seconds before SP substraction happen. - break; - case SC_CHASEWALK: - val2 = tick>0?tick:10000; //Interval at which SP is drained. - val3 = 35 - 5 * val1; //Speed adjustment. - if (sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_ROGUE) - val3 -= 40; - val4 = 10+val1*2; //SP cost. - if (map_flag_gvg(bl->m) || map[bl->m].flag.battleground) val4 *= 5; - break; - case SC_CLOAKING: - if (!sd) //Monsters should be able to walk with no penalties. [Skotlex] - val1 = 10; - tick_time = val2 = tick>0?tick:60000; //SP consumption rate. - tick = -1; // duration sent to the client should be infinite - val3 = 0; // unused, previously walk speed adjustment - //val4&1 signals the presence of a wall. - //val4&2 makes cloak not end on normal attacks [Skotlex] - //val4&4 makes cloak not end on using skills - if (bl->type == BL_PC || (bl->type == BL_MOB && ((TBL_MOB*)bl)->special_state.clone) ) //Standard cloaking. - val4 |= battle_config.pc_cloak_check_type&7; - else - val4 |= battle_config.monster_cloak_check_type&7; - break; - case SC_SIGHT: /* splash status */ - case SC_RUWACH: - case SC_SIGHTBLASTER: - val3 = skill_get_splash(val2, val1); //Val2 should bring the skill-id. - val2 = tick/250; - tick_time = 10; // [GodLesZ] tick time - break; - - //Permanent effects. - case SC_AETERNA: - case SC_MODECHANGE: - case SC_WEIGHT50: - case SC_WEIGHT90: - case SC_BROKENWEAPON: - case SC_BROKENARMOR: - case SC_READYSTORM: - case SC_READYDOWN: - case SC_READYCOUNTER: - case SC_READYTURN: - case SC_DODGE: - case SC_PUSH_CART: - tick = -1; - break; - - case SC_AUTOGUARD: - if( !(flag&1) ) - { - struct map_session_data *tsd; - int i,t; - for( i = val2 = 0; i < val1; i++) - { - t = 5-(i>>1); - val2 += (t < 0)? 1:t; - } - - if( bl->type&(BL_PC|BL_MER) ) - { - if( sd ) - { - for( i = 0; i < 5; i++ ) - { - if( sd->devotion[i] && (tsd = map_id2sd(sd->devotion[i])) ) - status_change_start(&tsd->bl, type, 10000, val1, val2, 0, 0, tick, 1); - } - } - else if( bl->type == BL_MER && ((TBL_MER*)bl)->devotion_flag && (tsd = ((TBL_MER*)bl)->master) ) - status_change_start(&tsd->bl, type, 10000, val1, val2, 0, 0, tick, 1); - } - } - break; - - case SC_DEFENDER: - if (!(flag&1)) - { - val2 = 5 + 15*val1; //Damage reduction - val3 = 0; // unused, previously speed adjustment - val4 = 250 - 50*val1; //Aspd adjustment - - if (sd) - { - struct map_session_data *tsd; - int i; - for (i = 0; i < 5; i++) - { //See if there are devoted characters, and pass the status to them. [Skotlex] - if (sd->devotion[i] && (tsd = map_id2sd(sd->devotion[i]))) - status_change_start(&tsd->bl,type,10000,val1,5+val1*5,val3,val4,tick,1); - } - } - } - break; - - case SC_TENSIONRELAX: - if (sd) { - pc_setsit(sd); - clif_sitting(&sd->bl); - } - val2 = 12; //SP cost - val4 = 10000; //Decrease at 10secs intervals. - val3 = tick/val4; - tick = -1; // duration sent to the client should be infinite - tick_time = val4; // [GodLesZ] tick time - break; - case SC_PARRYING: - val2 = 20 + val1*3; //Block Chance - break; - - case SC_WINDWALK: - val2 = (val1+1)/2; // Flee bonus is 1/1/2/2/3/3/4/4/5/5 - break; - - case SC_JOINTBEAT: - if( val2&BREAK_NECK ) - sc_start(bl,SC_BLEEDING,100,val1,skill_get_time2(status_sc2skill(type),val1)); - break; - - case SC_BERSERK: - if (!sc->data[SC_ENDURE] || !sc->data[SC_ENDURE]->val4) - sc_start4(bl, SC_ENDURE, 100,10,0,0,2, tick); - case SC__BLOODYLUST: - //HP healing is performing after the calc_status call. - //Val2 holds HP penalty - if (!val4) val4 = skill_get_time2(status_sc2skill(type),val1); - if (!val4) val4 = 10000; //Val4 holds damage interval - val3 = tick/val4; //val3 holds skill duration - tick_time = val4; // [GodLesZ] tick time - break; - - case SC_GOSPEL: - if(val4 == BCT_SELF) { // self effect - val2 = tick/10000; - tick_time = 10000; // [GodLesZ] tick time - status_change_clear_buffs(bl,3); //Remove buffs/debuffs - } - break; - - case SC_MARIONETTE: - { - int stat; - - val3 = 0; - val4 = 0; - stat = ( sd ? sd->status.str : status_get_base_status(bl)->str ) / 2; val3 |= cap_value(stat,0,0xFF)<<16; - stat = ( sd ? sd->status.agi : status_get_base_status(bl)->agi ) / 2; val3 |= cap_value(stat,0,0xFF)<<8; - stat = ( sd ? sd->status.vit : status_get_base_status(bl)->vit ) / 2; val3 |= cap_value(stat,0,0xFF); - stat = ( sd ? sd->status.int_: status_get_base_status(bl)->int_) / 2; val4 |= cap_value(stat,0,0xFF)<<16; - stat = ( sd ? sd->status.dex : status_get_base_status(bl)->dex ) / 2; val4 |= cap_value(stat,0,0xFF)<<8; - stat = ( sd ? sd->status.luk : status_get_base_status(bl)->luk ) / 2; val4 |= cap_value(stat,0,0xFF); - break; - } - case SC_MARIONETTE2: - { - int stat,max_stat; - // fetch caster information - struct block_list *pbl = map_id2bl(val1); - struct status_change *psc = pbl?status_get_sc(pbl):NULL; - struct status_change_entry *psce = psc?psc->data[SC_MARIONETTE]:NULL; - // fetch target's stats - struct status_data* status = status_get_status_data(bl); // battle status - - if (!psce) - return 0; - - val3 = 0; - val4 = 0; - max_stat = battle_config.max_parameter; //Cap to 99 (default) - stat = (psce->val3 >>16)&0xFF; stat = min(stat, max_stat - status->str ); val3 |= cap_value(stat,0,0xFF)<<16; - stat = (psce->val3 >> 8)&0xFF; stat = min(stat, max_stat - status->agi ); val3 |= cap_value(stat,0,0xFF)<<8; - stat = (psce->val3 >> 0)&0xFF; stat = min(stat, max_stat - status->vit ); val3 |= cap_value(stat,0,0xFF); - stat = (psce->val4 >>16)&0xFF; stat = min(stat, max_stat - status->int_); val4 |= cap_value(stat,0,0xFF)<<16; - stat = (psce->val4 >> 8)&0xFF; stat = min(stat, max_stat - status->dex ); val4 |= cap_value(stat,0,0xFF)<<8; - stat = (psce->val4 >> 0)&0xFF; stat = min(stat, max_stat - status->luk ); val4 |= cap_value(stat,0,0xFF); - break; - } - case SC_REJECTSWORD: - val2 = 15*val1; //Reflect chance - val3 = 3; //Reflections - tick = -1; - break; - - case SC_MEMORIZE: - val2 = 5; //Memorized casts. - tick = -1; - break; - - case SC_GRAVITATION: - val2 = 50*val1; //aspd reduction - break; - - case SC_REGENERATION: - if (val1 == 1) - val2 = 2; - else - val2 = val1; //HP Regerenation rate: 200% 200% 300% - val3 = val1; //SP Regeneration Rate: 100% 200% 300% - //if val4 comes set, this blocks regen rather than increase it. - break; - - case SC_DEVOTION: - { - struct block_list *d_bl; - struct status_change *d_sc; - - if( (d_bl = map_id2bl(val1)) && (d_sc = status_get_sc(d_bl)) && d_sc->count ) - { // Inherits Status From Source - const enum sc_type types[] = { SC_AUTOGUARD, SC_DEFENDER, SC_REFLECTSHIELD, SC_ENDURE }; - enum sc_type type2; - int i = (map_flag_gvg(bl->m) || map[bl->m].flag.battleground)?2:3; - while( i >= 0 ) - { - type2 = types[i]; - if( d_sc->data[type2] ) - sc_start(bl, type2, 100, d_sc->data[type2]->val1, skill_get_time(status_sc2skill(type2),d_sc->data[type2]->val1)); - i--; - } - } - break; - } - - case SC_COMA: //Coma. Sends a char to 1HP. If val2, do not zap sp - if( val3 && bl->type == BL_MOB ) { - struct block_list* src = map_id2bl(val3); - if( src ) - mob_log_damage((TBL_MOB*)bl,src,status->hp - 1); - } - status_zap(bl, status->hp-1, val2?0:status->sp); - return 1; - break; - case SC_CLOSECONFINE2: - { - struct block_list *src = val2?map_id2bl(val2):NULL; - struct status_change *sc2 = src?status_get_sc(src):NULL; - struct status_change_entry *sce2 = sc2?sc2->data[SC_CLOSECONFINE]:NULL; - if (src && sc2) { - if (!sce2) //Start lock on caster. - sc_start4(src,SC_CLOSECONFINE,100,val1,1,0,0,tick+1000); - else { //Increase count of locked enemies and refresh time. - (sce2->val2)++; - delete_timer(sce2->timer, status_change_timer); - sce2->timer = add_timer(gettick()+tick+1000, status_change_timer, src->id, SC_CLOSECONFINE); - } - } else //Status failed. - return 0; - } - break; - case SC_KAITE: - val2 = 1+val1/5; //Number of bounces: 1 + skill_lv/5 - break; - case SC_KAUPE: - switch (val1) { - case 3: //33*3 + 1 -> 100% - val2++; - case 1: - case 2: //33, 66% - val2 += 33*val1; - val3 = 1; //Dodge 1 attack total. - break; - default: //Custom. For high level mob usage, higher level means more blocks. [Skotlex] - val2 = 100; - val3 = val1-2; - break; - } - break; - - case SC_COMBO: { - //val1: Skill ID - //val2: When given, target (for autotargetting skills) - //val3: When set, this combo time should NOT delay attack/movement - //val3: TK: Last used kick - //val4: TK: Combo time - struct unit_data *ud = unit_bl2ud(bl); - if (ud && !val3) { - tick += 300 * battle_config.combo_delay_rate/100; - ud->attackabletime = gettick()+tick; - unit_set_walkdelay(bl, gettick(), tick, 1); - } - val3 = 0; - val4 = tick; - } - break; - case SC_EARTHSCROLL: - val2 = 11-val1; //Chance to consume: 11-skill_lv% - break; - case SC_RUN: - val4 = gettick(); //Store time at which you started running. - tick = -1; - break; - case SC_KAAHI: - val2 = 200*val1; //HP heal - val3 = 5*val1; //SP cost - val4 = INVALID_TIMER; //Kaahi Timer. - break; - case SC_BLESSING: - if ((!undead_flag && status->race!=RC_DEMON) || bl->type == BL_PC) - val2 = val1; - else - val2 = 0; //0 -> Half stat. - break; - case SC_TRICKDEAD: - if (vd) vd->dead_sit = 1; - tick = -1; - break; - case SC_CONCENTRATE: - val2 = 2 + val1; - if (sd) { //Store the card-bonus data that should not count in the % - val3 = sd->param_bonus[1]; //Agi - val4 = sd->param_bonus[4]; //Dex - } else { - val3 = val4 = 0; - } - break; - case SC_MAXOVERTHRUST: - val2 = 20*val1; //Power increase - break; - case SC_OVERTHRUST: - //val2 holds if it was casted on self, or is bonus received from others - val3 = 5*val1; //Power increase - if(sd && pc_checkskill(sd,BS_HILTBINDING)>0) - tick += tick / 10; - break; - case SC_ADRENALINE2: - case SC_ADRENALINE: - val3 = (val2) ? 300 : 200; // aspd increase - case SC_WEAPONPERFECTION: - if(sd && pc_checkskill(sd,BS_HILTBINDING)>0) - tick += tick / 10; - break; - case SC_CONCENTRATION: - val2 = 5*val1; //Batk/Watk Increase - val3 = 10*val1; //Hit Increase - val4 = 5*val1; //Def reduction - break; - case SC_ANGELUS: - val2 = 5*val1; //def increase - break; - case SC_IMPOSITIO: - val2 = 5*val1; //watk increase - break; - case SC_MELTDOWN: - val2 = 100*val1; //Chance to break weapon - val3 = 70*val1; //Change to break armor - break; - case SC_TRUESIGHT: - val2 = 10*val1; //Critical increase - val3 = 3*val1; //Hit increase - break; - case SC_SUN_COMFORT: - val2 = (status_get_lv(bl) + status->dex + status->luk)/2; //def increase - break; - case SC_MOON_COMFORT: - val2 = (status_get_lv(bl) + status->dex + status->luk)/10; //flee increase - break; - case SC_STAR_COMFORT: - val2 = (status_get_lv(bl) + status->dex + status->luk); //Aspd increase - break; - case SC_QUAGMIRE: - val2 = (sd?5:10)*val1; //Agi/Dex decrease. - break; - - // gs_something1 [Vicious] - case SC_GATLINGFEVER: - val2 = 20*val1; //Aspd increase - val3 = 20+10*val1; //Batk increase - val4 = 5*val1; //Flee decrease - break; - - case SC_FLING: - if (bl->type == BL_PC) - val2 = 0; //No armor reduction to players. - else - val2 = 5*val1; //Def reduction - val3 = 5*val1; //Def2 reduction - break; - case SC_PROVOKE: - //val2 signals autoprovoke. - val3 = 2+3*val1; //Atk increase - val4 = 5+5*val1; //Def reduction. - break; - case SC_AVOID: - //val2 = 10*val1; //Speed change rate. - break; - case SC_DEFENCE: - val2 = 2*val1; //Def bonus - break; - case SC_BLOODLUST: - val2 = 20+10*val1; //Atk rate change. - val3 = 3*val1; //Leech chance - val4 = 20; //Leech percent - break; - case SC_FLEET: - val2 = 30*val1; //Aspd change - val3 = 5+5*val1; //bAtk/wAtk rate change - break; - case SC_MINDBREAKER: - val2 = 20*val1; //matk increase. - val3 = 12*val1; //mdef2 reduction. - break; - case SC_SKA: - val2 = tick/1000; - val3 = rnd()%100; //Def changes randomly every second... - tick_time = 1000; // [GodLesZ] tick time - break; - case SC_JAILED: - //Val1 is duration in minutes. Use INT_MAX to specify 'unlimited' time. - tick = val1>0?1000:250; - if (sd) - { - if (sd->mapindex != val2) - { - int pos = (bl->x&0xFFFF)|(bl->y<<16), //Current Coordinates - map = sd->mapindex; //Current Map - //1. Place in Jail (val2 -> Jail Map, val3 -> x, val4 -> y - pc_setpos(sd,(unsigned short)val2,val3,val4, CLR_TELEPORT); - //2. Set restore point (val3 -> return map, val4 return coords - val3 = map; - val4 = pos; - } else if (!val3 || val3 == sd->mapindex) { //Use save point. - val3 = sd->status.save_point.map; - val4 = (sd->status.save_point.x&0xFFFF) - |(sd->status.save_point.y<<16); - } - } - break; - case SC_UTSUSEMI: - val2=(val1+1)/2; // number of hits blocked - val3=skill_get_blewcount(NJ_UTSUSEMI, val1); //knockback value. - break; - case SC_BUNSINJYUTSU: - val2=(val1+1)/2; // number of hits blocked - break; - case SC_CHANGE: - val2= 30*val1; //Vit increase - val3= 20*val1; //Int increase - break; - case SC_SWOO: - if(status->mode&MD_BOSS) - tick /= 5; //TODO: Reduce skill's duration. But for how long? - break; - case SC_SPIDERWEB: - if( bl->type == BL_PC ) - tick /= 2; - break; - case SC_ARMOR: - //NPC_DEFENDER: - val2 = 80; //Damage reduction - //Attack requirements to be blocked: - val3 = BF_LONG; //Range - val4 = BF_WEAPON|BF_MISC; //Type - break; - case SC_ENCHANTARMS: - //end previous enchants - skill_enchant_elemental_end(bl,type); - //Make sure the received element is valid. - if (val2 >= ELE_MAX) - val2 = val2%ELE_MAX; - else if (val2 < 0) - val2 = rnd()%ELE_MAX; - break; - case SC_CRITICALWOUND: - val2 = 20*val1; //Heal effectiveness decrease - break; - case SC_MAGICMIRROR: - case SC_SLOWCAST: - val2 = 20*val1; //Magic reflection/cast rate - break; - - case SC_ARMORCHANGE: - if (val2 == NPC_ANTIMAGIC) - { //Boost mdef - val2 =-20; - val3 = 20; - } else { //Boost def - val2 = 20; - val3 =-20; - } - val2*=val1; //20% per level - val3*=val1; - break; - case SC_EXPBOOST: - case SC_JEXPBOOST: - if (val1 < 0) - val1 = 0; - break; - case SC_INCFLEE2: - case SC_INCCRI: - val2 = val1*10; //Actual boost (since 100% = 1000) - break; - case SC_SUFFRAGIUM: - val2 = 15 * val1; //Speed cast decrease - break; - case SC_INCHEALRATE: - if (val1 < 1) - val1 = 1; - break; - case SC_HALLUCINATION: - val2 = 5+val1; //Factor by which displayed damage is increased by - break; - case SC_DOUBLECAST: - val2 = 30+10*val1; //Trigger rate - break; - case SC_KAIZEL: - val2 = 10*val1; //% of life to be revived with - break; - // case SC_ARMOR_ELEMENT: - // case SC_ARMOR_RESIST: - // Mod your resistance against elements: - // val1 = water | val2 = earth | val3 = fire | val4 = wind - // break; - //case ????: - //Place here SCs that have no SCB_* data, no skill associated, no ICON - //associated, and yet are not wrong/unknown. [Skotlex] - //break; - - case SC_MERC_FLEEUP: - case SC_MERC_ATKUP: - case SC_MERC_HITUP: - val2 = 15 * val1; - break; - case SC_MERC_HPUP: - case SC_MERC_SPUP: - val2 = 5 * val1; - break; - case SC_REBIRTH: - val2 = 20*val1; //% of life to be revived with - break; - - case SC_MANU_DEF: - case SC_MANU_ATK: - case SC_MANU_MATK: - val2 = 1; // Manuk group - break; - case SC_SPL_DEF: - case SC_SPL_ATK: - case SC_SPL_MATK: - val2 = 2; // Splendide group - break; - /** - * General - **/ - case SC_FEAR: - val2 = 2; - val4 = tick / 1000; - tick_time = 1000; // [GodLesZ] tick time - break; - case SC_BURNING: - val4 = tick / 2000; // Total Ticks to Burn!! - tick_time = 2000; // [GodLesZ] tick time - break; - /** - * Rune Knight - **/ - case SC_DEATHBOUND: - val2 = 500 + 100 * val1; - break; - case SC_FIGHTINGSPIRIT: - val_flag |= 1|2; - break; - case SC_ABUNDANCE: - val4 = tick / 10000; - tick_time = 10000; // [GodLesZ] tick time - break; - case SC_GIANTGROWTH: - val2 = 10; // Triple damage success rate. - break; - /** - * Arch Bishop - **/ - case SC_RENOVATIO: - val4 = tick / 5000; - tick_time = 5000; - break; - case SC_SECRAMENT: - val2 = 10 * val1; - break; - case SC_VENOMIMPRESS: - val2 = 10 * val1; - val_flag |= 1|2; - break; - case SC_POISONINGWEAPON: - val_flag |= 1|2|4; - break; - case SC_WEAPONBLOCKING: - val2 = 10 + 2 * val1; // Chance - val4 = tick / 3000; - tick_time = 3000; // [GodLesZ] tick time - val_flag |= 1|2; - break; - case SC_TOXIN: - val4 = tick / 10000; - tick_time = 10000; // [GodLesZ] tick time - break; - case SC_MAGICMUSHROOM: - val4 = tick / 4000; - tick_time = 4000; // [GodLesZ] tick time - break; - case SC_PYREXIA: - status_change_start(bl,SC_BLIND,10000,val1,0,0,0,30000,11); // Blind status that last for 30 seconds - val4 = tick / 3000; - tick_time = 3000; // [GodLesZ] tick time - break; - case SC_LEECHESEND: - val4 = tick / 1000; - tick_time = 1000; // [GodLesZ] tick time - break; - case SC_OBLIVIONCURSE: - val4 = tick / 3000; - tick_time = 3000; // [GodLesZ] tick time - break; - case SC_ROLLINGCUTTER: - val_flag |= 1; - break; - case SC_CLOAKINGEXCEED: - val2 = ( val1 + 1 ) / 2; // Hits - val3 = 90 + val1 * 10; // Walk speed - val_flag |= 1|2|4; - if (bl->type == BL_PC) - val4 |= battle_config.pc_cloak_check_type&7; - else - val4 |= battle_config.monster_cloak_check_type&7; - tick_time = 1000; // [GodLesZ] tick time - break; - case SC_HALLUCINATIONWALK: - val2 = 50 * val1; // Evasion rate of physical attacks. Flee - val3 = 10 * val1; // Evasion rate of magical attacks. - val_flag |= 1|2|4; - break; - case SC_WHITEIMPRISON: - status_change_end(bl, SC_BURNING, INVALID_TIMER); - status_change_end(bl, SC_FREEZING, INVALID_TIMER); - status_change_end(bl, SC_FREEZE, INVALID_TIMER); - status_change_end(bl, SC_STONE, INVALID_TIMER); - break; - case SC_FREEZING: - status_change_end(bl, SC_BURNING, INVALID_TIMER); - break; - case SC_READING_SB: - // val2 = sp reduction per second - tick_time = 5000; // [GodLesZ] tick time - break; - case SC_SPHERE_1: - case SC_SPHERE_2: - case SC_SPHERE_3: - case SC_SPHERE_4: - case SC_SPHERE_5: - if( !sd ) - return 0; // Should only work on players. - val4 = tick / 1000; - if( val4 < 1 ) - val4 = 1; - tick_time = 1000; // [GodLesZ] tick time - val_flag |= 1; - break; - case SC_SHAPESHIFT: - switch( val1 ) - { - case 1: val2 = ELE_FIRE; break; - case 2: val2 = ELE_EARTH; break; - case 3: val2 = ELE_WIND; break; - case 4: val2 = ELE_WATER; break; - } - break; - case SC_ELECTRICSHOCKER: - case SC_CRYSTALIZE: - case SC_MEIKYOUSISUI: - val4 = tick / 1000; - if( val4 < 1 ) - val4 = 1; - tick_time = 1000; // [GodLesZ] tick time - break; - case SC_CAMOUFLAGE: - val4 = tick/1000; - tick_time = 1000; // [GodLesZ] tick time - break; - case SC_WUGDASH: - val4 = gettick(); //Store time at which you started running. - tick = -1; - break; - case SC__SHADOWFORM: { - struct map_session_data * s_sd = map_id2sd(val2); - if( s_sd ) - s_sd->shadowform_id = bl->id; - val4 = tick / 1000; - val_flag |= 1|2|4; - tick_time = 1000; // [GodLesZ] tick time - } - break; - case SC__STRIPACCESSORY: - if (!sd) - val2 = 20; - break; - case SC__INVISIBILITY: - val2 = 50 - 10 * val1; // ASPD - val3 = 20 * val1; // CRITICAL - val4 = tick / 1000; - tick_time = 1000; // [GodLesZ] tick time - val_flag |= 1|2; - break; - case SC__ENERVATION: - val2 = 20 + 10 * val1; // ATK Reduction - val_flag |= 1|2; - if( sd ) pc_delspiritball(sd,sd->spiritball,0); - break; - case SC__GROOMY: - val2 = 20 + 10 * val1; //ASPD. Need to confirm if Movement Speed reduction is the same. [Jobbie] - val3 = 20 * val1; //HIT - val_flag |= 1|2|4; - if( sd ) - { // Removes Animals - if( pc_isriding(sd) ) pc_setriding(sd, 0); - if( pc_isridingdragon(sd) ) pc_setoption(sd, sd->sc.option&~OPTION_DRAGON); - if( pc_iswug(sd) ) pc_setoption(sd, sd->sc.option&~OPTION_WUG); - if( pc_isridingwug(sd) ) pc_setoption(sd, sd->sc.option&~OPTION_WUGRIDER); - if( pc_isfalcon(sd) ) pc_setoption(sd, sd->sc.option&~OPTION_FALCON); - if( sd->status.pet_id > 0 ) pet_menu(sd, 3); - if( merc_is_hom_active(sd->hd) ) merc_hom_vaporize(sd,1); - if( sd->md ) merc_delete(sd->md,3); - } - break; - case SC__LAZINESS: - val2 = 10 + 10 * val1; // Cast reduction - val3 = 10 * val1; // Flee Reduction - val_flag |= 1|2|4; - break; - case SC__UNLUCKY: - val2 = 10 * val1; // Crit and Flee2 Reduction - val_flag |= 1|2|4; - break; - case SC__WEAKNESS: - val2 = 10 * val1; - val_flag |= 1|2; - // bypasses coating protection and MADO - sc_start(bl,SC_STRIPWEAPON,100,val1,tick); - sc_start(bl,SC_STRIPSHIELD,100,val1,tick); - break; - break; - case SC_GN_CARTBOOST: - if( val1 < 3 ) - val2 = 50; - else if( val1 < 5 ) - val2 = 75; - else - val2 = 100; - break; - case SC_PROPERTYWALK: - val_flag |= 1|2; - val3 = 0; - break; - case SC_WARMER: - status_change_end(bl, SC_FREEZE, INVALID_TIMER); - status_change_end(bl, SC_FREEZING, INVALID_TIMER); - status_change_end(bl, SC_CRYSTALIZE, INVALID_TIMER); - break; - case SC_STRIKING: - val1 = 6 - val1;//spcost = 6 - level (lvl1:5 ... lvl 5: 1) - val4 = tick / 1000; - tick_time = 1000; // [GodLesZ] tick time - break; - case SC_BLOODSUCKER: - val4 = tick / 1000; - tick_time = 1000; // [GodLesZ] tick time - break; - case SC_VACUUM_EXTREME: - tick -= (status->str / 20) * 1000; - val4 = val3 = tick / 100; - tick_time = 100; // [GodLesZ] tick time - break; - case SC_SWINGDANCE: - val2 = 4 * val1; // Walk speed and aspd reduction. - break; - case SC_SYMPHONYOFLOVER: - case SC_RUSHWINDMILL: - case SC_ECHOSONG: - val2 = 6 * val1; - val2 += val3; //Adding 1% * Lesson Bonus - val2 += (int)(val4*2/10); //Adding 0.2% per JobLevel - break; - case SC_MOONLITSERENADE: - val2 = 10 * val1; - break; - case SC_HARMONIZE: - val2 = 5 + 5 * val1; - break; - case SC_VOICEOFSIREN: - val4 = tick / 2000; - tick_time = 2000; // [GodLesZ] tick time - break; - case SC_DEEPSLEEP: - val4 = tick / 2000; - tick_time = 2000; // [GodLesZ] tick time - break; - case SC_SIRCLEOFNATURE: - val2 = 1 + val1; //SP consume - val3 = 40 * val1; //HP recovery - val4 = tick / 1000; - tick_time = 1000; // [GodLesZ] tick time - break; - case SC_SONGOFMANA: - val3 = 10 + (2 * val2); - val4 = tick/3000; - tick_time = 3000; // [GodLesZ] tick time - break; - case SC_SATURDAYNIGHTFEVER: - if (!val4) val4 = skill_get_time2(status_sc2skill(type),val1); - if (!val4) val4 = 3000; - val3 = tick/val4; - tick_time = val4; // [GodLesZ] tick time - break; - case SC_GLOOMYDAY: - val2 = 20 + 5 * val1; // Flee reduction. - val3 = 15 + 5 * val1; // ASPD reduction. - if( sd && rand()%100 < val1 ){ // (Skill Lv) % - val4 = 1; // reduce walk speed by half. - if( pc_isriding(sd) ) pc_setriding(sd, 0); - if( pc_isridingdragon(sd) ) pc_setoption(sd, sd->sc.option&~OPTION_DRAGON); - } - break; - case SC_GLOOMYDAY_SK: - // Random number between [15 ~ (Voice Lesson Skill Level x 5) + (Skill Level x 10)] %. - val2 = 15 + rand()%( (sd?pc_checkskill(sd, WM_LESSON)*5:0) + val1*10 ); - break; - case SC_SITDOWN_FORCE: - case SC_BANANA_BOMB_SITDOWN: - if( sd && !pc_issit(sd) ) - { - pc_setsit(sd); - skill_sit(sd,1); - clif_sitting(bl); - } - break; - case SC_DANCEWITHWUG: - val3 = (5 * val1) + (1 * val2); //Still need official value. - break; - case SC_LERADSDEW: - val3 = (5 * val1) + (1 * val2); - break; - case SC_MELODYOFSINK: - val3 = (5 * val1) + (1 * val2); - break; - case SC_BEYONDOFWARCRY: - val3 = (5 * val1) + (1 * val2); - break; - case SC_UNLIMITEDHUMMINGVOICE: - { - struct unit_data *ud = unit_bl2ud(bl); - if( ud == NULL ) return 0; - ud->state.skillcastcancel = 0; - val3 = 15 - (2 * val2); - } - break; - case SC_REFLECTDAMAGE: - val2 = 15 + 5 * val1; - val3 = (val1==5)?20:(val1+4)*2; // SP consumption - val4 = tick/10000; - tick_time = 10000; // [GodLesZ] tick time - break; - case SC_FORCEOFVANGUARD: // This is not the official way to handle it but I think we should use it. [pakpil] - val2 = 20 + 12 * (val1 - 1); // Chance - val3 = 5 + (2 * val1); // Max rage counters - tick = -1; //endless duration in the client - tick_time = 6000; // [GodLesZ] tick time - val_flag |= 1|2|4; - break; - case SC_EXEEDBREAK: - val1 *= 150; // 150 * skill_lv - if( sd && sd->inventory_data[sd->equip_index[EQI_HAND_R]] ) { // Chars. - val1 += (sd->inventory_data[sd->equip_index[EQI_HAND_R]]->weight/10 * sd->inventory_data[sd->equip_index[EQI_HAND_R]]->wlv * status_get_lv(bl) / 100); - val1 += 15 * (sd ? sd->status.job_level:50) + 100; - } - else // Mobs - val1 += (400 * status_get_lv(bl) / 100) + (15 * (status_get_lv(bl) / 2)); // About 1138% at mob_lvl 99. Is an aproximation to a standard weapon. [pakpil] - break; - case SC_PRESTIGE: // Bassed on suggested formula in iRO Wiki and some test, still need more test. [pakpil] - val2 = ((status->int_ + status->luk) / 6) + 5; // Chance to evade magic damage. - val1 *= 15; // Defence added - if( sd ) - val1 += 10 * pc_checkskill(sd,CR_DEFENDER); - val_flag |= 1|2; - break; - case SC_BANDING: - tick_time = 5000; // [GodLesZ] tick time - val_flag |= 1; - break; - case SC_SHIELDSPELL_DEF: - case SC_SHIELDSPELL_MDEF: - case SC_SHIELDSPELL_REF: - val_flag |= 1|2; - break; - case SC_MAGNETICFIELD: - val3 = tick / 1000; - tick_time = 1000; // [GodLesZ] tick time - break; - case SC_INSPIRATION: - if( sd ) - { - val2 = (40 * val1) + (3 * sd->status.job_level); // ATK bonus - val3 = (sd->status.job_level / 10) * 2 + 12; // All stat bonus - } - val4 = tick / 1000; - tick_time = 1000; // [GodLesZ] tick time - status_change_clear_buffs(bl,3); //Remove buffs/debuffs - break; - case SC_SPELLFIST: - case SC_CURSEDCIRCLE_ATKER: - val_flag |= 1|2|4; - break; - case SC_CRESCENTELBOW: - val2 = 94 + val1; - val_flag |= 1|2; - break; - case SC_LIGHTNINGWALK: // [(Job Level / 2) + (40 + 5 * Skill Level)] % - val1 = (sd?sd->status.job_level:2)/2 + 40 + 5 * val1; - val_flag |= 1; - break; - case SC_RAISINGDRAGON: - val3 = tick / 5000; - tick_time = 5000; // [GodLesZ] tick time - break; - case SC_GT_CHANGE: - {// take note there is no def increase as skill desc says. [malufett] - struct block_list * src; - val3 = status->agi * val1 / 60; // ASPD increase: [(Target AGI x Skill Level) / 60] % - if( (src = map_id2bl(val2)) ) - val4 = ( 200/status_get_int(src) ) * val1;// MDEF decrease: MDEF [(200 / Caster INT) x Skill Level] - } - break; - case SC_GT_REVITALIZE: - {// take note there is no vit,aspd,speed increase as skill desc says. [malufett] - struct block_list * src; - val3 = val1 * 30 + 150; // Natural HP recovery increase: [(Skill Level x 30) + 50] % - if( (src = map_id2bl(val2)) ) // the stat def is not shown in the status window and it is process differently - val4 = ( status_get_vit(src)/4 ) * val1; // STAT DEF increase: [(Caster VIT / 4) x Skill Level] - } - break; - case SC_PYROTECHNIC_OPTION: - val_flag |= 1|2|4; - break; - case SC_HEATER_OPTION: - val2 = 120; // Watk. TODO: Renewal (Atk2) - val3 = 33; // % Increase effects. - val4 = 3; // Change into fire element. - val_flag |= 1|2|4; - break; - case SC_TROPIC_OPTION: - val2 = 180; // Watk. TODO: Renewal (Atk2) - val3 = MG_FIREBOLT; - break; - case SC_AQUAPLAY_OPTION: - val2 = 40; - val_flag |= 1|2|4; - break; - case SC_COOLER_OPTION: - val2 = 80; // % Freezing chance - val3 = 33; // % increased damage - val4 = 1; // Change into water elemet - val_flag |= 1|2|4; - break; - case SC_CHILLY_AIR_OPTION: - val2 = 120; // Matk. TODO: Renewal (Matk1) - val3 = MG_COLDBOLT; - val_flag |= 1|2; - break; - case SC_GUST_OPTION: - val_flag |= 1|2; - break; - case SC_WIND_STEP_OPTION: - val2 = 50; // % Increase speed and flee. - break; - case SC_BLAST_OPTION: - val2 = 20; - val3 = ELE_WIND; - val_flag |= 1|2|4; - break; - case SC_WILD_STORM_OPTION: - val2 = MG_LIGHTNINGBOLT; - val_flag |= 1|2; - break; - case SC_PETROLOGY_OPTION: - val2 = 5; - val3 = 50; - val_flag |= 1|2|4; - break; - case SC_CURSED_SOIL_OPTION: - val2 = 10; - val3 = 33; - val4 = 2; - val_flag |= 1|2|4; - break; - case SC_UPHEAVAL_OPTION: - val2 = WZ_EARTHSPIKE; - val_flag |= 1|2; - break; - case SC_CIRCLE_OF_FIRE_OPTION: - val2 = 300; - val_flag |= 1|2; - break; - case SC_FIRE_CLOAK_OPTION: - case SC_WATER_DROP_OPTION: - case SC_WIND_CURTAIN_OPTION: - case SC_STONE_SHIELD_OPTION: - val2 = 20; // Elemental modifier. Not confirmed. - break; - case SC_CIRCLE_OF_FIRE: - case SC_FIRE_CLOAK: - case SC_WATER_DROP: - case SC_WATER_SCREEN: - case SC_WIND_CURTAIN: - case SC_WIND_STEP: - case SC_STONE_SHIELD: - case SC_SOLID_SKIN: - val2 = 10; - tick_time = 2000; // [GodLesZ] tick time - break; - case SC_WATER_BARRIER: - val2 = 40; // Increasement. Mdef1 ??? - val3 = 20; // Reductions. Atk2, Flee1, Matk1 ???? - val_flag |= 1|2|4; - break; - case SC_ZEPHYR: - val2 = 22; // Flee. - break; - case SC_TIDAL_WEAPON: - val2 = 20; // Increase Elemental's attack. - break; - case SC_ROCK_CRUSHER: - case SC_ROCK_CRUSHER_ATK: - case SC_POWER_OF_GAIA: - val2 = 33; - break; - case SC_MELON_BOMB: - case SC_BANANA_BOMB: - val1 = 15; - break; - case SC_STOMACHACHE: - val2 = 8; // SP consume. - val4 = tick / 10000; - tick_time = 10000; // [GodLesZ] tick time - break; - case SC_KYOUGAKU: - val2 = 2*val1 + rand()%val1; - clif_status_change(bl,SI_ACTIVE_MONSTER_TRANSFORM,1,0,1002,0,0); - break; - case SC_KAGEMUSYA: - val3 = val1 * 2; - case SC_IZAYOI: - val2 = tick/1000; - tick_time = 1000; - break; - case SC_ZANGETSU: - if( (status_get_hp(bl)+status_get_sp(bl)) % 2 == 0) - val2 = status_get_lv(bl) / 2 + 50; - else - val2 -= 50; - break; - case SC_GENSOU: - { - int hp = status_get_hp(bl), lv = 5; - short per = 100 / (status_get_max_hp(bl) / hp); - - if( per <= 15 ) - lv = 1; - else if( per <= 30 ) - lv = 2; - else if( per <= 50 ) - lv = 3; - else if( per <= 75 ) - lv = 4; - if( hp % 2 == 0) - status_heal(bl, hp * (6-lv) * 4 / 100, status_get_sp(bl) * (6-lv) * 3 / 100, 1); - else - status_zap(bl, hp * (lv*4) / 100, status_get_sp(bl) * (lv*3) / 100); - } - break; - case SC_ANGRIFFS_MODUS: - val2 = 50 + 20 * val1; //atk bonus - val3 = 40 + 20 * val1; // Flee reduction. - val4 = tick/1000; // hp/sp reduction timer - tick_time = 1000; - break; - case SC_GOLDENE_FERSE: - val2 = 10 + 10*val1; //max hp bonus - val3 = 6 + 4 * val1; // Aspd Bonus - val4 = 2 + 2 * val1; // Chance of holy attack - break; - case SC_OVERED_BOOST: - val2 = 300 + 40*val1; //flee bonus - val3 = 179 + 2*val1; //aspd bonus - break; - case SC_GRANITIC_ARMOR: - val2 = 2*val1; //dmg reduction - val3 = 6*val1; //dmg on status end - break; - case SC_MAGMA_FLOW: - val2 = 3*val1; //activation chance - break; - case SC_PYROCLASTIC: - val2 += 10*val1; //atk bonus - break; - case SC_PARALYSIS: //[Lighta] need real info - val2 = 2*val1; //def reduction - val3 = 500*val1; //varcast augmentation - break; - case SC_PAIN_KILLER: //[Lighta] need real info - val2 = 2*val1; //aspd reduction % - val3 = 2*val1; //dmg reduction % - if(sc->data[SC_PARALYSIS]) - sc_start(bl, SC_ENDURE, 100, val1, tick); //start endure for same duration - break; - case SC_STYLE_CHANGE: //[Lighta] need real info - tick = -1; - if(val2 == MH_MD_FIGHTING) val2 = MH_MD_GRAPPLING; - else val2 = MH_MD_FIGHTING; - break; - default: - if( calc_flag == SCB_NONE && StatusSkillChangeTable[type] == 0 && StatusIconChangeTable[type] == 0 ) - { //Status change with no calc, no icon, and no skill associated...? - ShowError("UnknownStatusChange [%d]\n", type); - return 0; - } - } - else //Special considerations when loading SC data. - switch( type ) - { - case SC_WEDDING: - case SC_XMAS: - case SC_SUMMER: - clif_changelook(bl,LOOK_WEAPON,0); - clif_changelook(bl,LOOK_SHIELD,0); - clif_changelook(bl,LOOK_BASE,type==SC_WEDDING?JOB_WEDDING:type==SC_XMAS?JOB_XMAS:JOB_SUMMER); - clif_changelook(bl,LOOK_CLOTHES_COLOR,val4); - break; - case SC_KAAHI: - val4 = INVALID_TIMER; - break; - } - - //Those that make you stop attacking/walking.... - switch (type) { - case SC_FREEZE: - case SC_STUN: - case SC_SLEEP: - case SC_STONE: - case SC_DEEPSLEEP: - if (sd && pc_issit(sd)) //Avoid sprite sync problems. - pc_setstand(sd); - case SC_TRICKDEAD: - status_change_end(bl, SC_DANCING, INVALID_TIMER); - // Cancel cast when get status [LuzZza] - if (battle_config.sc_castcancel&bl->type) - unit_skillcastcancel(bl, 0); - case SC_WHITEIMPRISON: - unit_stop_attack(bl); - case SC_STOP: - case SC_CONFUSION: - case SC_CLOSECONFINE: - case SC_CLOSECONFINE2: - case SC_ANKLE: - case SC_SPIDERWEB: - case SC_ELECTRICSHOCKER: - case SC_BITE: - case SC_THORNSTRAP: - case SC__MANHOLE: - case SC_CRYSTALIZE: - case SC_CURSEDCIRCLE_ATKER: - case SC_CURSEDCIRCLE_TARGET: - case SC_FEAR: - case SC_NETHERWORLD: - case SC_MEIKYOUSISUI: - case SC_KYOUGAKU: - case SC_PARALYSIS: - unit_stop_walking(bl,1); - break; - case SC_HIDING: - case SC_CLOAKING: - case SC_CLOAKINGEXCEED: - case SC_CHASEWALK: - case SC_WEIGHT90: - case SC_CAMOUFLAGE: - case SC_VOICEOFSIREN: - unit_stop_attack(bl); - break; - case SC_SILENCE: - if (battle_config.sc_castcancel&bl->type) - unit_skillcastcancel(bl, 0); - break; - } - - // Set option as needed. - opt_flag = 1; - switch(type) - { - //OPT1 - case SC_STONE: sc->opt1 = OPT1_STONEWAIT; break; - case SC_FREEZE: sc->opt1 = OPT1_FREEZE; break; - case SC_STUN: sc->opt1 = OPT1_STUN; break; - case SC_DEEPSLEEP: opt_flag = 0; - case SC_SLEEP: sc->opt1 = OPT1_SLEEP; break; - case SC_BURNING: sc->opt1 = OPT1_BURNING; break; // Burning need this to be showed correctly. [pakpil] - case SC_WHITEIMPRISON: sc->opt1 = OPT1_IMPRISON; break; - case SC_CRYSTALIZE: sc->opt1 = OPT1_CRYSTALIZE; break; - //OPT2 - case SC_POISON: sc->opt2 |= OPT2_POISON; break; - case SC_CURSE: sc->opt2 |= OPT2_CURSE; break; - case SC_SILENCE: sc->opt2 |= OPT2_SILENCE; break; - - case SC_SIGNUMCRUCIS: - sc->opt2 |= OPT2_SIGNUMCRUCIS; - break; - - case SC_BLIND: sc->opt2 |= OPT2_BLIND; break; - case SC_ANGELUS: sc->opt2 |= OPT2_ANGELUS; break; - case SC_BLEEDING: sc->opt2 |= OPT2_BLEEDING; break; - case SC_DPOISON: sc->opt2 |= OPT2_DPOISON; break; - //OPT3 - case SC_TWOHANDQUICKEN: - case SC_ONEHAND: - case SC_SPEARQUICKEN: - case SC_CONCENTRATION: - case SC_MERC_QUICKEN: - sc->opt3 |= OPT3_QUICKEN; - opt_flag = 0; - break; - case SC_MAXOVERTHRUST: - case SC_OVERTHRUST: - case SC_SWOO: //Why does it shares the same opt as Overthrust? Perhaps we'll never know... - sc->opt3 |= OPT3_OVERTHRUST; - opt_flag = 0; - break; - case SC_ENERGYCOAT: - case SC_SKE: - sc->opt3 |= OPT3_ENERGYCOAT; - opt_flag = 0; - break; - case SC_INCATKRATE: - //Simulate Explosion Spirits effect for NPC_POWERUP [Skotlex] - if (bl->type != BL_MOB) { - opt_flag = 0; - break; - } - case SC_EXPLOSIONSPIRITS: - sc->opt3 |= OPT3_EXPLOSIONSPIRITS; - opt_flag = 0; - break; - case SC_STEELBODY: - case SC_SKA: - sc->opt3 |= OPT3_STEELBODY; - opt_flag = 0; - break; - case SC_BLADESTOP: - sc->opt3 |= OPT3_BLADESTOP; - opt_flag = 0; - break; - case SC_AURABLADE: - sc->opt3 |= OPT3_AURABLADE; - opt_flag = 0; - break; - case SC_BERSERK: - opt_flag = 0; -// case SC__BLOODYLUST: - sc->opt3 |= OPT3_BERSERK; - break; -// case ???: // doesn't seem to do anything -// sc->opt3 |= OPT3_LIGHTBLADE; -// opt_flag = 0; -// break; - case SC_DANCING: - if ((val1&0xFFFF) == CG_MOONLIT) - sc->opt3 |= OPT3_MOONLIT; - opt_flag = 0; - break; - case SC_MARIONETTE: - case SC_MARIONETTE2: - sc->opt3 |= OPT3_MARIONETTE; - opt_flag = 0; - break; - case SC_ASSUMPTIO: - sc->opt3 |= OPT3_ASSUMPTIO; - opt_flag = 0; - break; - case SC_WARM: //SG skills [Komurka] - sc->opt3 |= OPT3_WARM; - opt_flag = 0; - break; - case SC_KAITE: - sc->opt3 |= OPT3_KAITE; - opt_flag = 0; - break; - case SC_BUNSINJYUTSU: - sc->opt3 |= OPT3_BUNSIN; - opt_flag = 0; - break; - case SC_SPIRIT: - sc->opt3 |= OPT3_SOULLINK; - opt_flag = 0; - break; - case SC_CHANGEUNDEAD: - sc->opt3 |= OPT3_UNDEAD; - opt_flag = 0; - break; -// case ???: // from DA_CONTRACT (looks like biolab mobs aura) -// sc->opt3 |= OPT3_CONTRACT; -// opt_flag = 0; -// break; - //OPTION - case SC_HIDING: - sc->option |= OPTION_HIDE; - opt_flag = 2; - break; - case SC_CLOAKING: - case SC_CLOAKINGEXCEED: - case SC__INVISIBILITY: - sc->option |= OPTION_CLOAK; - opt_flag = 2; - break; - case SC_CHASEWALK: - sc->option |= OPTION_CHASEWALK|OPTION_CLOAK; - opt_flag = 2; - break; - case SC_SIGHT: - sc->option |= OPTION_SIGHT; - break; - case SC_RUWACH: - sc->option |= OPTION_RUWACH; - break; - case SC_WEDDING: - sc->option |= OPTION_WEDDING; - break; - case SC_XMAS: - sc->option |= OPTION_XMAS; - break; - case SC_SUMMER: - sc->option |= OPTION_SUMMER; - break; - case SC_ORCISH: - sc->option |= OPTION_ORCISH; - break; - case SC_FUSION: - sc->option |= OPTION_FLYING; - break; - default: - opt_flag = 0; - } - - //On Aegis, when turning on a status change, first goes the option packet, then the sc packet. - if(opt_flag) - clif_changeoption(bl); - - if (calc_flag&SCB_DYE) - { //Reset DYE color - if (vd && vd->cloth_color) - { - val4 = vd->cloth_color; - clif_changelook(bl,LOOK_CLOTHES_COLOR,0); - } - calc_flag&=~SCB_DYE; - } - - clif_status_change(bl,StatusIconChangeTable[type],1,tick,(val_flag&1)?val1:1,(val_flag&2)?val2:0,(val_flag&4)?val3:0); - - /** - * used as temporary storage for scs with interval ticks, so that the actual duration is sent to the client first. - **/ - if( tick_time ) - tick = tick_time; - - //Don't trust the previous sce assignment, in case the SC ended somewhere between there and here. - if((sce=sc->data[type])) {// reuse old sc - if( sce->timer != INVALID_TIMER ) - delete_timer(sce->timer, status_change_timer); - sc_isnew = false; - } else {// new sc - ++(sc->count); - sce = sc->data[type] = ers_alloc(sc_data_ers, struct status_change_entry); - } - sce->val1 = val1; - sce->val2 = val2; - sce->val3 = val3; - sce->val4 = val4; - if (tick >= 0) - sce->timer = add_timer(gettick() + tick, status_change_timer, bl->id, type); - else - sce->timer = INVALID_TIMER; //Infinite duration - - if (calc_flag) - status_calc_bl(bl,calc_flag); - - if ( sc_isnew && StatusChangeStateTable[type] ) /* non-zero */ - status_calc_state(bl,sc,( enum scs_flag ) StatusChangeStateTable[type],true); - - - if(sd && sd->pd) - pet_sc_check(sd, type); //Skotlex: Pet Status Effect Healing - - switch (type) { - case SC__BLOODYLUST: - case SC_BERSERK: - if (!(sce->val2)) { //don't heal if already set - status_heal(bl, status->max_hp, 0, 1); //Do not use percent_heal as this healing must override BERSERK's block. - status_set_sp(bl, 0, 0); //Damage all SP - } - sce->val2 = 5 * status->max_hp / 100; - break; - case SC_CHANGE: - status_percent_heal(bl, 100, 100); - break; - case SC_RUN: - { - struct unit_data *ud = unit_bl2ud(bl); - if( ud ) - ud->state.running = unit_run(bl); - } - break; - case SC_BOSSMAPINFO: - clif_bossmapinfo(sd->fd, map_id2boss(sce->val1), 0); // First Message - break; - case SC_MERC_HPUP: - status_percent_heal(bl, 100, 0); // Recover Full HP - break; - case SC_MERC_SPUP: - status_percent_heal(bl, 0, 100); // Recover Full SP - break; - /** - * Ranger - **/ - case SC_WUGDASH: - { - struct unit_data *ud = unit_bl2ud(bl); - if( ud ) - ud->state.running = unit_wugdash(bl, sd); - } - break; - case SC_COMBO: - switch (sce->val1) { - case TK_STORMKICK: - clif_skill_nodamage(bl,bl,TK_READYSTORM,1,1); - break; - case TK_DOWNKICK: - clif_skill_nodamage(bl,bl,TK_READYDOWN,1,1); - break; - case TK_TURNKICK: - clif_skill_nodamage(bl,bl,TK_READYTURN,1,1); - break; - case TK_COUNTER: - clif_skill_nodamage(bl,bl,TK_READYCOUNTER,1,1); - break; - case MO_COMBOFINISH: - case CH_TIGERFIST: - case CH_CHAINCRUSH: - if (sd) - clif_skillinfo(sd,MO_EXTREMITYFIST, INF_SELF_SKILL); - break; - case TK_JUMPKICK: - if (sd) - clif_skillinfo(sd,TK_JUMPKICK, INF_SELF_SKILL); - break; - case MO_TRIPLEATTACK: - if (sd && pc_checkskill(sd, SR_DRAGONCOMBO) > 0) - clif_skillinfo(sd,SR_DRAGONCOMBO, INF_SELF_SKILL); - break; - case SR_FALLENEMPIRE: - if (sd){ - clif_skillinfo(sd,SR_GATEOFHELL, INF_SELF_SKILL); - clif_skillinfo(sd,SR_TIGERCANNON, INF_SELF_SKILL); - } - break; - } - break; - case SC_RAISINGDRAGON: - sce->val2 = status->max_hp / 100;// Officially tested its 1%hp drain. [Jobbie] - break; - } - - if( opt_flag&2 && sd && sd->touching_id ) - npc_touchnext_areanpc(sd,false); // run OnTouch_ on next char in range - - return 1; -} - -/*========================================== - * Ending all status except those listed. - * @TODO maybe usefull for dispel instead reseting a liste there. - * type: - * 0 - PC killed -> Place here statuses that do not dispel on death. - * 1 - If for some reason status_change_end decides to still keep the status when quitting. - * 2 - Do clif - * 3 - Do not remove some permanent/time-independent effects - *------------------------------------------*/ -int status_change_clear(struct block_list* bl, int type) -{ - struct status_change* sc; - int i; - - sc = status_get_sc(bl); - - if (!sc || !sc->count) - return 0; - - for(i = 0; i < SC_MAX; i++) - { - if(!sc->data[i]) - continue; - - if(type == 0) - switch (i) - { //Type 0: PC killed -> Place here statuses that do not dispel on death. - case SC_ELEMENTALCHANGE://Only when its Holy or Dark that it doesn't dispell on death - if( sc->data[i]->val2 != ELE_HOLY && sc->data[i]->val2 != ELE_DARK ) - break; - case SC_WEIGHT50: - case SC_WEIGHT90: - case SC_EDP: - case SC_MELTDOWN: - case SC_XMAS: - case SC_SUMMER: - case SC_NOCHAT: - case SC_FUSION: - case SC_EARTHSCROLL: - case SC_READYSTORM: - case SC_READYDOWN: - case SC_READYCOUNTER: - case SC_READYTURN: - case SC_DODGE: - case SC_JAILED: - case SC_EXPBOOST: - case SC_ITEMBOOST: - case SC_HELLPOWER: - case SC_JEXPBOOST: - case SC_AUTOTRADE: - case SC_WHISTLE: - case SC_ASSNCROS: - case SC_POEMBRAGI: - case SC_APPLEIDUN: - case SC_HUMMING: - case SC_DONTFORGETME: - case SC_FORTUNE: - case SC_SERVICE4U: - case SC_FOOD_STR_CASH: - case SC_FOOD_AGI_CASH: - case SC_FOOD_VIT_CASH: - case SC_FOOD_DEX_CASH: - case SC_FOOD_INT_CASH: - case SC_FOOD_LUK_CASH: - case SC_DEF_RATE: - case SC_MDEF_RATE: - case SC_INCHEALRATE: - case SC_INCFLEE2: - case SC_INCHIT: - case SC_ATKPOTION: - case SC_MATKPOTION: - case SC_S_LIFEPOTION: - case SC_L_LIFEPOTION: - case SC_PUSH_CART: - continue; - - } - - if( type == 3 ) - { - switch (i) - {// TODO: This list may be incomplete - case SC_WEIGHT50: - case SC_WEIGHT90: - case SC_NOCHAT: - case SC_PUSH_CART: - continue; - } - } - - status_change_end(bl, (sc_type)i, INVALID_TIMER); - - if( type == 1 && sc->data[i] ) - { //If for some reason status_change_end decides to still keep the status when quitting. [Skotlex] - (sc->count)--; - if (sc->data[i]->timer != INVALID_TIMER) - delete_timer(sc->data[i]->timer, status_change_timer); - ers_free(sc_data_ers, sc->data[i]); - sc->data[i] = NULL; - } - } - - sc->opt1 = 0; - sc->opt2 = 0; - sc->opt3 = 0; - sc->option &= OPTION_MASK; - - if( type == 0 || type == 2 ) - clif_changeoption(bl); - - return 1; -} - -/*========================================== - * Special condition we want to effectuate, check before ending a status. - *------------------------------------------*/ -int status_change_end_(struct block_list* bl, enum sc_type type, int tid, const char* file, int line) -{ - struct map_session_data *sd; - struct status_change *sc; - struct status_change_entry *sce; - struct status_data *status; - struct view_data *vd; - int opt_flag=0, calc_flag; - - nullpo_ret(bl); - - sc = status_get_sc(bl); - status = status_get_status_data(bl); - - if(type < 0 || type >= SC_MAX || !sc || !(sce = sc->data[type])) - return 0; - - sd = BL_CAST(BL_PC,bl); - - if (sce->timer != tid && tid != INVALID_TIMER) - return 0; - - if (tid == INVALID_TIMER) { - if (type == SC_ENDURE && sce->val4) - //Do not end infinite endure. - return 0; - if (sce->timer != INVALID_TIMER) //Could be a SC with infinite duration - delete_timer(sce->timer,status_change_timer); - if (sc->opt1) - switch (type) { - //"Ugly workaround" [Skotlex] - //delays status change ending so that a skill that sets opt1 fails to - //trigger when it also removed one - case SC_STONE: - sce->val3 = 0; //Petrify time counter. - case SC_FREEZE: - case SC_STUN: - case SC_SLEEP: - if (sce->val1) { - //Removing the 'level' shouldn't affect anything in the code - //since these SC are not affected by it, and it lets us know - //if we have already delayed this attack or not. - sce->val1 = 0; - sce->timer = add_timer(gettick()+10, status_change_timer, bl->id, type); - return 1; - } - } - } - - (sc->count)--; - - if ( StatusChangeStateTable[type] ) - status_calc_state(bl,sc,( enum scs_flag ) StatusChangeStateTable[type],false); - - sc->data[type] = NULL; - - vd = status_get_viewdata(bl); - calc_flag = StatusChangeFlagTable[type]; - switch(type){ - case SC_GRANITIC_ARMOR:{ - int dammage = status->max_hp*sce->val3/100; - if(status->hp < dammage) //to not kill him - dammage = status->hp-1; - status_damage(NULL, bl, dammage,0,0,1); - break; - } - case SC_PYROCLASTIC: - if(bl->type == BL_PC) - skill_break_equip(bl,EQP_WEAPON,10000,BCT_SELF); - break; - case SC_WEDDING: - case SC_XMAS: - case SC_SUMMER: - if (!vd) break; - if (sd) - { //Load data from sd->status.* as the stored values could have changed. - //Must remove OPTION to prevent class being rechanged. - sc->option &= type==SC_WEDDING?~OPTION_WEDDING:type==SC_XMAS?~OPTION_XMAS:~OPTION_SUMMER; - clif_changeoption(&sd->bl); - status_set_viewdata(bl, sd->status.class_); - } else { - vd->class_ = sce->val1; - vd->weapon = sce->val2; - vd->shield = sce->val3; - vd->cloth_color = sce->val4; - } - clif_changelook(bl,LOOK_BASE,vd->class_); - clif_changelook(bl,LOOK_CLOTHES_COLOR,vd->cloth_color); - clif_changelook(bl,LOOK_WEAPON,vd->weapon); - clif_changelook(bl,LOOK_SHIELD,vd->shield); - if(sd) clif_skillinfoblock(sd); - break; - case SC_RUN: - { - struct unit_data *ud = unit_bl2ud(bl); - bool begin_spurt = true; - if (ud) { - if(!ud->state.running) - begin_spurt = false; - ud->state.running = 0; - if (ud->walktimer != INVALID_TIMER) - unit_stop_walking(bl,1); - } - if (begin_spurt && sce->val1 >= 7 && - DIFF_TICK(gettick(), sce->val4) <= 1000 && - (!sd || (sd->weapontype1 == 0 && sd->weapontype2 == 0)) - ) - sc_start(bl,SC_SPURT,100,sce->val1,skill_get_time2(status_sc2skill(type), sce->val1)); - } - break; - case SC_AUTOBERSERK: - if (sc->data[SC_PROVOKE] && sc->data[SC_PROVOKE]->val2 == 1) - status_change_end(bl, SC_PROVOKE, INVALID_TIMER); - break; - - case SC_ENDURE: - case SC_DEFENDER: - case SC_REFLECTSHIELD: - case SC_AUTOGUARD: - { - struct map_session_data *tsd; - if( bl->type == BL_PC ) - { // Clear Status from others - int i; - for( i = 0; i < 5; i++ ) - { - if( sd->devotion[i] && (tsd = map_id2sd(sd->devotion[i])) && tsd->sc.data[type] ) - status_change_end(&tsd->bl, type, INVALID_TIMER); - } - } - else if( bl->type == BL_MER && ((TBL_MER*)bl)->devotion_flag ) - { // Clear Status from Master - tsd = ((TBL_MER*)bl)->master; - if( tsd && tsd->sc.data[type] ) - status_change_end(&tsd->bl, type, INVALID_TIMER); - } - } - break; - case SC_DEVOTION: - { - struct block_list *d_bl = map_id2bl(sce->val1); - if( d_bl ) - { - if( d_bl->type == BL_PC ) - ((TBL_PC*)d_bl)->devotion[sce->val2] = 0; - else if( d_bl->type == BL_MER ) - ((TBL_MER*)d_bl)->devotion_flag = 0; - clif_devotion(d_bl, NULL); - } - - status_change_end(bl, SC_AUTOGUARD, INVALID_TIMER); - status_change_end(bl, SC_DEFENDER, INVALID_TIMER); - status_change_end(bl, SC_REFLECTSHIELD, INVALID_TIMER); - status_change_end(bl, SC_ENDURE, INVALID_TIMER); - } - break; - - case SC_BLADESTOP: - if(sce->val4) - { - int tid = sce->val4; - struct block_list *tbl = map_id2bl(tid); - struct status_change *tsc = status_get_sc(tbl); - sce->val4 = 0; - if(tbl && tsc && tsc->data[SC_BLADESTOP]) - { - tsc->data[SC_BLADESTOP]->val4 = 0; - status_change_end(tbl, SC_BLADESTOP, INVALID_TIMER); - } - clif_bladestop(bl, tid, 0); - } - break; - case SC_DANCING: - { - const char* prevfile = "<unknown>"; - int prevline = 0; - struct map_session_data *dsd; - struct status_change_entry *dsc; - struct skill_unit_group *group; - - if( sd ) - { - if( sd->delunit_prevfile ) - {// initially this is NULL, when a character logs in - prevfile = sd->delunit_prevfile; - prevline = sd->delunit_prevline; - } - else - { - prevfile = "<none>"; - } - sd->delunit_prevfile = file; - sd->delunit_prevline = line; - } - - if(sce->val4 && sce->val4 != BCT_SELF && (dsd=map_id2sd(sce->val4))) - {// end status on partner as well - dsc = dsd->sc.data[SC_DANCING]; - if(dsc) { - - //This will prevent recursive loops. - dsc->val2 = dsc->val4 = 0; - - status_change_end(&dsd->bl, SC_DANCING, INVALID_TIMER); - } - } - - if(sce->val2) - {// erase associated land skill - group = skill_id2group(sce->val2); - - if( group == NULL ) - { - ShowDebug("status_change_end: SC_DANCING is missing skill unit group (val1=%d, val2=%d, val3=%d, val4=%d, timer=%d, tid=%d, char_id=%d, map=%s, x=%d, y=%d, prev=%s:%d, from=%s:%d). Please report this! (#3504)\n", - sce->val1, sce->val2, sce->val3, sce->val4, sce->timer, tid, - sd ? sd->status.char_id : 0, - mapindex_id2name(map_id2index(bl->m)), bl->x, bl->y, - prevfile, prevline, - file, line); - } - - sce->val2 = 0; - skill_delunitgroup(group); - } - - if((sce->val1&0xFFFF) == CG_MOONLIT) - clif_status_change(bl,SI_MOONLIT,0,0,0,0,0); - - status_change_end(bl, SC_LONGING, INVALID_TIMER); - } - break; - case SC_NOCHAT: - if (sd && sd->status.manner < 0 && tid != INVALID_TIMER) - sd->status.manner = 0; - if (sd && tid == INVALID_TIMER) - { - clif_changestatus(sd,SP_MANNER,sd->status.manner); - clif_updatestatus(sd,SP_MANNER); - } - break; - case SC_SPLASHER: - { - struct block_list *src=map_id2bl(sce->val3); - if(src && tid != INVALID_TIMER) - skill_castend_damage_id(src, bl, sce->val2, sce->val1, gettick(), SD_LEVEL ); - } - break; - case SC_CLOSECONFINE2: - { - struct block_list *src = sce->val2?map_id2bl(sce->val2):NULL; - struct status_change *sc2 = src?status_get_sc(src):NULL; - if (src && sc2 && sc2->data[SC_CLOSECONFINE]) { - //If status was already ended, do nothing. - //Decrease count - if (--(sc2->data[SC_CLOSECONFINE]->val1) <= 0) //No more holds, free him up. - status_change_end(src, SC_CLOSECONFINE, INVALID_TIMER); - } - } - case SC_CLOSECONFINE: - if (sce->val2 > 0) { - //Caster has been unlocked... nearby chars need to be unlocked. - int range = 1 - +skill_get_range2(bl, status_sc2skill(type), sce->val1) - +skill_get_range2(bl, TF_BACKSLIDING, 1); //Since most people use this to escape the hold.... - map_foreachinarea(status_change_timer_sub, - bl->m, bl->x-range, bl->y-range, bl->x+range,bl->y+range,BL_CHAR,bl,sce,type,gettick()); - } - break; - case SC_COMBO: - if( sd ) - switch (sce->val1) { - case MO_COMBOFINISH: - case CH_TIGERFIST: - case CH_CHAINCRUSH: - clif_skillinfo(sd, MO_EXTREMITYFIST, 0); - break; - case TK_JUMPKICK: - clif_skillinfo(sd, TK_JUMPKICK, 0); - break; - case MO_TRIPLEATTACK: - if (pc_checkskill(sd, SR_DRAGONCOMBO) > 0) - clif_skillinfo(sd, SR_DRAGONCOMBO, 0); - break; - case SR_FALLENEMPIRE: - clif_skillinfo(sd, SR_GATEOFHELL, 0); - clif_skillinfo(sd, SR_TIGERCANNON, 0); - break; - } - break; - - case SC_MARIONETTE: - case SC_MARIONETTE2: /// Marionette target - if (sce->val1) - { // check for partner and end their marionette status as well - enum sc_type type2 = (type == SC_MARIONETTE) ? SC_MARIONETTE2 : SC_MARIONETTE; - struct block_list *pbl = map_id2bl(sce->val1); - struct status_change* sc2 = pbl?status_get_sc(pbl):NULL; - - if (sc2 && sc2->data[type2]) - { - sc2->data[type2]->val1 = 0; - status_change_end(pbl, type2, INVALID_TIMER); - } - } - break; - - case SC_BERSERK: - case SC_SATURDAYNIGHTFEVER: - //If val2 is removed, no HP penalty (dispelled?) [Skotlex] - if (status->hp > 100 && sce->val2) - status_set_hp(bl, 100, 0); - if(sc->data[SC_ENDURE] && sc->data[SC_ENDURE]->val4 == 2) - { - sc->data[SC_ENDURE]->val4 = 0; - status_change_end(bl, SC_ENDURE, INVALID_TIMER); - } - case SC__BLOODYLUST: - sc_start4(bl, SC_REGENERATION, 100, 10,0,0,(RGN_HP|RGN_SP), skill_get_time(LK_BERSERK, sce->val1)); - if( type == SC_SATURDAYNIGHTFEVER ) //Sit down force of Saturday Night Fever has the duration of only 3 seconds. - sc_start(bl,SC_SITDOWN_FORCE,100,sce->val1,skill_get_time2(WM_SATURDAY_NIGHT_FEVER,sce->val1)); - break; - case SC_GOSPEL: - if (sce->val3) { //Clear the group. - struct skill_unit_group* group = skill_id2group(sce->val3); - sce->val3 = 0; - skill_delunitgroup(group); - } - break; - case SC_HERMODE: - if(sce->val3 == BCT_SELF) - skill_clear_unitgroup(bl); - break; - case SC_BASILICA: //Clear the skill area. [Skotlex] - skill_clear_unitgroup(bl); - break; - case SC_TRICKDEAD: - if (vd) vd->dead_sit = 0; - break; - case SC_WARM: - case SC__MANHOLE: - if (sce->val4) { //Clear the group. - struct skill_unit_group* group = skill_id2group(sce->val4); - sce->val4 = 0; - if( group ) /* might have been cleared before status ended, e.g. land protector */ - skill_delunitgroup(group); - } - break; - case SC_KAAHI: - //Delete timer if it exists. - if (sce->val4 != INVALID_TIMER) - delete_timer(sce->val4,kaahi_heal_timer); - break; - case SC_JAILED: - if(tid == INVALID_TIMER) - break; - //natural expiration. - if(sd && sd->mapindex == sce->val2) - pc_setpos(sd,(unsigned short)sce->val3,sce->val4&0xFFFF, sce->val4>>16, CLR_TELEPORT); - break; //guess hes not in jail :P - case SC_CHANGE: - if (tid == INVALID_TIMER) - break; - // "lose almost all their HP and SP" on natural expiration. - status_set_hp(bl, 10, 0); - status_set_sp(bl, 10, 0); - break; - case SC_AUTOTRADE: - if (tid == INVALID_TIMER) - break; - // Note: vending/buying is closed by unit_remove_map, no - // need to do it here. - map_quit(sd); - // Because map_quit calls status_change_end with tid -1 - // from here it's not neccesary to continue - return 1; - break; - case SC_STOP: - if( sce->val2 ) - { - struct block_list* tbl = map_id2bl(sce->val2); - sce->val2 = 0; - if( tbl && (sc = status_get_sc(tbl)) && sc->data[SC_STOP] && sc->data[SC_STOP]->val2 == bl->id ) - status_change_end(tbl, SC_STOP, INVALID_TIMER); - } - break; - /** - * 3rd Stuff - **/ - case SC_MILLENNIUMSHIELD: - clif_millenniumshield(sd,0); - break; - case SC_HALLUCINATIONWALK: - sc_start(bl,SC_HALLUCINATIONWALK_POSTDELAY,100,sce->val1,skill_get_time2(GC_HALLUCINATIONWALK,sce->val1)); - break; - case SC_WHITEIMPRISON: - { - struct block_list* src = map_id2bl(sce->val2); - if( tid == -1 || !src) - break; // Terminated by Damage - status_fix_damage(src,bl,400*sce->val1,clif_damage(bl,bl,gettick(),0,0,400*sce->val1,0,0,0)); - } - break; - case SC_WUGDASH: - { - struct unit_data *ud = unit_bl2ud(bl); - if (ud) { - ud->state.running = 0; - if (ud->walktimer != -1) - unit_stop_walking(bl,1); - } - } - break; - case SC_ADORAMUS: - status_change_end(bl, SC_BLIND, INVALID_TIMER); - break; - case SC__SHADOWFORM: { - struct map_session_data *s_sd = map_id2sd(sce->val2); - if( !s_sd ) - break; - s_sd->shadowform_id = 0; - } - break; - case SC_SITDOWN_FORCE: - if( sd && pc_issit(sd) ) { - pc_setstand(sd); - clif_standing(bl); - } - break; - case SC_NEUTRALBARRIER_MASTER: - case SC_STEALTHFIELD_MASTER: - if( sce->val2 ) { - struct skill_unit_group* group = skill_id2group(sce->val2); - sce->val2 = 0; - if( group ) /* might have been cleared before status ended, e.g. land protector */ - skill_delunitgroup(group); - } - break; - case SC_BANDING: - if(sce->val4) { - struct skill_unit_group *group = skill_id2group(sce->val4); - sce->val4 = 0; - if( group ) /* might have been cleared before status ended, e.g. land protector */ - skill_delunitgroup(group); - } - break; - case SC_CURSEDCIRCLE_ATKER: - if( sce->val2 ) // used the default area size cause there is a chance the caster could knock back and can't clear the target. - map_foreachinrange(status_change_timer_sub, bl, battle_config.area_size,BL_CHAR, bl, sce, SC_CURSEDCIRCLE_TARGET, gettick()); - break; - case SC_RAISINGDRAGON: - if( sd && sce->val2 && !pc_isdead(sd) ) { - int i; - i = min(sd->spiritball,5); - pc_delspiritball(sd, sd->spiritball, 0); - status_change_end(bl, SC_EXPLOSIONSPIRITS, INVALID_TIMER); - while( i > 0 ) { - pc_addspiritball(sd, skill_get_time(MO_CALLSPIRITS, pc_checkskill(sd,MO_CALLSPIRITS)), 5); - --i; - } - } - break; - case SC_CURSEDCIRCLE_TARGET: - { - struct block_list *src = map_id2bl(sce->val2); - struct status_change *sc = status_get_sc(src); - if( sc && sc->data[SC_CURSEDCIRCLE_ATKER] && --(sc->data[SC_CURSEDCIRCLE_ATKER]->val2) == 0 ){ - status_change_end(src, SC_CURSEDCIRCLE_ATKER, INVALID_TIMER); - clif_bladestop(bl, sce->val2, 0); - } - } - break; - case SC_BLOODSUCKER: - if( sce->val2 ){ - struct block_list *src = map_id2bl(sce->val2); - if(src){ - struct status_change *sc = status_get_sc(src); - sc->bs_counter--; - } - } - break; - case SC_VACUUM_EXTREME: - if(sc && sc->cant.move > 0) sc->cant.move--; - break; - case SC_KYOUGAKU: - clif_status_load(bl, SI_KYOUGAKU, 0); // Avoid client crash - clif_status_load(bl, SI_ACTIVE_MONSTER_TRANSFORM, 0); - break; - case SC_INTRAVISION: - calc_flag = SCB_ALL;/* required for overlapping */ - break; - } - - opt_flag = 1; - switch(type){ - case SC_STONE: - case SC_FREEZE: - case SC_STUN: - case SC_SLEEP: - case SC_DEEPSLEEP: - case SC_BURNING: - case SC_WHITEIMPRISON: - case SC_CRYSTALIZE: - sc->opt1 = 0; - break; - - case SC_POISON: - case SC_CURSE: - case SC_SILENCE: - case SC_BLIND: - sc->opt2 &= ~(1<<(type-SC_POISON)); - break; - case SC_DPOISON: - sc->opt2 &= ~OPT2_DPOISON; - break; - case SC_SIGNUMCRUCIS: - sc->opt2 &= ~OPT2_SIGNUMCRUCIS; - break; - - case SC_HIDING: - sc->option &= ~OPTION_HIDE; - opt_flag|= 2|4; //Check for warp trigger + AoE trigger - break; - case SC_CLOAKING: - case SC_CLOAKINGEXCEED: - case SC__INVISIBILITY: - sc->option &= ~OPTION_CLOAK; - case SC_CAMOUFLAGE: - opt_flag|= 2; - break; - case SC_CHASEWALK: - sc->option &= ~(OPTION_CHASEWALK|OPTION_CLOAK); - opt_flag|= 2; - break; - case SC_SIGHT: - sc->option &= ~OPTION_SIGHT; - break; - case SC_WEDDING: - sc->option &= ~OPTION_WEDDING; - break; - case SC_XMAS: - sc->option &= ~OPTION_XMAS; - break; - case SC_SUMMER: - sc->option &= ~OPTION_SUMMER; - break; - case SC_ORCISH: - sc->option &= ~OPTION_ORCISH; - break; - case SC_RUWACH: - sc->option &= ~OPTION_RUWACH; - break; - case SC_FUSION: - sc->option &= ~OPTION_FLYING; - break; - //opt3 - case SC_TWOHANDQUICKEN: - case SC_ONEHAND: - case SC_SPEARQUICKEN: - case SC_CONCENTRATION: - case SC_MERC_QUICKEN: - sc->opt3 &= ~OPT3_QUICKEN; - opt_flag = 0; - break; - case SC_OVERTHRUST: - case SC_MAXOVERTHRUST: - case SC_SWOO: - sc->opt3 &= ~OPT3_OVERTHRUST; - if( type == SC_SWOO ) - opt_flag = 8; - else - opt_flag = 0; - break; - case SC_ENERGYCOAT: - case SC_SKE: - sc->opt3 &= ~OPT3_ENERGYCOAT; - opt_flag = 0; - break; - case SC_INCATKRATE: //Simulated Explosion spirits effect. - if (bl->type != BL_MOB) - { - opt_flag = 0; - break; - } - case SC_EXPLOSIONSPIRITS: - sc->opt3 &= ~OPT3_EXPLOSIONSPIRITS; - opt_flag = 0; - break; - case SC_STEELBODY: - case SC_SKA: - sc->opt3 &= ~OPT3_STEELBODY; - opt_flag = 0; - break; - case SC_BLADESTOP: - sc->opt3 &= ~OPT3_BLADESTOP; - opt_flag = 0; - break; - case SC_AURABLADE: - sc->opt3 &= ~OPT3_AURABLADE; - opt_flag = 0; - break; - case SC_BERSERK: - opt_flag = 0; -// case SC__BLOODYLUST: - sc->opt3 &= ~OPT3_BERSERK; - break; -// case ???: // doesn't seem to do anything -// sc->opt3 &= ~OPT3_LIGHTBLADE; -// opt_flag = 0; -// break; - case SC_DANCING: - if ((sce->val1&0xFFFF) == CG_MOONLIT) - sc->opt3 &= ~OPT3_MOONLIT; - opt_flag = 0; - break; - case SC_MARIONETTE: - case SC_MARIONETTE2: - sc->opt3 &= ~OPT3_MARIONETTE; - opt_flag = 0; - break; - case SC_ASSUMPTIO: - sc->opt3 &= ~OPT3_ASSUMPTIO; - opt_flag = 0; - break; - case SC_WARM: //SG skills [Komurka] - sc->opt3 &= ~OPT3_WARM; - opt_flag = 0; - break; - case SC_KAITE: - sc->opt3 &= ~OPT3_KAITE; - opt_flag = 0; - break; - case SC_BUNSINJYUTSU: - sc->opt3 &= ~OPT3_BUNSIN; - opt_flag = 0; - break; - case SC_SPIRIT: - sc->opt3 &= ~OPT3_SOULLINK; - opt_flag = 0; - break; - case SC_CHANGEUNDEAD: - sc->opt3 &= ~OPT3_UNDEAD; - opt_flag = 0; - break; -// case ???: // from DA_CONTRACT (looks like biolab mobs aura) -// sc->opt3 &= ~OPT3_CONTRACT; -// opt_flag = 0; -// break; - default: - opt_flag = 0; - } - - if (calc_flag&SCB_DYE) - { //Restore DYE color - if (vd && !vd->cloth_color && sce->val4) - clif_changelook(bl,LOOK_CLOTHES_COLOR,sce->val4); - calc_flag&=~SCB_DYE; - } - - //On Aegis, when turning off a status change, first goes the sc packet, then the option packet. - clif_status_change(bl,StatusIconChangeTable[type],0,0,0,0,0); - - if( opt_flag&8 ) //bugreport:681 - clif_changeoption2(bl); - else if(opt_flag) - clif_changeoption(bl); - - if (calc_flag) - status_calc_bl(bl,calc_flag); - - if(opt_flag&4) //Out of hiding, invoke on place. - skill_unit_move(bl,gettick(),1); - - if(opt_flag&2 && sd && map_getcell(bl->m,bl->x,bl->y,CELL_CHKNPC)) - npc_touch_areanpc(sd,bl->m,bl->x,bl->y); //Trigger on-touch event. - - ers_free(sc_data_ers, sce); - return 1; -} - -int kaahi_heal_timer(int tid, unsigned int tick, int id, intptr_t data) -{ - struct block_list *bl; - struct status_change *sc; - struct status_change_entry *sce; - struct status_data *status; - int hp; - - if(!((bl=map_id2bl(id))&& - (sc=status_get_sc(bl)) && - (sce = sc->data[SC_KAAHI]))) - return 0; - - if(sce->val4 != tid) { - ShowError("kaahi_heal_timer: Timer mismatch: %d != %d\n", tid, sce->val4); - sce->val4 = INVALID_TIMER; - return 0; - } - - status=status_get_status_data(bl); - if(!status_charge(bl, 0, sce->val3)) { - sce->val4 = INVALID_TIMER; - return 0; - } - - hp = status->max_hp - status->hp; - if (hp > sce->val2) - hp = sce->val2; - if (hp) - status_heal(bl, hp, 0, 2); - sce->val4 = INVALID_TIMER; - return 1; -} - -/*========================================== - * For recusive status, like for each 5s we drop sp etc. - * Reseting the end timer. - *------------------------------------------*/ -int status_change_timer(int tid, unsigned int tick, int id, intptr_t data) -{ - enum sc_type type = (sc_type)data; - struct block_list *bl; - struct map_session_data *sd; - struct status_data *status; - struct status_change *sc; - struct status_change_entry *sce; - - bl = map_id2bl(id); - if(!bl) - { - ShowDebug("status_change_timer: Null pointer id: %d data: %d\n", id, data); - return 0; - } - sc = status_get_sc(bl); - status = status_get_status_data(bl); - - if(!(sc && (sce = sc->data[type]))) - { - ShowDebug("status_change_timer: Null pointer id: %d data: %d bl-type: %d\n", id, data, bl->type); - return 0; - } - - if( sce->timer != tid ) - { - ShowError("status_change_timer: Mismatch for type %d: %d != %d (bl id %d)\n",type,tid,sce->timer, bl->id); - return 0; - } - - sd = BL_CAST(BL_PC, bl); - -// set the next timer of the sce (don't assume the status still exists) -#define sc_timer_next(t,f,i,d) \ - if( (sce=sc->data[type]) ) \ - sce->timer = add_timer(t,f,i,d); \ - else \ - ShowError("status_change_timer: Unexpected NULL status change id: %d data: %d\n", id, data) - - switch(type) - { - case SC_MAXIMIZEPOWER: - case SC_CLOAKING: - if(!status_charge(bl, 0, 1)) - break; //Not enough SP to continue. - sc_timer_next(sce->val2+tick, status_change_timer, bl->id, data); - return 0; - - case SC_CHASEWALK: - if(!status_charge(bl, 0, sce->val4)) - break; //Not enough SP to continue. - - if (!sc->data[SC_INCSTR]) { - sc_start(bl, SC_INCSTR,100,1<<(sce->val1-1), - (sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_ROGUE?10:1) //SL bonus -> x10 duration - *skill_get_time2(status_sc2skill(type),sce->val1)); - } - sc_timer_next(sce->val2+tick, status_change_timer, bl->id, data); - return 0; - break; - - case SC_SKA: - if(--(sce->val2)>0){ - sce->val3 = rnd()%100; //Random defense. - sc_timer_next(1000+tick, status_change_timer,bl->id, data); - return 0; - } - break; - - case SC_HIDING: - if(--(sce->val2)>0){ - - if(sce->val2 % sce->val4 == 0 && !status_charge(bl, 0, 1)) - break; //Fail if it's time to substract SP and there isn't. - - sc_timer_next(1000+tick, status_change_timer,bl->id, data); - return 0; - } - break; - - case SC_SIGHT: - case SC_RUWACH: - case SC_SIGHTBLASTER: - if(type == SC_SIGHTBLASTER) - map_foreachinrange( status_change_timer_sub, bl, sce->val3, BL_CHAR|BL_SKILL, bl, sce, type, tick); - else - map_foreachinrange( status_change_timer_sub, bl, sce->val3, BL_CHAR, bl, sce, type, tick); - - if( --(sce->val2)>0 ){ - sce->val4 += 250; // use for Shadow Form 2 seconds checking. - sc_timer_next(250+tick, status_change_timer, bl->id, data); - return 0; - } - break; - - case SC_PROVOKE: - if(sce->val2) { //Auto-provoke (it is ended in status_heal) - sc_timer_next(1000*60+tick,status_change_timer, bl->id, data ); - return 0; - } - break; - - case SC_STONE: - if(sc->opt1 == OPT1_STONEWAIT && sce->val3) { - sce->val4 = 0; - unit_stop_walking(bl,1); - unit_stop_attack(bl); - sc->opt1 = OPT1_STONE; - clif_changeoption(bl); - sc_timer_next(1000+tick,status_change_timer, bl->id, data ); - status_calc_bl(bl, StatusChangeFlagTable[type]); - return 0; - } - if(--(sce->val3) > 0) { - if(++(sce->val4)%5 == 0 && status->hp > status->max_hp/4) - status_percent_damage(NULL, bl, 1, 0, false); - sc_timer_next(1000+tick,status_change_timer, bl->id, data ); - return 0; - } - break; - - case SC_POISON: - if(status->hp <= max(status->max_hp>>2, sce->val4)) //Stop damaging after 25% HP left. - break; - case SC_DPOISON: - if (--(sce->val3) > 0) { - if (!sc->data[SC_SLOWPOISON]) { - if( sce->val2 && bl->type == BL_MOB ) { - struct block_list* src = map_id2bl(sce->val2); - if( src ) - mob_log_damage((TBL_MOB*)bl,src,sce->val4); - } - map_freeblock_lock(); - status_zap(bl, sce->val4, 0); - if (sc->data[type]) { // Check if the status still last ( can be dead since then ). - sc_timer_next(1000 + tick, status_change_timer, bl->id, data ); - } - map_freeblock_unlock(); - } - return 0; - } - break; - - case SC_TENSIONRELAX: - if(status->max_hp > status->hp && --(sce->val3) > 0){ - sc_timer_next(sce->val4+tick, status_change_timer, bl->id, data); - return 0; - } - break; - - case SC_KNOWLEDGE: - if (!sd) break; - if(bl->m == sd->feel_map[0].m || - bl->m == sd->feel_map[1].m || - bl->m == sd->feel_map[2].m) - { //Timeout will be handled by pc_setpos - sce->timer = INVALID_TIMER; - return 0; - } - break; - - case SC_BLEEDING: - if (--(sce->val4) >= 0) { - int hp = rnd()%600 + 200; - map_freeblock_lock(); - status_fix_damage(NULL, bl, sd||hp<status->hp?hp:status->hp-1, 1); - if( sc->data[type] ) { - if( status->hp == 1 ) { - map_freeblock_unlock(); - break; - } - sc_timer_next(10000 + tick, status_change_timer, bl->id, data); - } - map_freeblock_unlock(); - return 0; - } - break; - - case SC_S_LIFEPOTION: - case SC_L_LIFEPOTION: - if( sd && --(sce->val4) >= 0 ) - { - // val1 < 0 = per max% | val1 > 0 = exact amount - int hp = 0; - if( status->hp < status->max_hp ) - hp = (sce->val1 < 0) ? (int)(sd->status.max_hp * -1 * sce->val1 / 100.) : sce->val1 ; - status_heal(bl, hp, 0, 2); - sc_timer_next((sce->val2 * 1000) + tick, status_change_timer, bl->id, data); - return 0; - } - break; - - case SC_BOSSMAPINFO: - if( sd && --(sce->val4) >= 0 ) - { - struct mob_data *boss_md = map_id2boss(sce->val1); - if( boss_md && sd->bl.m == boss_md->bl.m ) - { - clif_bossmapinfo(sd->fd, boss_md, 1); // Update X - Y on minimap - if (boss_md->bl.prev != NULL) { - sc_timer_next(5000 + tick, status_change_timer, bl->id, data); - return 0; - } - } - } - break; - - case SC_DANCING: //SP consumption by time of dancing skills - { - int s = 0; - int sp = 1; - if (--sce->val3 <= 0) - break; - switch(sce->val1&0xFFFF){ - case BD_RICHMANKIM: - case BD_DRUMBATTLEFIELD: - case BD_RINGNIBELUNGEN: - case BD_SIEGFRIED: - case BA_DISSONANCE: - case BA_ASSASSINCROSS: - case DC_UGLYDANCE: - s=3; - break; - case BD_LULLABY: - case BD_ETERNALCHAOS: - case BD_ROKISWEIL: - case DC_FORTUNEKISS: - s=4; - break; - case CG_HERMODE: - case BD_INTOABYSS: - case BA_WHISTLE: - case DC_HUMMING: - case BA_POEMBRAGI: - case DC_SERVICEFORYOU: - s=5; - break; - case BA_APPLEIDUN: - #ifdef RENEWAL - s=5; - #else - s=6; - #endif - break; - case CG_MOONLIT: - //Moonlit's cost is 4sp*skill_lv [Skotlex] - sp= 4*(sce->val1>>16); - //Upkeep is also every 10 secs. - case DC_DONTFORGETME: - s=10; - break; - } - if( s != 0 && sce->val3 % s == 0 ) - { - if (sc->data[SC_LONGING]) - sp*= 3; - if (!status_charge(bl, 0, sp)) - break; - } - sc_timer_next(1000+tick, status_change_timer, bl->id, data); - return 0; - } - break; - case SC__BLOODYLUST: - case SC_BERSERK: - // 5% every 10 seconds [DracoRPG] - if( --( sce->val3 ) > 0 && status_charge(bl, sce->val2, 0) && status->hp > 100 ) - { - sc_timer_next(sce->val4+tick, status_change_timer, bl->id, data); - return 0; - } - break; - - case SC_NOCHAT: - if(sd){ - sd->status.manner++; - clif_changestatus(sd,SP_MANNER,sd->status.manner); - clif_updatestatus(sd,SP_MANNER); - if (sd->status.manner < 0) - { //Every 60 seconds your manner goes up by 1 until it gets back to 0. - sc_timer_next(60000+tick, status_change_timer, bl->id, data); - return 0; - } - } - break; - - case SC_SPLASHER: - // custom Venom Splasher countdown timer - //if (sce->val4 % 1000 == 0) { - // char timer[10]; - // snprintf (timer, 10, "%d", sce->val4/1000); - // clif_message(bl, timer); - //} - if((sce->val4 -= 500) > 0) { - sc_timer_next(500 + tick, status_change_timer, bl->id, data); - return 0; - } - break; - - case SC_MARIONETTE: - case SC_MARIONETTE2: - { - struct block_list *pbl = map_id2bl(sce->val1); - if( pbl && check_distance_bl(bl, pbl, 7) ) - { - sc_timer_next(1000 + tick, status_change_timer, bl->id, data); - return 0; - } - } - break; - - case SC_GOSPEL: - if(sce->val4 == BCT_SELF && --(sce->val2) > 0) - { - int hp, sp; - hp = (sce->val1 > 5) ? 45 : 30; - sp = (sce->val1 > 5) ? 35 : 20; - if(!status_charge(bl, hp, sp)) - break; - sc_timer_next(10000+tick, status_change_timer, bl->id, data); - return 0; - } - break; - - case SC_JAILED: - if(sce->val1 == INT_MAX || --(sce->val1) > 0) - { - sc_timer_next(60000+tick, status_change_timer, bl->id,data); - return 0; - } - break; - - case SC_BLIND: - if(sc->data[SC_FOGWALL]) - { //Blind lasts forever while you are standing on the fog. - sc_timer_next(5000+tick, status_change_timer, bl->id, data); - return 0; - } - break; - case SC_ABUNDANCE: - if(--(sce->val4) > 0) { - status_heal(bl,0,60,0); - sc_timer_next(10000+tick, status_change_timer, bl->id, data); - } - break; - - case SC_PYREXIA: - if( --(sce->val4) >= 0 ) { - map_freeblock_lock(); - clif_damage(bl,bl,tick,status_get_amotion(bl),status_get_dmotion(bl)+500,100,0,0,0); - status_fix_damage(NULL,bl,100,0); - if( sc->data[type] ) { - sc_timer_next(3000+tick,status_change_timer,bl->id,data); - } - map_freeblock_unlock(); - return 0; - } - break; - - case SC_LEECHESEND: - if( --(sce->val4) >= 0 ) { - int damage = status->max_hp/100; // {Target VIT x (New Poison Research Skill Level - 3)} + (Target HP/100) - damage += status->vit * (sce->val1 - 3); - unit_skillcastcancel(bl,2); - map_freeblock_lock(); - status_damage(bl, bl, damage, 0, clif_damage(bl,bl,tick,status_get_amotion(bl),status_get_dmotion(bl)+500,damage,1,0,0), 1); - if( sc->data[type] ) { - sc_timer_next(1000 + tick, status_change_timer, bl->id, data ); - } - map_freeblock_unlock(); - return 0; - } - break; - - case SC_MAGICMUSHROOM: - if( --(sce->val4) >= 0 ) { - bool flag = 0; - int damage = status->max_hp * 3 / 100; - if( status->hp <= damage ) - damage = status->hp - 1; // Cannot Kill - - if( damage > 0 ) { // 3% Damage each 4 seconds - map_freeblock_lock(); - status_zap(bl,damage,0); - flag = !sc->data[type]; // Killed? Should not - map_freeblock_unlock(); - } - - if( !flag ) { // Random Skill Cast - if (sd && !pc_issit(sd)) { //can't cast if sit - int mushroom_skill_id = 0, i; - unit_stop_attack(bl); - unit_skillcastcancel(bl,1); - do { - i = rnd() % MAX_SKILL_MAGICMUSHROOM_DB; - mushroom_skill_id = skill_magicmushroom_db[i].skill_id; - } - while( mushroom_skill_id == 0 ); - - switch( skill_get_casttype(mushroom_skill_id) ) { // Magic Mushroom skills are buffs or area damage - case CAST_GROUND: - skill_castend_pos2(bl,bl->x,bl->y,mushroom_skill_id,1,tick,0); - break; - case CAST_NODAMAGE: - skill_castend_nodamage_id(bl,bl,mushroom_skill_id,1,tick,0); - break; - case CAST_DAMAGE: - skill_castend_damage_id(bl,bl,mushroom_skill_id,1,tick,0); - break; - } - } - - clif_emotion(bl,E_HEH); - sc_timer_next(4000+tick,status_change_timer,bl->id,data); - } - return 0; - } - break; - - case SC_TOXIN: - if( --(sce->val4) >= 0 ) - { //Damage is every 10 seconds including 3%sp drain. - map_freeblock_lock(); - clif_damage(bl,bl,tick,status_get_amotion(bl),1,1,0,0,0); - status_damage(NULL, bl, 1, status->max_sp * 3 / 100, 0, 0); //cancel dmg only if cancelable - if( sc->data[type] ) { - sc_timer_next(10000 + tick, status_change_timer, bl->id, data ); - } - map_freeblock_unlock(); - return 0; - } - break; - - case SC_OBLIVIONCURSE: - if( --(sce->val4) >= 0 ) - { - clif_emotion(bl,E_WHAT); - sc_timer_next(3000 + tick, status_change_timer, bl->id, data ); - return 0; - } - break; - - case SC_WEAPONBLOCKING: - if( --(sce->val4) >= 0 ) - { - if( !status_charge(bl,0,3) ) - break; - sc_timer_next(3000+tick,status_change_timer,bl->id,data); - return 0; - } - break; - - case SC_CLOAKINGEXCEED: - if(!status_charge(bl,0,10-sce->val1)) - break; - sc_timer_next(1000 + tick, status_change_timer, bl->id, data); - return 0; - - case SC_RENOVATIO: - if( --(sce->val4) >= 0 ) - { - int heal = status->max_hp * 3 / 100; - if( sc && sc->data[SC_AKAITSUKI] && heal ) - heal = ~heal + 1; - status_heal(bl, heal, 0, 2); - sc_timer_next(5000 + tick, status_change_timer, bl->id, data); - return 0; - } - break; - - case SC_BURNING: - if( --(sce->val4) >= 0 ) - { - struct block_list *src = map_id2bl(sce->val3); - int damage = 1000 + 3 * status_get_max_hp(bl) / 100; // Deals fixed (1000 + 3%*MaxHP) - - map_freeblock_lock(); - clif_damage(bl,bl,tick,0,0,damage,1,9,0); //damage is like endure effect with no walk delay - status_damage(src, bl, damage, 0, 0, 1); - - if( sc->data[type]){ // Target still lives. [LimitLine] - sc_timer_next(2000 + tick, status_change_timer, bl->id, data); - } - map_freeblock_unlock(); - return 0; - } - break; - - case SC_FEAR: - if( --(sce->val4) >= 0 ) - { - if( sce->val2 > 0 ) - sce->val2--; - sc_timer_next(1000 + tick, status_change_timer, bl->id, data); - return 0; - } - break; - - case SC_SPHERE_1: - case SC_SPHERE_2: - case SC_SPHERE_3: - case SC_SPHERE_4: - case SC_SPHERE_5: - if( --(sce->val4) >= 0 ) - { - if( !status_charge(bl, 0, 1) ) - break; - sc_timer_next(1000 + tick, status_change_timer, bl->id, data); - return 0; - } - break; - - case SC_READING_SB: - if( !status_charge(bl, 0, sce->val2) ){ - int i; - for(i = SC_SPELLBOOK1; i <= SC_MAXSPELLBOOK; i++) // Also remove stored spell as well. - status_change_end(bl, (sc_type)i, INVALID_TIMER); - break; - } - sc_timer_next(5000 + tick, status_change_timer, bl->id, data); - return 0; - - case SC_ELECTRICSHOCKER: - if( --(sce->val4) >= 0 ) - { - status_charge(bl, 0, status->max_sp / 100 * sce->val1 ); - sc_timer_next(1000 + tick, status_change_timer, bl->id, data); - return 0; - } - break; - - case SC_CAMOUFLAGE: - if(--(sce->val4) > 0){ - status_charge(bl,0,7 - sce->val1); - sc_timer_next(1000 + tick, status_change_timer, bl->id, data); - return 0; - } - break; - - case SC__REPRODUCE: - if(!status_charge(bl, 0, 1)) - break; - sc_timer_next(1000+tick, status_change_timer, bl->id, data); - return 0; - - case SC__SHADOWFORM: - if( --(sce->val4) >= 0 ) - { - if( !status_charge(bl, 0, sce->val1 - (sce->val1 - 1)) ) - break; - sc_timer_next(1000 + tick, status_change_timer, bl->id, data); - return 0; - } - break; - - case SC__INVISIBILITY: - if( --(sce->val4) >= 0 ) - { - if( !status_charge(bl, 0, (status->sp * 6 - sce->val1) / 100) )// 6% - skill_lv. - break; - sc_timer_next(1000 + tick, status_change_timer, bl->id, data); - return 0; - } - break; - - case SC_STRIKING: - if( --(sce->val4) >= 0 ) - { - if( !status_charge(bl,0, sce->val1 ) ) - break; - sc_timer_next(1000 + tick, status_change_timer, bl->id, data); - return 0; - } - break; - case SC_VACUUM_EXTREME: - if( --(sce->val4) >= 0 ){ - if( !unit_is_walking(bl) && !sce->val2 ){ - sc->cant.move++; - sce->val2 = 1; - } - sc_timer_next(100 + tick, status_change_timer, bl->id, data); - return 0; - } - break; - case SC_BLOODSUCKER: - if( --(sce->val4) >= 0 ) { - struct block_list *src = map_id2bl(sce->val2); - int damage; - if( !src || (src && (status_isdead(src) || src->m != bl->m || distance_bl(src, bl) >= 12)) ) - break; - map_freeblock_lock(); - damage = 200 + 100 * sce->val1 + status_get_int(src); - status_damage(src, bl, damage, 0, clif_damage(bl,bl,tick,status->amotion,status->dmotion+200,damage,1,0,0), 1); - unit_skillcastcancel(bl,1); - if ( sc->data[type] ) { - sc_timer_next(1000 + tick, status_change_timer, bl->id, data); - } - map_freeblock_unlock(); - status_heal(src, damage*(5 + 5 * sce->val1)/100, 0, 0); // 5 + 5% per level - return 0; - } - break; - - case SC_VOICEOFSIREN: - if( --(sce->val4) >= 0 ) - { - clif_emotion(bl,E_LV); - sc_timer_next(2000 + tick, status_change_timer, bl->id, data); - return 0; - } - break; - - case SC_DEEPSLEEP: - if( --(sce->val4) >= 0 ) - { // Recovers 1% HP/SP every 2 seconds. - status_heal(bl, status->max_hp / 100, status->max_sp / 100, 2); - sc_timer_next(2000 + tick, status_change_timer, bl->id, data); - return 0; - } - break; - - case SC_SIRCLEOFNATURE: - if( --(sce->val4) >= 0 ) - { - if( !status_charge(bl,0,sce->val2) ) - break; - status_heal(bl, sce->val3, 0, 1); - sc_timer_next(1000 + tick, status_change_timer, bl->id, data); - return 0; - } - break; - - case SC_SONGOFMANA: - if( --(sce->val4) >= 0 ) - { - status_heal(bl,0,sce->val3,3); - sc_timer_next(3000 + tick, status_change_timer, bl->id, data); - return 0; - } - break; - - - case SC_SATURDAYNIGHTFEVER: - // 1% HP/SP drain every val4 seconds [Jobbie] - if( --(sce->val3) >= 0 ) - { - int hp = status->hp / 100; - int sp = status->sp / 100; - if( !status_charge(bl, hp, sp) ) - break; - sc_timer_next(sce->val4+tick, status_change_timer, bl->id, data); - return 0; - } - break; - - case SC_CRYSTALIZE: - if( --(sce->val4) >= 0 ) - { // Drains 2% of HP and 1% of SP every seconds. - if( bl->type != BL_MOB) // doesn't work on mobs - status_charge(bl, status->max_hp * 2 / 100, status->max_sp / 100); - sc_timer_next(1000 + tick, status_change_timer, bl->id, data); - return 0; - } - break; - - case SC_FORCEOFVANGUARD: - if( !status_charge(bl,0,20) ) - break; - sc_timer_next(6000 + tick, status_change_timer, bl->id, data); - return 0; - - case SC_BANDING: - if( status_charge(bl, 0, 7 - sce->val1) ) - { - if( sd ) pc_banding(sd, sce->val1); - sc_timer_next(5000 + tick, status_change_timer, bl->id, data); - return 0; - } - break; - - case SC_REFLECTDAMAGE: - if( --(sce->val4) >= 0 ) { - if( !status_charge(bl,0,sce->val3) ) - break; - sc_timer_next(10000 + tick, status_change_timer, bl->id, data); - return 0; - } - break; - - case SC_OVERHEAT_LIMITPOINT: - if( --(sce->val1) > 0 ) { // Cooling - sc_timer_next(30000 + tick, status_change_timer, bl->id, data); - } - break; - - case SC_OVERHEAT: - { - int damage = status->max_hp / 100; // Suggestion 1% each second - if( damage >= status->hp ) damage = status->hp - 1; // Do not kill, just keep you with 1 hp minimum - map_freeblock_lock(); - status_fix_damage(NULL,bl,damage,clif_damage(bl,bl,tick,0,0,damage,0,0,0)); - if( sc->data[type] ) { - sc_timer_next(1000 + tick, status_change_timer, bl->id, data); - } - map_freeblock_unlock(); - } - break; - - case SC_MAGNETICFIELD: - { - if( --(sce->val3) <= 0 ) - break; // Time out - if( sce->val2 == bl->id ) - { - if( !status_charge(bl,0,14 + (3 * sce->val1)) ) - break; // No more SP status should end, and in the next second will end for the other affected players - } - else - { - struct block_list *src = map_id2bl(sce->val2); - struct status_change *ssc; - if( !src || (ssc = status_get_sc(src)) == NULL || !ssc->data[SC_MAGNETICFIELD] ) - break; // Source no more under Magnetic Field - } - sc_timer_next(1000 + tick, status_change_timer, bl->id, data); - } - break; - - case SC_INSPIRATION: - if(--(sce->val4) >= 0) - { - int hp = status->max_hp * (7-sce->val1) / 100; - int sp = status->max_sp * (9-sce->val1) / 100; - - if( !status_charge(bl,hp,sp) ) break; - - sc_timer_next(1000+tick,status_change_timer,bl->id, data); - return 0; - } - break; - - case SC_RAISINGDRAGON: - // 1% every 5 seconds [Jobbie] - if( --(sce->val3)>0 && status_charge(bl, sce->val2, 0) ) - { - if( !sc->data[type] ) return 0; - sc_timer_next(5000 + tick, status_change_timer, bl->id, data); - return 0; - } - break; - - case SC_CIRCLE_OF_FIRE: - case SC_FIRE_CLOAK: - case SC_WATER_DROP: - case SC_WATER_SCREEN: - case SC_WIND_CURTAIN: - case SC_WIND_STEP: - case SC_STONE_SHIELD: - case SC_SOLID_SKIN: - if( !status_charge(bl,0,sce->val2) ){ - struct block_list *s_bl = battle_get_master(bl); - if( s_bl ) - status_change_end(s_bl,type+1,INVALID_TIMER); - status_change_end(bl,type,INVALID_TIMER); - break; - } - sc_timer_next(2000 + tick, status_change_timer, bl->id, data); - return 0; - - case SC_STOMACHACHE: - if( --(sce->val4) > 0 ){ - status_charge(bl,0,sce->val2); // Reduce 8 every 10 seconds. - if( sd && !pc_issit(sd) ) // Force to sit every 10 seconds. - { - pc_stop_walking(sd,1|4); - pc_stop_attack(sd); - pc_setsit(sd); - clif_sitting(bl); - } - sc_timer_next(10000 + tick, status_change_timer, bl->id, data); - return 0; - } - break; - case SC_LEADERSHIP: - case SC_GLORYWOUNDS: - case SC_SOULCOLD: - case SC_HAWKEYES: - /* they only end by status_change_end */ - sc_timer_next(600000 + tick, status_change_timer, bl->id, data); - return 0; - case SC_MEIKYOUSISUI: - if( --(sce->val4) > 0 ){ - status_heal(bl, status->max_hp * (sce->val1+1) / 100, status->max_sp * sce->val1 / 100, 0); - sc_timer_next(1000 + tick, status_change_timer, bl->id, data); - return 0; - } - break; - case SC_IZAYOI: - case SC_KAGEMUSYA: - if( --(sce->val2) > 0 ){ - if(!status_charge(bl, 0, 1)) break; - sc_timer_next(1000+tick, status_change_timer, bl->id, data); - return 0; - } - break; - case SC_ANGRIFFS_MODUS: - if(--(sce->val4) >= 0) { //drain hp/sp - if( !status_charge(bl,100,20) ) break; - sc_timer_next(1000+tick,status_change_timer,bl->id, data); - return 0; - } - break; - } - - // default for all non-handled control paths is to end the status - return status_change_end( bl,type,tid ); -#undef sc_timer_next -} - -/*========================================== - * Foreach iteration of repetitive status - *------------------------------------------*/ -int status_change_timer_sub(struct block_list* bl, va_list ap) -{ - struct status_change* tsc; - - struct block_list* src = va_arg(ap,struct block_list*); - struct status_change_entry* sce = va_arg(ap,struct status_change_entry*); - enum sc_type type = (sc_type)va_arg(ap,int); //gcc: enum args get promoted to int - unsigned int tick = va_arg(ap,unsigned int); - - if (status_isdead(bl)) - return 0; - - tsc = status_get_sc(bl); - - switch( type ) { - case SC_SIGHT: /* Reveal hidden ennemy on 3*3 range */ - if( tsc && tsc->data[SC__SHADOWFORM] && (sce && sce->val4 >0 && sce->val4%2000 == 0) && // for every 2 seconds do the checking - rnd()%100 < 100-tsc->data[SC__SHADOWFORM]->val1*10 ) // [100 - (Skill Level x 10)] % - status_change_end(bl, SC__SHADOWFORM, INVALID_TIMER); - case SC_CONCENTRATE: - status_change_end(bl, SC_HIDING, INVALID_TIMER); - status_change_end(bl, SC_CLOAKING, INVALID_TIMER); - status_change_end(bl, SC_CLOAKINGEXCEED, INVALID_TIMER); - status_change_end(bl, SC_CAMOUFLAGE, INVALID_TIMER); - status_change_end(bl, SC__INVISIBILITY, INVALID_TIMER); - break; - case SC_RUWACH: /* Reveal hidden target and deal little dammages if ennemy */ - if (tsc && (tsc->data[SC_HIDING] || tsc->data[SC_CLOAKING] || - tsc->data[SC_CAMOUFLAGE] || tsc->data[SC_CLOAKINGEXCEED] || - tsc->data[SC__INVISIBILITY])) { - status_change_end(bl, SC_HIDING, INVALID_TIMER); - status_change_end(bl, SC_CLOAKING, INVALID_TIMER); - status_change_end(bl, SC_CAMOUFLAGE, INVALID_TIMER); - status_change_end(bl, SC_CLOAKINGEXCEED, INVALID_TIMER); - status_change_end(bl, SC__INVISIBILITY, INVALID_TIMER); - if(battle_check_target( src, bl, BCT_ENEMY ) > 0) - skill_attack(BF_MAGIC,src,src,bl,AL_RUWACH,1,tick,0); - } - if( tsc && tsc->data[SC__SHADOWFORM] && (sce && sce->val4 >0 && sce->val4%2000 == 0) && // for every 2 seconds do the checking - rnd()%100 < 100-tsc->data[SC__SHADOWFORM]->val1*10 ) // [100 - (Skill Level x 10)] % - status_change_end(bl, SC__SHADOWFORM, INVALID_TIMER); - break; - case SC_SIGHTBLASTER: - if (battle_check_target( src, bl, BCT_ENEMY ) > 0 && - status_check_skilluse(src, bl, WZ_SIGHTBLASTER, 2)) - { - skill_attack(BF_MAGIC,src,src,bl,WZ_SIGHTBLASTER,1,tick,0); - if (sce && !(bl->type&BL_SKILL)) //The hit is not counted if it's against a trap - sce->val2 = 0; //This signals it to end. - } - break; - case SC_CLOSECONFINE: - //Lock char has released the hold on everyone... - if (tsc && tsc->data[SC_CLOSECONFINE2] && tsc->data[SC_CLOSECONFINE2]->val2 == src->id) { - tsc->data[SC_CLOSECONFINE2]->val2 = 0; - status_change_end(bl, SC_CLOSECONFINE2, INVALID_TIMER); - } - break; - case SC_CURSEDCIRCLE_TARGET: - if( tsc && tsc->data[SC_CURSEDCIRCLE_TARGET] && tsc->data[SC_CURSEDCIRCLE_TARGET]->val2 == src->id ) { - clif_bladestop(bl, tsc->data[SC_CURSEDCIRCLE_TARGET]->val2, 0); - status_change_end(bl, type, INVALID_TIMER); - } - break; - } - return 0; -} - -/*========================================== - * Clears buffs/debuffs of a character. - * type&1 -> buffs, type&2 -> debuffs - * type&4 -> especific debuffs(implemented with refresh) - *------------------------------------------*/ -int status_change_clear_buffs (struct block_list* bl, int type) -{ - int i; - struct status_change *sc= status_get_sc(bl); - - if (!sc || !sc->count) - return 0; - - if (type&6) //Debuffs - for (i = SC_COMMON_MIN; i <= SC_COMMON_MAX; i++) - status_change_end(bl, (sc_type)i, INVALID_TIMER); - - for( i = SC_COMMON_MAX+1; i < SC_MAX; i++ ) - { - if(!sc->data[i]) - continue; - - switch (i) { - //Stuff that cannot be removed - case SC_WEIGHT50: - case SC_WEIGHT90: - case SC_COMBO: - case SC_SMA: - case SC_DANCING: - case SC_LEADERSHIP: - case SC_GLORYWOUNDS: - case SC_SOULCOLD: - case SC_HAWKEYES: - case SC_GUILDAURA: - case SC_SAFETYWALL: - case SC_PNEUMA: - case SC_NOCHAT: - case SC_JAILED: - case SC_ANKLE: - case SC_BLADESTOP: - case SC_CP_WEAPON: - case SC_CP_SHIELD: - case SC_CP_ARMOR: - case SC_CP_HELM: - case SC_STRFOOD: - case SC_AGIFOOD: - case SC_VITFOOD: - case SC_INTFOOD: - case SC_DEXFOOD: - case SC_LUKFOOD: - case SC_HITFOOD: - case SC_FLEEFOOD: - case SC_BATKFOOD: - case SC_WATKFOOD: - case SC_MATKFOOD: - case SC_FOOD_STR_CASH: - case SC_FOOD_AGI_CASH: - case SC_FOOD_VIT_CASH: - case SC_FOOD_DEX_CASH: - case SC_FOOD_INT_CASH: - case SC_FOOD_LUK_CASH: - case SC_EXPBOOST: - case SC_JEXPBOOST: - case SC_ITEMBOOST: - case SC_ELECTRICSHOCKER: - case SC__MANHOLE: - case SC_GIANTGROWTH: - case SC_MILLENNIUMSHIELD: - case SC_REFRESH: - case SC_STONEHARDSKIN: - case SC_VITALITYACTIVATION: - case SC_FIGHTINGSPIRIT: - case SC_ABUNDANCE: - case SC_CURSEDCIRCLE_ATKER: - case SC_CURSEDCIRCLE_TARGET: - continue; - - //Debuffs that can be removed. - case SC_DEEPSLEEP: - case SC_BURNING: - case SC_FREEZING: - case SC_CRYSTALIZE: - case SC_TOXIN: - case SC_PARALYSE: - case SC_VENOMBLEED: - case SC_MAGICMUSHROOM: - case SC_DEATHHURT: - case SC_PYREXIA: - case SC_OBLIVIONCURSE: - case SC_LEECHESEND: - case SC_MARSHOFABYSS: - case SC_MANDRAGORA: - if(!(type&4)) - continue; - break; - case SC_HALLUCINATION: - case SC_QUAGMIRE: - case SC_SIGNUMCRUCIS: - case SC_DECREASEAGI: - case SC_SLOWDOWN: - case SC_MINDBREAKER: - case SC_WINKCHARM: - case SC_STOP: - case SC_ORCISH: - case SC_STRIPWEAPON: - case SC_STRIPSHIELD: - case SC_STRIPARMOR: - case SC_STRIPHELM: - case SC_BITE: - case SC_ADORAMUS: - case SC_VACUUM_EXTREME: - case SC_FEAR: - case SC_MAGNETICFIELD: - case SC_NETHERWORLD: - if (!(type&2)) - continue; - break; - //The rest are buffs that can be removed. - case SC__BLOODYLUST: - case SC_BERSERK: - case SC_SATURDAYNIGHTFEVER: - if (!(type&1)) - continue; - sc->data[i]->val2 = 0; - break; - default: - if (!(type&1)) - continue; - break; - } - status_change_end(bl, (sc_type)i, INVALID_TIMER); - } - return 0; -} - -int status_change_spread( struct block_list *src, struct block_list *bl ) { - int i, flag = 0; - struct status_change *sc = status_get_sc(src); - const struct TimerData *timer; - unsigned int tick; - struct status_change_data data; - - if( !sc || !sc->count ) - return 0; - - tick = gettick(); - - for( i = SC_COMMON_MIN; i < SC_MAX; i++ ) { - if( !sc->data[i] || i == SC_COMMON_MAX ) - continue; - - switch( i ) { - //Debuffs that can be spreaded. - // NOTE: We'll add/delte SCs when we are able to confirm it. - case SC_CURSE: - case SC_SILENCE: - case SC_CONFUSION: - case SC_BLIND: - case SC_NOCHAT: - case SC_HALLUCINATION: - case SC_SIGNUMCRUCIS: - case SC_DECREASEAGI: - case SC_SLOWDOWN: - case SC_MINDBREAKER: - case SC_WINKCHARM: - case SC_STOP: - case SC_ORCISH: - //case SC_STRIPWEAPON://Omg I got infected and had the urge to strip myself physically. - //case SC_STRIPSHIELD://No this is stupid and shouldnt be spreadable at all. - //case SC_STRIPARMOR:// Disabled until I can confirm if it does or not. [Rytech] - //case SC_STRIPHELM: - //case SC__STRIPACCESSORY: - case SC_BITE: - case SC_FREEZING: - case SC_VENOMBLEED: - case SC_DEATHHURT: - case SC_PARALYSE: - if( sc->data[i]->timer != INVALID_TIMER ) { - timer = get_timer(sc->data[i]->timer); - if (timer == NULL || timer->func != status_change_timer || DIFF_TICK(timer->tick,tick) < 0) - continue; - data.tick = DIFF_TICK(timer->tick,tick); - } else - data.tick = INVALID_TIMER; - break; - // Special cases - case SC_POISON: - case SC_DPOISON: - data.tick = sc->data[i]->val3 * 1000; - break; - case SC_FEAR: - case SC_LEECHESEND: - data.tick = sc->data[i]->val4 * 1000; - break; - case SC_BURNING: - data.tick = sc->data[i]->val4 * 2000; - break; - case SC_PYREXIA: - case SC_OBLIVIONCURSE: - data.tick = sc->data[i]->val4 * 3000; - break; - case SC_MAGICMUSHROOM: - data.tick = sc->data[i]->val4 * 4000; - break; - case SC_TOXIN: - case SC_BLEEDING: - data.tick = sc->data[i]->val4 * 10000; - break; - default: - continue; - break; - } - if( i ){ - data.val1 = sc->data[i]->val1; - data.val2 = sc->data[i]->val2; - data.val3 = sc->data[i]->val3; - data.val4 = sc->data[i]->val4; - status_change_start(bl,(sc_type)i,10000,data.val1,data.val2,data.val3,data.val4,data.tick,1|2|8); - flag = 1; - } - } - - return flag; -} - -//Natural regen related stuff. -static unsigned int natural_heal_prev_tick,natural_heal_diff_tick; -static int status_natural_heal(struct block_list* bl, va_list args) -{ - struct regen_data *regen; - struct status_data *status; - struct status_change *sc; - struct unit_data *ud; - struct view_data *vd = NULL; - struct regen_data_sub *sregen; - struct map_session_data *sd; - int val,rate,bonus = 0,flag; - - regen = status_get_regen_data(bl); - if (!regen) return 0; - status = status_get_status_data(bl); - sc = status_get_sc(bl); - if (sc && !sc->count) - sc = NULL; - sd = BL_CAST(BL_PC,bl); - - flag = regen->flag; - if (flag&RGN_HP && (status->hp >= status->max_hp || regen->state.block&1)) - flag&=~(RGN_HP|RGN_SHP); - if (flag&RGN_SP && (status->sp >= status->max_sp || regen->state.block&2)) - flag&=~(RGN_SP|RGN_SSP); - - if (flag && ( - status_isdead(bl) || - (sc && (sc->option&(OPTION_HIDE|OPTION_CLOAK|OPTION_CHASEWALK) || sc->data[SC__INVISIBILITY])) - )) - flag=0; - - if (sd) { - if (sd->hp_loss.value || sd->sp_loss.value) - pc_bleeding(sd, natural_heal_diff_tick); - if (sd->hp_regen.value || sd->sp_regen.value) - pc_regen(sd, natural_heal_diff_tick); - } - - if(flag&(RGN_SHP|RGN_SSP) && regen->ssregen && - (vd = status_get_viewdata(bl)) && vd->dead_sit == 2) - { //Apply sitting regen bonus. - sregen = regen->ssregen; - if(flag&(RGN_SHP)) - { //Sitting HP regen - val = natural_heal_diff_tick * sregen->rate.hp; - if (regen->state.overweight) - val>>=1; //Half as fast when overweight. - sregen->tick.hp += val; - while(sregen->tick.hp >= (unsigned int)battle_config.natural_heal_skill_interval) - { - sregen->tick.hp -= battle_config.natural_heal_skill_interval; - if(status_heal(bl, sregen->hp, 0, 3) < sregen->hp) - { //Full - flag&=~(RGN_HP|RGN_SHP); - break; - } - } - } - if(flag&(RGN_SSP)) - { //Sitting SP regen - val = natural_heal_diff_tick * sregen->rate.sp; - if (regen->state.overweight) - val>>=1; //Half as fast when overweight. - sregen->tick.sp += val; - while(sregen->tick.sp >= (unsigned int)battle_config.natural_heal_skill_interval) - { - sregen->tick.sp -= battle_config.natural_heal_skill_interval; - if(status_heal(bl, 0, sregen->sp, 3) < sregen->sp) - { //Full - flag&=~(RGN_SP|RGN_SSP); - break; - } - } - } - } - - if (flag && regen->state.overweight) - flag=0; - - ud = unit_bl2ud(bl); - - if (flag&(RGN_HP|RGN_SHP|RGN_SSP) && ud && ud->walktimer != INVALID_TIMER) - { - flag&=~(RGN_SHP|RGN_SSP); - if(!regen->state.walk) - flag&=~RGN_HP; - } - - if (!flag) - return 0; - - if (flag&(RGN_HP|RGN_SP)) - { - if(!vd) vd = status_get_viewdata(bl); - if(vd && vd->dead_sit == 2) - bonus++; - if(regen->state.gc) - bonus++; - } - - //Natural Hp regen - if (flag&RGN_HP) - { - rate = natural_heal_diff_tick*(regen->rate.hp+bonus); - if (ud && ud->walktimer != INVALID_TIMER) - rate/=2; - // Homun HP regen fix (they should regen as if they were sitting (twice as fast) - if(bl->type==BL_HOM) rate *=2; - - regen->tick.hp += rate; - - if(regen->tick.hp >= (unsigned int)battle_config.natural_healhp_interval) - { - val = 0; - do { - val += regen->hp; - regen->tick.hp -= battle_config.natural_healhp_interval; - } while(regen->tick.hp >= (unsigned int)battle_config.natural_healhp_interval); - if (status_heal(bl, val, 0, 1) < val) - flag&=~RGN_SHP; //full. - } - } - - //Natural SP regen - if(flag&RGN_SP) - { - rate = natural_heal_diff_tick*(regen->rate.sp+bonus); - // Homun SP regen fix (they should regen as if they were sitting (twice as fast) - if(bl->type==BL_HOM) rate *=2; - - regen->tick.sp += rate; - - if(regen->tick.sp >= (unsigned int)battle_config.natural_healsp_interval) - { - val = 0; - do { - val += regen->sp; - regen->tick.sp -= battle_config.natural_healsp_interval; - } while(regen->tick.sp >= (unsigned int)battle_config.natural_healsp_interval); - if (status_heal(bl, 0, val, 1) < val) - flag&=~RGN_SSP; //full. - } - } - - if (!regen->sregen) - return flag; - - //Skill regen - sregen = regen->sregen; - - if(flag&RGN_SHP) - { //Skill HP regen - sregen->tick.hp += natural_heal_diff_tick * sregen->rate.hp; - - while(sregen->tick.hp >= (unsigned int)battle_config.natural_heal_skill_interval) - { - sregen->tick.hp -= battle_config.natural_heal_skill_interval; - if(status_heal(bl, sregen->hp, 0, 3) < sregen->hp) - break; //Full - } - } - if(flag&RGN_SSP) - { //Skill SP regen - sregen->tick.sp += natural_heal_diff_tick * sregen->rate.sp; - while(sregen->tick.sp >= (unsigned int)battle_config.natural_heal_skill_interval) - { - val = sregen->sp; - if (sd && sd->state.doridori) { - val*=2; - sd->state.doridori = 0; - if ((rate = pc_checkskill(sd,TK_SPTIME))) - sc_start(bl,status_skill2sc(TK_SPTIME), - 100,rate,skill_get_time(TK_SPTIME, rate)); - if ( - (sd->class_&MAPID_UPPERMASK) == MAPID_STAR_GLADIATOR && - rnd()%10000 < battle_config.sg_angel_skill_ratio - ) { //Angel of the Sun/Moon/Star - clif_feel_hate_reset(sd); - pc_resethate(sd); - pc_resetfeel(sd); - } - } - sregen->tick.sp -= battle_config.natural_heal_skill_interval; - if(status_heal(bl, 0, val, 3) < val) - break; //Full - } - } - return flag; -} - -//Natural heal main timer. -static int status_natural_heal_timer(int tid, unsigned int tick, int id, intptr_t data) -{ - natural_heal_diff_tick = DIFF_TICK(tick,natural_heal_prev_tick); - map_foreachregen(status_natural_heal); - natural_heal_prev_tick = tick; - return 0; -} - -/** - * Get the chance to upgrade a piece of equipment. - * @param wlv The weapon type of the item to refine (see see enum refine_type) - * @param refine The target refine level - * @return The chance to refine the item, in percent (0~100) - **/ -int status_get_refine_chance(enum refine_type wlv, int refine) { - - if ( refine < 0 || refine >= MAX_REFINE) - return 0; - - return refine_info[wlv].chance[refine]; -} - - -/*------------------------------------------ - * DB reading. - * job_db1.txt - weight, hp, sp, aspd - * job_db2.txt - job level stat bonuses - * size_fix.txt - size adjustment table for weapons - * refine_db.txt - refining data table - *------------------------------------------*/ -static bool status_readdb_job1(char* fields[], int columns, int current) -{// Job-specific values (weight, HP, SP, ASPD) - int idx, class_; - unsigned int i; - - class_ = atoi(fields[0]); - - if(!pcdb_checkid(class_)) - { - ShowWarning("status_readdb_job1: Invalid job class %d specified.\n", class_); - return false; - } - idx = pc_class2idx(class_); - - max_weight_base[idx] = atoi(fields[1]); - hp_coefficient[idx] = atoi(fields[2]); - hp_coefficient2[idx] = atoi(fields[3]); - sp_coefficient[idx] = atoi(fields[4]); -#ifdef RENEWAL_ASPD - for(i = 0; i <= MAX_WEAPON_TYPE; i++) -#else - for(i = 0; i < MAX_WEAPON_TYPE; i++) -#endif - { - aspd_base[idx][i] = atoi(fields[i+5]); - } - return true; -} - -static bool status_readdb_job2(char* fields[], int columns, int current) -{ - int idx, class_, i; - - class_ = atoi(fields[0]); - - if(!pcdb_checkid(class_)) - { - ShowWarning("status_readdb_job2: Invalid job class %d specified.\n", class_); - return false; - } - idx = pc_class2idx(class_); - - for(i = 1; i < columns; i++) - { - job_bonus[idx][i-1] = atoi(fields[i]); - } - return true; -} - -static bool status_readdb_sizefix(char* fields[], int columns, int current) -{ - unsigned int i; - - for(i = 0; i < MAX_WEAPON_TYPE; i++) - { - atkmods[current][i] = atoi(fields[i]); - } - return true; -} - -static bool status_readdb_refine(char* fields[], int columns, int current) -{ - int i, bonus_per_level, random_bonus, random_bonus_start_level; - - current = atoi(fields[0]); - - if (current < 0 || current >= REFINE_TYPE_MAX) - return false; - - bonus_per_level = atoi(fields[1]); - random_bonus_start_level = atoi(fields[2]); - random_bonus = atoi(fields[3]); - - for(i = 0; i < MAX_REFINE; i++) - { - char* delim; - - if (!(delim = strchr(fields[4+i], ':'))) - return false; - - *delim = '\0'; - - refine_info[current].chance[i] = atoi(fields[4+i]); - - if (i >= random_bonus_start_level - 1) - refine_info[current].randombonus_max[i] = random_bonus * (i - random_bonus_start_level + 2); - - refine_info[current].bonus[i] = bonus_per_level + atoi(delim+1); - if (i > 0) - refine_info[current].bonus[i] += refine_info[current].bonus[i-1]; - } - return true; -} - -/* -* Read status db -* job1.txt -* job2.txt -* size_fixe.txt -* refine_db.txt -*/ -int status_readdb(void) -{ - int i, j; - - // initialize databases to default - // - - // reset job_db1.txt data - memset(max_weight_base, 0, sizeof(max_weight_base)); - memset(hp_coefficient, 0, sizeof(hp_coefficient)); - memset(hp_coefficient2, 0, sizeof(hp_coefficient2)); - memset(sp_coefficient, 0, sizeof(sp_coefficient)); - memset(aspd_base, 0, sizeof(aspd_base)); - // reset job_db2.txt data - memset(job_bonus,0,sizeof(job_bonus)); // Job-specific stats bonus - - // size_fix.txt - for(i=0;i<ARRAYLENGTH(atkmods);i++) - for(j=0;j<MAX_WEAPON_TYPE;j++) - atkmods[i][j]=100; - - // refine_db.txt - for(i=0;i<ARRAYLENGTH(refine_info);i++) - { - for(j=0;j<MAX_REFINE; j++) - { - refine_info[i].chance[j] = 100; - refine_info[i].bonus[j] = 0; - refine_info[i].randombonus_max[j] = 0; - } - } - - // read databases - // - - -#ifdef RENEWAL_ASPD - sv_readdb(db_path, "re/job_db1.txt", ',', 6+MAX_WEAPON_TYPE, 6+MAX_WEAPON_TYPE, -1, &status_readdb_job1); -#else - sv_readdb(db_path, "pre-re/job_db1.txt", ',', 5+MAX_WEAPON_TYPE, 5+MAX_WEAPON_TYPE, -1, &status_readdb_job1); -#endif - sv_readdb(db_path, "job_db2.txt", ',', 1, 1+MAX_LEVEL, -1, &status_readdb_job2); - sv_readdb(db_path, "size_fix.txt", ',', MAX_WEAPON_TYPE, MAX_WEAPON_TYPE, ARRAYLENGTH(atkmods), &status_readdb_sizefix); - sv_readdb(db_path, DBPATH"refine_db.txt", ',', 4+MAX_REFINE, 4+MAX_REFINE, ARRAYLENGTH(refine_info), &status_readdb_refine); - - return 0; -} - -/*========================================== - * Status db init and destroy. - *------------------------------------------*/ -int do_init_status(void) -{ - add_timer_func_list(status_change_timer,"status_change_timer"); - add_timer_func_list(kaahi_heal_timer,"kaahi_heal_timer"); - add_timer_func_list(status_natural_heal_timer,"status_natural_heal_timer"); - initChangeTables(); - initDummyData(); - status_readdb(); - status_calc_sigma(); - natural_heal_prev_tick = gettick(); - sc_data_ers = ers_new(sizeof(struct status_change_entry),"status.c::sc_data_ers",ERS_OPT_NONE); - add_timer_interval(natural_heal_prev_tick + NATURAL_HEAL_INTERVAL, status_natural_heal_timer, 0, 0, NATURAL_HEAL_INTERVAL); - return 0; -} -void do_final_status(void) -{ - ers_destroy(sc_data_ers); -} +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/timer.h" +#include "../common/nullpo.h" +#include "../common/random.h" +#include "../common/showmsg.h" +#include "../common/malloc.h" +#include "../common/utils.h" +#include "../common/ers.h" +#include "../common/strlib.h" + +#include "map.h" +#include "path.h" +#include "pc.h" +#include "pet.h" +#include "npc.h" +#include "mob.h" +#include "clif.h" +#include "guild.h" +#include "skill.h" +#include "itemdb.h" +#include "battle.h" +#include "chrif.h" +#include "skill.h" +#include "status.h" +#include "script.h" +#include "unit.h" +#include "homunculus.h" +#include "mercenary.h" +#include "elemental.h" +#include "vending.h" + +#include <time.h> +#include <stdio.h> +#include <stdlib.h> +#include <memory.h> +#include <string.h> +#include <math.h> + +//Regen related flags. +enum e_regen +{ + RGN_HP = 0x01, + RGN_SP = 0x02, + RGN_SHP = 0x04, + RGN_SSP = 0x08, +}; + +static int max_weight_base[CLASS_COUNT]; +static int hp_coefficient[CLASS_COUNT]; +static int hp_coefficient2[CLASS_COUNT]; +static int hp_sigma_val[CLASS_COUNT][MAX_LEVEL+1]; +static int sp_coefficient[CLASS_COUNT]; +#ifdef RENEWAL_ASPD +static int aspd_base[CLASS_COUNT][MAX_WEAPON_TYPE+1]; +#else +static int aspd_base[CLASS_COUNT][MAX_WEAPON_TYPE]; //[blackhole89] +#endif + +// bonus values and upgrade chances for refining equipment +static struct { + int chance[MAX_REFINE]; // success chance + int bonus[MAX_REFINE]; // cumulative fixed bonus damage + int randombonus_max[MAX_REFINE]; // cumulative maximum random bonus damage +} refine_info[REFINE_TYPE_MAX]; + +static int atkmods[3][MAX_WEAPON_TYPE]; //ATK weapon modification for size (size_fix.txt) +static char job_bonus[CLASS_COUNT][MAX_LEVEL]; + +static struct eri *sc_data_ers; //For sc_data entries +static struct status_data dummy_status; + +int current_equip_item_index; //Contains inventory index of an equipped item. To pass it into the EQUP_SCRIPT [Lupus] +int current_equip_card_id; //To prevent card-stacking (from jA) [Skotlex] +//we need it for new cards 15 Feb 2005, to check if the combo cards are insrerted into the CURRENT weapon only +//to avoid cards exploits + +static sc_type SkillStatusChangeTable[MAX_SKILL]; // skill -> status +static int StatusIconChangeTable[SC_MAX]; // status -> "icon" (icon is a bit of a misnomer, since there exist values with no icon associated) +static unsigned int StatusChangeFlagTable[SC_MAX]; // status -> flags +static int StatusSkillChangeTable[SC_MAX]; // status -> skill +static int StatusRelevantBLTypes[SI_MAX]; // "icon" -> enum bl_type (for clif_status_change to identify for which bl types to send packets) +static unsigned int StatusChangeStateTable[SC_MAX]; // status -> flags + + +/** + * Returns the status change associated with a skill. + * @param skill The skill to look up + * @return The status registered for this skill + **/ +sc_type status_skill2sc(int skill) +{ + int idx = skill_get_index(skill); + if( idx == 0 ) { + ShowError("status_skill2sc: Unsupported skill id %d\n", skill); + return SC_NONE; + } + return SkillStatusChangeTable[idx]; +} + +/** + * Returns the FIRST skill (in order of definition in initChangeTables) to use a given status change. + * Utilized for various duration lookups. Use with caution! + * @param sc The status to look up + * @return A skill associated with the status + **/ +int status_sc2skill(sc_type sc) +{ + if( sc < 0 || sc >= SC_MAX ) { + ShowError("status_sc2skill: Unsupported status change id %d\n", sc); + return 0; + } + + return StatusSkillChangeTable[sc]; +} + +/** + * Returns the status calculation flag associated with a given status change. + * @param sc The status to look up + * @return The scb_flag registered for this status (see enum scb_flag) + **/ +unsigned int status_sc2scb_flag(sc_type sc) +{ + if( sc < 0 || sc >= SC_MAX ) { + ShowError("status_sc2scb_flag: Unsupported status change id %d\n", sc); + return SCB_NONE; + } + + return StatusChangeFlagTable[sc]; +} + +/** + * Returns the bl types which require a status change packet to be sent for a given client status identifier. + * @param type The client-side status identifier to look up (see enum si_type) + * @return The bl types relevant to the type (see enum bl_type) + **/ +int status_type2relevant_bl_types(int type) +{ + if( type < 0 || type >= SI_MAX ) { + ShowError("status_type2relevant_bl_types: Unsupported type %d\n", type); + return SI_BLANK; + } + + return StatusRelevantBLTypes[type]; +} + +#define add_sc(skill,sc) set_sc(skill,sc,SI_BLANK,SCB_NONE) +// indicates that the status displays a visual effect for the affected unit, and should be sent to the client for all supported units +#define set_sc_with_vfx(skill, sc, icon, flag) set_sc((skill), (sc), (icon), (flag)); if((icon) < SI_MAX) StatusRelevantBLTypes[(icon)] |= BL_SCEFFECT + +static void set_sc(uint16 skill_id, sc_type sc, int icon, unsigned int flag) +{ + uint16 idx = skill_get_index(skill_id); + if( idx == 0 ) { + ShowError("set_sc: Unsupported skill id %d\n", skill_id); + return; + } + if( sc < 0 || sc >= SC_MAX ) { + ShowError("set_sc: Unsupported status change id %d\n", sc); + return; + } + + if( StatusSkillChangeTable[sc] == 0 ) + StatusSkillChangeTable[sc] = skill_id; + if( StatusIconChangeTable[sc] == SI_BLANK ) + StatusIconChangeTable[sc] = icon; + StatusChangeFlagTable[sc] |= flag; + + if( SkillStatusChangeTable[idx] == SC_NONE ) + SkillStatusChangeTable[idx] = sc; +} + +void initChangeTables(void) { + int i; + + for (i = 0; i < SC_MAX; i++) + StatusIconChangeTable[i] = SI_BLANK; + + for (i = 0; i < MAX_SKILL; i++) + SkillStatusChangeTable[i] = SC_NONE; + + for (i = 0; i < SI_MAX; i++) + StatusRelevantBLTypes[i] = BL_PC; + + memset(StatusSkillChangeTable, 0, sizeof(StatusSkillChangeTable)); + memset(StatusChangeFlagTable, 0, sizeof(StatusChangeFlagTable)); + memset(StatusChangeStateTable, 0, sizeof(StatusChangeStateTable)); + + + //First we define the skill for common ailments. These are used in skill_additional_effect through sc cards. [Skotlex] + set_sc( NPC_PETRIFYATTACK , SC_STONE , SI_BLANK , SCB_DEF_ELE|SCB_DEF|SCB_MDEF ); + set_sc( NPC_WIDEFREEZE , SC_FREEZE , SI_BLANK , SCB_DEF_ELE|SCB_DEF|SCB_MDEF ); + set_sc( NPC_STUNATTACK , SC_STUN , SI_BLANK , SCB_NONE ); + set_sc( NPC_SLEEPATTACK , SC_SLEEP , SI_BLANK , SCB_NONE ); + set_sc( NPC_POISON , SC_POISON , SI_BLANK , SCB_DEF2|SCB_REGEN ); + set_sc( NPC_CURSEATTACK , SC_CURSE , SI_BLANK , SCB_LUK|SCB_BATK|SCB_WATK|SCB_SPEED ); + set_sc( NPC_SILENCEATTACK , SC_SILENCE , SI_BLANK , SCB_NONE ); + set_sc( NPC_WIDECONFUSE , SC_CONFUSION , SI_BLANK , SCB_NONE ); + set_sc( NPC_BLINDATTACK , SC_BLIND , SI_BLANK , SCB_HIT|SCB_FLEE ); + set_sc( NPC_BLEEDING , SC_BLEEDING , SI_BLEEDING , SCB_REGEN ); + set_sc( NPC_POISON , SC_DPOISON , SI_BLANK , SCB_DEF2|SCB_REGEN ); + + //The main status definitions + add_sc( SM_BASH , SC_STUN ); + set_sc( SM_PROVOKE , SC_PROVOKE , SI_PROVOKE , SCB_DEF|SCB_DEF2|SCB_BATK|SCB_WATK ); + add_sc( SM_MAGNUM , SC_WATK_ELEMENT ); + set_sc( SM_ENDURE , SC_ENDURE , SI_ENDURE , SCB_MDEF|SCB_DSPD ); + add_sc( MG_SIGHT , SC_SIGHT ); + add_sc( MG_SAFETYWALL , SC_SAFETYWALL ); + add_sc( MG_FROSTDIVER , SC_FREEZE ); + add_sc( MG_STONECURSE , SC_STONE ); + add_sc( AL_RUWACH , SC_RUWACH ); + add_sc( AL_PNEUMA , SC_PNEUMA ); + set_sc( AL_INCAGI , SC_INCREASEAGI , SI_INCREASEAGI , SCB_AGI|SCB_SPEED ); + set_sc( AL_DECAGI , SC_DECREASEAGI , SI_DECREASEAGI , SCB_AGI|SCB_SPEED ); + set_sc( AL_CRUCIS , SC_SIGNUMCRUCIS , SI_SIGNUMCRUCIS , SCB_DEF ); + set_sc( AL_ANGELUS , SC_ANGELUS , SI_ANGELUS , SCB_DEF2 ); + set_sc( AL_BLESSING , SC_BLESSING , SI_BLESSING , SCB_STR|SCB_INT|SCB_DEX ); + set_sc( AC_CONCENTRATION , SC_CONCENTRATE , SI_CONCENTRATE , SCB_AGI|SCB_DEX ); + set_sc( TF_HIDING , SC_HIDING , SI_HIDING , SCB_SPEED ); + add_sc( TF_POISON , SC_POISON ); + set_sc( KN_TWOHANDQUICKEN , SC_TWOHANDQUICKEN , SI_TWOHANDQUICKEN , SCB_ASPD ); + add_sc( KN_AUTOCOUNTER , SC_AUTOCOUNTER ); + set_sc( PR_IMPOSITIO , SC_IMPOSITIO , SI_IMPOSITIO , SCB_WATK ); + set_sc( PR_SUFFRAGIUM , SC_SUFFRAGIUM , SI_SUFFRAGIUM , SCB_NONE ); + set_sc( PR_ASPERSIO , SC_ASPERSIO , SI_ASPERSIO , SCB_ATK_ELE ); + set_sc( PR_BENEDICTIO , SC_BENEDICTIO , SI_BENEDICTIO , SCB_DEF_ELE ); + set_sc( PR_SLOWPOISON , SC_SLOWPOISON , SI_SLOWPOISON , SCB_REGEN ); + set_sc( PR_KYRIE , SC_KYRIE , SI_KYRIE , SCB_NONE ); + set_sc( PR_MAGNIFICAT , SC_MAGNIFICAT , SI_MAGNIFICAT , SCB_REGEN ); + set_sc( PR_GLORIA , SC_GLORIA , SI_GLORIA , SCB_LUK ); + add_sc( PR_LEXDIVINA , SC_SILENCE ); + set_sc( PR_LEXAETERNA , SC_AETERNA , SI_AETERNA , SCB_NONE ); + add_sc( WZ_METEOR , SC_STUN ); + add_sc( WZ_VERMILION , SC_BLIND ); + add_sc( WZ_FROSTNOVA , SC_FREEZE ); + add_sc( WZ_STORMGUST , SC_FREEZE ); + set_sc( WZ_QUAGMIRE , SC_QUAGMIRE , SI_QUAGMIRE , SCB_AGI|SCB_DEX|SCB_ASPD|SCB_SPEED ); + set_sc( BS_ADRENALINE , SC_ADRENALINE , SI_ADRENALINE , SCB_ASPD ); + set_sc( BS_WEAPONPERFECT , SC_WEAPONPERFECTION, SI_WEAPONPERFECTION, SCB_NONE ); + set_sc( BS_OVERTHRUST , SC_OVERTHRUST , SI_OVERTHRUST , SCB_NONE ); + set_sc( BS_MAXIMIZE , SC_MAXIMIZEPOWER , SI_MAXIMIZEPOWER , SCB_REGEN ); + add_sc( HT_LANDMINE , SC_STUN ); + add_sc( HT_ANKLESNARE , SC_ANKLE ); + add_sc( HT_SANDMAN , SC_SLEEP ); + add_sc( HT_FLASHER , SC_BLIND ); + add_sc( HT_FREEZINGTRAP , SC_FREEZE ); + set_sc( AS_CLOAKING , SC_CLOAKING , SI_CLOAKING , SCB_CRI|SCB_SPEED ); + add_sc( AS_SONICBLOW , SC_STUN ); + set_sc( AS_ENCHANTPOISON , SC_ENCPOISON , SI_ENCPOISON , SCB_ATK_ELE ); + set_sc( AS_POISONREACT , SC_POISONREACT , SI_POISONREACT , SCB_NONE ); + add_sc( AS_VENOMDUST , SC_POISON ); + add_sc( AS_SPLASHER , SC_SPLASHER ); + set_sc( NV_TRICKDEAD , SC_TRICKDEAD , SI_TRICKDEAD , SCB_REGEN ); + set_sc( SM_AUTOBERSERK , SC_AUTOBERSERK , SI_AUTOBERSERK , SCB_NONE ); + add_sc( TF_SPRINKLESAND , SC_BLIND ); + add_sc( TF_THROWSTONE , SC_STUN ); + set_sc( MC_LOUD , SC_LOUD , SI_LOUD , SCB_STR ); + set_sc( MG_ENERGYCOAT , SC_ENERGYCOAT , SI_ENERGYCOAT , SCB_NONE ); + set_sc( NPC_EMOTION , SC_MODECHANGE , SI_BLANK , SCB_MODE ); + add_sc( NPC_EMOTION_ON , SC_MODECHANGE ); + set_sc( NPC_ATTRICHANGE , SC_ELEMENTALCHANGE , SI_ARMOR_PROPERTY , SCB_DEF_ELE ); + add_sc( NPC_CHANGEWATER , SC_ELEMENTALCHANGE ); + add_sc( NPC_CHANGEGROUND , SC_ELEMENTALCHANGE ); + add_sc( NPC_CHANGEFIRE , SC_ELEMENTALCHANGE ); + add_sc( NPC_CHANGEWIND , SC_ELEMENTALCHANGE ); + add_sc( NPC_CHANGEPOISON , SC_ELEMENTALCHANGE ); + add_sc( NPC_CHANGEHOLY , SC_ELEMENTALCHANGE ); + add_sc( NPC_CHANGEDARKNESS , SC_ELEMENTALCHANGE ); + add_sc( NPC_CHANGETELEKINESIS, SC_ELEMENTALCHANGE ); + add_sc( NPC_POISON , SC_POISON ); + add_sc( NPC_BLINDATTACK , SC_BLIND ); + add_sc( NPC_SILENCEATTACK , SC_SILENCE ); + add_sc( NPC_STUNATTACK , SC_STUN ); + add_sc( NPC_PETRIFYATTACK , SC_STONE ); + add_sc( NPC_CURSEATTACK , SC_CURSE ); + add_sc( NPC_SLEEPATTACK , SC_SLEEP ); + add_sc( NPC_MAGICALATTACK , SC_MAGICALATTACK ); + set_sc( NPC_KEEPING , SC_KEEPING , SI_BLANK , SCB_DEF ); + add_sc( NPC_DARKBLESSING , SC_COMA ); + set_sc( NPC_BARRIER , SC_BARRIER , SI_BLANK , SCB_MDEF|SCB_DEF ); + add_sc( NPC_DEFENDER , SC_ARMOR ); + add_sc( NPC_LICK , SC_STUN ); + set_sc( NPC_HALLUCINATION , SC_HALLUCINATION , SI_HALLUCINATION , SCB_NONE ); + add_sc( NPC_REBIRTH , SC_REBIRTH ); + add_sc( RG_RAID , SC_STUN ); +#ifdef RENEWAL + add_sc( RG_RAID , SC_RAID ); + add_sc( RG_BACKSTAP , SC_STUN ); +#endif + set_sc( RG_STRIPWEAPON , SC_STRIPWEAPON , SI_STRIPWEAPON , SCB_WATK ); + set_sc( RG_STRIPSHIELD , SC_STRIPSHIELD , SI_STRIPSHIELD , SCB_DEF ); + set_sc( RG_STRIPARMOR , SC_STRIPARMOR , SI_STRIPARMOR , SCB_VIT ); + set_sc( RG_STRIPHELM , SC_STRIPHELM , SI_STRIPHELM , SCB_INT ); + add_sc( AM_ACIDTERROR , SC_BLEEDING ); + set_sc( AM_CP_WEAPON , SC_CP_WEAPON , SI_CP_WEAPON , SCB_NONE ); + set_sc( AM_CP_SHIELD , SC_CP_SHIELD , SI_CP_SHIELD , SCB_NONE ); + set_sc( AM_CP_ARMOR , SC_CP_ARMOR , SI_CP_ARMOR , SCB_NONE ); + set_sc( AM_CP_HELM , SC_CP_HELM , SI_CP_HELM , SCB_NONE ); + set_sc( CR_AUTOGUARD , SC_AUTOGUARD , SI_AUTOGUARD , SCB_NONE ); + add_sc( CR_SHIELDCHARGE , SC_STUN ); + set_sc( CR_REFLECTSHIELD , SC_REFLECTSHIELD , SI_REFLECTSHIELD , SCB_NONE ); + add_sc( CR_HOLYCROSS , SC_BLIND ); + add_sc( CR_GRANDCROSS , SC_BLIND ); + add_sc( CR_DEVOTION , SC_DEVOTION ); + set_sc( CR_PROVIDENCE , SC_PROVIDENCE , SI_PROVIDENCE , SCB_ALL ); + set_sc( CR_DEFENDER , SC_DEFENDER , SI_DEFENDER , SCB_SPEED|SCB_ASPD ); + set_sc( CR_SPEARQUICKEN , SC_SPEARQUICKEN , SI_SPEARQUICKEN , SCB_ASPD|SCB_CRI|SCB_FLEE ); + set_sc( MO_STEELBODY , SC_STEELBODY , SI_STEELBODY , SCB_DEF|SCB_MDEF|SCB_ASPD|SCB_SPEED ); + add_sc( MO_BLADESTOP , SC_BLADESTOP_WAIT ); + add_sc( MO_BLADESTOP , SC_BLADESTOP ); + set_sc( MO_EXPLOSIONSPIRITS , SC_EXPLOSIONSPIRITS, SI_EXPLOSIONSPIRITS, SCB_CRI|SCB_REGEN ); + set_sc( MO_EXTREMITYFIST , SC_EXTREMITYFIST , SI_BLANK , SCB_REGEN ); +#ifdef RENEWAL + set_sc( MO_EXTREMITYFIST , SC_EXTREMITYFIST2 , SI_EXTREMITYFIST , SCB_NONE ); +#endif + add_sc( SA_MAGICROD , SC_MAGICROD ); + set_sc( SA_AUTOSPELL , SC_AUTOSPELL , SI_AUTOSPELL , SCB_NONE ); + set_sc( SA_FLAMELAUNCHER , SC_FIREWEAPON , SI_FIREWEAPON , SCB_ATK_ELE ); + set_sc( SA_FROSTWEAPON , SC_WATERWEAPON , SI_WATERWEAPON , SCB_ATK_ELE ); + set_sc( SA_LIGHTNINGLOADER , SC_WINDWEAPON , SI_WINDWEAPON , SCB_ATK_ELE ); + set_sc( SA_SEISMICWEAPON , SC_EARTHWEAPON , SI_EARTHWEAPON , SCB_ATK_ELE ); + set_sc( SA_VOLCANO , SC_VOLCANO , SI_LANDENDOW , SCB_WATK ); + set_sc( SA_DELUGE , SC_DELUGE , SI_LANDENDOW , SCB_MAXHP ); + set_sc( SA_VIOLENTGALE , SC_VIOLENTGALE , SI_LANDENDOW , SCB_FLEE ); + add_sc( SA_REVERSEORCISH , SC_ORCISH ); + add_sc( SA_COMA , SC_COMA ); + set_sc( BD_ENCORE , SC_DANCING , SI_BLANK , SCB_SPEED|SCB_REGEN ); + add_sc( BD_RICHMANKIM , SC_RICHMANKIM ); + set_sc( BD_ETERNALCHAOS , SC_ETERNALCHAOS , SI_BLANK , SCB_DEF2 ); + set_sc( BD_DRUMBATTLEFIELD , SC_DRUMBATTLE , SI_BLANK , SCB_WATK|SCB_DEF ); + set_sc( BD_RINGNIBELUNGEN , SC_NIBELUNGEN , SI_BLANK , SCB_WATK ); + add_sc( BD_ROKISWEIL , SC_ROKISWEIL ); + add_sc( BD_INTOABYSS , SC_INTOABYSS ); + set_sc( BD_SIEGFRIED , SC_SIEGFRIED , SI_BLANK , SCB_ALL ); + add_sc( BA_FROSTJOKER , SC_FREEZE ); + set_sc( BA_WHISTLE , SC_WHISTLE , SI_BLANK , SCB_FLEE|SCB_FLEE2 ); + set_sc( BA_ASSASSINCROSS , SC_ASSNCROS , SI_BLANK , SCB_ASPD ); + add_sc( BA_POEMBRAGI , SC_POEMBRAGI ); + set_sc( BA_APPLEIDUN , SC_APPLEIDUN , SI_BLANK , SCB_MAXHP ); + add_sc( DC_SCREAM , SC_STUN ); + set_sc( DC_HUMMING , SC_HUMMING , SI_BLANK , SCB_HIT ); + set_sc( DC_DONTFORGETME , SC_DONTFORGETME , SI_BLANK , SCB_SPEED|SCB_ASPD ); + set_sc( DC_FORTUNEKISS , SC_FORTUNE , SI_BLANK , SCB_CRI ); + set_sc( DC_SERVICEFORYOU , SC_SERVICE4U , SI_BLANK , SCB_ALL ); + add_sc( NPC_DARKCROSS , SC_BLIND ); + add_sc( NPC_GRANDDARKNESS , SC_BLIND ); + set_sc( NPC_STOP , SC_STOP , SI_STOP , SCB_NONE ); + set_sc( NPC_WEAPONBRAKER , SC_BROKENWEAPON , SI_BROKENWEAPON , SCB_NONE ); + set_sc( NPC_ARMORBRAKE , SC_BROKENARMOR , SI_BROKENARMOR , SCB_NONE ); + set_sc( NPC_CHANGEUNDEAD , SC_CHANGEUNDEAD , SI_UNDEAD , SCB_DEF_ELE ); + set_sc( NPC_POWERUP , SC_INCHITRATE , SI_BLANK , SCB_HIT ); + set_sc( NPC_AGIUP , SC_INCFLEERATE , SI_BLANK , SCB_FLEE ); + add_sc( NPC_INVISIBLE , SC_CLOAKING ); + set_sc( LK_AURABLADE , SC_AURABLADE , SI_AURABLADE , SCB_NONE ); + set_sc( LK_PARRYING , SC_PARRYING , SI_PARRYING , SCB_NONE ); + set_sc( LK_CONCENTRATION , SC_CONCENTRATION , SI_CONCENTRATION , SCB_BATK|SCB_WATK|SCB_HIT|SCB_DEF|SCB_DEF2|SCB_MDEF|SCB_DSPD ); + set_sc( LK_TENSIONRELAX , SC_TENSIONRELAX , SI_TENSIONRELAX , SCB_REGEN ); + set_sc( LK_BERSERK , SC_BERSERK , SI_BERSERK , SCB_DEF|SCB_DEF2|SCB_MDEF|SCB_MDEF2|SCB_FLEE|SCB_SPEED|SCB_ASPD|SCB_MAXHP|SCB_REGEN ); + set_sc( HP_ASSUMPTIO , SC_ASSUMPTIO , SI_ASSUMPTIO , SCB_NONE ); + add_sc( HP_BASILICA , SC_BASILICA ); + set_sc( HW_MAGICPOWER , SC_MAGICPOWER , SI_MAGICPOWER , SCB_MATK ); + add_sc( PA_SACRIFICE , SC_SACRIFICE ); + set_sc( PA_GOSPEL , SC_GOSPEL , SI_BLANK , SCB_SPEED|SCB_ASPD ); + add_sc( PA_GOSPEL , SC_SCRESIST ); + add_sc( CH_TIGERFIST , SC_STOP ); + set_sc( ASC_EDP , SC_EDP , SI_EDP , SCB_NONE ); + set_sc( SN_SIGHT , SC_TRUESIGHT , SI_TRUESIGHT , SCB_STR|SCB_AGI|SCB_VIT|SCB_INT|SCB_DEX|SCB_LUK|SCB_CRI|SCB_HIT ); + set_sc( SN_WINDWALK , SC_WINDWALK , SI_WINDWALK , SCB_FLEE|SCB_SPEED ); + set_sc( WS_MELTDOWN , SC_MELTDOWN , SI_MELTDOWN , SCB_NONE ); + set_sc( WS_CARTBOOST , SC_CARTBOOST , SI_CARTBOOST , SCB_SPEED ); + set_sc( ST_CHASEWALK , SC_CHASEWALK , SI_BLANK , SCB_SPEED ); + set_sc( ST_REJECTSWORD , SC_REJECTSWORD , SI_REJECTSWORD , SCB_NONE ); + add_sc( ST_REJECTSWORD , SC_AUTOCOUNTER ); + set_sc( CG_MARIONETTE , SC_MARIONETTE , SI_MARIONETTE , SCB_STR|SCB_AGI|SCB_VIT|SCB_INT|SCB_DEX|SCB_LUK ); + set_sc( CG_MARIONETTE , SC_MARIONETTE2 , SI_MARIONETTE2 , SCB_STR|SCB_AGI|SCB_VIT|SCB_INT|SCB_DEX|SCB_LUK ); + add_sc( LK_SPIRALPIERCE , SC_STOP ); + add_sc( LK_HEADCRUSH , SC_BLEEDING ); + set_sc( LK_JOINTBEAT , SC_JOINTBEAT , SI_JOINTBEAT , SCB_BATK|SCB_DEF2|SCB_SPEED|SCB_ASPD ); + add_sc( HW_NAPALMVULCAN , SC_CURSE ); + set_sc( PF_MINDBREAKER , SC_MINDBREAKER , SI_BLANK , SCB_MATK|SCB_MDEF2 ); + add_sc( PF_MEMORIZE , SC_MEMORIZE ); + add_sc( PF_FOGWALL , SC_FOGWALL ); + set_sc( PF_SPIDERWEB , SC_SPIDERWEB , SI_BLANK , SCB_FLEE ); + set_sc( WE_BABY , SC_BABY , SI_BABY , SCB_NONE ); + set_sc( TK_RUN , SC_RUN , SI_RUN , SCB_SPEED|SCB_DSPD ); + set_sc( TK_RUN , SC_SPURT , SI_SPURT , SCB_STR ); + set_sc( TK_READYSTORM , SC_READYSTORM , SI_READYSTORM , SCB_NONE ); + set_sc( TK_READYDOWN , SC_READYDOWN , SI_READYDOWN , SCB_NONE ); + add_sc( TK_DOWNKICK , SC_STUN ); + set_sc( TK_READYTURN , SC_READYTURN , SI_READYTURN , SCB_NONE ); + set_sc( TK_READYCOUNTER , SC_READYCOUNTER , SI_READYCOUNTER , SCB_NONE ); + set_sc( TK_DODGE , SC_DODGE , SI_DODGE , SCB_NONE ); + set_sc( TK_SPTIME , SC_EARTHSCROLL , SI_EARTHSCROLL , SCB_NONE ); + add_sc( TK_SEVENWIND , SC_SEVENWIND ); + set_sc( TK_SEVENWIND , SC_GHOSTWEAPON , SI_GHOSTWEAPON , SCB_ATK_ELE ); + set_sc( TK_SEVENWIND , SC_SHADOWWEAPON , SI_SHADOWWEAPON , SCB_ATK_ELE ); + set_sc( SG_SUN_WARM , SC_WARM , SI_WARM , SCB_NONE ); + add_sc( SG_MOON_WARM , SC_WARM ); + add_sc( SG_STAR_WARM , SC_WARM ); + set_sc( SG_SUN_COMFORT , SC_SUN_COMFORT , SI_SUN_COMFORT , SCB_DEF2 ); + set_sc( SG_MOON_COMFORT , SC_MOON_COMFORT , SI_MOON_COMFORT , SCB_FLEE ); + set_sc( SG_STAR_COMFORT , SC_STAR_COMFORT , SI_STAR_COMFORT , SCB_ASPD ); + add_sc( SG_FRIEND , SC_SKILLRATE_UP ); + set_sc( SG_KNOWLEDGE , SC_KNOWLEDGE , SI_BLANK , SCB_ALL ); + set_sc( SG_FUSION , SC_FUSION , SI_BLANK , SCB_SPEED ); + set_sc( BS_ADRENALINE2 , SC_ADRENALINE2 , SI_ADRENALINE2 , SCB_ASPD ); + set_sc( SL_KAIZEL , SC_KAIZEL , SI_KAIZEL , SCB_NONE ); + set_sc( SL_KAAHI , SC_KAAHI , SI_KAAHI , SCB_NONE ); + set_sc( SL_KAUPE , SC_KAUPE , SI_KAUPE , SCB_NONE ); + set_sc( SL_KAITE , SC_KAITE , SI_KAITE , SCB_NONE ); + add_sc( SL_STUN , SC_STUN ); + set_sc( SL_SWOO , SC_SWOO , SI_BLANK , SCB_SPEED ); + set_sc( SL_SKE , SC_SKE , SI_BLANK , SCB_BATK|SCB_WATK|SCB_DEF|SCB_DEF2 ); + set_sc( SL_SKA , SC_SKA , SI_BLANK , SCB_DEF|SCB_MDEF|SCB_ASPD ); + set_sc( SL_SMA , SC_SMA , SI_SMA , SCB_NONE ); + set_sc( SM_SELFPROVOKE , SC_PROVOKE , SI_PROVOKE , SCB_DEF|SCB_DEF2|SCB_BATK|SCB_WATK ); + set_sc( ST_PRESERVE , SC_PRESERVE , SI_PRESERVE , SCB_NONE ); + set_sc( PF_DOUBLECASTING , SC_DOUBLECAST , SI_DOUBLECAST , SCB_NONE ); + set_sc( HW_GRAVITATION , SC_GRAVITATION , SI_BLANK , SCB_ASPD ); + add_sc( WS_CARTTERMINATION , SC_STUN ); + set_sc( WS_OVERTHRUSTMAX , SC_MAXOVERTHRUST , SI_MAXOVERTHRUST , SCB_NONE ); + set_sc( CG_LONGINGFREEDOM , SC_LONGING , SI_BLANK , SCB_SPEED|SCB_ASPD ); + add_sc( CG_HERMODE , SC_HERMODE ); + set_sc( ITEM_ENCHANTARMS , SC_ENCHANTARMS , SI_BLANK , SCB_ATK_ELE ); + set_sc( SL_HIGH , SC_SPIRIT , SI_SPIRIT , SCB_ALL ); + set_sc( KN_ONEHAND , SC_ONEHAND , SI_ONEHAND , SCB_ASPD ); + set_sc( GS_FLING , SC_FLING , SI_BLANK , SCB_DEF|SCB_DEF2 ); + add_sc( GS_CRACKER , SC_STUN ); + add_sc( GS_DISARM , SC_STRIPWEAPON ); + add_sc( GS_PIERCINGSHOT , SC_BLEEDING ); + set_sc( GS_MADNESSCANCEL , SC_MADNESSCANCEL , SI_MADNESSCANCEL , SCB_BATK|SCB_ASPD ); + set_sc( GS_ADJUSTMENT , SC_ADJUSTMENT , SI_ADJUSTMENT , SCB_HIT|SCB_FLEE ); + set_sc( GS_INCREASING , SC_INCREASING , SI_ACCURACY , SCB_AGI|SCB_DEX|SCB_HIT ); + set_sc( GS_GATLINGFEVER , SC_GATLINGFEVER , SI_GATLINGFEVER , SCB_BATK|SCB_FLEE|SCB_SPEED|SCB_ASPD ); + set_sc( NJ_TATAMIGAESHI , SC_TATAMIGAESHI , SI_BLANK , SCB_NONE ); + set_sc( NJ_SUITON , SC_SUITON , SI_BLANK , SCB_AGI|SCB_SPEED ); + add_sc( NJ_HYOUSYOURAKU , SC_FREEZE ); + set_sc( NJ_NEN , SC_NEN , SI_NEN , SCB_STR|SCB_INT ); + set_sc( NJ_UTSUSEMI , SC_UTSUSEMI , SI_UTSUSEMI , SCB_NONE ); + set_sc( NJ_BUNSINJYUTSU , SC_BUNSINJYUTSU , SI_BUNSINJYUTSU , SCB_DYE ); + + add_sc( NPC_ICEBREATH , SC_FREEZE ); + add_sc( NPC_ACIDBREATH , SC_POISON ); + add_sc( NPC_HELLJUDGEMENT , SC_CURSE ); + add_sc( NPC_WIDESILENCE , SC_SILENCE ); + add_sc( NPC_WIDEFREEZE , SC_FREEZE ); + add_sc( NPC_WIDEBLEEDING , SC_BLEEDING ); + add_sc( NPC_WIDESTONE , SC_STONE ); + add_sc( NPC_WIDECONFUSE , SC_CONFUSION ); + add_sc( NPC_WIDESLEEP , SC_SLEEP ); + add_sc( NPC_WIDESIGHT , SC_SIGHT ); + add_sc( NPC_EVILLAND , SC_BLIND ); + add_sc( NPC_MAGICMIRROR , SC_MAGICMIRROR ); + set_sc( NPC_SLOWCAST , SC_SLOWCAST , SI_SLOWCAST , SCB_NONE ); + set_sc( NPC_CRITICALWOUND , SC_CRITICALWOUND , SI_CRITICALWOUND , SCB_NONE ); + set_sc( NPC_STONESKIN , SC_ARMORCHANGE , SI_BLANK , SCB_DEF|SCB_MDEF ); + add_sc( NPC_ANTIMAGIC , SC_ARMORCHANGE ); + add_sc( NPC_WIDECURSE , SC_CURSE ); + add_sc( NPC_WIDESTUN , SC_STUN ); + + set_sc( NPC_HELLPOWER , SC_HELLPOWER , SI_HELLPOWER , SCB_NONE ); + set_sc( NPC_WIDEHELLDIGNITY , SC_HELLPOWER , SI_HELLPOWER , SCB_NONE ); + set_sc( NPC_INVINCIBLE , SC_INVINCIBLE , SI_INVINCIBLE , SCB_SPEED ); + set_sc( NPC_INVINCIBLEOFF , SC_INVINCIBLEOFF , SI_BLANK , SCB_SPEED ); + + set_sc( CASH_BLESSING , SC_BLESSING , SI_BLESSING , SCB_STR|SCB_INT|SCB_DEX ); + set_sc( CASH_INCAGI , SC_INCREASEAGI , SI_INCREASEAGI , SCB_AGI|SCB_SPEED ); + set_sc( CASH_ASSUMPTIO , SC_ASSUMPTIO , SI_ASSUMPTIO , SCB_NONE ); + + set_sc( ALL_PARTYFLEE , SC_PARTYFLEE , SI_PARTYFLEE , SCB_NONE ); + set_sc( ALL_ODINS_POWER , SC_ODINS_POWER , SI_ODINS_POWER , SCB_MATK|SCB_BATK|SCB_MDEF|SCB_DEF ); + + set_sc( CR_SHRINK , SC_SHRINK , SI_SHRINK , SCB_NONE ); + set_sc( RG_CLOSECONFINE , SC_CLOSECONFINE2 , SI_CLOSECONFINE2 , SCB_NONE ); + set_sc( RG_CLOSECONFINE , SC_CLOSECONFINE , SI_CLOSECONFINE , SCB_FLEE ); + set_sc( WZ_SIGHTBLASTER , SC_SIGHTBLASTER , SI_SIGHTBLASTER , SCB_NONE ); + set_sc( DC_WINKCHARM , SC_WINKCHARM , SI_WINKCHARM , SCB_NONE ); + add_sc( MO_BALKYOUNG , SC_STUN ); + add_sc( SA_ELEMENTWATER , SC_ELEMENTALCHANGE ); + add_sc( SA_ELEMENTFIRE , SC_ELEMENTALCHANGE ); + add_sc( SA_ELEMENTGROUND , SC_ELEMENTALCHANGE ); + add_sc( SA_ELEMENTWIND , SC_ELEMENTALCHANGE ); + + set_sc( HLIF_AVOID , SC_AVOID , SI_BLANK , SCB_SPEED ); + set_sc( HLIF_CHANGE , SC_CHANGE , SI_BLANK , SCB_VIT|SCB_INT ); + set_sc( HFLI_FLEET , SC_FLEET , SI_BLANK , SCB_ASPD|SCB_BATK|SCB_WATK ); + set_sc( HFLI_SPEED , SC_SPEED , SI_BLANK , SCB_FLEE ); + set_sc( HAMI_DEFENCE , SC_DEFENCE , SI_BLANK , SCB_DEF ); + set_sc( HAMI_BLOODLUST , SC_BLOODLUST , SI_BLANK , SCB_BATK|SCB_WATK ); + + // Homunculus S + add_sc(MH_STAHL_HORN, SC_STUN); + set_sc(MH_ANGRIFFS_MODUS, SC_ANGRIFFS_MODUS, SI_ANGRIFFS_MODUS, SCB_BATK | SCB_DEF | SCB_FLEE | SCB_MAXHP); + set_sc(MH_GOLDENE_FERSE, SC_GOLDENE_FERSE, SI_GOLDENE_FERSE, SCB_ASPD|SCB_MAXHP); + add_sc( MH_STEINWAND, SC_SAFETYWALL ); + add_sc(MH_ERASER_CUTTER, SC_ERASER_CUTTER); + set_sc(MH_OVERED_BOOST, SC_OVERED_BOOST, SI_BLANK, SCB_FLEE|SCB_ASPD); + add_sc(MH_LIGHT_OF_REGENE, SC_LIGHT_OF_REGENE); + set_sc(MH_VOLCANIC_ASH, SC_ASH, SI_VOLCANIC_ASH, SCB_DEF|SCB_DEF2|SCB_HIT|SCB_BATK|SCB_FLEE); + set_sc(MH_GRANITIC_ARMOR, SC_GRANITIC_ARMOR, SI_GRANITIC_ARMOR, SCB_NONE); + set_sc(MH_MAGMA_FLOW, SC_MAGMA_FLOW, SI_MAGMA_FLOW, SCB_NONE); + set_sc(MH_PYROCLASTIC, SC_PYROCLASTIC, SI_PYROCLASTIC, SCB_BATK|SCB_ATK_ELE); + add_sc(MH_LAVA_SLIDE, SC_BURNING); + set_sc(MH_NEEDLE_OF_PARALYZE, SC_PARALYSIS, SI_NEEDLE_OF_PARALYZE, SCB_DEF2); + add_sc(MH_POISON_MIST, SC_BLIND); + set_sc(MH_PAIN_KILLER, SC_PAIN_KILLER, SI_PAIN_KILLER, SCB_ASPD); + + add_sc(MH_STYLE_CHANGE, SC_STYLE_CHANGE); + set_sc( MH_TINDER_BREAKER , SC_CLOSECONFINE2 , SI_CLOSECONFINE2 , SCB_NONE ); + set_sc( MH_TINDER_BREAKER , SC_CLOSECONFINE , SI_CLOSECONFINE , SCB_FLEE ); + + + add_sc( MER_CRASH , SC_STUN ); + set_sc( MER_PROVOKE , SC_PROVOKE , SI_PROVOKE , SCB_DEF|SCB_DEF2|SCB_BATK|SCB_WATK ); + add_sc( MS_MAGNUM , SC_WATK_ELEMENT ); + add_sc( MER_SIGHT , SC_SIGHT ); + set_sc( MER_DECAGI , SC_DECREASEAGI , SI_DECREASEAGI , SCB_AGI|SCB_SPEED ); + set_sc( MER_MAGNIFICAT , SC_MAGNIFICAT , SI_MAGNIFICAT , SCB_REGEN ); + add_sc( MER_LEXDIVINA , SC_SILENCE ); + add_sc( MA_LANDMINE , SC_STUN ); + add_sc( MA_SANDMAN , SC_SLEEP ); + add_sc( MA_FREEZINGTRAP , SC_FREEZE ); + set_sc( MER_AUTOBERSERK , SC_AUTOBERSERK , SI_AUTOBERSERK , SCB_NONE ); + set_sc( ML_AUTOGUARD , SC_AUTOGUARD , SI_AUTOGUARD , SCB_NONE ); + set_sc( MS_REFLECTSHIELD , SC_REFLECTSHIELD , SI_REFLECTSHIELD , SCB_NONE ); + set_sc( ML_DEFENDER , SC_DEFENDER , SI_DEFENDER , SCB_SPEED|SCB_ASPD ); + set_sc( MS_PARRYING , SC_PARRYING , SI_PARRYING , SCB_NONE ); + set_sc( MS_BERSERK , SC_BERSERK , SI_BERSERK , SCB_DEF|SCB_DEF2|SCB_MDEF|SCB_MDEF2|SCB_FLEE|SCB_SPEED|SCB_ASPD|SCB_MAXHP|SCB_REGEN ); + add_sc( ML_SPIRALPIERCE , SC_STOP ); + set_sc( MER_QUICKEN , SC_MERC_QUICKEN , SI_BLANK , SCB_ASPD ); + add_sc( ML_DEVOTION , SC_DEVOTION ); + set_sc( MER_KYRIE , SC_KYRIE , SI_KYRIE , SCB_NONE ); + set_sc( MER_BLESSING , SC_BLESSING , SI_BLESSING , SCB_STR|SCB_INT|SCB_DEX ); + set_sc( MER_INCAGI , SC_INCREASEAGI , SI_INCREASEAGI , SCB_AGI|SCB_SPEED ); + + set_sc( GD_LEADERSHIP , SC_LEADERSHIP , SI_BLANK , SCB_STR ); + set_sc( GD_GLORYWOUNDS , SC_GLORYWOUNDS , SI_BLANK , SCB_VIT ); + set_sc( GD_SOULCOLD , SC_SOULCOLD , SI_BLANK , SCB_AGI ); + set_sc( GD_HAWKEYES , SC_HAWKEYES , SI_BLANK , SCB_DEX ); + + set_sc( GD_BATTLEORDER , SC_BATTLEORDERS , SI_BLANK , SCB_STR|SCB_INT|SCB_DEX ); + set_sc( GD_REGENERATION , SC_REGENERATION , SI_BLANK , SCB_REGEN ); + + /** + * Rune Knight + **/ + set_sc( RK_ENCHANTBLADE , SC_ENCHANTBLADE , SI_ENCHANTBLADE , SCB_NONE ); + set_sc( RK_DRAGONHOWLING , SC_FEAR , SI_BLANK , SCB_FLEE|SCB_HIT ); + set_sc( RK_DEATHBOUND , SC_DEATHBOUND , SI_DEATHBOUND , SCB_NONE ); + set_sc( RK_WINDCUTTER , SC_FEAR , SI_BLANK , SCB_FLEE|SCB_HIT ); + add_sc( RK_DRAGONBREATH , SC_BURNING ); + set_sc( RK_MILLENNIUMSHIELD , SC_MILLENNIUMSHIELD , SI_REUSE_MILLENNIUMSHIELD , SCB_NONE ); + set_sc( RK_REFRESH , SC_REFRESH , SI_REFRESH , SCB_NONE ); + set_sc( RK_GIANTGROWTH , SC_GIANTGROWTH , SI_GIANTGROWTH , SCB_STR ); + set_sc( RK_STONEHARDSKIN , SC_STONEHARDSKIN , SI_STONEHARDSKIN , SCB_NONE ); + set_sc( RK_VITALITYACTIVATION, SC_VITALITYACTIVATION, SI_VITALITYACTIVATION, SCB_REGEN ); + set_sc( RK_FIGHTINGSPIRIT , SC_FIGHTINGSPIRIT , SI_FIGHTINGSPIRIT , SCB_WATK|SCB_ASPD ); + set_sc( RK_ABUNDANCE , SC_ABUNDANCE , SI_ABUNDANCE , SCB_NONE ); + set_sc( RK_CRUSHSTRIKE , SC_CRUSHSTRIKE , SI_CRUSHSTRIKE , SCB_NONE ); + /** + * GC Guillotine Cross + **/ + set_sc_with_vfx( GC_VENOMIMPRESS , SC_VENOMIMPRESS , SI_VENOMIMPRESS , SCB_NONE ); + set_sc( GC_POISONINGWEAPON , SC_POISONINGWEAPON , SI_POISONINGWEAPON , SCB_NONE ); + set_sc( GC_WEAPONBLOCKING , SC_WEAPONBLOCKING , SI_WEAPONBLOCKING , SCB_NONE ); + set_sc( GC_CLOAKINGEXCEED , SC_CLOAKINGEXCEED , SI_CLOAKINGEXCEED , SCB_SPEED ); + set_sc( GC_HALLUCINATIONWALK , SC_HALLUCINATIONWALK, SI_HALLUCINATIONWALK, SCB_FLEE ); + set_sc( GC_ROLLINGCUTTER , SC_ROLLINGCUTTER , SI_ROLLINGCUTTER , SCB_NONE ); + /** + * Arch Bishop + **/ + set_sc( AB_ADORAMUS , SC_ADORAMUS , SI_ADORAMUS , SCB_AGI|SCB_SPEED ); + add_sc( AB_CLEMENTIA , SC_BLESSING ); + add_sc( AB_CANTO , SC_INCREASEAGI ); + set_sc( AB_EPICLESIS , SC_EPICLESIS , SI_EPICLESIS , SCB_MAXHP ); + add_sc( AB_PRAEFATIO , SC_KYRIE ); + set_sc_with_vfx( AB_ORATIO , SC_ORATIO , SI_ORATIO , SCB_NONE ); + set_sc( AB_LAUDAAGNUS , SC_LAUDAAGNUS , SI_LAUDAAGNUS , SCB_VIT ); + set_sc( AB_LAUDARAMUS , SC_LAUDARAMUS , SI_LAUDARAMUS , SCB_LUK ); + set_sc( AB_RENOVATIO , SC_RENOVATIO , SI_RENOVATIO , SCB_REGEN ); + set_sc( AB_EXPIATIO , SC_EXPIATIO , SI_EXPIATIO , SCB_ATK_ELE ); + set_sc( AB_DUPLELIGHT , SC_DUPLELIGHT , SI_DUPLELIGHT , SCB_NONE ); + set_sc( AB_SECRAMENT , SC_SECRAMENT , SI_SECRAMENT , SCB_NONE ); + /** + * Warlock + **/ + add_sc( WL_WHITEIMPRISON , SC_WHITEIMPRISON ); + set_sc_with_vfx( WL_FROSTMISTY , SC_FREEZING , SI_FROSTMISTY , SCB_ASPD|SCB_SPEED|SCB_DEF|SCB_DEF2 ); + set_sc( WL_MARSHOFABYSS , SC_MARSHOFABYSS , SI_MARSHOFABYSS , SCB_SPEED|SCB_FLEE|SCB_DEF|SCB_MDEF ); + set_sc(WL_RECOGNIZEDSPELL , SC_RECOGNIZEDSPELL , SI_RECOGNIZEDSPELL , SCB_MATK); + set_sc( WL_STASIS , SC_STASIS , SI_STASIS , SCB_NONE ); + /** + * Ranger + **/ + set_sc( RA_FEARBREEZE , SC_FEARBREEZE , SI_FEARBREEZE , SCB_NONE ); + set_sc( RA_ELECTRICSHOCKER , SC_ELECTRICSHOCKER , SI_ELECTRICSHOCKER , SCB_NONE ); + set_sc( RA_WUGDASH , SC_WUGDASH , SI_WUGDASH , SCB_SPEED ); + set_sc( RA_CAMOUFLAGE , SC_CAMOUFLAGE , SI_CAMOUFLAGE , SCB_SPEED ); + add_sc( RA_MAGENTATRAP , SC_ELEMENTALCHANGE ); + add_sc( RA_COBALTTRAP , SC_ELEMENTALCHANGE ); + add_sc( RA_MAIZETRAP , SC_ELEMENTALCHANGE ); + add_sc( RA_VERDURETRAP , SC_ELEMENTALCHANGE ); + add_sc( RA_FIRINGTRAP , SC_BURNING ); + set_sc_with_vfx( RA_ICEBOUNDTRAP , SC_FREEZING , SI_FROSTMISTY , SCB_NONE ); + /** + * Mechanic + **/ + set_sc( NC_ACCELERATION , SC_ACCELERATION , SI_ACCELERATION , SCB_SPEED ); + set_sc( NC_HOVERING , SC_HOVERING , SI_HOVERING , SCB_SPEED ); + set_sc( NC_SHAPESHIFT , SC_SHAPESHIFT , SI_SHAPESHIFT , SCB_DEF_ELE ); + set_sc( NC_INFRAREDSCAN , SC_INFRAREDSCAN , SI_INFRAREDSCAN , SCB_FLEE ); + set_sc( NC_ANALYZE , SC_ANALYZE , SI_ANALYZE , SCB_DEF|SCB_DEF2|SCB_MDEF|SCB_MDEF2 ); + set_sc( NC_MAGNETICFIELD , SC_MAGNETICFIELD , SI_MAGNETICFIELD , SCB_NONE ); + set_sc( NC_NEUTRALBARRIER , SC_NEUTRALBARRIER , SI_NEUTRALBARRIER , SCB_NONE ); + set_sc( NC_STEALTHFIELD , SC_STEALTHFIELD , SI_STEALTHFIELD , SCB_NONE ); + /** + * Royal Guard + **/ + set_sc( LG_REFLECTDAMAGE , SC_REFLECTDAMAGE , SI_LG_REFLECTDAMAGE, SCB_NONE ); + set_sc( LG_FORCEOFVANGUARD , SC_FORCEOFVANGUARD , SI_FORCEOFVANGUARD , SCB_MAXHP|SCB_DEF ); + set_sc( LG_EXEEDBREAK , SC_EXEEDBREAK , SI_EXEEDBREAK , SCB_NONE ); + set_sc( LG_PRESTIGE , SC_PRESTIGE , SI_PRESTIGE , SCB_DEF ); + set_sc( LG_BANDING , SC_BANDING , SI_BANDING , SCB_DEF2|SCB_WATK );// Renewal: atk2 & def2 + set_sc( LG_PIETY , SC_BENEDICTIO , SI_BENEDICTIO , SCB_DEF_ELE ); + set_sc( LG_EARTHDRIVE , SC_EARTHDRIVE , SI_EARTHDRIVE , SCB_DEF|SCB_ASPD ); + set_sc( LG_INSPIRATION , SC_INSPIRATION , SI_INSPIRATION , SCB_MAXHP|SCB_WATK|SCB_HIT|SCB_VIT|SCB_AGI|SCB_STR|SCB_DEX|SCB_INT|SCB_LUK); + set_sc( LG_SHIELDSPELL , SC_SHIELDSPELL_DEF , SI_SHIELDSPELL_DEF , SCB_WATK ); + set_sc( LG_SHIELDSPELL , SC_SHIELDSPELL_REF , SI_SHIELDSPELL_REF , SCB_DEF ); + /** + * Shadow Chaser + **/ + set_sc( SC_REPRODUCE , SC__REPRODUCE , SI_REPRODUCE , SCB_NONE ); + set_sc( SC_AUTOSHADOWSPELL , SC__AUTOSHADOWSPELL, SI_AUTOSHADOWSPELL , SCB_NONE ); + set_sc( SC_SHADOWFORM , SC__SHADOWFORM , SI_SHADOWFORM , SCB_NONE ); + set_sc( SC_BODYPAINT , SC__BODYPAINT , SI_BODYPAINT , SCB_ASPD ); + set_sc( SC_INVISIBILITY , SC__INVISIBILITY , SI_INVISIBILITY , SCB_ASPD|SCB_CRI|SCB_ATK_ELE ); + set_sc( SC_DEADLYINFECT , SC__DEADLYINFECT , SI_DEADLYINFECT , SCB_NONE ); + set_sc( SC_ENERVATION , SC__ENERVATION , SI_ENERVATION , SCB_BATK ); + set_sc( SC_GROOMY , SC__GROOMY , SI_GROOMY , SCB_ASPD|SCB_HIT|SCB_SPEED ); + set_sc( SC_IGNORANCE , SC__IGNORANCE , SI_IGNORANCE , SCB_NONE ); + set_sc( SC_LAZINESS , SC__LAZINESS , SI_LAZINESS , SCB_FLEE ); + set_sc( SC_UNLUCKY , SC__UNLUCKY , SI_UNLUCKY , SCB_CRI|SCB_FLEE2 ); + set_sc( SC_WEAKNESS , SC__WEAKNESS , SI_WEAKNESS , SCB_FLEE2|SCB_MAXHP ); + set_sc( SC_STRIPACCESSARY , SC__STRIPACCESSORY , SI_STRIPACCESSARY , SCB_DEX|SCB_INT|SCB_LUK ); + set_sc_with_vfx( SC_MANHOLE , SC__MANHOLE , SI_MANHOLE , SCB_NONE ); + add_sc( SC_CHAOSPANIC , SC_CONFUSION ); + set_sc_with_vfx( SC_BLOODYLUST , SC__BLOODYLUST , SI_BLOODYLUST , SCB_DEF | SCB_DEF2 | SCB_MDEF | SCB_MDEF2 | SCB_FLEE | SCB_SPEED | SCB_ASPD | SCB_MAXHP | SCB_REGEN ); + /** + * Sura + **/ + add_sc( SR_DRAGONCOMBO , SC_STUN ); + add_sc( SR_EARTHSHAKER , SC_STUN ); + set_sc( SR_CRESCENTELBOW , SC_CRESCENTELBOW , SI_CRESCENTELBOW , SCB_NONE ); + set_sc_with_vfx( SR_CURSEDCIRCLE , SC_CURSEDCIRCLE_TARGET, SI_CURSEDCIRCLE_TARGET , SCB_NONE ); + set_sc( SR_LIGHTNINGWALK , SC_LIGHTNINGWALK , SI_LIGHTNINGWALK , SCB_NONE ); + set_sc( SR_RAISINGDRAGON , SC_RAISINGDRAGON , SI_RAISINGDRAGON , SCB_REGEN|SCB_MAXHP|SCB_MAXSP ); + set_sc( SR_GENTLETOUCH_ENERGYGAIN, SC_GT_ENERGYGAIN , SI_GENTLETOUCH_ENERGYGAIN, SCB_NONE ); + set_sc( SR_GENTLETOUCH_CHANGE , SC_GT_CHANGE , SI_GENTLETOUCH_CHANGE , SCB_ASPD|SCB_MDEF|SCB_MAXHP ); + set_sc( SR_GENTLETOUCH_REVITALIZE, SC_GT_REVITALIZE , SI_GENTLETOUCH_REVITALIZE, SCB_MAXHP|SCB_REGEN ); + /** + * Wanderer / Minstrel + **/ + set_sc( WA_SWING_DANCE , SC_SWINGDANCE , SI_SWINGDANCE , SCB_SPEED|SCB_ASPD ); + set_sc( WA_SYMPHONY_OF_LOVER , SC_SYMPHONYOFLOVER , SI_SYMPHONYOFLOVERS , SCB_MDEF ); + set_sc( WA_MOONLIT_SERENADE , SC_MOONLITSERENADE , SI_MOONLITSERENADE , SCB_MATK ); + set_sc( MI_RUSH_WINDMILL , SC_RUSHWINDMILL , SI_RUSHWINDMILL , SCB_BATK ); + set_sc( MI_ECHOSONG , SC_ECHOSONG , SI_ECHOSONG , SCB_DEF2 ); + set_sc( MI_HARMONIZE , SC_HARMONIZE , SI_HARMONIZE , SCB_STR|SCB_AGI|SCB_VIT|SCB_INT|SCB_DEX|SCB_LUK ); + set_sc_with_vfx( WM_POEMOFNETHERWORLD , SC_NETHERWORLD , SI_NETHERWORLD , SCB_NONE ); + set_sc_with_vfx( WM_VOICEOFSIREN , SC_VOICEOFSIREN , SI_VOICEOFSIREN , SCB_NONE ); + set_sc_with_vfx( WM_LULLABY_DEEPSLEEP , SC_DEEPSLEEP , SI_DEEPSLEEP , SCB_NONE ); + set_sc( WM_SIRCLEOFNATURE , SC_SIRCLEOFNATURE , SI_SIRCLEOFNATURE , SCB_NONE ); + set_sc( WM_GLOOMYDAY , SC_GLOOMYDAY , SI_GLOOMYDAY , SCB_FLEE|SCB_ASPD ); + set_sc( WM_SONG_OF_MANA , SC_SONGOFMANA , SI_SONGOFMANA , SCB_NONE ); + set_sc( WM_DANCE_WITH_WUG , SC_DANCEWITHWUG , SI_DANCEWITHWUG , SCB_ASPD ); + set_sc( WM_SATURDAY_NIGHT_FEVER , SC_SATURDAYNIGHTFEVER , SI_SATURDAYNIGHTFEVER , SCB_BATK|SCB_DEF|SCB_FLEE|SCB_REGEN ); + set_sc( WM_LERADS_DEW , SC_LERADSDEW , SI_LERADSDEW , SCB_MAXHP ); + set_sc( WM_MELODYOFSINK , SC_MELODYOFSINK , SI_MELODYOFSINK , SCB_BATK|SCB_MATK ); + set_sc( WM_BEYOND_OF_WARCRY , SC_BEYONDOFWARCRY , SI_WARCRYOFBEYOND , SCB_BATK|SCB_MATK ); + set_sc( WM_UNLIMITED_HUMMING_VOICE, SC_UNLIMITEDHUMMINGVOICE, SI_UNLIMITEDHUMMINGVOICE, SCB_NONE ); + /** + * Sorcerer + **/ + set_sc( SO_FIREWALK , SC_PROPERTYWALK , SI_PROPERTYWALK , SCB_NONE ); + set_sc( SO_ELECTRICWALK , SC_PROPERTYWALK , SI_PROPERTYWALK , SCB_NONE ); + set_sc( SO_SPELLFIST , SC_SPELLFIST , SI_SPELLFIST , SCB_NONE ); + set_sc_with_vfx( SO_DIAMONDDUST , SC_CRYSTALIZE , SI_COLD , SCB_NONE ); // it does show the snow icon on mobs but doesn't affect it. + add_sc( SO_CLOUD_KILL , SC_POISON ); + set_sc( SO_STRIKING , SC_STRIKING , SI_STRIKING , SCB_WATK|SCB_CRI ); + set_sc( SO_WARMER , SC_WARMER , SI_WARMER , SCB_NONE ); + set_sc( SO_VACUUM_EXTREME , SC_VACUUM_EXTREME , SI_VACUUM_EXTREME , SCB_NONE ); + set_sc( SO_ARRULLO , SC_DEEPSLEEP , SI_DEEPSLEEP , SCB_NONE ); + set_sc( SO_FIRE_INSIGNIA , SC_FIRE_INSIGNIA , SI_FIRE_INSIGNIA , SCB_MATK | SCB_BATK | SCB_WATK | SCB_ATK_ELE | SCB_REGEN ); + set_sc( SO_WATER_INSIGNIA , SC_WATER_INSIGNIA , SI_WATER_INSIGNIA , SCB_WATK | SCB_ATK_ELE | SCB_REGEN ); + set_sc( SO_WIND_INSIGNIA , SC_WIND_INSIGNIA , SI_WIND_INSIGNIA , SCB_WATK | SCB_ATK_ELE | SCB_REGEN ); + set_sc( SO_EARTH_INSIGNIA , SC_EARTH_INSIGNIA , SI_EARTH_INSIGNIA , SCB_MDEF|SCB_DEF|SCB_MAXHP|SCB_MAXSP|SCB_WATK | SCB_ATK_ELE | SCB_REGEN ); + /** + * Genetic + **/ + set_sc( GN_CARTBOOST , SC_GN_CARTBOOST, SI_CARTSBOOST , SCB_SPEED ); + set_sc( GN_THORNS_TRAP , SC_THORNSTRAP , SI_THORNTRAP , SCB_NONE ); + set_sc_with_vfx( GN_BLOOD_SUCKER , SC_BLOODSUCKER , SI_BLOODSUCKER , SCB_NONE ); + set_sc( GN_WALLOFTHORN , SC_STOP , SI_BLANK , SCB_NONE ); + set_sc( GN_FIRE_EXPANSION_SMOKE_POWDER, SC_SMOKEPOWDER , SI_FIRE_EXPANSION_SMOKE_POWDER, SCB_NONE ); + set_sc( GN_FIRE_EXPANSION_TEAR_GAS , SC_TEARGAS , SI_FIRE_EXPANSION_TEAR_GAS , SCB_NONE ); + set_sc( GN_MANDRAGORA , SC_MANDRAGORA , SI_MANDRAGORA , SCB_INT ); + + // Elemental Spirit summoner's 'side' status changes. + set_sc( EL_CIRCLE_OF_FIRE , SC_CIRCLE_OF_FIRE_OPTION, SI_CIRCLE_OF_FIRE_OPTION, SCB_NONE ); + set_sc( EL_FIRE_CLOAK , SC_FIRE_CLOAK_OPTION , SI_FIRE_CLOAK_OPTION , SCB_ALL ); + set_sc( EL_WATER_SCREEN , SC_WATER_SCREEN_OPTION , SI_WATER_SCREEN_OPTION , SCB_NONE ); + set_sc( EL_WATER_DROP , SC_WATER_DROP_OPTION , SI_WATER_DROP_OPTION , SCB_ALL ); + set_sc( EL_WATER_BARRIER , SC_WATER_BARRIER , SI_WATER_BARRIER , SCB_MDEF|SCB_WATK|SCB_MATK|SCB_FLEE ); + set_sc( EL_WIND_STEP , SC_WIND_STEP_OPTION , SI_WIND_STEP_OPTION , SCB_SPEED|SCB_FLEE ); + set_sc( EL_WIND_CURTAIN , SC_WIND_CURTAIN_OPTION , SI_WIND_CURTAIN_OPTION , SCB_ALL ); + set_sc( EL_ZEPHYR , SC_ZEPHYR , SI_ZEPHYR , SCB_FLEE ); + set_sc( EL_SOLID_SKIN , SC_SOLID_SKIN_OPTION , SI_SOLID_SKIN_OPTION , SCB_DEF|SCB_MAXHP ); + set_sc( EL_STONE_SHIELD , SC_STONE_SHIELD_OPTION , SI_STONE_SHIELD_OPTION , SCB_ALL ); + set_sc( EL_POWER_OF_GAIA , SC_POWER_OF_GAIA , SI_POWER_OF_GAIA , SCB_MAXHP|SCB_DEF|SCB_SPEED ); + set_sc( EL_PYROTECHNIC , SC_PYROTECHNIC_OPTION , SI_PYROTECHNIC_OPTION , SCB_WATK ); + set_sc( EL_HEATER , SC_HEATER_OPTION , SI_HEATER_OPTION , SCB_WATK ); + set_sc( EL_TROPIC , SC_TROPIC_OPTION , SI_TROPIC_OPTION , SCB_WATK ); + set_sc( EL_AQUAPLAY , SC_AQUAPLAY_OPTION , SI_AQUAPLAY_OPTION , SCB_MATK ); + set_sc( EL_COOLER , SC_COOLER_OPTION , SI_COOLER_OPTION , SCB_MATK ); + set_sc( EL_CHILLY_AIR , SC_CHILLY_AIR_OPTION , SI_CHILLY_AIR_OPTION , SCB_MATK ); + set_sc( EL_GUST , SC_GUST_OPTION , SI_GUST_OPTION , SCB_ASPD ); + set_sc( EL_BLAST , SC_BLAST_OPTION , SI_BLAST_OPTION , SCB_ASPD ); + set_sc( EL_WILD_STORM , SC_WILD_STORM_OPTION , SI_WILD_STORM_OPTION , SCB_ASPD ); + set_sc( EL_PETROLOGY , SC_PETROLOGY_OPTION , SI_PETROLOGY_OPTION , SCB_MAXHP ); + set_sc( EL_CURSED_SOIL , SC_CURSED_SOIL_OPTION , SI_CURSED_SOIL_OPTION , SCB_NONE ); + set_sc( EL_UPHEAVAL , SC_UPHEAVAL_OPTION , SI_UPHEAVAL_OPTION , SCB_NONE ); + set_sc( EL_TIDAL_WEAPON , SC_TIDAL_WEAPON_OPTION , SI_TIDAL_WEAPON_OPTION , SCB_ALL ); + set_sc( EL_ROCK_CRUSHER , SC_ROCK_CRUSHER , SI_ROCK_CRUSHER , SCB_DEF ); + set_sc( EL_ROCK_CRUSHER_ATK, SC_ROCK_CRUSHER_ATK , SI_ROCK_CRUSHER_ATK , SCB_SPEED ); + + add_sc( KO_YAMIKUMO , SC_HIDING ); + set_sc_with_vfx( KO_JYUMONJIKIRI , SC_JYUMONJIKIRI , SI_KO_JYUMONJIKIRI , SCB_NONE ); + add_sc( KO_MAKIBISHI , SC_STUN ); + set_sc( KO_MEIKYOUSISUI , SC_MEIKYOUSISUI , SI_MEIKYOUSISUI , SCB_NONE ); + set_sc( KO_KYOUGAKU , SC_KYOUGAKU , SI_KYOUGAKU , SCB_STR|SCB_AGI|SCB_VIT|SCB_INT|SCB_DEX|SCB_LUK ); + add_sc( KO_JYUSATSU , SC_CURSE ); + set_sc( KO_ZENKAI , SC_ZENKAI , SI_ZENKAI , SCB_NONE ); + set_sc( KO_IZAYOI , SC_IZAYOI , SI_IZAYOI , SCB_MATK ); + set_sc( KG_KYOMU , SC_KYOMU , SI_KYOMU , SCB_NONE ); + set_sc( KG_KAGEMUSYA , SC_KAGEMUSYA , SI_KAGEMUSYA , SCB_NONE ); + set_sc( KG_KAGEHUMI , SC_KAGEHUMI , SI_KG_KAGEHUMI , SCB_NONE ); + set_sc( OB_ZANGETSU , SC_ZANGETSU , SI_ZANGETSU , SCB_MATK|SCB_BATK ); + set_sc_with_vfx( OB_AKAITSUKI , SC_AKAITSUKI , SI_AKAITSUKI , SCB_NONE ); + set_sc( OB_OBOROGENSOU , SC_GENSOU , SI_GENSOU , SCB_NONE ); + + // Storing the target job rather than simply SC_SPIRIT simplifies code later on. + SkillStatusChangeTable[SL_ALCHEMIST] = (sc_type)MAPID_ALCHEMIST, + SkillStatusChangeTable[SL_MONK] = (sc_type)MAPID_MONK, + SkillStatusChangeTable[SL_STAR] = (sc_type)MAPID_STAR_GLADIATOR, + SkillStatusChangeTable[SL_SAGE] = (sc_type)MAPID_SAGE, + SkillStatusChangeTable[SL_CRUSADER] = (sc_type)MAPID_CRUSADER, + SkillStatusChangeTable[SL_SUPERNOVICE] = (sc_type)MAPID_SUPER_NOVICE, + SkillStatusChangeTable[SL_KNIGHT] = (sc_type)MAPID_KNIGHT, + SkillStatusChangeTable[SL_WIZARD] = (sc_type)MAPID_WIZARD, + SkillStatusChangeTable[SL_PRIEST] = (sc_type)MAPID_PRIEST, + SkillStatusChangeTable[SL_BARDDANCER] = (sc_type)MAPID_BARDDANCER, + SkillStatusChangeTable[SL_ROGUE] = (sc_type)MAPID_ROGUE, + SkillStatusChangeTable[SL_ASSASIN] = (sc_type)MAPID_ASSASSIN, + SkillStatusChangeTable[SL_BLACKSMITH] = (sc_type)MAPID_BLACKSMITH, + SkillStatusChangeTable[SL_HUNTER] = (sc_type)MAPID_HUNTER, + SkillStatusChangeTable[SL_SOULLINKER] = (sc_type)MAPID_SOUL_LINKER, + + //Status that don't have a skill associated. + StatusIconChangeTable[SC_WEIGHT50] = SI_WEIGHT50; + StatusIconChangeTable[SC_WEIGHT90] = SI_WEIGHT90; + StatusIconChangeTable[SC_ASPDPOTION0] = SI_ASPDPOTION0; + StatusIconChangeTable[SC_ASPDPOTION1] = SI_ASPDPOTION1; + StatusIconChangeTable[SC_ASPDPOTION2] = SI_ASPDPOTION2; + StatusIconChangeTable[SC_ASPDPOTION3] = SI_ASPDPOTIONINFINITY; + StatusIconChangeTable[SC_SPEEDUP0] = SI_MOVHASTE_HORSE; + StatusIconChangeTable[SC_SPEEDUP1] = SI_SPEEDPOTION1; + StatusIconChangeTable[SC_INCSTR] = SI_INCSTR; + StatusIconChangeTable[SC_MIRACLE] = SI_SPIRIT; + StatusIconChangeTable[SC_INTRAVISION] = SI_INTRAVISION; + StatusIconChangeTable[SC_STRFOOD] = SI_FOODSTR; + StatusIconChangeTable[SC_AGIFOOD] = SI_FOODAGI; + StatusIconChangeTable[SC_VITFOOD] = SI_FOODVIT; + StatusIconChangeTable[SC_INTFOOD] = SI_FOODINT; + StatusIconChangeTable[SC_DEXFOOD] = SI_FOODDEX; + StatusIconChangeTable[SC_LUKFOOD] = SI_FOODLUK; + StatusIconChangeTable[SC_FLEEFOOD]= SI_FOODFLEE; + StatusIconChangeTable[SC_HITFOOD] = SI_FOODHIT; + StatusIconChangeTable[SC_MANU_ATK] = SI_MANU_ATK; + StatusIconChangeTable[SC_MANU_DEF] = SI_MANU_DEF; + StatusIconChangeTable[SC_SPL_ATK] = SI_SPL_ATK; + StatusIconChangeTable[SC_SPL_DEF] = SI_SPL_DEF; + StatusIconChangeTable[SC_MANU_MATK] = SI_MANU_MATK; + StatusIconChangeTable[SC_SPL_MATK] = SI_SPL_MATK; + StatusIconChangeTable[SC_ATKPOTION] = SI_PLUSATTACKPOWER; + StatusIconChangeTable[SC_MATKPOTION] = SI_PLUSMAGICPOWER; + //Cash Items + StatusIconChangeTable[SC_FOOD_STR_CASH] = SI_FOOD_STR_CASH; + StatusIconChangeTable[SC_FOOD_AGI_CASH] = SI_FOOD_AGI_CASH; + StatusIconChangeTable[SC_FOOD_VIT_CASH] = SI_FOOD_VIT_CASH; + StatusIconChangeTable[SC_FOOD_DEX_CASH] = SI_FOOD_DEX_CASH; + StatusIconChangeTable[SC_FOOD_INT_CASH] = SI_FOOD_INT_CASH; + StatusIconChangeTable[SC_FOOD_LUK_CASH] = SI_FOOD_LUK_CASH; + StatusIconChangeTable[SC_EXPBOOST] = SI_EXPBOOST; + StatusIconChangeTable[SC_ITEMBOOST] = SI_ITEMBOOST; + StatusIconChangeTable[SC_JEXPBOOST] = SI_CASH_PLUSONLYJOBEXP; + StatusIconChangeTable[SC_LIFEINSURANCE] = SI_LIFEINSURANCE; + StatusIconChangeTable[SC_BOSSMAPINFO] = SI_BOSSMAPINFO; + StatusIconChangeTable[SC_DEF_RATE] = SI_DEF_RATE; + StatusIconChangeTable[SC_MDEF_RATE] = SI_MDEF_RATE; + StatusIconChangeTable[SC_INCCRI] = SI_INCCRI; + StatusIconChangeTable[SC_INCFLEE2] = SI_PLUSAVOIDVALUE; + StatusIconChangeTable[SC_INCHEALRATE] = SI_INCHEALRATE; + StatusIconChangeTable[SC_S_LIFEPOTION] = SI_S_LIFEPOTION; + StatusIconChangeTable[SC_L_LIFEPOTION] = SI_L_LIFEPOTION; + StatusIconChangeTable[SC_SPCOST_RATE] = SI_ATKER_BLOOD; + StatusIconChangeTable[SC_COMMONSC_RESIST] = SI_TARGET_BLOOD; + // Mercenary Bonus Effects + StatusIconChangeTable[SC_MERC_FLEEUP] = SI_MERC_FLEEUP; + StatusIconChangeTable[SC_MERC_ATKUP] = SI_MERC_ATKUP; + StatusIconChangeTable[SC_MERC_HPUP] = SI_MERC_HPUP; + StatusIconChangeTable[SC_MERC_SPUP] = SI_MERC_SPUP; + StatusIconChangeTable[SC_MERC_HITUP] = SI_MERC_HITUP; + // Warlock Spheres + StatusIconChangeTable[SC_SPHERE_1] = SI_SPHERE_1; + StatusIconChangeTable[SC_SPHERE_2] = SI_SPHERE_2; + StatusIconChangeTable[SC_SPHERE_3] = SI_SPHERE_3; + StatusIconChangeTable[SC_SPHERE_4] = SI_SPHERE_4; + StatusIconChangeTable[SC_SPHERE_5] = SI_SPHERE_5; + // Warlock Preserved spells + StatusIconChangeTable[SC_SPELLBOOK1] = SI_SPELLBOOK1; + StatusIconChangeTable[SC_SPELLBOOK2] = SI_SPELLBOOK2; + StatusIconChangeTable[SC_SPELLBOOK3] = SI_SPELLBOOK3; + StatusIconChangeTable[SC_SPELLBOOK4] = SI_SPELLBOOK4; + StatusIconChangeTable[SC_SPELLBOOK5] = SI_SPELLBOOK5; + StatusIconChangeTable[SC_SPELLBOOK6] = SI_SPELLBOOK6; + StatusIconChangeTable[SC_MAXSPELLBOOK] = SI_SPELLBOOK7; + + StatusIconChangeTable[SC_NEUTRALBARRIER_MASTER] = SI_NEUTRALBARRIER_MASTER; + StatusIconChangeTable[SC_STEALTHFIELD_MASTER] = SI_STEALTHFIELD_MASTER; + StatusIconChangeTable[SC_OVERHEAT] = SI_OVERHEAT; + StatusIconChangeTable[SC_OVERHEAT_LIMITPOINT] = SI_OVERHEAT_LIMITPOINT; + + StatusIconChangeTable[SC_HALLUCINATIONWALK_POSTDELAY] = SI_HALLUCINATIONWALK_POSTDELAY; + StatusIconChangeTable[SC_TOXIN] = SI_TOXIN; + StatusIconChangeTable[SC_PARALYSE] = SI_PARALYSE; + StatusIconChangeTable[SC_VENOMBLEED] = SI_VENOMBLEED; + StatusIconChangeTable[SC_MAGICMUSHROOM] = SI_MAGICMUSHROOM; + StatusIconChangeTable[SC_DEATHHURT] = SI_DEATHHURT; + StatusIconChangeTable[SC_PYREXIA] = SI_PYREXIA; + StatusIconChangeTable[SC_OBLIVIONCURSE] = SI_OBLIVIONCURSE; + StatusIconChangeTable[SC_LEECHESEND] = SI_LEECHESEND; + + StatusIconChangeTable[SC_SHIELDSPELL_DEF] = SI_SHIELDSPELL_DEF; + StatusIconChangeTable[SC_SHIELDSPELL_MDEF] = SI_SHIELDSPELL_MDEF; + StatusIconChangeTable[SC_SHIELDSPELL_REF] = SI_SHIELDSPELL_REF; + StatusIconChangeTable[SC_BANDING_DEFENCE] = SI_BANDING_DEFENCE; + + StatusIconChangeTable[SC_GLOOMYDAY_SK] = SI_GLOOMYDAY; + + StatusIconChangeTable[SC_CURSEDCIRCLE_ATKER] = SI_CURSEDCIRCLE_ATKER; + + StatusIconChangeTable[SC_STOMACHACHE] = SI_STOMACHACHE; + StatusIconChangeTable[SC_MYSTERIOUS_POWDER] = SI_MYSTERIOUS_POWDER; + StatusIconChangeTable[SC_MELON_BOMB] = SI_MELON_BOMB; + StatusIconChangeTable[SC_BANANA_BOMB] = SI_BANANA_BOMB; + StatusIconChangeTable[SC_BANANA_BOMB_SITDOWN] = SI_BANANA_BOMB_SITDOWN_POSTDELAY; + + //Genetics New Food Items Status Icons + StatusIconChangeTable[SC_SAVAGE_STEAK] = SI_SAVAGE_STEAK; + StatusIconChangeTable[SC_COCKTAIL_WARG_BLOOD] = SI_COCKTAIL_WARG_BLOOD; + StatusIconChangeTable[SC_MINOR_BBQ] = SI_MINOR_BBQ; + StatusIconChangeTable[SC_SIROMA_ICE_TEA] = SI_SIROMA_ICE_TEA; + StatusIconChangeTable[SC_DROCERA_HERB_STEAMED] = SI_DROCERA_HERB_STEAMED; + StatusIconChangeTable[SC_PUTTI_TAILS_NOODLES] = SI_PUTTI_TAILS_NOODLES; + + StatusIconChangeTable[SC_BOOST500] |= SI_BOOST500; + StatusIconChangeTable[SC_FULL_SWING_K] |= SI_FULL_SWING_K; + StatusIconChangeTable[SC_MANA_PLUS] |= SI_MANA_PLUS; + StatusIconChangeTable[SC_MUSTLE_M] |= SI_MUSTLE_M; + StatusIconChangeTable[SC_LIFE_FORCE_F] |= SI_LIFE_FORCE_F; + StatusIconChangeTable[SC_EXTRACT_WHITE_POTION_Z] |= SI_EXTRACT_WHITE_POTION_Z; + StatusIconChangeTable[SC_VITATA_500] |= SI_VITATA_500; + StatusIconChangeTable[SC_EXTRACT_SALAMINE_JUICE] |= SI_EXTRACT_SALAMINE_JUICE; + + // Elemental Spirit's 'side' status change icons. + StatusIconChangeTable[SC_CIRCLE_OF_FIRE] = SI_CIRCLE_OF_FIRE; + StatusIconChangeTable[SC_FIRE_CLOAK] = SI_FIRE_CLOAK; + StatusIconChangeTable[SC_WATER_SCREEN] = SI_WATER_SCREEN; + StatusIconChangeTable[SC_WATER_DROP] = SI_WATER_DROP; + StatusIconChangeTable[SC_WIND_STEP] = SI_WIND_STEP; + StatusIconChangeTable[SC_WIND_CURTAIN] = SI_WIND_CURTAIN; + StatusIconChangeTable[SC_SOLID_SKIN] = SI_SOLID_SKIN; + StatusIconChangeTable[SC_STONE_SHIELD] = SI_STONE_SHIELD; + StatusIconChangeTable[SC_PYROTECHNIC] = SI_PYROTECHNIC; + StatusIconChangeTable[SC_HEATER] = SI_HEATER; + StatusIconChangeTable[SC_TROPIC] = SI_TROPIC; + StatusIconChangeTable[SC_AQUAPLAY] = SI_AQUAPLAY; + StatusIconChangeTable[SC_COOLER] = SI_COOLER; + StatusIconChangeTable[SC_CHILLY_AIR] = SI_CHILLY_AIR; + StatusIconChangeTable[SC_GUST] = SI_GUST; + StatusIconChangeTable[SC_BLAST] = SI_BLAST; + StatusIconChangeTable[SC_WILD_STORM] = SI_WILD_STORM; + StatusIconChangeTable[SC_PETROLOGY] = SI_PETROLOGY; + StatusIconChangeTable[SC_CURSED_SOIL] = SI_CURSED_SOIL; + StatusIconChangeTable[SC_UPHEAVAL] = SI_UPHEAVAL; + StatusIconChangeTable[SC_PUSH_CART] = SI_ON_PUSH_CART; + + //Other SC which are not necessarily associated to skills. + StatusChangeFlagTable[SC_ASPDPOTION0] = SCB_ASPD; + StatusChangeFlagTable[SC_ASPDPOTION1] = SCB_ASPD; + StatusChangeFlagTable[SC_ASPDPOTION2] = SCB_ASPD; + StatusChangeFlagTable[SC_ASPDPOTION3] = SCB_ASPD; + StatusChangeFlagTable[SC_SPEEDUP0] = SCB_SPEED; + StatusChangeFlagTable[SC_SPEEDUP1] = SCB_SPEED; + StatusChangeFlagTable[SC_ATKPOTION] = SCB_BATK; + StatusChangeFlagTable[SC_MATKPOTION] = SCB_MATK; + StatusChangeFlagTable[SC_INCALLSTATUS] |= SCB_STR|SCB_AGI|SCB_VIT|SCB_INT|SCB_DEX|SCB_LUK; + StatusChangeFlagTable[SC_INCSTR] |= SCB_STR; + StatusChangeFlagTable[SC_INCAGI] |= SCB_AGI; + StatusChangeFlagTable[SC_INCVIT] |= SCB_VIT; + StatusChangeFlagTable[SC_INCINT] |= SCB_INT; + StatusChangeFlagTable[SC_INCDEX] |= SCB_DEX; + StatusChangeFlagTable[SC_INCLUK] |= SCB_LUK; + StatusChangeFlagTable[SC_INCHIT] |= SCB_HIT; + StatusChangeFlagTable[SC_INCHITRATE] |= SCB_HIT; + StatusChangeFlagTable[SC_INCFLEE] |= SCB_FLEE; + StatusChangeFlagTable[SC_INCFLEERATE] |= SCB_FLEE; + StatusChangeFlagTable[SC_INCCRI] |= SCB_CRI; + StatusChangeFlagTable[SC_INCASPDRATE] |= SCB_ASPD; + StatusChangeFlagTable[SC_INCFLEE2] |= SCB_FLEE2; + StatusChangeFlagTable[SC_INCMHPRATE] |= SCB_MAXHP; + StatusChangeFlagTable[SC_INCMSPRATE] |= SCB_MAXSP; + StatusChangeFlagTable[SC_INCMHP] |= SCB_MAXHP; + StatusChangeFlagTable[SC_INCMSP] |= SCB_MAXSP; + StatusChangeFlagTable[SC_INCATKRATE] |= SCB_BATK|SCB_WATK; + StatusChangeFlagTable[SC_INCMATKRATE] |= SCB_MATK; + StatusChangeFlagTable[SC_INCDEFRATE] |= SCB_DEF; + StatusChangeFlagTable[SC_STRFOOD] |= SCB_STR; + StatusChangeFlagTable[SC_AGIFOOD] |= SCB_AGI; + StatusChangeFlagTable[SC_VITFOOD] |= SCB_VIT; + StatusChangeFlagTable[SC_INTFOOD] |= SCB_INT; + StatusChangeFlagTable[SC_DEXFOOD] |= SCB_DEX; + StatusChangeFlagTable[SC_LUKFOOD] |= SCB_LUK; + StatusChangeFlagTable[SC_HITFOOD] |= SCB_HIT; + StatusChangeFlagTable[SC_FLEEFOOD] |= SCB_FLEE; + StatusChangeFlagTable[SC_BATKFOOD] |= SCB_BATK; + StatusChangeFlagTable[SC_WATKFOOD] |= SCB_WATK; + StatusChangeFlagTable[SC_MATKFOOD] |= SCB_MATK; + StatusChangeFlagTable[SC_ARMOR_ELEMENT] |= SCB_ALL; + StatusChangeFlagTable[SC_ARMOR_RESIST] |= SCB_ALL; + StatusChangeFlagTable[SC_SPCOST_RATE] |= SCB_ALL; + StatusChangeFlagTable[SC_WALKSPEED] |= SCB_SPEED; + StatusChangeFlagTable[SC_ITEMSCRIPT] |= SCB_ALL; + // Cash Items + StatusChangeFlagTable[SC_FOOD_STR_CASH] = SCB_STR; + StatusChangeFlagTable[SC_FOOD_AGI_CASH] = SCB_AGI; + StatusChangeFlagTable[SC_FOOD_VIT_CASH] = SCB_VIT; + StatusChangeFlagTable[SC_FOOD_DEX_CASH] = SCB_DEX; + StatusChangeFlagTable[SC_FOOD_INT_CASH] = SCB_INT; + StatusChangeFlagTable[SC_FOOD_LUK_CASH] = SCB_LUK; + // Mercenary Bonus Effects + StatusChangeFlagTable[SC_MERC_FLEEUP] |= SCB_FLEE; + StatusChangeFlagTable[SC_MERC_ATKUP] |= SCB_WATK; + StatusChangeFlagTable[SC_MERC_HPUP] |= SCB_MAXHP; + StatusChangeFlagTable[SC_MERC_SPUP] |= SCB_MAXSP; + StatusChangeFlagTable[SC_MERC_HITUP] |= SCB_HIT; + // Guillotine Cross Poison Effects + StatusChangeFlagTable[SC_PARALYSE] |= SCB_ASPD|SCB_FLEE|SCB_SPEED; + StatusChangeFlagTable[SC_DEATHHURT] |= SCB_REGEN; + StatusChangeFlagTable[SC_VENOMBLEED] |= SCB_MAXHP; + StatusChangeFlagTable[SC_OBLIVIONCURSE] |= SCB_REGEN; + + StatusChangeFlagTable[SC_SAVAGE_STEAK] |= SCB_STR; + StatusChangeFlagTable[SC_COCKTAIL_WARG_BLOOD] |= SCB_INT; + StatusChangeFlagTable[SC_MINOR_BBQ] |= SCB_VIT; + StatusChangeFlagTable[SC_SIROMA_ICE_TEA] |= SCB_DEX; + StatusChangeFlagTable[SC_DROCERA_HERB_STEAMED] |= SCB_AGI; + StatusChangeFlagTable[SC_PUTTI_TAILS_NOODLES] |= SCB_LUK; + StatusChangeFlagTable[SC_BOOST500] |= SCB_ASPD; + StatusChangeFlagTable[SC_FULL_SWING_K] |= SCB_BATK; + StatusChangeFlagTable[SC_MANA_PLUS] |= SCB_MATK; + StatusChangeFlagTable[SC_MUSTLE_M] |= SCB_MAXHP; + StatusChangeFlagTable[SC_LIFE_FORCE_F] |= SCB_MAXSP; + StatusChangeFlagTable[SC_EXTRACT_WHITE_POTION_Z] |= SCB_REGEN; + StatusChangeFlagTable[SC_VITATA_500] |= SCB_REGEN; + StatusChangeFlagTable[SC_EXTRACT_SALAMINE_JUICE] |= SCB_ASPD; + +#ifdef RENEWAL_EDP + // renewal EDP increases your weapon atk + StatusChangeFlagTable[SC_EDP] |= SCB_WATK; +#endif + + if( !battle_config.display_hallucination ) //Disable Hallucination. + StatusIconChangeTable[SC_HALLUCINATION] = SI_BLANK; + + /* StatusChangeState (SCS_) NOMOVE */ + StatusChangeStateTable[SC_ANKLE] |= SCS_NOMOVE; + StatusChangeStateTable[SC_AUTOCOUNTER] |= SCS_NOMOVE; + StatusChangeStateTable[SC_TRICKDEAD] |= SCS_NOMOVE; + StatusChangeStateTable[SC_BLADESTOP] |= SCS_NOMOVE; + StatusChangeStateTable[SC_BLADESTOP_WAIT] |= SCS_NOMOVE; + StatusChangeStateTable[SC_GOSPEL] |= SCS_NOMOVE|SCS_NOMOVECOND; + StatusChangeStateTable[SC_BASILICA] |= SCS_NOMOVE|SCS_NOMOVECOND; + StatusChangeStateTable[SC_STOP] |= SCS_NOMOVE; + StatusChangeStateTable[SC_CLOSECONFINE] |= SCS_NOMOVE; + StatusChangeStateTable[SC_CLOSECONFINE2] |= SCS_NOMOVE; + StatusChangeStateTable[SC_MADNESSCANCEL] |= SCS_NOMOVE; + StatusChangeStateTable[SC_GRAVITATION] |= SCS_NOMOVE|SCS_NOMOVECOND; + StatusChangeStateTable[SC_WHITEIMPRISON] |= SCS_NOMOVE; + StatusChangeStateTable[SC_ELECTRICSHOCKER] |= SCS_NOMOVE; + StatusChangeStateTable[SC_BITE] |= SCS_NOMOVE; + StatusChangeStateTable[SC_THORNSTRAP] |= SCS_NOMOVE; + StatusChangeStateTable[SC_MAGNETICFIELD] |= SCS_NOMOVE; + StatusChangeStateTable[SC__MANHOLE] |= SCS_NOMOVE; + StatusChangeStateTable[SC_CURSEDCIRCLE_ATKER] |= SCS_NOMOVE; + StatusChangeStateTable[SC_CURSEDCIRCLE_TARGET] |= SCS_NOMOVE; + StatusChangeStateTable[SC_CRYSTALIZE] |= SCS_NOMOVE|SCS_NOMOVECOND; + StatusChangeStateTable[SC_NETHERWORLD] |= SCS_NOMOVE; + StatusChangeStateTable[SC_CAMOUFLAGE] |= SCS_NOMOVE|SCS_NOMOVECOND; + StatusChangeStateTable[SC_MEIKYOUSISUI] |= SCS_NOMOVE; + StatusChangeStateTable[SC_KAGEHUMI] |= SCS_NOMOVE; + StatusChangeStateTable[SC_KYOUGAKU] |= SCS_NOMOVE; + + /* StatusChangeState (SCS_) NOPICKUPITEMS */ + StatusChangeStateTable[SC_HIDING] |= SCS_NOPICKITEM; + StatusChangeStateTable[SC_CLOAKING] |= SCS_NOPICKITEM; + StatusChangeStateTable[SC_TRICKDEAD] |= SCS_NOPICKITEM; + StatusChangeStateTable[SC_BLADESTOP] |= SCS_NOPICKITEM; + StatusChangeStateTable[SC_CLOAKINGEXCEED] |= SCS_NOPICKITEM; + StatusChangeStateTable[SC_NOCHAT] |= SCS_NOPICKITEM|SCS_NOPICKITEMCOND; + + /* StatusChangeState (SCS_) NODROPITEMS */ + StatusChangeStateTable[SC_AUTOCOUNTER] |= SCS_NODROPITEM; + StatusChangeStateTable[SC_BLADESTOP] |= SCS_NODROPITEM; + StatusChangeStateTable[SC_NOCHAT] |= SCS_NODROPITEM|SCS_NODROPITEMCOND; + + /* StatusChangeState (SCS_) NOCAST (skills) */ + StatusChangeStateTable[SC_SILENCE] |= SCS_NOCAST; + StatusChangeStateTable[SC_STEELBODY] |= SCS_NOCAST; + StatusChangeStateTable[SC_BERSERK] |= SCS_NOCAST; + StatusChangeStateTable[SC__BLOODYLUST] |= SCS_NOCAST; + StatusChangeStateTable[SC_OBLIVIONCURSE] |= SCS_NOCAST; + StatusChangeStateTable[SC_WHITEIMPRISON] |= SCS_NOCAST; + StatusChangeStateTable[SC__INVISIBILITY] |= SCS_NOCAST; + StatusChangeStateTable[SC_CRYSTALIZE] |= SCS_NOCAST|SCS_NOCASTCOND; + StatusChangeStateTable[SC__IGNORANCE] |= SCS_NOCAST; + StatusChangeStateTable[SC_DEEPSLEEP] |= SCS_NOCAST; + StatusChangeStateTable[SC_SATURDAYNIGHTFEVER] |= SCS_NOCAST; + StatusChangeStateTable[SC_CURSEDCIRCLE_TARGET] |= SCS_NOCAST; + StatusChangeStateTable[SC_SILENCE] |= SCS_NOCAST; + + //Homon S + StatusChangeStateTable[SC_PARALYSIS] |= SCS_NOMOVE; + +} + +static void initDummyData(void) +{ + memset(&dummy_status, 0, sizeof(dummy_status)); + dummy_status.hp = + dummy_status.max_hp = + dummy_status.max_sp = + dummy_status.str = + dummy_status.agi = + dummy_status.vit = + dummy_status.int_ = + dummy_status.dex = + dummy_status.luk = + dummy_status.hit = 1; + dummy_status.speed = 2000; + dummy_status.adelay = 4000; + dummy_status.amotion = 2000; + dummy_status.dmotion = 2000; + dummy_status.ele_lv = 1; //Min elemental level. + dummy_status.mode = MD_CANMOVE; +} + + +//For copying a status_data structure from b to a, without overwriting current Hp and Sp +static inline void status_cpy(struct status_data* a, const struct status_data* b) +{ + memcpy((void*)&a->max_hp, (const void*)&b->max_hp, sizeof(struct status_data)-(sizeof(a->hp)+sizeof(a->sp))); +} + +//Sets HP to given value. Flag is the flag passed to status_heal in case +//final value is higher than current (use 2 to make a healing effect display +//on players) It will always succeed (overrides Berserk block), but it can't kill. +int status_set_hp(struct block_list *bl, unsigned int hp, int flag) +{ + struct status_data *status; + if (hp < 1) return 0; + status = status_get_status_data(bl); + if (status == &dummy_status) + return 0; + + if (hp > status->max_hp) hp = status->max_hp; + if (hp == status->hp) return 0; + if (hp > status->hp) + return status_heal(bl, hp - status->hp, 0, 1|flag); + return status_zap(bl, status->hp - hp, 0); +} + +//Sets SP to given value. Flag is the flag passed to status_heal in case +//final value is higher than current (use 2 to make a healing effect display +//on players) +int status_set_sp(struct block_list *bl, unsigned int sp, int flag) +{ + struct status_data *status; + + status = status_get_status_data(bl); + if (status == &dummy_status) + return 0; + + if (sp > status->max_sp) sp = status->max_sp; + if (sp == status->sp) return 0; + if (sp > status->sp) + return status_heal(bl, 0, sp - status->sp, 1|flag); + return status_zap(bl, 0, status->sp - sp); +} + +int status_charge(struct block_list* bl, int hp, int sp) +{ + if(!(bl->type&BL_CONSUME)) + return hp+sp; //Assume all was charged so there are no 'not enough' fails. + return status_damage(NULL, bl, hp, sp, 0, 3); +} + +//Inflicts damage on the target with the according walkdelay. +//If flag&1, damage is passive and does not triggers cancelling status changes. +//If flag&2, fail if target does not has enough to substract. +//If flag&4, if killed, mob must not give exp/loot. +//flag will be set to &8 when damaging sp of a dead character +int status_damage(struct block_list *src,struct block_list *target,int hp, int sp, int walkdelay, int flag) +{ + struct status_data *status; + struct status_change *sc; + + if(sp && !(target->type&BL_CONSUME)) + sp = 0; //Not a valid SP target. + + if (hp < 0) { //Assume absorbed damage. + status_heal(target, -hp, 0, 1); + hp = 0; + } + + if (sp < 0) { + status_heal(target, 0, -sp, 1); + sp = 0; + } + + if (target->type == BL_SKILL) + return skill_unit_ondamaged((struct skill_unit *)target, src, hp, gettick()); + + status = status_get_status_data(target); + if( status == &dummy_status ) + return 0; + + if ((unsigned int)hp >= status->hp) { + if (flag&2) return 0; + hp = status->hp; + } + + if ((unsigned int)sp > status->sp) { + if (flag&2) return 0; + sp = status->sp; + } + + if (!hp && !sp) + return 0; + + if( !status->hp ) + flag |= 8; + +// Let through. battle.c/skill.c have the whole logic of when it's possible or +// not to hurt someone (and this check breaks pet catching) [Skotlex] +// if (!target->prev && !(flag&2)) +// return 0; //Cannot damage a bl not on a map, except when "charging" hp/sp + + sc = status_get_sc(target); + if( hp && battle_config.invincible_nodamage && src && sc && sc->data[SC_INVINCIBLE] && !sc->data[SC_INVINCIBLEOFF] ) + hp = 1; + + if( hp && !(flag&1) ) { + if( sc ) { + struct status_change_entry *sce; + if (sc->data[SC_STONE] && sc->opt1 == OPT1_STONE) + status_change_end(target, SC_STONE, INVALID_TIMER); + status_change_end(target, SC_FREEZE, INVALID_TIMER); + status_change_end(target, SC_SLEEP, INVALID_TIMER); + status_change_end(target, SC_WINKCHARM, INVALID_TIMER); + status_change_end(target, SC_CONFUSION, INVALID_TIMER); + status_change_end(target, SC_TRICKDEAD, INVALID_TIMER); + status_change_end(target, SC_HIDING, INVALID_TIMER); + status_change_end(target, SC_CLOAKING, INVALID_TIMER); + status_change_end(target, SC_CHASEWALK, INVALID_TIMER); + status_change_end(target, SC_CAMOUFLAGE, INVALID_TIMER); + status_change_end(target, SC__INVISIBILITY, INVALID_TIMER); + status_change_end(target, SC_DEEPSLEEP, INVALID_TIMER); + if ((sce=sc->data[SC_ENDURE]) && !sce->val4) { + //Endure count is only reduced by non-players on non-gvg maps. + //val4 signals infinite endure. [Skotlex] + if (src && src->type != BL_PC && !map_flag_gvg(target->m) && !map[target->m].flag.battleground && --(sce->val2) < 0) + status_change_end(target, SC_ENDURE, INVALID_TIMER); + } + if ((sce=sc->data[SC_GRAVITATION]) && sce->val3 == BCT_SELF) { + struct skill_unit_group* sg = skill_id2group(sce->val4); + if (sg) { + skill_delunitgroup(sg); + sce->val4 = 0; + status_change_end(target, SC_GRAVITATION, INVALID_TIMER); + } + } + if(sc->data[SC_DANCING] && (unsigned int)hp > status->max_hp>>2) + status_change_end(target, SC_DANCING, INVALID_TIMER); + if(sc->data[SC_CLOAKINGEXCEED] && --(sc->data[SC_CLOAKINGEXCEED]->val2) <= 0) + status_change_end(target, SC_CLOAKINGEXCEED, INVALID_TIMER); + if(sc->data[SC_KAGEMUSYA] && --(sc->data[SC_KAGEMUSYA]->val3) <= 0) + status_change_end(target, SC_KAGEMUSYA, INVALID_TIMER); + } + unit_skillcastcancel(target, 2); + } + + status->hp-= hp; + status->sp-= sp; + + if (sc && hp && status->hp) { + if (sc->data[SC_AUTOBERSERK] && + (!sc->data[SC_PROVOKE] || !sc->data[SC_PROVOKE]->val2) && + status->hp < status->max_hp>>2) + sc_start4(target,SC_PROVOKE,100,10,1,0,0,0); + if (sc->data[SC_BERSERK] && status->hp <= 100) + status_change_end(target, SC_BERSERK, INVALID_TIMER); + if( sc->data[SC_RAISINGDRAGON] && status->hp <= 1000 ) + status_change_end(target, SC_RAISINGDRAGON, INVALID_TIMER); + if (sc->data[SC_SATURDAYNIGHTFEVER] && status->hp <= 100) + status_change_end(target, SC_SATURDAYNIGHTFEVER, INVALID_TIMER); + if (sc->data[SC__BLOODYLUST] && status->hp <= 100) + status_change_end(target, SC__BLOODYLUST, INVALID_TIMER); + } + + switch (target->type) { + case BL_PC: pc_damage((TBL_PC*)target,src,hp,sp); break; + case BL_MOB: mob_damage((TBL_MOB*)target, src, hp); break; + case BL_HOM: merc_damage((TBL_HOM*)target); break; + case BL_MER: mercenary_heal((TBL_MER*)target,hp,sp); break; + case BL_ELEM: elemental_heal((TBL_ELEM*)target,hp,sp); break; + } + + if( src && target->type == BL_PC && ((TBL_PC*)target)->disguise ) {// stop walking when attacked in disguise to prevent walk-delay bug + unit_stop_walking( target, 1 ); + } + + if( status->hp || (flag&8) ) + { //Still lives or has been dead before this damage. + if (walkdelay) + unit_set_walkdelay(target, gettick(), walkdelay, 0); + return hp+sp; + } + + status->hp = 1; //To let the dead function cast skills and all that. + //NOTE: These dead functions should return: [Skotlex] + //0: Death cancelled, auto-revived. + //Non-zero: Standard death. Clear status, cancel move/attack, etc + //&2: Also remove object from map. + //&4: Also delete object from memory. + switch (target->type) { + case BL_PC: flag = pc_dead((TBL_PC*)target,src); break; + case BL_MOB: flag = mob_dead((TBL_MOB*)target, src, flag&4?3:0); break; + case BL_HOM: flag = merc_hom_dead((TBL_HOM*)target); break; + case BL_MER: flag = mercenary_dead((TBL_MER*)target); break; + case BL_ELEM: flag = elemental_dead((TBL_ELEM*)target); break; + default: //Unhandled case, do nothing to object. + flag = 0; + break; + } + + if(!flag) //Death cancelled. + return hp+sp; + + //Normal death + status->hp = 0; + if (battle_config.clear_unit_ondeath && + battle_config.clear_unit_ondeath&target->type) + skill_clear_unitgroup(target); + + if(target->type&BL_REGEN) + { //Reset regen ticks. + struct regen_data *regen = status_get_regen_data(target); + if (regen) { + memset(®en->tick, 0, sizeof(regen->tick)); + if (regen->sregen) + memset(®en->sregen->tick, 0, sizeof(regen->sregen->tick)); + if (regen->ssregen) + memset(®en->ssregen->tick, 0, sizeof(regen->ssregen->tick)); + } + } + + if( sc && sc->data[SC_KAIZEL] && !map_flag_gvg(target->m) ) + { //flag&8 = disable Kaizel + int time = skill_get_time2(SL_KAIZEL,sc->data[SC_KAIZEL]->val1); + //Look for Osiris Card's bonus effect on the character and revive 100% or revive normally + if ( target->type == BL_PC && BL_CAST(BL_PC,target)->special_state.restart_full_recover ) + status_revive(target, 100, 100); + else + status_revive(target, sc->data[SC_KAIZEL]->val2, 0); + status_change_clear(target,0); + clif_skill_nodamage(target,target,ALL_RESURRECTION,1,1); + sc_start(target,status_skill2sc(PR_KYRIE),100,10,time); + + if( target->type == BL_MOB ) + ((TBL_MOB*)target)->state.rebirth = 1; + + return hp+sp; + } + if(target->type == BL_PC){ + TBL_PC *sd = BL_CAST(BL_PC,target); + TBL_HOM *hd = sd->hd; + if(hd && hd->sc.data[SC_LIGHT_OF_REGENE]){ + clif_skillcasting(&hd->bl, hd->bl.id, target->id, 0,0, MH_LIGHT_OF_REGENE, skill_get_ele(MH_LIGHT_OF_REGENE, 1), 10); //just to display usage + clif_skill_nodamage(&sd->bl, target, ALL_RESURRECTION, 1, status_revive(&sd->bl,10*hd->sc.data[SC_LIGHT_OF_REGENE]->val1,0)); + status_change_end(&sd->hd->bl,SC_LIGHT_OF_REGENE,INVALID_TIMER); + return hp + sp; + } + } + if (target->type == BL_MOB && sc && sc->data[SC_REBIRTH] && !((TBL_MOB*) target)->state.rebirth) {// Ensure the monster has not already rebirthed before doing so. + status_revive(target, sc->data[SC_REBIRTH]->val2, 0); + status_change_clear(target,0); + ((TBL_MOB*)target)->state.rebirth = 1; + + return hp+sp; + } + + status_change_clear(target,0); + + if(flag&4) //Delete from memory. (also invokes map removal code) + unit_free(target,CLR_DEAD); + else + if(flag&2) //remove from map + unit_remove_map(target,CLR_DEAD); + else + { //Some death states that would normally be handled by unit_remove_map + unit_stop_attack(target); + unit_stop_walking(target,1); + unit_skillcastcancel(target,0); + clif_clearunit_area(target,CLR_DEAD); + skill_unit_move(target,gettick(),4); + skill_cleartimerskill(target); + } + + return hp+sp; +} + +//Heals a character. If flag&1, this is forced healing (otherwise stuff like Berserk can block it) +//If flag&2, when the player is healed, show the HP/SP heal effect. +int status_heal(struct block_list *bl,int hp,int sp, int flag) +{ + struct status_data *status; + struct status_change *sc; + + status = status_get_status_data(bl); + + if (status == &dummy_status || !status->hp) + return 0; + + sc = status_get_sc(bl); + if (sc && !sc->count) + sc = NULL; + + if (hp < 0) { + if (hp == INT_MIN) hp++; //-INT_MIN == INT_MIN in some architectures! + status_damage(NULL, bl, -hp, 0, 0, 1); + hp = 0; + } + + if(hp) { + if( sc && (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) ) { + if( flag&1 ) + flag &= ~2; + else + hp = 0; + } + + if((unsigned int)hp > status->max_hp - status->hp) + hp = status->max_hp - status->hp; + } + + if(sp < 0) { + if (sp==INT_MIN) sp++; + status_damage(NULL, bl, 0, -sp, 0, 1); + sp = 0; + } + + if(sp) { + if((unsigned int)sp > status->max_sp - status->sp) + sp = status->max_sp - status->sp; + } + + if(!sp && !hp) return 0; + + status->hp+= hp; + status->sp+= sp; + + if(hp && sc && + sc->data[SC_AUTOBERSERK] && + sc->data[SC_PROVOKE] && + sc->data[SC_PROVOKE]->val2==1 && + status->hp>=status->max_hp>>2 + ) //End auto berserk. + status_change_end(bl, SC_PROVOKE, INVALID_TIMER); + + // send hp update to client + switch(bl->type) { + case BL_PC: pc_heal((TBL_PC*)bl,hp,sp,flag&2?1:0); break; + case BL_MOB: mob_heal((TBL_MOB*)bl,hp); break; + case BL_HOM: merc_hom_heal((TBL_HOM*)bl); break; + case BL_MER: mercenary_heal((TBL_MER*)bl,hp,sp); break; + case BL_ELEM: elemental_heal((TBL_ELEM*)bl,hp,sp); break; + } + + return hp+sp; +} + +//Does percentual non-flinching damage/heal. If mob is killed this way, +//no exp/drops will be awarded if there is no src (or src is target) +//If rates are > 0, percent is of current HP/SP +//If rates are < 0, percent is of max HP/SP +//If !flag, this is heal, otherwise it is damage. +//Furthermore, if flag==2, then the target must not die from the substraction. +int status_percent_change(struct block_list *src,struct block_list *target,signed char hp_rate, signed char sp_rate, int flag) +{ + struct status_data *status; + unsigned int hp =0, sp = 0; + + status = status_get_status_data(target); + + + //It's safe now [MarkZD] + if (hp_rate > 99) + hp = status->hp; + else if (hp_rate > 0) + hp = status->hp>10000? + hp_rate*(status->hp/100): + ((int64)hp_rate*status->hp)/100; + else if (hp_rate < -99) + hp = status->max_hp; + else if (hp_rate < 0) + hp = status->max_hp>10000? + (-hp_rate)*(status->max_hp/100): + ((int64)-hp_rate*status->max_hp)/100; + if (hp_rate && !hp) + hp = 1; + + if (flag == 2 && hp >= status->hp) + hp = status->hp-1; //Must not kill target. + + if (sp_rate > 99) + sp = status->sp; + else if (sp_rate > 0) + sp = ((int64)sp_rate*status->sp)/100; + else if (sp_rate < -99) + sp = status->max_sp; + else if (sp_rate < 0) + sp = ((int64)-sp_rate)*status->max_sp/100; + if (sp_rate && !sp) + sp = 1; + + //Ugly check in case damage dealt is too much for the received args of + //status_heal / status_damage. [Skotlex] + if (hp > INT_MAX) { + hp -= INT_MAX; + if (flag) + status_damage(src, target, INT_MAX, 0, 0, (!src||src==target?5:1)); + else + status_heal(target, INT_MAX, 0, 0); + } + if (sp > INT_MAX) { + sp -= INT_MAX; + if (flag) + status_damage(src, target, 0, INT_MAX, 0, (!src||src==target?5:1)); + else + status_heal(target, 0, INT_MAX, 0); + } + if (flag) + return status_damage(src, target, hp, sp, 0, (!src||src==target?5:1)); + return status_heal(target, hp, sp, 0); +} + +int status_revive(struct block_list *bl, unsigned char per_hp, unsigned char per_sp) +{ + struct status_data *status; + unsigned int hp, sp; + if (!status_isdead(bl)) return 0; + + status = status_get_status_data(bl); + if (status == &dummy_status) + return 0; //Invalid target. + + hp = (int64)status->max_hp * per_hp/100; + sp = (int64)status->max_sp * per_sp/100; + + if(hp > status->max_hp - status->hp) + hp = status->max_hp - status->hp; + else if (per_hp && !hp) + hp = 1; + + if(sp > status->max_sp - status->sp) + sp = status->max_sp - status->sp; + else if (per_sp && !sp) + sp = 1; + + status->hp += hp; + status->sp += sp; + + if (bl->prev) //Animation only if character is already on a map. + clif_resurrection(bl, 1); + switch (bl->type) { + case BL_PC: pc_revive((TBL_PC*)bl, hp, sp); break; + case BL_MOB: mob_revive((TBL_MOB*)bl, hp); break; + case BL_HOM: merc_hom_revive((TBL_HOM*)bl, hp, sp); break; + } + return 1; +} + +/*========================================== + * Checks whether the src can use the skill on the target, + * taking into account status/option of both source/target. [Skotlex] + * flag: + * 0 - Trying to use skill on target. + * 1 - Cast bar is done. + * 2 - Skill already pulled off, check is due to ground-based skills or splash-damage ones. + * src MAY be null to indicate we shouldn't check it, this is a ground-based skill attack. + * target MAY Be null, in which case the checks are only to see + * whether the source can cast or not the skill on the ground. + *------------------------------------------*/ +int status_check_skilluse(struct block_list *src, struct block_list *target, uint16 skill_id, int flag) +{ + struct status_data *status; + struct status_change *sc=NULL, *tsc; + int hide_flag; + + status = src?status_get_status_data(src):&dummy_status; + + if (src && src->type != BL_PC && status_isdead(src)) + return 0; + + if (!skill_id) { //Normal attack checks. + if (!(status->mode&MD_CANATTACK)) + return 0; //This mode is only needed for melee attacking. + //Dead state is not checked for skills as some skills can be used + //on dead characters, said checks are left to skill.c [Skotlex] + if (target && status_isdead(target)) + return 0; + if( src && (sc = status_get_sc(src)) && sc->data[SC_CRYSTALIZE] && src->type != BL_MOB) + return 0; + } + + switch( skill_id ) { + case PA_PRESSURE: + if( flag && target ) { + //Gloria Avoids pretty much everything.... + tsc = status_get_sc(target); + if(tsc && tsc->option&OPTION_HIDE) + return 0; + } + break; + case GN_WALLOFTHORN: + if( target && status_isdead(target) ) + return 0; + break; + case AL_TELEPORT: + //Should fail when used on top of Land Protector [Skotlex] + if (src && map_getcell(src->m, src->x, src->y, CELL_CHKLANDPROTECTOR) + && !(status->mode&MD_BOSS) + && (src->type != BL_PC || ((TBL_PC*)src)->skillitem != skill_id)) + return 0; + break; + default: + break; + } + + if ( src ) sc = status_get_sc(src); + + if( sc && sc->count ) { + + if (skill_id != RK_REFRESH && sc->opt1 >0 && (sc->opt1 != OPT1_CRYSTALIZE && src->type != BL_MOB) && sc->opt1 != OPT1_BURNING && skill_id != SR_GENTLETOUCH_CURE) { //Stuned/Frozen/etc + if (flag != 1) //Can't cast, casted stuff can't damage. + return 0; + if (!(skill_get_inf(skill_id)&INF_GROUND_SKILL)) + return 0; //Targetted spells can't come off. + } + + if ( + (sc->data[SC_TRICKDEAD] && skill_id != NV_TRICKDEAD) + || (sc->data[SC_AUTOCOUNTER] && !flag) + || (sc->data[SC_GOSPEL] && sc->data[SC_GOSPEL]->val4 == BCT_SELF && skill_id != PA_GOSPEL) + || (sc->data[SC_GRAVITATION] && sc->data[SC_GRAVITATION]->val3 == BCT_SELF && flag != 2) + ) + return 0; + + if (sc->data[SC_WINKCHARM] && target && !flag) { //Prevents skill usage + if( unit_bl2ud(src) && (unit_bl2ud(src))->walktimer == INVALID_TIMER ) + unit_walktobl(src, map_id2bl(sc->data[SC_WINKCHARM]->val2), 3, 1); + clif_emotion(src, E_LV); + return 0; + } + + if (sc->data[SC_BLADESTOP]) { + switch (sc->data[SC_BLADESTOP]->val1) + { + case 5: if (skill_id == MO_EXTREMITYFIST) break; + case 4: if (skill_id == MO_CHAINCOMBO) break; + case 3: if (skill_id == MO_INVESTIGATE) break; + case 2: if (skill_id == MO_FINGEROFFENSIVE) break; + default: return 0; + } + } + + if (sc->data[SC_DANCING] && flag!=2) { + if( src->type == BL_PC && skill_id >= WA_SWING_DANCE && skill_id <= WM_UNLIMITED_HUMMING_VOICE ) + { // Lvl 5 Lesson or higher allow you use 3rd job skills while dancing.v + if( pc_checkskill((TBL_PC*)src,WM_LESSON) < 5 ) + return 0; + } else if(sc->data[SC_LONGING]) { //Allow everything except dancing/re-dancing. [Skotlex] + if (skill_id == BD_ENCORE || + skill_get_inf2(skill_id)&(INF2_SONG_DANCE|INF2_ENSEMBLE_SKILL) + ) + return 0; + } else { + switch (skill_id) { + case BD_ADAPTATION: + case CG_LONGINGFREEDOM: + case BA_MUSICALSTRIKE: + case DC_THROWARROW: + break; + default: + return 0; + } + } + if ((sc->data[SC_DANCING]->val1&0xFFFF) == CG_HERMODE && skill_id == BD_ADAPTATION) + return 0; //Can't amp out of Wand of Hermode :/ [Skotlex] + } + + if (skill_id && //Do not block item-casted skills. + (src->type != BL_PC || ((TBL_PC*)src)->skillitem != skill_id) + ) { //Skills blocked through status changes... + if (!flag && ( //Blocked only from using the skill (stuff like autospell may still go through + sc->cant.cast || + (sc->data[SC_MARIONETTE] && skill_id != CG_MARIONETTE) || //Only skill you can use is marionette again to cancel it + (sc->data[SC_MARIONETTE2] && skill_id == CG_MARIONETTE) || //Cannot use marionette if you are being buffed by another + (sc->data[SC_STASIS] && skill_block_check(src, SC_STASIS, skill_id)) || + (sc->data[SC_KAGEHUMI] && skill_block_check(src, SC_KAGEHUMI, skill_id)) + )) + return 0; + + //Skill blocking. + if ( + (sc->data[SC_VOLCANO] && skill_id == WZ_ICEWALL) || + (sc->data[SC_ROKISWEIL] && skill_id != BD_ADAPTATION) || + (sc->data[SC_HERMODE] && skill_get_inf(skill_id) & INF_SUPPORT_SKILL) || + (sc->data[SC_NOCHAT] && sc->data[SC_NOCHAT]->val1&MANNER_NOSKILL) + ) + return 0; + + if( sc->data[SC__MANHOLE] || ((tsc = status_get_sc(target)) && tsc->data[SC__MANHOLE]) ) { + switch(skill_id) {//##TODO## make this a flag in skill_db? + // Skills that can be used even under Man Hole effects. + case SC_SHADOWFORM: + case SC_STRIPACCESSARY: + break; + default: + return 0; + } + } + + } + } + + if (sc && sc->option) + { + if (sc->option&OPTION_HIDE) + switch (skill_id) { //Usable skills while hiding. + case TF_HIDING: + case AS_GRIMTOOTH: + case RG_BACKSTAP: + case RG_RAID: + case NJ_SHADOWJUMP: + case NJ_KIRIKAGE: + case KO_YAMIKUMO: + break; + default: + //Non players can use all skills while hidden. + if (!skill_id || src->type == BL_PC) + return 0; + } + if (sc->option&OPTION_CHASEWALK && skill_id != ST_CHASEWALK) + return 0; + if(sc->option&OPTION_MOUNTING) + return 0;//New mounts can't attack nor use skills in the client; this check makes it cheat-safe [Ind] + } + + if (target == NULL || target == src) //No further checking needed. + return 1; + + tsc = status_get_sc(target); + + if(tsc && tsc->count) { + /* attacks in invincible are capped to 1 damage and handled in batte.c; allow spell break and eske for sealed shrine GDB when in INVINCIBLE state. */ + if( tsc->data[SC_INVINCIBLE] && !tsc->data[SC_INVINCIBLEOFF] && skill_id && !(skill_id&(SA_SPELLBREAKER|SL_SKE)) ) + return 0; + if(!skill_id && tsc->data[SC_TRICKDEAD]) + return 0; + if((skill_id == WZ_STORMGUST || skill_id == WZ_FROSTNOVA || skill_id == NJ_HYOUSYOURAKU) + && tsc->data[SC_FREEZE]) + return 0; + if(skill_id == PR_LEXAETERNA && (tsc->data[SC_FREEZE] || (tsc->data[SC_STONE] && tsc->opt1 == OPT1_STONE))) + return 0; + } + + //If targetting, cloak+hide protect you, otherwise only hiding does. + hide_flag = flag?OPTION_HIDE:(OPTION_HIDE|OPTION_CLOAK|OPTION_CHASEWALK); + + //You cannot hide from ground skills. + if( skill_get_ele(skill_id,1) == ELE_EARTH ) //TODO: Need Skill Lv here :/ + hide_flag &= ~OPTION_HIDE; + + switch( target->type ) { + case BL_PC: { + struct map_session_data *sd = (TBL_PC*) target; + bool is_boss = (status->mode&MD_BOSS); + bool is_detect = ((status->mode&MD_DETECTOR)?true:false);//god-knows-why gcc doesn't shut up until this happens + if (pc_isinvisible(sd)) + return 0; + if (tsc->option&hide_flag && !is_boss && + ((sd->special_state.perfect_hiding || !is_detect) || + (tsc->data[SC_CLOAKINGEXCEED] && is_detect))) + return 0; + if( tsc->data[SC_CAMOUFLAGE] && !(is_boss || is_detect) && !skill_id ) + return 0; + if( tsc->data[SC_STEALTHFIELD] && !is_boss ) + return 0; + } + break; + case BL_ITEM: //Allow targetting of items to pick'em up (or in the case of mobs, to loot them). + //TODO: Would be nice if this could be used to judge whether the player can or not pick up the item it targets. [Skotlex] + if (status->mode&MD_LOOTER) + return 1; + return 0; + case BL_HOM: + case BL_MER: + case BL_ELEM: + if( target->type == BL_HOM && skill_id && battle_config.hom_setting&0x1 && skill_get_inf(skill_id)&INF_SUPPORT_SKILL && battle_get_master(target) != src ) + return 0; // Can't use support skills on Homunculus (only Master/Self) + if( target->type == BL_MER && (skill_id == PR_ASPERSIO || (skill_id >= SA_FLAMELAUNCHER && skill_id <= SA_SEISMICWEAPON)) && battle_get_master(target) != src ) + return 0; // Can't use Weapon endow skills on Mercenary (only Master) + if( skill_id == AM_POTIONPITCHER && ( target->type == BL_MER || target->type == BL_ELEM) ) + return 0; // Can't use Potion Pitcher on Mercenaries + default: + //Check for chase-walk/hiding/cloaking opponents. + if( tsc ) { + if( tsc->option&hide_flag && !(status->mode&(MD_BOSS|MD_DETECTOR))) + return 0; + if( tsc->data[SC_STEALTHFIELD] && !(status->mode&MD_BOSS) ) + return 0; + } + } + return 1; +} + +//Checks whether the source can see and chase target. +int status_check_visibility(struct block_list *src, struct block_list *target) +{ + int view_range; + struct status_data* status = status_get_status_data(src); + struct status_change* tsc = status_get_sc(target); + switch (src->type) { + case BL_MOB: + view_range = ((TBL_MOB*)src)->min_chase; + break; + case BL_PET: + view_range = ((TBL_PET*)src)->db->range2; + break; + default: + view_range = AREA_SIZE; + } + + if (src->m != target->m || !check_distance_bl(src, target, view_range)) + return 0; + + if( tsc && tsc->data[SC_STEALTHFIELD] ) + return 0; + + switch (target->type) + { //Check for chase-walk/hiding/cloaking opponents. + case BL_PC: + if ( tsc->data[SC_CLOAKINGEXCEED] && !(status->mode&MD_BOSS) ) + return 0; + if( (tsc->option&(OPTION_HIDE|OPTION_CLOAK|OPTION_CHASEWALK) || tsc->data[SC__INVISIBILITY] || tsc->data[SC_CAMOUFLAGE]) && !(status->mode&MD_BOSS) && + ( ((TBL_PC*)target)->special_state.perfect_hiding || !(status->mode&MD_DETECTOR) ) ) + return 0; + break; + default: + if( tsc && (tsc->option&(OPTION_HIDE|OPTION_CLOAK|OPTION_CHASEWALK) || tsc->data[SC__INVISIBILITY] || tsc->data[SC_CAMOUFLAGE]) && !(status->mode&(MD_BOSS|MD_DETECTOR)) ) + return 0; + + } + + return 1; +} + +// Basic ASPD value +int status_base_amotion_pc(struct map_session_data* sd, struct status_data* status) +{ + int amotion; +#ifdef RENEWAL_ASPD + short mod = -1; + + switch( sd->weapontype2 ){ // adjustment for dual weilding + case W_DAGGER: mod = 0; break; // 0, 1, 1 + case W_1HSWORD: + case W_1HAXE: mod = 1; + if( (sd->class_&MAPID_THIRDMASK) == MAPID_GUILLOTINE_CROSS ) // 0, 2, 3 + mod = sd->weapontype2 / W_1HSWORD + W_1HSWORD / sd->weapontype2 ; + } + + amotion = ( sd->status.weapon < MAX_WEAPON_TYPE && mod < 0 ) + ? (aspd_base[pc_class2idx(sd->status.class_)][sd->status.weapon]) // single weapon + : ((aspd_base[pc_class2idx(sd->status.class_)][sd->weapontype2] // dual-wield + + aspd_base[pc_class2idx(sd->status.class_)][sd->weapontype2]) * 6 / 10 + 10 * mod + - aspd_base[pc_class2idx(sd->status.class_)][sd->weapontype2] + + aspd_base[pc_class2idx(sd->status.class_)][sd->weapontype1]); + + if ( sd->status.shield ) + amotion += ( 2000 - aspd_base[pc_class2idx(sd->status.class_)][W_FIST] ) + + ( aspd_base[pc_class2idx(sd->status.class_)][MAX_WEAPON_TYPE] - 2000 ); + +#else + // base weapon delay + amotion = (sd->status.weapon < MAX_WEAPON_TYPE) + ? (aspd_base[pc_class2idx(sd->status.class_)][sd->status.weapon]) // single weapon + : (aspd_base[pc_class2idx(sd->status.class_)][sd->weapontype1] + aspd_base[pc_class2idx(sd->status.class_)][sd->weapontype2])*7/10; // dual-wield + + // percentual delay reduction from stats + amotion -= amotion * (4*status->agi + status->dex)/1000; +#endif + // raw delay adjustment from bAspd bonus + amotion += sd->bonus.aspd_add; + + return amotion; +} + +static unsigned short status_base_atk(const struct block_list *bl, const struct status_data *status) +{ + int flag = 0, str, dex, +#ifdef RENEWAL + rstr, +#endif + dstr; + + + if(!(bl->type&battle_config.enable_baseatk)) + return 0; + + if (bl->type == BL_PC) + switch(((TBL_PC*)bl)->status.weapon){ + case W_BOW: + case W_MUSICAL: + case W_WHIP: + case W_REVOLVER: + case W_RIFLE: + case W_GATLING: + case W_SHOTGUN: + case W_GRENADE: + flag = 1; + } + if (flag) { +#ifdef RENEWAL + rstr = +#endif + str = status->dex; + dex = status->str; + } else { +#ifdef RENEWAL + rstr = +#endif + str = status->str; + dex = status->dex; + } + //Normally only players have base-atk, but homunc have a different batk + // equation, hinting that perhaps non-players should use this for batk. + // [Skotlex] + dstr = str/10; + str += dstr*dstr; + if (bl->type == BL_PC) +#ifdef RENEWAL + str = (rstr*10 + dex*10/5 + status->luk*10/3 + ((TBL_PC*)bl)->status.base_level*10/4)/10; +#else + str+= dex/5 + status->luk/5; +#endif + return cap_value(str, 0, USHRT_MAX); +} + +#ifndef RENEWAL +static inline unsigned short status_base_matk_min(const struct status_data* status){ return status->int_+(status->int_/7)*(status->int_/7); } +static inline unsigned short status_base_matk_max(const struct status_data* status){ return status->int_+(status->int_/5)*(status->int_/5); } +#else +unsigned short status_base_matk(const struct status_data* status, int level){ return status->int_+(status->int_/2)+(status->dex/5)+(status->luk/3)+(level/4); } +#endif + +//Fills in the misc data that can be calculated from the other status info (except for level) +void status_calc_misc(struct block_list *bl, struct status_data *status, int level) +{ + //Non players get the value set, players need to stack with previous bonuses. + if( bl->type != BL_PC ) + status->batk = + status->hit = status->flee = + status->def2 = status->mdef2 = + status->cri = status->flee2 = 0; + +#ifdef RENEWAL // renewal formulas + status->matk_min = status->matk_max = status_base_matk(status, level); + status->hit += level + status->dex + status->luk/3 + 175; //base level + ( every 1 dex = +1 hit ) + (every 3 luk = +1 hit) + 175 + status->flee += level + status->agi + status->luk/5 + 100; //base level + ( every 1 agi = +1 flee ) + (every 5 luk = +1 flee) + 100 + status->def2 += (int)(((float)level + status->vit)/2 + ((float)status->agi/5)); //base level + (every 2 vit = +1 def) + (every 5 agi = +1 def) + status->mdef2 += (int)(status->int_ + ((float)level/4) + ((float)status->dex/5) + ((float)status->vit/5)); //(every 4 base level = +1 mdef) + (every 1 int = +1 mdef) + (every 5 dex = +1 mdef) + (every 5 vit = +1 mdef) +#else + status->matk_min = status_base_matk_min(status); + status->matk_max = status_base_matk_max(status); + status->hit += level + status->dex; + status->flee += level + status->agi; + status->def2 += status->vit; + status->mdef2 += status->int_ + (status->vit>>1); +#endif + + if( bl->type&battle_config.enable_critical ) + status->cri += 10 + (status->luk*10/3); //(every 1 luk = +0.3 critical) + else + status->cri = 0; + + if (bl->type&battle_config.enable_perfect_flee) + status->flee2 += status->luk + 10; //(every 10 luk = +1 perfect flee) + else + status->flee2 = 0; + + if (status->batk) { + int temp = status->batk + status_base_atk(bl, status); + status->batk = cap_value(temp, 0, USHRT_MAX); + } else + status->batk = status_base_atk(bl, status); + if (status->cri) + switch (bl->type) { + case BL_MOB: + if(battle_config.mob_critical_rate != 100) + status->cri = status->cri*battle_config.mob_critical_rate/100; + if(!status->cri && battle_config.mob_critical_rate) + status->cri = 10; + break; + case BL_PC: + //Players don't have a critical adjustment setting as of yet. + break; + default: + if(battle_config.critical_rate != 100) + status->cri = status->cri*battle_config.critical_rate/100; + if (!status->cri && battle_config.critical_rate) + status->cri = 10; + } + if(bl->type&BL_REGEN) + status_calc_regen(bl, status, status_get_regen_data(bl)); +} + +//Skotlex: Calculates the initial status for the given mob +//first will only be false when the mob leveled up or got a GuardUp level. +int status_calc_mob_(struct mob_data* md, bool first) +{ + struct status_data *status; + struct block_list *mbl = NULL; + int flag=0; + + if(first) + { //Set basic level on respawn. + if (md->level > 0 && md->level <= MAX_LEVEL && md->level != md->db->lv) + ; + else + md->level = md->db->lv; + } + + //Check if we need custom base-status + if (battle_config.mobs_level_up && md->level > md->db->lv) + flag|=1; + + if (md->special_state.size) + flag|=2; + + if (md->guardian_data && md->guardian_data->guardup_lv) + flag|=4; + if (md->class_ == MOBID_EMPERIUM) + flag|=4; + + if (battle_config.slaves_inherit_speed && md->master_id) + flag|=8; + + if (md->master_id && md->special_state.ai>1) + flag|=16; + + if (!flag) + { //No special status required. + if (md->base_status) { + aFree(md->base_status); + md->base_status = NULL; + } + if(first) + memcpy(&md->status, &md->db->status, sizeof(struct status_data)); + return 0; + } + if (!md->base_status) + md->base_status = (struct status_data*)aCalloc(1, sizeof(struct status_data)); + + status = md->base_status; + memcpy(status, &md->db->status, sizeof(struct status_data)); + + if (flag&(8|16)) + mbl = map_id2bl(md->master_id); + + if (flag&8 && mbl) { + struct status_data *mstatus = status_get_base_status(mbl); + if (mstatus && + battle_config.slaves_inherit_speed&(mstatus->mode&MD_CANMOVE?1:2)) + status->speed = mstatus->speed; + if( status->speed < 2 ) /* minimum for the unit to function properly */ + status->speed = 2; + } + + if (flag&16 && mbl) + { //Max HP setting from Summon Flora/marine Sphere + struct unit_data *ud = unit_bl2ud(mbl); + //Remove special AI when this is used by regular mobs. + if (mbl->type == BL_MOB && !((TBL_MOB*)mbl)->special_state.ai) + md->special_state.ai = 0; + if (ud) + { // different levels of HP according to skill level + if (ud->skill_id == AM_SPHEREMINE) { + status->max_hp = 2000 + 400*ud->skill_lv; + } else if(ud->skill_id == KO_ZANZOU){ + status->max_hp = 3000 + 3000 * ud->skill_lv; + } else { //AM_CANNIBALIZE + status->max_hp = 1500 + 200*ud->skill_lv + 10*status_get_lv(mbl); + status->mode|= MD_CANATTACK|MD_AGGRESSIVE; + } + status->hp = status->max_hp; + } + } + + if (flag&1) + { // increase from mobs leveling up [Valaris] + int diff = md->level - md->db->lv; + status->str+= diff; + status->agi+= diff; + status->vit+= diff; + status->int_+= diff; + status->dex+= diff; + status->luk+= diff; + status->max_hp += diff*status->vit; + status->max_sp += diff*status->int_; + status->hp = status->max_hp; + status->sp = status->max_sp; + status->speed -= cap_value(diff, 0, status->speed - 10); + } + + + if (flag&2 && battle_config.mob_size_influence) + { // change for sized monsters [Valaris] + if (md->special_state.size==SZ_MEDIUM) { + status->max_hp>>=1; + status->max_sp>>=1; + if (!status->max_hp) status->max_hp = 1; + if (!status->max_sp) status->max_sp = 1; + status->hp=status->max_hp; + status->sp=status->max_sp; + status->str>>=1; + status->agi>>=1; + status->vit>>=1; + status->int_>>=1; + status->dex>>=1; + status->luk>>=1; + if (!status->str) status->str = 1; + if (!status->agi) status->agi = 1; + if (!status->vit) status->vit = 1; + if (!status->int_) status->int_ = 1; + if (!status->dex) status->dex = 1; + if (!status->luk) status->luk = 1; + } else if (md->special_state.size==SZ_BIG) { + status->max_hp<<=1; + status->max_sp<<=1; + status->hp=status->max_hp; + status->sp=status->max_sp; + status->str<<=1; + status->agi<<=1; + status->vit<<=1; + status->int_<<=1; + status->dex<<=1; + status->luk<<=1; + } + } + + status_calc_misc(&md->bl, status, md->level); + + if(flag&4) + { // Strengthen Guardians - custom value +10% / lv + struct guild_castle *gc; + gc=guild_mapname2gc(map[md->bl.m].name); + if (!gc) + ShowError("status_calc_mob: No castle set at map %s\n", map[md->bl.m].name); + else + if(gc->castle_id < 24 || md->class_ == MOBID_EMPERIUM) { +#ifdef RENEWAL + status->max_hp += 50 * gc->defense; + status->max_sp += 70 * gc->defense; +#else + status->max_hp += 1000 * gc->defense; + status->max_sp += 200 * gc->defense; +#endif + status->hp = status->max_hp; + status->sp = status->max_sp; + status->def += (gc->defense+2)/3; + status->mdef += (gc->defense+2)/3; + } + if(md->class_ != MOBID_EMPERIUM) { + status->batk += status->batk * 10*md->guardian_data->guardup_lv/100; + status->rhw.atk += status->rhw.atk * 10*md->guardian_data->guardup_lv/100; + status->rhw.atk2 += status->rhw.atk2 * 10*md->guardian_data->guardup_lv/100; + status->aspd_rate -= 100*md->guardian_data->guardup_lv; + } + } + + if( first ) //Initial battle status + memcpy(&md->status, status, sizeof(struct status_data)); + + return 1; +} + +//Skotlex: Calculates the stats of the given pet. +int status_calc_pet_(struct pet_data *pd, bool first) +{ + nullpo_ret(pd); + + if (first) { + memcpy(&pd->status, &pd->db->status, sizeof(struct status_data)); + pd->status.mode = MD_CANMOVE; // pets discard all modes, except walking + pd->status.speed = pd->petDB->speed; + + if(battle_config.pet_attack_support || battle_config.pet_damage_support) + {// attack support requires the pet to be able to attack + pd->status.mode|= MD_CANATTACK; + } + } + + if (battle_config.pet_lv_rate && pd->msd) + { + struct map_session_data *sd = pd->msd; + int lv; + + lv =sd->status.base_level*battle_config.pet_lv_rate/100; + if (lv < 0) + lv = 1; + if (lv != pd->pet.level || first) + { + struct status_data *bstat = &pd->db->status, *status = &pd->status; + pd->pet.level = lv; + if (!first) //Lv Up animation + clif_misceffect(&pd->bl, 0); + status->rhw.atk = (bstat->rhw.atk*lv)/pd->db->lv; + status->rhw.atk2 = (bstat->rhw.atk2*lv)/pd->db->lv; + status->str = (bstat->str*lv)/pd->db->lv; + status->agi = (bstat->agi*lv)/pd->db->lv; + status->vit = (bstat->vit*lv)/pd->db->lv; + status->int_ = (bstat->int_*lv)/pd->db->lv; + status->dex = (bstat->dex*lv)/pd->db->lv; + status->luk = (bstat->luk*lv)/pd->db->lv; + + status->rhw.atk = cap_value(status->rhw.atk, 1, battle_config.pet_max_atk1); + status->rhw.atk2 = cap_value(status->rhw.atk2, 2, battle_config.pet_max_atk2); + status->str = cap_value(status->str,1,battle_config.pet_max_stats); + status->agi = cap_value(status->agi,1,battle_config.pet_max_stats); + status->vit = cap_value(status->vit,1,battle_config.pet_max_stats); + status->int_= cap_value(status->int_,1,battle_config.pet_max_stats); + status->dex = cap_value(status->dex,1,battle_config.pet_max_stats); + status->luk = cap_value(status->luk,1,battle_config.pet_max_stats); + + status_calc_misc(&pd->bl, &pd->status, lv); + + if (!first) //Not done the first time because the pet is not visible yet + clif_send_petstatus(sd); + } + } else if (first) { + status_calc_misc(&pd->bl, &pd->status, pd->db->lv); + if (!battle_config.pet_lv_rate && pd->pet.level != pd->db->lv) + pd->pet.level = pd->db->lv; + } + + //Support rate modifier (1000 = 100%) + pd->rate_fix = 1000*(pd->pet.intimate - battle_config.pet_support_min_friendly)/(1000- battle_config.pet_support_min_friendly) +500; + if(battle_config.pet_support_rate != 100) + pd->rate_fix = pd->rate_fix*battle_config.pet_support_rate/100; + + return 1; +} + +/// Helper function for status_base_pc_maxhp(), used to pre-calculate the hp_sigma_val[] array +static void status_calc_sigma(void) +{ + int i,j; + + for(i = 0; i < CLASS_COUNT; i++) + { + unsigned int k = 0; + hp_sigma_val[i][0] = hp_sigma_val[i][1] = 0; + for(j = 2; j <= MAX_LEVEL; j++) + { + k += (hp_coefficient[i]*j + 50) / 100; + hp_sigma_val[i][j] = k; + if (k >= INT_MAX) + break; //Overflow protection. [Skotlex] + } + for(; j <= MAX_LEVEL; j++) + hp_sigma_val[i][j] = INT_MAX; + } +} + +/// Calculates base MaxHP value according to class and base level +/// The recursive equation used to calculate level bonus is (using integer operations) +/// f(0) = 35 | f(x+1) = f(x) + A + (x + B)*C/D +/// which reduces to something close to +/// f(x) = 35 + x*(A + B*C/D) + sum(i=2..x){ i*C/D } +static unsigned int status_base_pc_maxhp(struct map_session_data* sd, struct status_data* status) +{ + uint64 val = pc_class2idx(sd->status.class_); + val = 35 + sd->status.base_level*(int64)hp_coefficient2[val]/100 + hp_sigma_val[val][sd->status.base_level]; + + if((sd->class_&MAPID_UPPERMASK) == MAPID_NINJA || (sd->class_&MAPID_UPPERMASK) == MAPID_GUNSLINGER) + val += 100; //Since their HP can't be approximated well enough without this. + if((sd->class_&MAPID_UPPERMASK) == MAPID_TAEKWON && sd->status.base_level >= 90 && pc_famerank(sd->status.char_id, MAPID_TAEKWON)) + val *= 3; //Triple max HP for top ranking Taekwons over level 90. + if((sd->class_&MAPID_UPPERMASK) == MAPID_SUPER_NOVICE && sd->status.base_level >= 99) + val += 2000; //Supernovice lvl99 hp bonus. + + val += val * status->vit/100; // +1% per each point of VIT + + if (sd->class_&JOBL_UPPER) + val += val * 25/100; //Trans classes get a 25% hp bonus + else if (sd->class_&JOBL_BABY) + val -= val * 30/100; //Baby classes get a 30% hp penalty + return (unsigned int)val; +} + +static unsigned int status_base_pc_maxsp(struct map_session_data* sd, struct status_data *status) +{ + uint64 val; + + val = 10 + sd->status.base_level*(int64)sp_coefficient[pc_class2idx(sd->status.class_)]/100; + val += val * status->int_/100; + + if (sd->class_&JOBL_UPPER) + val += val * 25/100; + else if (sd->class_&JOBL_BABY) + val -= val * 30/100; + if ((sd->class_&MAPID_UPPERMASK) == MAPID_TAEKWON && sd->status.base_level >= 90 && pc_famerank(sd->status.char_id, MAPID_TAEKWON)) + val *= 3; //Triple max SP for top ranking Taekwons over level 90. + + return (unsigned int)val; +} + +//Calculates player data from scratch without counting SC adjustments. +//Should be invoked whenever players raise stats, learn passive skills or change equipment. +int status_calc_pc_(struct map_session_data* sd, bool first) +{ + static int calculating = 0; //Check for recursive call preemption. [Skotlex] + struct status_data *status; // pointer to the player's base status + const struct status_change *sc = &sd->sc; + struct s_skill b_skill[MAX_SKILL]; // previous skill tree + int b_weight, b_max_weight, b_cart_weight_max, // previous weight + i, index, skill,refinedef=0; + int64 i64; + + if (++calculating > 10) //Too many recursive calls! + return -1; + + // remember player-specific values that are currently being shown to the client (for refresh purposes) + memcpy(b_skill, &sd->status.skill, sizeof(b_skill)); + b_weight = sd->weight; + b_max_weight = sd->max_weight; + b_cart_weight_max = sd->cart_weight_max; + + pc_calc_skilltree(sd); // SkillTree calculation + + sd->max_weight = max_weight_base[pc_class2idx(sd->status.class_)]+sd->status.str*300; + + if(first) { + //Load Hp/SP from char-received data. + sd->battle_status.hp = sd->status.hp; + sd->battle_status.sp = sd->status.sp; + sd->regen.sregen = &sd->sregen; + sd->regen.ssregen = &sd->ssregen; + 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_weight=0; + 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++; + } + } + + status = &sd->base_status; + // these are not zeroed. [zzo] + sd->hprate=100; + sd->sprate=100; + sd->castrate=100; + sd->delayrate=100; + sd->dsprate=100; + sd->hprecov_rate = 100; + sd->sprecov_rate = 100; + sd->matk_rate = 100; + 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->regen.state.block = 0; + + // zeroed arrays, order follows the order in pc.h. + // add new arrays to the end of zeroed area in pc.h (see comments) and size here. [zzo] + memset (sd->param_bonus, 0, sizeof(sd->param_bonus) + + sizeof(sd->param_equip) + + sizeof(sd->subele) + + sizeof(sd->subrace) + + sizeof(sd->subrace2) + + sizeof(sd->subsize) + + sizeof(sd->reseff) + + sizeof(sd->weapon_coma_ele) + + sizeof(sd->weapon_coma_race) + + sizeof(sd->weapon_atk) + + sizeof(sd->weapon_atk_rate) + + sizeof(sd->arrow_addele) + + sizeof(sd->arrow_addrace) + + sizeof(sd->arrow_addsize) + + sizeof(sd->magic_addele) + + sizeof(sd->magic_addrace) + + sizeof(sd->magic_addsize) + + sizeof(sd->magic_atk_ele) + + sizeof(sd->critaddrace) + + sizeof(sd->expaddrace) + + sizeof(sd->ignore_mdef) + + sizeof(sd->ignore_def) + + sizeof(sd->itemgrouphealrate) + + sizeof(sd->sp_gain_race) + + sizeof(sd->sp_gain_race_attack) + + sizeof(sd->hp_gain_race_attack) + ); + + memset (&sd->right_weapon.overrefine, 0, sizeof(sd->right_weapon) - sizeof(sd->right_weapon.atkmods)); + memset (&sd->left_weapon.overrefine, 0, sizeof(sd->left_weapon) - sizeof(sd->left_weapon.atkmods)); + + if (sd->special_state.intravision && !sd->sc.data[SC_INTRAVISION]) //Clear intravision as long as nothing else is using it + clif_status_load(&sd->bl, SI_INTRAVISION, 0); + + memset(&sd->special_state,0,sizeof(sd->special_state)); + memset(&status->max_hp, 0, sizeof(struct status_data)-(sizeof(status->hp)+sizeof(status->sp))); + + //FIXME: Most of these stuff should be calculated once, but how do I fix the memset above to do that? [Skotlex] + status->speed = DEFAULT_WALK_SPEED; + //Give them all modes except these (useful for clones) + status->mode = MD_MASK&~(MD_BOSS|MD_PLANT|MD_DETECTOR|MD_ANGRY|MD_TARGETWEAK); + + status->size = (sd->class_&JOBL_BABY)?SZ_SMALL:SZ_MEDIUM; + if (battle_config.character_size && pc_isriding(sd)) { //[Lupus] + if (sd->class_&JOBL_BABY) { + if (battle_config.character_size&SZ_BIG) + status->size++; + } else + if(battle_config.character_size&SZ_MEDIUM) + status->size++; + } + status->aspd_rate = 1000; + status->ele_lv = 1; + status->race = RC_DEMIHUMAN; + + //zero up structures... + memset(&sd->autospell,0,sizeof(sd->autospell) + + sizeof(sd->autospell2) + + sizeof(sd->autospell3) + + sizeof(sd->addeff) + + sizeof(sd->addeff2) + + sizeof(sd->addeff3) + + sizeof(sd->skillatk) + + sizeof(sd->skillusesprate) + + sizeof(sd->skillusesp) + + sizeof(sd->skillheal) + + sizeof(sd->skillheal2) + + sizeof(sd->hp_loss) + + sizeof(sd->sp_loss) + + sizeof(sd->hp_regen) + + sizeof(sd->sp_regen) + + sizeof(sd->skillblown) + + sizeof(sd->skillcast) + + sizeof(sd->add_def) + + sizeof(sd->add_mdef) + + sizeof(sd->add_mdmg) + + sizeof(sd->add_drop) + + sizeof(sd->itemhealrate) + + sizeof(sd->subele2) + + sizeof(sd->skillcooldown) + + sizeof(sd->skillfixcast) + + sizeof(sd->skillvarcast) + ); + + memset (&sd->bonus, 0,sizeof(sd->bonus)); + + // Autobonus + pc_delautobonus(sd,sd->autobonus,ARRAYLENGTH(sd->autobonus),true); + pc_delautobonus(sd,sd->autobonus2,ARRAYLENGTH(sd->autobonus2),true); + pc_delautobonus(sd,sd->autobonus3,ARRAYLENGTH(sd->autobonus3),true); + + // Parse equipment. + for(i=0;i<EQI_MAX-1;i++) { + current_equip_item_index = index = sd->equip_index[i]; //We pass INDEX to current_equip_item_index - for EQUIP_SCRIPT (new cards solution) [Lupus] + if(index < 0) + continue; + if(i == EQI_HAND_R && sd->equip_index[EQI_HAND_L] == index) + continue; + if(i == EQI_HEAD_MID && sd->equip_index[EQI_HEAD_LOW] == index) + continue; + if(i == EQI_HEAD_TOP && (sd->equip_index[EQI_HEAD_MID] == index || sd->equip_index[EQI_HEAD_LOW] == index)) + continue; + if(i == EQI_COSTUME_MID && sd->equip_index[EQI_COSTUME_LOW] == index) + continue; + if(i == EQI_COSTUME_TOP && (sd->equip_index[EQI_COSTUME_MID] == index || sd->equip_index[EQI_COSTUME_LOW] == index)) + continue; + if(!sd->inventory_data[index]) + continue; + + status->def += sd->inventory_data[index]->def; + + if(first && sd->inventory_data[index]->equip_script) + { //Execute equip-script on login + run_script(sd->inventory_data[index]->equip_script,0,sd->bl.id,0); + if (!calculating) + return 1; + } + + // sanitize the refine level in case someone decreased the value inbetween + if (sd->status.inventory[index].refine > MAX_REFINE) + sd->status.inventory[index].refine = MAX_REFINE; + + if(sd->inventory_data[index]->type == IT_WEAPON) { + int r,wlv = sd->inventory_data[index]->wlv; + struct weapon_data *wd; + struct weapon_atk *wa; + if (wlv >= REFINE_TYPE_MAX) + wlv = REFINE_TYPE_MAX - 1; + if(i == EQI_HAND_L && sd->status.inventory[index].equip == EQP_HAND_L) { + wd = &sd->left_weapon; // Left-hand weapon + wa = &status->lhw; + } else { + wd = &sd->right_weapon; + wa = &status->rhw; + } + wa->atk += sd->inventory_data[index]->atk; + if ( (r = sd->status.inventory[index].refine) ) + wa->atk2 = refine_info[wlv].bonus[r-1] / 100; + +#ifdef RENEWAL + wa->matk += sd->inventory_data[index]->matk; + wa->wlv = wlv; + if( r ) // renewal magic attack refine bonus + wa->matk += refine_info[wlv].bonus[r-1] / 100; +#endif + + //Overrefine bonus. + if (r) + wd->overrefine = refine_info[wlv].randombonus_max[r-1] / 100; + + wa->range += sd->inventory_data[index]->range; + if(sd->inventory_data[index]->script) { + if (wd == &sd->left_weapon) { + sd->state.lr_flag = 1; + run_script(sd->inventory_data[index]->script,0,sd->bl.id,0); + sd->state.lr_flag = 0; + } else + run_script(sd->inventory_data[index]->script,0,sd->bl.id,0); + if (!calculating) //Abort, run_script retriggered this. [Skotlex] + return 1; + } + + if(sd->status.inventory[index].card[0]==CARD0_FORGE) + { // Forged weapon + wd->star += (sd->status.inventory[index].card[1]>>8); + if(wd->star >= 15) wd->star = 40; // 3 Star Crumbs now give +40 dmg + if(pc_famerank(MakeDWord(sd->status.inventory[index].card[2],sd->status.inventory[index].card[3]) ,MAPID_BLACKSMITH)) + wd->star += 10; + + if (!wa->ele) //Do not overwrite element from previous bonuses. + wa->ele = (sd->status.inventory[index].card[1]&0x0f); + } + } + else if(sd->inventory_data[index]->type == IT_ARMOR) { + int r; + if ( (r = sd->status.inventory[index].refine) ) + refinedef += refine_info[REFINE_TYPE_ARMOR].bonus[r-1]; + if(sd->inventory_data[index]->script) { + if( i == EQI_HAND_L ) //Shield + sd->state.lr_flag = 3; + run_script(sd->inventory_data[index]->script,0,sd->bl.id,0); + if( i == EQI_HAND_L ) //Shield + sd->state.lr_flag = 0; + if (!calculating) //Abort, run_script retriggered this. [Skotlex] + return 1; + } + } + } + + if(sd->equip_index[EQI_AMMO] >= 0){ + index = sd->equip_index[EQI_AMMO]; + if(sd->inventory_data[index]){ // Arrows + sd->bonus.arrow_atk += sd->inventory_data[index]->atk; + sd->state.lr_flag = 2; + if( !itemdb_is_GNthrowable(sd->inventory_data[index]->nameid) ) //don't run scripts on throwable items + run_script(sd->inventory_data[index]->script,0,sd->bl.id,0); + sd->state.lr_flag = 0; + if (!calculating) //Abort, run_script retriggered status_calc_pc. [Skotlex] + return 1; + } + } + + /* we've got combos to process */ + if( sd->combos.count ) { + for( i = 0; i < sd->combos.count; i++ ) { + run_script(sd->combos.bonus[i],0,sd->bl.id,0); + if (!calculating) //Abort, run_script retriggered this. + return 1; + } + } + + //Store equipment script bonuses + memcpy(sd->param_equip,sd->param_bonus,sizeof(sd->param_equip)); + memset(sd->param_bonus, 0, sizeof(sd->param_bonus)); + + status->def += (refinedef+50)/100; + + //Parse Cards + for(i=0;i<EQI_MAX-1;i++) { + current_equip_item_index = index = sd->equip_index[i]; //We pass INDEX to current_equip_item_index - for EQUIP_SCRIPT (new cards solution) [Lupus] + if(index < 0) + continue; + if(i == EQI_HAND_R && sd->equip_index[EQI_HAND_L] == index) + continue; + if(i == EQI_HEAD_MID && sd->equip_index[EQI_HEAD_LOW] == index) + continue; + if(i == EQI_HEAD_TOP && (sd->equip_index[EQI_HEAD_MID] == index || sd->equip_index[EQI_HEAD_LOW] == index)) + continue; + + if(sd->inventory_data[index]) { + int j,c; + struct item_data *data; + + //Card script execution. + if(itemdb_isspecial(sd->status.inventory[index].card[0])) + continue; + for(j=0;j<MAX_SLOTS;j++){ // Uses MAX_SLOTS to support Soul Bound system [Inkfish] + current_equip_card_id= c= sd->status.inventory[index].card[j]; + if(!c) + continue; + data = itemdb_exists(c); + if(!data) + continue; + if(first && data->equip_script) + { //Execute equip-script on login + run_script(data->equip_script,0,sd->bl.id,0); + if (!calculating) + return 1; + } + if(!data->script) + continue; + if(data->flag.no_equip) { //Card restriction checks. + if(map[sd->bl.m].flag.restricted && data->flag.no_equip&(8*map[sd->bl.m].zone)) + continue; + if(!map_flag_vs(sd->bl.m) && data->flag.no_equip&1) + continue; + if(map[sd->bl.m].flag.pvp && data->flag.no_equip&2) + continue; + if(map_flag_gvg(sd->bl.m) && data->flag.no_equip&4) + continue; + if(map[sd->bl.m].flag.battleground && data->flag.no_equip&8) + continue; + } + if(i == EQI_HAND_L && sd->status.inventory[index].equip == EQP_HAND_L) + { //Left hand status. + sd->state.lr_flag = 1; + run_script(data->script,0,sd->bl.id,0); + sd->state.lr_flag = 0; + } else + run_script(data->script,0,sd->bl.id,0); + if (!calculating) //Abort, run_script his function. [Skotlex] + return 1; + } + } + } + + if( sc->count && sc->data[SC_ITEMSCRIPT] ) + { + struct item_data *data = itemdb_exists(sc->data[SC_ITEMSCRIPT]->val1); + if( data && data->script ) + run_script(data->script,0,sd->bl.id,0); + } + + if( sd->pd ) + { // Pet Bonus + struct pet_data *pd = sd->pd; + if( pd && pd->petDB && pd->petDB->equip_script && pd->pet.intimate >= battle_config.pet_equip_min_friendly ) + run_script(pd->petDB->equip_script,0,sd->bl.id,0); + if( pd && pd->pet.intimate > 0 && (!battle_config.pet_equip_required || pd->pet.equip > 0) && pd->state.skillbonus == 1 && pd->bonus ) + pc_bonus(sd,pd->bonus->type, pd->bonus->val); + } + + //param_bonus now holds card bonuses. + if(status->rhw.range < 1) status->rhw.range = 1; + if(status->lhw.range < 1) status->lhw.range = 1; + if(status->rhw.range < status->lhw.range) + status->rhw.range = status->lhw.range; + + sd->bonus.double_rate += sd->bonus.double_add_rate; + sd->bonus.perfect_hit += sd->bonus.perfect_hit_add; + sd->bonus.splash_range += sd->bonus.splash_add_range; + + // Damage modifiers from weapon type + sd->right_weapon.atkmods[0] = atkmods[0][sd->weapontype1]; + sd->right_weapon.atkmods[1] = atkmods[1][sd->weapontype1]; + sd->right_weapon.atkmods[2] = atkmods[2][sd->weapontype1]; + sd->left_weapon.atkmods[0] = atkmods[0][sd->weapontype2]; + sd->left_weapon.atkmods[1] = atkmods[1][sd->weapontype2]; + sd->left_weapon.atkmods[2] = atkmods[2][sd->weapontype2]; + + if(pc_isriding(sd) && + (sd->status.weapon==W_1HSPEAR || sd->status.weapon==W_2HSPEAR)) + { //When Riding with spear, damage modifier to mid-class becomes + //same as versus large size. + sd->right_weapon.atkmods[1] = sd->right_weapon.atkmods[2]; + sd->left_weapon.atkmods[1] = sd->left_weapon.atkmods[2]; + } + +// ----- STATS CALCULATION ----- + + // Job bonuses + index = pc_class2idx(sd->status.class_); + for(i=0;i<(int)sd->status.job_level && i<MAX_LEVEL;i++){ + if(!job_bonus[index][i]) + continue; + switch(job_bonus[index][i]) { + case 1: status->str++; break; + case 2: status->agi++; break; + case 3: status->vit++; break; + case 4: status->int_++; break; + case 5: status->dex++; break; + case 6: status->luk++; break; + } + } + + // If a Super Novice has never died and is at least joblv 70, he gets all stats +10 + if((sd->class_&MAPID_UPPERMASK) == MAPID_SUPER_NOVICE && sd->die_counter == 0 && sd->status.job_level >= 70){ + status->str += 10; + status->agi += 10; + status->vit += 10; + status->int_+= 10; + status->dex += 10; + status->luk += 10; + } + + // Absolute modifiers from passive skills + if(pc_checkskill(sd,BS_HILTBINDING)>0) + status->str++; + if((skill=pc_checkskill(sd,SA_DRAGONOLOGY))>0) + status->int_ += (skill+1)/2; // +1 INT / 2 lv + if((skill=pc_checkskill(sd,AC_OWL))>0) + status->dex += skill; + if((skill = pc_checkskill(sd,RA_RESEARCHTRAP))>0) + status->int_ += skill; + + // Bonuses from cards and equipment as well as base stat, remember to avoid overflows. + i = status->str + sd->status.str + sd->param_bonus[0] + sd->param_equip[0]; + status->str = cap_value(i,0,USHRT_MAX); + i = status->agi + sd->status.agi + sd->param_bonus[1] + sd->param_equip[1]; + status->agi = cap_value(i,0,USHRT_MAX); + i = status->vit + sd->status.vit + sd->param_bonus[2] + sd->param_equip[2]; + status->vit = cap_value(i,0,USHRT_MAX); + i = status->int_+ sd->status.int_+ sd->param_bonus[3] + sd->param_equip[3]; + status->int_ = cap_value(i,0,USHRT_MAX); + i = status->dex + sd->status.dex + sd->param_bonus[4] + sd->param_equip[4]; + status->dex = cap_value(i,0,USHRT_MAX); + i = status->luk + sd->status.luk + sd->param_bonus[5] + sd->param_equip[5]; + status->luk = cap_value(i,0,USHRT_MAX); + +// ------ BASE ATTACK CALCULATION ------ + + // Base batk value is set on status_calc_misc + // weapon-type bonus (FIXME: Why is the weapon_atk bonus applied to base attack?) + if (sd->status.weapon < MAX_WEAPON_TYPE && sd->weapon_atk[sd->status.weapon]) + status->batk += sd->weapon_atk[sd->status.weapon]; + // Absolute modifiers from passive skills + if((skill=pc_checkskill(sd,BS_HILTBINDING))>0) + status->batk += 4; + +// ----- HP MAX CALCULATION ----- + + // Basic MaxHP value + //We hold the standard Max HP here to make it faster to recalculate on vit changes. + sd->status.max_hp = status_base_pc_maxhp(sd,status); + //This is done to handle underflows from negative Max HP bonuses + i64 = sd->status.max_hp + (int)status->max_hp; + status->max_hp = (unsigned int)cap_value(i64, 0, INT_MAX); + + // Absolute modifiers from passive skills + if((skill=pc_checkskill(sd,CR_TRUST))>0) + status->max_hp += skill*200; + + // Apply relative modifiers from equipment + if(sd->hprate < 0) + sd->hprate = 0; + if(sd->hprate!=100) + status->max_hp = (int64)status->max_hp * sd->hprate/100; + if(battle_config.hp_rate != 100) + status->max_hp = (int64)status->max_hp * battle_config.hp_rate/100; + + if(status->max_hp > (unsigned int)battle_config.max_hp) + status->max_hp = battle_config.max_hp; + else if(!status->max_hp) + status->max_hp = 1; + +// ----- SP MAX CALCULATION ----- + + // Basic MaxSP value + sd->status.max_sp = status_base_pc_maxsp(sd,status); + //This is done to handle underflows from negative Max SP bonuses + i64 = sd->status.max_sp + (int)status->max_sp; + status->max_sp = (unsigned int)cap_value(i64, 0, INT_MAX); + + // Absolute modifiers from passive skills + if((skill=pc_checkskill(sd,SL_KAINA))>0) + status->max_sp += 30*skill; + if((skill=pc_checkskill(sd,HP_MEDITATIO))>0) + status->max_sp += (int64)status->max_sp * skill/100; + if((skill=pc_checkskill(sd,HW_SOULDRAIN))>0) + status->max_sp += (int64)status->max_sp * 2*skill/100; + if( (skill = pc_checkskill(sd,RA_RESEARCHTRAP)) > 0 ) + status->max_sp += 200 + 20 * skill; + if( (skill = pc_checkskill(sd,WM_LESSON)) > 0 ) + status->max_sp += 30 * skill; + + + // Apply relative modifiers from equipment + if(sd->sprate < 0) + sd->sprate = 0; + if(sd->sprate!=100) + status->max_sp = (int64)status->max_sp * sd->sprate/100; + if(battle_config.sp_rate != 100) + status->max_sp = (int64)status->max_sp * battle_config.sp_rate/100; + + if(status->max_sp > (unsigned int)battle_config.max_sp) + status->max_sp = battle_config.max_sp; + else if(!status->max_sp) + status->max_sp = 1; + +// ----- RESPAWN HP/SP ----- +// + //Calc respawn hp and store it on base_status + if (sd->special_state.restart_full_recover) + { + status->hp = status->max_hp; + status->sp = status->max_sp; + } else { + if((sd->class_&MAPID_BASEMASK) == MAPID_NOVICE && !(sd->class_&JOBL_2) + && battle_config.restart_hp_rate < 50) + status->hp = status->max_hp>>1; + else + status->hp = (int64)status->max_hp * battle_config.restart_hp_rate/100; + if(!status->hp) + status->hp = 1; + + status->sp = (int64)status->max_sp * battle_config.restart_sp_rate /100; + + if( !status->sp ) /* the minimum for the respawn setting is SP:1 */ + status->sp = 1; + } + +// ----- MISC CALCULATION ----- + status_calc_misc(&sd->bl, status, sd->status.base_level); + + //Equipment modifiers for misc settings + if(sd->matk_rate < 0) + sd->matk_rate = 0; + + if(sd->matk_rate != 100){ + status->matk_max = status->matk_max * sd->matk_rate/100; + status->matk_min = status->matk_min * sd->matk_rate/100; + } + + if(sd->hit_rate < 0) + sd->hit_rate = 0; + if(sd->hit_rate != 100) + status->hit = status->hit * sd->hit_rate/100; + + if(sd->flee_rate < 0) + sd->flee_rate = 0; + if(sd->flee_rate != 100) + status->flee = status->flee * sd->flee_rate/100; + + if(sd->def2_rate < 0) + sd->def2_rate = 0; + if(sd->def2_rate != 100) + status->def2 = status->def2 * sd->def2_rate/100; + + if(sd->mdef2_rate < 0) + sd->mdef2_rate = 0; + if(sd->mdef2_rate != 100) + status->mdef2 = status->mdef2 * sd->mdef2_rate/100; + + if(sd->critical_rate < 0) + sd->critical_rate = 0; + if(sd->critical_rate != 100) + status->cri = status->cri * sd->critical_rate/100; + + if(sd->flee2_rate < 0) + sd->flee2_rate = 0; + if(sd->flee2_rate != 100) + status->flee2 = status->flee2 * sd->flee2_rate/100; + +// ----- HIT CALCULATION ----- + + // Absolute modifiers from passive skills + if((skill=pc_checkskill(sd,BS_WEAPONRESEARCH))>0) + status->hit += skill*2; + if((skill=pc_checkskill(sd,AC_VULTURE))>0){ +#ifndef RENEWAL + status->hit += skill; +#endif + if(sd->status.weapon == W_BOW) + status->rhw.range += skill; + } + if(sd->status.weapon >= W_REVOLVER && sd->status.weapon <= W_GRENADE) + { + if((skill=pc_checkskill(sd,GS_SINGLEACTION))>0) + status->hit += 2*skill; + if((skill=pc_checkskill(sd,GS_SNAKEEYE))>0) { + status->hit += skill; + status->rhw.range += skill; + } + } + +// ----- FLEE CALCULATION ----- + + // Absolute modifiers from passive skills + if((skill=pc_checkskill(sd,TF_MISS))>0) + status->flee += skill*(sd->class_&JOBL_2 && (sd->class_&MAPID_BASEMASK) == MAPID_THIEF? 4 : 3); + if((skill=pc_checkskill(sd,MO_DODGE))>0) + status->flee += (skill*3)>>1; +// ----- EQUIPMENT-DEF CALCULATION ----- + + // Apply relative modifiers from equipment + if(sd->def_rate < 0) + sd->def_rate = 0; + if(sd->def_rate != 100) { + i = status->def * sd->def_rate/100; + status->def = cap_value(i, DEFTYPE_MIN, DEFTYPE_MAX); + } + +#ifndef RENEWAL + if (!battle_config.weapon_defense_type && status->def > battle_config.max_def) + { + status->def2 += battle_config.over_def_bonus*(status->def -battle_config.max_def); + status->def = (unsigned char)battle_config.max_def; + } +#endif + +// ----- EQUIPMENT-MDEF CALCULATION ----- + + // Apply relative modifiers from equipment + if(sd->mdef_rate < 0) + sd->mdef_rate = 0; + if(sd->mdef_rate != 100) { + i = status->mdef * sd->mdef_rate/100; + status->mdef = cap_value(i, DEFTYPE_MIN, DEFTYPE_MAX); + } + +#ifndef RENEWAL + if (!battle_config.magic_defense_type && status->mdef > battle_config.max_def) + { + status->mdef2 += battle_config.over_def_bonus*(status->mdef -battle_config.max_def); + status->mdef = (signed char)battle_config.max_def; + } +#endif + +// ----- ASPD CALCULATION ----- +// Unlike other stats, ASPD rate modifiers from skills/SCs/items/etc are first all added together, then the final modifier is applied + + // Basic ASPD value + i = status_base_amotion_pc(sd,status); + status->amotion = cap_value(i,((sd->class_&JOBL_THIRD) ? battle_config.max_third_aspd : battle_config.max_aspd),2000); + + // Relative modifiers from passive skills +#ifndef RENEWAL_ASPD + if((skill=pc_checkskill(sd,SA_ADVANCEDBOOK))>0 && sd->status.weapon == W_BOOK) + status->aspd_rate -= 5*skill; + if((skill = pc_checkskill(sd,SG_DEVIL)) > 0 && !pc_nextjobexp(sd)) + status->aspd_rate -= 30*skill; + if((skill=pc_checkskill(sd,GS_SINGLEACTION))>0 && + (sd->status.weapon >= W_REVOLVER && sd->status.weapon <= W_GRENADE)) + status->aspd_rate -= ((skill+1)/2) * 10; + if(pc_isriding(sd)) + status->aspd_rate += 500-100*pc_checkskill(sd,KN_CAVALIERMASTERY); + else if(pc_isridingdragon(sd)) + status->aspd_rate += 250-50*pc_checkskill(sd,RK_DRAGONTRAINING); +#else // needs more info + if((skill=pc_checkskill(sd,SA_ADVANCEDBOOK))>0 && sd->status.weapon == W_BOOK) + status->aspd_rate += 5*skill; + if((skill = pc_checkskill(sd,SG_DEVIL)) > 0 && !pc_nextjobexp(sd)) + status->aspd_rate += 30*skill; + if((skill=pc_checkskill(sd,GS_SINGLEACTION))>0 && + (sd->status.weapon >= W_REVOLVER && sd->status.weapon <= W_GRENADE)) + status->aspd_rate += ((skill+1)/2) * 10; + if(pc_isriding(sd)) + status->aspd_rate -= 500-100*pc_checkskill(sd,KN_CAVALIERMASTERY); + else if(pc_isridingdragon(sd)) + status->aspd_rate -= 250-50*pc_checkskill(sd,RK_DRAGONTRAINING); +#endif + status->adelay = 2*status->amotion; + + +// ----- DMOTION ----- +// + i = 800-status->agi*4; + status->dmotion = cap_value(i, 400, 800); + if(battle_config.pc_damage_delay_rate != 100) + status->dmotion = status->dmotion*battle_config.pc_damage_delay_rate/100; + +// ----- MISC CALCULATIONS ----- + + // Weight + if((skill=pc_checkskill(sd,MC_INCCARRY))>0) + sd->max_weight += 2000*skill; + if(pc_isriding(sd) && pc_checkskill(sd,KN_RIDING)>0) + sd->max_weight += 10000; + else if(pc_isridingdragon(sd)) + sd->max_weight += 5000+2000*pc_checkskill(sd,RK_DRAGONTRAINING); + if(sc->data[SC_KNOWLEDGE]) + sd->max_weight += sd->max_weight*sc->data[SC_KNOWLEDGE]->val1/10; + if((skill=pc_checkskill(sd,ALL_INCCARRY))>0) + sd->max_weight += 2000*skill; + + sd->cart_weight_max = battle_config.max_cart_weight + (pc_checkskill(sd, GN_REMODELING_CART)*5000); + + if (pc_checkskill(sd,SM_MOVINGRECOVERY)>0) + sd->regen.state.walk = 1; + else + sd->regen.state.walk = 0; + + // Skill SP cost + if((skill=pc_checkskill(sd,HP_MANARECHARGE))>0 ) + sd->dsprate -= 4*skill; + + if(sc->data[SC_SERVICE4U]) + sd->dsprate -= sc->data[SC_SERVICE4U]->val3; + + if(sc->data[SC_SPCOST_RATE]) + sd->dsprate -= sc->data[SC_SPCOST_RATE]->val1; + + //Underflow protections. + if(sd->dsprate < 0) + sd->dsprate = 0; + if(sd->castrate < 0) + sd->castrate = 0; + if(sd->delayrate < 0) + sd->delayrate = 0; + if(sd->hprecov_rate < 0) + sd->hprecov_rate = 0; + if(sd->sprecov_rate < 0) + sd->sprecov_rate = 0; + + // Anti-element and anti-race + if((skill=pc_checkskill(sd,CR_TRUST))>0) + sd->subele[ELE_HOLY] += skill*5; + if((skill=pc_checkskill(sd,BS_SKINTEMPER))>0) { + sd->subele[ELE_NEUTRAL] += skill; + sd->subele[ELE_FIRE] += skill*4; + } + if((skill=pc_checkskill(sd,SA_DRAGONOLOGY))>0 ){ + skill = skill*4; + sd->right_weapon.addrace[RC_DRAGON]+=skill; + sd->left_weapon.addrace[RC_DRAGON]+=skill; + sd->magic_addrace[RC_DRAGON]+=skill; + sd->subrace[RC_DRAGON]+=skill; + } + + if(sc->count){ + if(sc->data[SC_CONCENTRATE]) { //Update the card-bonus data + sc->data[SC_CONCENTRATE]->val3 = sd->param_bonus[1]; //Agi + sc->data[SC_CONCENTRATE]->val4 = sd->param_bonus[4]; //Dex + } + if(sc->data[SC_SIEGFRIED]){ + i = sc->data[SC_SIEGFRIED]->val2; + sd->subele[ELE_WATER] += i; + sd->subele[ELE_EARTH] += i; + sd->subele[ELE_FIRE] += i; + sd->subele[ELE_WIND] += i; + sd->subele[ELE_POISON] += i; + sd->subele[ELE_HOLY] += i; + sd->subele[ELE_DARK] += i; + sd->subele[ELE_GHOST] += i; + sd->subele[ELE_UNDEAD] += i; + } + if(sc->data[SC_PROVIDENCE]){ + sd->subele[ELE_HOLY] += sc->data[SC_PROVIDENCE]->val2; + sd->subrace[RC_DEMON] += sc->data[SC_PROVIDENCE]->val2; + } + if(sc->data[SC_ARMOR_ELEMENT]) { //This status change should grant card-type elemental resist. + sd->subele[ELE_WATER] += sc->data[SC_ARMOR_ELEMENT]->val1; + sd->subele[ELE_EARTH] += sc->data[SC_ARMOR_ELEMENT]->val2; + sd->subele[ELE_FIRE] += sc->data[SC_ARMOR_ELEMENT]->val3; + sd->subele[ELE_WIND] += sc->data[SC_ARMOR_ELEMENT]->val4; + } + if(sc->data[SC_ARMOR_RESIST]) { // Undead Scroll + sd->subele[ELE_WATER] += sc->data[SC_ARMOR_RESIST]->val1; + sd->subele[ELE_EARTH] += sc->data[SC_ARMOR_RESIST]->val2; + sd->subele[ELE_FIRE] += sc->data[SC_ARMOR_RESIST]->val3; + sd->subele[ELE_WIND] += sc->data[SC_ARMOR_RESIST]->val4; + } + if( sc->data[SC_FIRE_CLOAK_OPTION] ) { + i = sc->data[SC_FIRE_CLOAK_OPTION]->val2; + sd->subele[ELE_FIRE] += i; + sd->subele[ELE_WATER] -= i; + } + if( sc->data[SC_WATER_DROP_OPTION] ) { + i = sc->data[SC_WATER_DROP_OPTION]->val2; + sd->subele[ELE_WATER] += i; + sd->subele[ELE_WIND] -= i; + } + if( sc->data[SC_WIND_CURTAIN_OPTION] ) { + i = sc->data[SC_WIND_CURTAIN_OPTION]->val2; + sd->subele[ELE_WIND] += i; + sd->subele[ELE_EARTH] -= i; + } + if( sc->data[SC_STONE_SHIELD_OPTION] ) { + i = sc->data[SC_STONE_SHIELD_OPTION]->val2; + sd->subele[ELE_EARTH] += i; + sd->subele[ELE_FIRE] -= i; + } + if( sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 3 ) + sd->magic_addele[ELE_FIRE] += 25; + if( sc->data[SC_WATER_INSIGNIA] && sc->data[SC_WATER_INSIGNIA]->val1 == 3 ) + sd->magic_addele[ELE_WATER] += 25; + if( sc->data[SC_WIND_INSIGNIA] && sc->data[SC_WIND_INSIGNIA]->val1 == 3 ) + sd->magic_addele[ELE_WIND] += 25; + if( sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 3 ) + sd->magic_addele[ELE_EARTH] += 25; + } + status_cpy(&sd->battle_status, status); + +// ----- CLIENT-SIDE REFRESH ----- + if(!sd->bl.prev) { + //Will update on LoadEndAck + calculating = 0; + return 0; + } + if(memcmp(b_skill,sd->status.skill,sizeof(sd->status.skill))) + clif_skillinfoblock(sd); + if(b_weight != sd->weight) + clif_updatestatus(sd,SP_WEIGHT); + if(b_max_weight != sd->max_weight) { + clif_updatestatus(sd,SP_MAXWEIGHT); + pc_updateweightstatus(sd); + } + if( b_cart_weight_max != sd->cart_weight_max ) { + clif_updatestatus(sd,SP_CARTINFO); + } + + calculating = 0; + + return 0; +} + +int status_calc_mercenary_(struct mercenary_data *md, bool first) +{ + struct status_data *status = &md->base_status; + struct s_mercenary *merc = &md->mercenary; + + if( first ) + { + memcpy(status, &md->db->status, sizeof(struct status_data)); + status->mode = MD_CANMOVE|MD_CANATTACK; + status->hp = status->max_hp; + status->sp = status->max_sp; + md->battle_status.hp = merc->hp; + md->battle_status.sp = merc->sp; + } + + status_calc_misc(&md->bl, status, md->db->lv); + status_cpy(&md->battle_status, status); + + return 0; +} + +int status_calc_homunculus_(struct homun_data *hd, bool first) +{ + struct status_data *status = &hd->base_status; + struct s_homunculus *hom = &hd->homunculus; + int skill; + int amotion; + + status->str = hom->str / 10; + status->agi = hom->agi / 10; + status->vit = hom->vit / 10; + status->dex = hom->dex / 10; + status->int_ = hom->int_ / 10; + status->luk = hom->luk / 10; + + if (first) { //[orn] + const struct s_homunculus_db *db = hd->homunculusDB; + status->def_ele = db->element; + status->ele_lv = 1; + status->race = db->race; + status->size = (hom->class_ == db->evo_class)?db->evo_size:db->base_size; + status->rhw.range = 1 + status->size; + status->mode = MD_CANMOVE|MD_CANATTACK; + status->speed = DEFAULT_WALK_SPEED; + if (battle_config.hom_setting&0x8 && hd->master) + status->speed = status_get_speed(&hd->master->bl); + + status->hp = 1; + status->sp = 1; + } + skill = hom->level/10 + status->vit/5; + status->def = cap_value(skill, 0, 99); + + skill = hom->level/10 + status->int_/5; + status->mdef = cap_value(skill, 0, 99); + + status->max_hp = hom->max_hp ; + status->max_sp = hom->max_sp ; + + merc_hom_calc_skilltree(hd, 0); + + if((skill=merc_hom_checkskill(hd,HAMI_SKIN)) > 0) + status->def += skill * 4; + + if((skill = merc_hom_checkskill(hd,HVAN_INSTRUCT)) > 0) + { + status->int_ += 1 +skill/2 +skill/4 +skill/5; + status->str += 1 +skill/3 +skill/3 +skill/4; + } + + if((skill=merc_hom_checkskill(hd,HAMI_SKIN)) > 0) + status->max_hp += skill * 2 * status->max_hp / 100; + + if((skill = merc_hom_checkskill(hd,HLIF_BRAIN)) > 0) + status->max_sp += (1 +skill/2 -skill/4 +skill/5) * status->max_sp / 100 ; + + if (first) { + hd->battle_status.hp = hom->hp ; + hd->battle_status.sp = hom->sp ; + } + + status->rhw.atk = status->dex; + status->rhw.atk2 = status->str + hom->level; + + status->aspd_rate = 1000; + + amotion = (1000 -4*status->agi -status->dex) * hd->homunculusDB->baseASPD/1000; + status->amotion = cap_value(amotion,battle_config.max_aspd,2000); + status->adelay = status->amotion; //It seems adelay = amotion for Homunculus. + + status_calc_misc(&hd->bl, status, hom->level); + +#ifdef RENEWAL + status->matk_max = status->matk_min; +#endif + + status_cpy(&hd->battle_status, status); + return 1; +} + +int status_calc_elemental_(struct elemental_data *ed, bool first) { + struct status_data *status = &ed->base_status; + struct s_elemental *ele = &ed->elemental; + struct map_session_data *sd = ed->master; + + if( !sd ) + return 0; + + if( first ) { + memcpy(status, &ed->db->status, sizeof(struct status_data)); + if( !ele->mode ) + status->mode = EL_MODE_PASSIVE; + else + status->mode = ele->mode; + + status_calc_misc(&ed->bl, status, 0); + + status->max_hp = ele->max_hp; + status->max_sp = ele->max_sp; + status->hp = ele->hp; + status->sp = ele->sp; + status->rhw.atk = ele->atk; + status->rhw.atk2 = ele->atk2; + + status->matk_min += ele->matk; + status->def += ele->def; + status->mdef += ele->mdef; + status->flee = ele->flee; + status->hit = ele->hit; + + memcpy(&ed->battle_status,status,sizeof(struct status_data)); + } else { + status_calc_misc(&ed->bl, status, 0); + status_cpy(&ed->battle_status, status); + } + + return 0; +} + +int status_calc_npc_(struct npc_data *nd, bool first) { + struct status_data *status = &nd->status; + + if (!nd) + return 0; + + if (first) { + status->hp = 1; + status->sp = 1; + status->max_hp = 1; + status->max_sp = 1; + + status->def_ele = ELE_NEUTRAL; + status->ele_lv = 1; + status->race = RC_DEMIHUMAN; + status->size = nd->size; + status->rhw.range = 1 + status->size; + status->mode = (MD_CANMOVE|MD_CANATTACK); + status->speed = nd->speed; + } + + status->str = nd->stat_point; + status->agi = nd->stat_point; + status->vit = nd->stat_point; + status->int_= nd->stat_point; + status->dex = nd->stat_point; + status->luk = nd->stat_point; + + status_calc_misc(&nd->bl, status, nd->level); + status_cpy(&nd->status, status); + + return 0; +} + +static unsigned short status_calc_str(struct block_list *,struct status_change *,int); +static unsigned short status_calc_agi(struct block_list *,struct status_change *,int); +static unsigned short status_calc_vit(struct block_list *,struct status_change *,int); +static unsigned short status_calc_int(struct block_list *,struct status_change *,int); +static unsigned short status_calc_dex(struct block_list *,struct status_change *,int); +static unsigned short status_calc_luk(struct block_list *,struct status_change *,int); +static unsigned short status_calc_batk(struct block_list *,struct status_change *,int); +static unsigned short status_calc_watk(struct block_list *,struct status_change *,int); +static unsigned short status_calc_matk(struct block_list *,struct status_change *,int); +static signed short status_calc_hit(struct block_list *,struct status_change *,int); +static signed short status_calc_critical(struct block_list *,struct status_change *,int); +static signed short status_calc_flee(struct block_list *,struct status_change *,int); +static signed short status_calc_flee2(struct block_list *,struct status_change *,int); +static defType status_calc_def(struct block_list *bl, struct status_change *sc, int); +static signed short status_calc_def2(struct block_list *,struct status_change *,int); +static defType status_calc_mdef(struct block_list *bl, struct status_change *sc, int); +static signed short status_calc_mdef2(struct block_list *,struct status_change *,int); +static unsigned short status_calc_speed(struct block_list *,struct status_change *,int); +static short status_calc_aspd_rate(struct block_list *,struct status_change *,int); +static unsigned short status_calc_dmotion(struct block_list *bl, struct status_change *sc, int dmotion); +#ifdef RENEWAL_ASPD +static short status_calc_aspd(struct block_list *bl, struct status_change *sc, short flag); +#endif +static short status_calc_fix_aspd(struct block_list *bl, struct status_change *sc, int); +static unsigned int status_calc_maxhp(struct block_list *,struct status_change *, uint64); +static unsigned int status_calc_maxsp(struct block_list *,struct status_change *,unsigned int); +static unsigned char status_calc_element(struct block_list *bl, struct status_change *sc, int element); +static unsigned char status_calc_element_lv(struct block_list *bl, struct status_change *sc, int lv); +static unsigned short status_calc_mode(struct block_list *bl, struct status_change *sc, int mode); +#ifdef RENEWAL +static unsigned short status_calc_ematk(struct block_list *,struct status_change *,int); +#endif + +//Calculates base regen values. +void status_calc_regen(struct block_list *bl, struct status_data *status, struct regen_data *regen) +{ + struct map_session_data *sd; + int val, skill, reg_flag; + + if( !(bl->type&BL_REGEN) || !regen ) + return; + + sd = BL_CAST(BL_PC,bl); + val = 1 + (status->vit/5) + (status->max_hp/200); + + if( sd && sd->hprecov_rate != 100 ) + val = val*sd->hprecov_rate/100; + + reg_flag = bl->type == BL_PC ? 0 : 1; + + regen->hp = cap_value(val, reg_flag, SHRT_MAX); + + val = 1 + (status->int_/6) + (status->max_sp/100); + if( status->int_ >= 120 ) + val += ((status->int_-120)>>1) + 4; + + if( sd && sd->sprecov_rate != 100 ) + val = val*sd->sprecov_rate/100; + + regen->sp = cap_value(val, reg_flag, SHRT_MAX); + + if( sd ) + { + struct regen_data_sub *sregen; + if( (skill=pc_checkskill(sd,HP_MEDITATIO)) > 0 ) + { + val = regen->sp*(100+3*skill)/100; + regen->sp = cap_value(val, 1, SHRT_MAX); + } + //Only players have skill/sitting skill regen for now. + sregen = regen->sregen; + + val = 0; + if( (skill=pc_checkskill(sd,SM_RECOVERY)) > 0 ) + val += skill*5 + skill*status->max_hp/500; + sregen->hp = cap_value(val, 0, SHRT_MAX); + + val = 0; + if( (skill=pc_checkskill(sd,MG_SRECOVERY)) > 0 ) + val += skill*3 + skill*status->max_sp/500; + if( (skill=pc_checkskill(sd,NJ_NINPOU)) > 0 ) + val += skill*3 + skill*status->max_sp/500; + if( (skill=pc_checkskill(sd,WM_LESSON)) > 0 ) + val += 3 + 3 * skill; + + sregen->sp = cap_value(val, 0, SHRT_MAX); + + // Skill-related recovery (only when sit) + sregen = regen->ssregen; + + val = 0; + if( (skill=pc_checkskill(sd,MO_SPIRITSRECOVERY)) > 0 ) + val += skill*4 + skill*status->max_hp/500; + + if( (skill=pc_checkskill(sd,TK_HPTIME)) > 0 && sd->state.rest ) + val += skill*30 + skill*status->max_hp/500; + sregen->hp = cap_value(val, 0, SHRT_MAX); + + val = 0; + if( (skill=pc_checkskill(sd,TK_SPTIME)) > 0 && sd->state.rest ) + { + val += skill*3 + skill*status->max_sp/500; + if ((skill=pc_checkskill(sd,SL_KAINA)) > 0) //Power up Enjoyable Rest + val += (30+10*skill)*val/100; + } + if( (skill=pc_checkskill(sd,MO_SPIRITSRECOVERY)) > 0 ) + val += skill*2 + skill*status->max_sp/500; + sregen->sp = cap_value(val, 0, SHRT_MAX); + } + + if( bl->type == BL_HOM ) { + struct homun_data *hd = (TBL_HOM*)bl; + if( (skill = merc_hom_checkskill(hd,HAMI_SKIN)) > 0 ) { + val = regen->hp*(100+5*skill)/100; + regen->hp = cap_value(val, 1, SHRT_MAX); + } + if( (skill = merc_hom_checkskill(hd,HLIF_BRAIN)) > 0 ) { + val = regen->sp*(100+3*skill)/100; + regen->sp = cap_value(val, 1, SHRT_MAX); + } + } else if( bl->type == BL_MER ) { + val = (status->max_hp * status->vit / 10000 + 1) * 6; + regen->hp = cap_value(val, 1, SHRT_MAX); + + val = (status->max_sp * (status->int_ + 10) / 750) + 1; + regen->sp = cap_value(val, 1, SHRT_MAX); + } else if( bl->type == BL_ELEM ) { + val = (status->max_hp * status->vit / 10000 + 1) * 6; + regen->hp = cap_value(val, 1, SHRT_MAX); + + val = (status->max_sp * (status->int_ + 10) / 750) + 1; + regen->sp = cap_value(val, 1, SHRT_MAX); + } +} + +//Calculates SC related regen rates. +void status_calc_regen_rate(struct block_list *bl, struct regen_data *regen, struct status_change *sc) +{ + if (!(bl->type&BL_REGEN) || !regen) + return; + + regen->flag = RGN_HP|RGN_SP; + if(regen->sregen) + { + if (regen->sregen->hp) + regen->flag|=RGN_SHP; + + if (regen->sregen->sp) + regen->flag|=RGN_SSP; + regen->sregen->rate.hp = regen->sregen->rate.sp = 1; + } + if (regen->ssregen) + { + if (regen->ssregen->hp) + regen->flag|=RGN_SHP; + + if (regen->ssregen->sp) + regen->flag|=RGN_SSP; + regen->ssregen->rate.hp = regen->ssregen->rate.sp = 1; + } + regen->rate.hp = regen->rate.sp = 1; + + if (!sc || !sc->count) + return; + + if ( + (sc->data[SC_POISON] && !sc->data[SC_SLOWPOISON]) + || (sc->data[SC_DPOISON] && !sc->data[SC_SLOWPOISON]) + || sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST] + || sc->data[SC_TRICKDEAD] + || sc->data[SC_BLEEDING] + || sc->data[SC_MAGICMUSHROOM] + || sc->data[SC_RAISINGDRAGON] + || sc->data[SC_SATURDAYNIGHTFEVER] + ) //No regen + regen->flag = 0; + + if ( + sc->data[SC_DANCING] || sc->data[SC_OBLIVIONCURSE] || sc->data[SC_MAXIMIZEPOWER] + || ( + (bl->type == BL_PC && ((TBL_PC*)bl)->class_&MAPID_UPPERMASK) == MAPID_MONK && + (sc->data[SC_EXTREMITYFIST] || (sc->data[SC_EXPLOSIONSPIRITS] && (!sc->data[SC_SPIRIT] || sc->data[SC_SPIRIT]->val2 != SL_MONK))) + ) + ) //No natural SP regen + regen->flag &=~RGN_SP; + + if( + sc->data[SC_TENSIONRELAX] + ) { + regen->rate.hp += 2; + if (regen->sregen) + regen->sregen->rate.hp += 3; + } + if (sc->data[SC_MAGNIFICAT]) + { + regen->rate.hp += 1; + regen->rate.sp += 1; + } + if (sc->data[SC_REGENERATION]) + { + const struct status_change_entry *sce = sc->data[SC_REGENERATION]; + if (!sce->val4) + { + regen->rate.hp += sce->val2; + regen->rate.sp += sce->val3; + } else + regen->flag&=~sce->val4; //Remove regen as specified by val4 + } + if(sc->data[SC_GT_REVITALIZE]){ + regen->hp = cap_value(regen->hp*sc->data[SC_GT_REVITALIZE]->val3/100, 1, SHRT_MAX); + regen->state.walk= 1; + } + if ((sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 1) //if insignia lvl 1 + || (sc->data[SC_WATER_INSIGNIA] && sc->data[SC_WATER_INSIGNIA]->val1 == 1) + || (sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 1) + || (sc->data[SC_WIND_INSIGNIA] && sc->data[SC_WIND_INSIGNIA]->val1 == 1)) + regen->rate.hp *= 2; + +} +void status_calc_state( struct block_list *bl, struct status_change *sc, enum scs_flag flag, bool start ) { + + /* no sc at all, we can zero without any extra weight over our conciousness */ + if( !sc->count ) { + memset(&sc->cant, 0, sizeof (sc->cant)); + return; + } + + /* can move? */ + if( flag&SCS_NOMOVE ) { + if( !(flag&SCS_NOMOVECOND) ) { + sc->cant.move += ( start ? 1 : -1 ); + } else if( + (sc->data[SC_GOSPEL] && sc->data[SC_GOSPEL]->val4 == BCT_SELF) // cannot move while gospel is in effect + || (sc->data[SC_BASILICA] && sc->data[SC_BASILICA]->val4 == bl->id) // Basilica caster cannot move + || (sc->data[SC_GRAVITATION] && sc->data[SC_GRAVITATION]->val3 == BCT_SELF) + || (sc->data[SC_CRYSTALIZE] && bl->type != BL_MOB) + || (sc->data[SC_CAMOUFLAGE] && sc->data[SC_CAMOUFLAGE]->val1 < 3 + && !(sc->data[SC_CAMOUFLAGE]->val3&1)) + ) { + sc->cant.move += ( start ? 1 : -1 ); + } + } + + /* can't use skills */ + if( flag&SCS_NOCAST ) { + if( !(flag&SCS_NOCASTCOND) ) { + sc->cant.cast += ( start ? 1 : -1 ); + } else if( (sc->data[SC_CRYSTALIZE] && bl->type != BL_MOB) ){ + sc->cant.cast += ( start ? 1 : -1 ); + } + } + + /* player-only states */ + if( bl->type == BL_PC ) { + + /* can pick items? */ + if( flag&SCS_NOPICKITEM ) { + if( !(flag&SCS_NOPICKITEMCOND) ) { + sc->cant.pickup += ( start ? 1 : -1 ); + } else if( (sc->data[SC_NOCHAT] && sc->data[SC_NOCHAT]->val1&MANNER_NOITEM) ) { + sc->cant.pickup += ( start ? 1 : -1 ); + } + } + + /* can drop items? */ + if( flag&SCS_NODROPITEM ) { + if( !(flag&SCS_NODROPITEMCOND) ) { + sc->cant.drop += ( start ? 1 : -1 ); + } else if( (sc->data[SC_NOCHAT] && sc->data[SC_NOCHAT]->val1&MANNER_NOITEM) ) { + sc->cant.drop += ( start ? 1 : -1 ); + } + } + } + + return; +} +/// Recalculates parts of an object's battle status according to the specified flags. +/// @param flag bitfield of values from enum scb_flag +void status_calc_bl_main(struct block_list *bl, /*enum scb_flag*/int flag) +{ + const struct status_data *b_status = status_get_base_status(bl); + struct status_data *status = status_get_status_data(bl); + struct status_change *sc = status_get_sc(bl); + TBL_PC *sd = BL_CAST(BL_PC,bl); + int temp; + + if (!b_status || !status) + return; + + if((!(bl->type&BL_REGEN)) && (!sc || !sc->count)) { //No difference. + status_cpy(status, b_status); + return; + } + + if(flag&SCB_STR) { + status->str = status_calc_str(bl, sc, b_status->str); + flag|=SCB_BATK; + if( bl->type&BL_HOM ) + flag |= SCB_WATK; + } + + if(flag&SCB_AGI) { + status->agi = status_calc_agi(bl, sc, b_status->agi); + flag|=SCB_FLEE +#ifdef RENEWAL + |SCB_DEF2 +#endif + ; + if( bl->type&(BL_PC|BL_HOM) ) + flag |= SCB_ASPD|SCB_DSPD; + } + + if(flag&SCB_VIT) { + status->vit = status_calc_vit(bl, sc, b_status->vit); + flag|=SCB_DEF2|SCB_MDEF2; + if( bl->type&(BL_PC|BL_HOM|BL_MER|BL_ELEM) ) + flag |= SCB_MAXHP; + if( bl->type&BL_HOM ) + flag |= SCB_DEF; + } + + if(flag&SCB_INT) { + status->int_ = status_calc_int(bl, sc, b_status->int_); + flag|=SCB_MATK|SCB_MDEF2; + if( bl->type&(BL_PC|BL_HOM|BL_MER|BL_ELEM) ) + flag |= SCB_MAXSP; + if( bl->type&BL_HOM ) + flag |= SCB_MDEF; + } + + if(flag&SCB_DEX) { + status->dex = status_calc_dex(bl, sc, b_status->dex); + flag|=SCB_BATK|SCB_HIT +#ifdef RENEWAL + |SCB_MATK|SCB_MDEF2 +#endif + ; + if( bl->type&(BL_PC|BL_HOM) ) + flag |= SCB_ASPD; + if( bl->type&BL_HOM ) + flag |= SCB_WATK; + } + + if(flag&SCB_LUK) { + status->luk = status_calc_luk(bl, sc, b_status->luk); + flag|=SCB_BATK|SCB_CRI|SCB_FLEE2 +#ifdef RENEWAL + |SCB_MATK|SCB_HIT|SCB_FLEE +#endif + ; + } + + if(flag&SCB_BATK && b_status->batk) { + status->batk = status_base_atk(bl,status); + temp = b_status->batk - status_base_atk(bl,b_status); + if (temp) + { + temp += status->batk; + status->batk = cap_value(temp, 0, USHRT_MAX); + } + status->batk = status_calc_batk(bl, sc, status->batk); + } + + if(flag&SCB_WATK) { + + status->rhw.atk = status_calc_watk(bl, sc, b_status->rhw.atk); + if (!sd) //Should not affect weapon refine bonus + status->rhw.atk2 = status_calc_watk(bl, sc, b_status->rhw.atk2); + + if(b_status->lhw.atk) { + if (sd) { + sd->state.lr_flag = 1; + status->lhw.atk = status_calc_watk(bl, sc, b_status->lhw.atk); + sd->state.lr_flag = 0; + } else { + status->lhw.atk = status_calc_watk(bl, sc, b_status->lhw.atk); + status->lhw.atk2= status_calc_watk(bl, sc, b_status->lhw.atk2); + } + } + + if( bl->type&BL_HOM ) + { + status->rhw.atk += (status->dex - b_status->dex); + status->rhw.atk2 += (status->str - b_status->str); + if( status->rhw.atk2 < status->rhw.atk ) + status->rhw.atk2 = status->rhw.atk; + } + } + + if(flag&SCB_HIT) { + if (status->dex == b_status->dex +#ifdef RENEWAL + && status->luk == b_status->luk +#endif + ) + status->hit = status_calc_hit(bl, sc, b_status->hit); + else + status->hit = status_calc_hit(bl, sc, b_status->hit + (status->dex - b_status->dex) +#ifdef RENEWAL + + (status->luk/3 - b_status->luk/3) +#endif + ); + } + + if(flag&SCB_FLEE) { + if (status->agi == b_status->agi +#ifdef RENEWAL + && status->luk == b_status->luk +#endif + ) + status->flee = status_calc_flee(bl, sc, b_status->flee); + else + status->flee = status_calc_flee(bl, sc, b_status->flee +(status->agi - b_status->agi) +#ifdef RENEWAL + + (status->luk/5 - b_status->luk/5) +#endif + ); + } + + if(flag&SCB_DEF) + { + status->def = status_calc_def(bl, sc, b_status->def); + + if( bl->type&BL_HOM ) + status->def += (status->vit/5 - b_status->vit/5); + } + + if(flag&SCB_DEF2) { + if (status->vit == b_status->vit +#ifdef RENEWAL + && status->agi == b_status->agi +#endif + ) + status->def2 = status_calc_def2(bl, sc, b_status->def2); + else + status->def2 = status_calc_def2(bl, sc, b_status->def2 +#ifdef RENEWAL + + (int)( ((float)status->vit/2 + (float)b_status->vit/2) + ((float)status->agi/5 + (float)b_status->agi/5) ) +#else + + (status->vit - b_status->vit) +#endif + ); + } + + if(flag&SCB_MDEF) + { + status->mdef = status_calc_mdef(bl, sc, b_status->mdef); + + if( bl->type&BL_HOM ) + status->mdef += (status->int_/5 - b_status->int_/5); + } + + if(flag&SCB_MDEF2) { + if (status->int_ == b_status->int_ && status->vit == b_status->vit +#ifdef RENEWAL + && status->dex == b_status->dex +#endif + ) + status->mdef2 = status_calc_mdef2(bl, sc, b_status->mdef2); + else + status->mdef2 = status_calc_mdef2(bl, sc, b_status->mdef2 +(status->int_ - b_status->int_) +#ifdef RENEWAL + + (int)( ((float)status->dex/5 - (float)b_status->dex/5) + ((float)status->vit/5 + (float)b_status->vit/5) ) +#else + + ((status->vit - b_status->vit)>>1) +#endif + ); + } + + if(flag&SCB_SPEED) { + struct unit_data *ud = unit_bl2ud(bl); + status->speed = status_calc_speed(bl, sc, b_status->speed); + + //Re-walk to adjust speed (we do not check if walktimer != INVALID_TIMER + //because if you step on something while walking, the moment this + //piece of code triggers the walk-timer is set on INVALID_TIMER) [Skotlex] + if (ud) + ud->state.change_walk_target = ud->state.speed_changed = 1; + + if( bl->type&BL_PC && status->speed < battle_config.max_walk_speed ) + status->speed = battle_config.max_walk_speed; + + if( bl->type&BL_HOM && battle_config.hom_setting&0x8 && ((TBL_HOM*)bl)->master) + status->speed = status_get_speed(&((TBL_HOM*)bl)->master->bl); + + + } + + if(flag&SCB_CRI && b_status->cri) { + if (status->luk == b_status->luk) + status->cri = status_calc_critical(bl, sc, b_status->cri); + else + status->cri = status_calc_critical(bl, sc, b_status->cri + 3*(status->luk - b_status->luk)); + /** + * after status_calc_critical so the bonus is applied despite if you have or not a sc bugreport:5240 + **/ + if( bl->type == BL_PC && ((TBL_PC*)bl)->status.weapon == W_KATAR ) + status->cri <<= 1; + + } + + if(flag&SCB_FLEE2 && b_status->flee2) { + if (status->luk == b_status->luk) + status->flee2 = status_calc_flee2(bl, sc, b_status->flee2); + else + status->flee2 = status_calc_flee2(bl, sc, b_status->flee2 +(status->luk - b_status->luk)); + } + + if(flag&SCB_ATK_ELE) { + status->rhw.ele = status_calc_attack_element(bl, sc, b_status->rhw.ele); + if (sd) sd->state.lr_flag = 1; + status->lhw.ele = status_calc_attack_element(bl, sc, b_status->lhw.ele); + if (sd) sd->state.lr_flag = 0; + } + + if(flag&SCB_DEF_ELE) { + status->def_ele = status_calc_element(bl, sc, b_status->def_ele); + status->ele_lv = status_calc_element_lv(bl, sc, b_status->ele_lv); + } + + if(flag&SCB_MODE) + { + status->mode = status_calc_mode(bl, sc, b_status->mode); + //Since mode changed, reset their state. + if (!(status->mode&MD_CANATTACK)) + unit_stop_attack(bl); + if (!(status->mode&MD_CANMOVE)) + unit_stop_walking(bl,1); + } + +// No status changes alter these yet. +// if(flag&SCB_SIZE) +// if(flag&SCB_RACE) +// if(flag&SCB_RANGE) + + if(flag&SCB_MAXHP) { + if( bl->type&BL_PC ) + { + status->max_hp = status_base_pc_maxhp(sd,status); + status->max_hp += b_status->max_hp - sd->status.max_hp; + + status->max_hp = status_calc_maxhp(bl, sc, status->max_hp); + + if( status->max_hp > (unsigned int)battle_config.max_hp ) + status->max_hp = (unsigned int)battle_config.max_hp; + } + else + { + status->max_hp = status_calc_maxhp(bl, sc, b_status->max_hp); + } + + if( status->hp > status->max_hp ) //FIXME: Should perhaps a status_zap should be issued? + { + status->hp = status->max_hp; + if( sd ) clif_updatestatus(sd,SP_HP); + } + } + + if(flag&SCB_MAXSP) { + if( bl->type&BL_PC ) + { + status->max_sp = status_base_pc_maxsp(sd,status); + status->max_sp += b_status->max_sp - sd->status.max_sp; + + status->max_sp = status_calc_maxsp(&sd->bl, &sd->sc, status->max_sp); + + if( status->max_sp > (unsigned int)battle_config.max_sp ) + status->max_sp = (unsigned int)battle_config.max_sp; + } + else + { + status->max_sp = status_calc_maxsp(bl, sc, b_status->max_sp); + } + + if( status->sp > status->max_sp ) + { + status->sp = status->max_sp; + if( sd ) clif_updatestatus(sd,SP_SP); + } + } + + if(flag&SCB_MATK) { +#ifndef RENEWAL + status->matk_min = status_base_matk_min(status) + (sd?sd->bonus.ematk:0); + status->matk_max = status_base_matk_max(status) + (sd?sd->bonus.ematk:0); +#else + /** + * RE MATK Formula (from irowiki:http://irowiki.org/wiki/MATK) + * MATK = (sMATK + wMATK + eMATK) * Multiplicative Modifiers + **/ + status->matk_min = status->matk_max = status_base_matk(status, status_get_lv(bl)); + if( bl->type&BL_PC ){ + // Any +MATK you get from skills and cards, including cards in weapon, is added here. + if( sd->bonus.ematk > 0 ){ + status->matk_max += sd->bonus.ematk; + status->matk_min += sd->bonus.ematk; + } + status->matk_min = status_calc_ematk(bl, sc, status->matk_min); + status->matk_max = status_calc_ematk(bl, sc, status->matk_max); + //This is the only portion in MATK that varies depending on the weapon level and refinement rate. + if( status->rhw.matk > 0 ){ + int wMatk = status->rhw.matk; + int variance = wMatk * status->rhw.wlv / 10; + status->matk_min += wMatk - variance; + status->matk_max += wMatk + variance; + } + } +#endif + if (bl->type&BL_PC && sd->matk_rate != 100) { + status->matk_max = status->matk_max * sd->matk_rate/100; + status->matk_min = status->matk_min * sd->matk_rate/100; + } + + status->matk_min = status_calc_matk(bl, sc, status->matk_min); + status->matk_max = status_calc_matk(bl, sc, status->matk_max); + + if ((bl->type&BL_HOM && battle_config.hom_setting&0x20) //Hom Min Matk is always the same as Max Matk + || sc->data[SC_RECOGNIZEDSPELL]) + status->matk_min = status->matk_max; + +#ifdef RENEWAL + if( sd && sd->right_weapon.overrefine > 0){ + status->matk_min++; + status->matk_max += sd->right_weapon.overrefine - 1; + } +#endif + + } + + if(flag&SCB_ASPD) { + int amotion; + if( bl->type&BL_PC ) + { + amotion = status_base_amotion_pc(sd,status); +#ifndef RENEWAL_ASPD + status->aspd_rate = status_calc_aspd_rate(bl, sc, b_status->aspd_rate); + + if(status->aspd_rate != 1000) + amotion = amotion*status->aspd_rate/1000; +#else + // aspd = baseaspd + floor(sqrt((agi^2/2) + (dex^2/5))/4 + (potskillbonus*agi/200)) + amotion -= (int)(sqrt( (pow(status->agi, 2) / 2) + (pow(status->dex, 2) / 5) ) / 4 + (status_calc_aspd(bl, sc, 1) * status->agi / 200)) * 10; + + if( (status_calc_aspd(bl, sc, 2) + status->aspd_rate2) != 0 ) // RE ASPD percertage modifier + amotion -= ( amotion - ((sd->class_&JOBL_THIRD) ? battle_config.max_third_aspd : battle_config.max_aspd) ) + * (status_calc_aspd(bl, sc, 2) + status->aspd_rate2) / 100; + + if(status->aspd_rate != 1000) // absolute percentage modifier + amotion = ( 200 - (200-amotion/10) * status->aspd_rate / 1000 ) * 10; +#endif + amotion = status_calc_fix_aspd(bl, sc, amotion); + status->amotion = cap_value(amotion,((sd->class_&JOBL_THIRD) ? battle_config.max_third_aspd : battle_config.max_aspd),2000); + + status->adelay = 2*status->amotion; + } + else + if( bl->type&BL_HOM ) + { + amotion = (1000 -4*status->agi -status->dex) * ((TBL_HOM*)bl)->homunculusDB->baseASPD/1000; + status->aspd_rate = status_calc_aspd_rate(bl, sc, b_status->aspd_rate); + + if(status->aspd_rate != 1000) + amotion = amotion*status->aspd_rate/1000; + + amotion = status_calc_fix_aspd(bl, sc, amotion); + status->amotion = cap_value(amotion,battle_config.max_aspd,2000); + + status->adelay = status->amotion; + } + else // mercenary and mobs + { + amotion = b_status->amotion; + status->aspd_rate = status_calc_aspd_rate(bl, sc, b_status->aspd_rate); + + if(status->aspd_rate != 1000) + amotion = amotion*status->aspd_rate/1000; + + amotion = status_calc_fix_aspd(bl, sc, amotion); + status->amotion = cap_value(amotion, battle_config.monster_max_aspd, 2000); + + temp = b_status->adelay*status->aspd_rate/1000; + status->adelay = cap_value(temp, battle_config.monster_max_aspd*2, 4000); + } + } + + if(flag&SCB_DSPD) { + int dmotion; + if( bl->type&BL_PC ) + { + if (b_status->agi == status->agi) + status->dmotion = status_calc_dmotion(bl, sc, b_status->dmotion); + else { + dmotion = 800-status->agi*4; + status->dmotion = cap_value(dmotion, 400, 800); + if(battle_config.pc_damage_delay_rate != 100) + status->dmotion = status->dmotion*battle_config.pc_damage_delay_rate/100; + //It's safe to ignore b_status->dmotion since no bonus affects it. + status->dmotion = status_calc_dmotion(bl, sc, status->dmotion); + } + } + else + if( bl->type&BL_HOM ) + { + dmotion = 800-status->agi*4; + status->dmotion = cap_value(dmotion, 400, 800); + status->dmotion = status_calc_dmotion(bl, sc, b_status->dmotion); + } + else // mercenary and mobs + { + status->dmotion = status_calc_dmotion(bl, sc, b_status->dmotion); + } + } + + if(flag&(SCB_VIT|SCB_MAXHP|SCB_INT|SCB_MAXSP) && bl->type&BL_REGEN) + status_calc_regen(bl, status, status_get_regen_data(bl)); + + if(flag&SCB_REGEN && bl->type&BL_REGEN) + status_calc_regen_rate(bl, status_get_regen_data(bl), sc); +} +/// Recalculates parts of an object's base status and battle status according to the specified flags. +/// Also sends updates to the client wherever applicable. +/// @param flag bitfield of values from enum scb_flag +/// @param first if true, will cause status_calc_* functions to run their base status initialization code +void status_calc_bl_(struct block_list* bl, enum scb_flag flag, bool first) +{ + struct status_data b_status; // previous battle status + struct status_data* status; // pointer to current battle status + + // remember previous values + status = status_get_status_data(bl); + memcpy(&b_status, status, sizeof(struct status_data)); + + if( flag&SCB_BASE ) {// calculate the object's base status too + switch( bl->type ) { + case BL_PC: status_calc_pc_(BL_CAST(BL_PC,bl), first); break; + case BL_MOB: status_calc_mob_(BL_CAST(BL_MOB,bl), first); break; + case BL_PET: status_calc_pet_(BL_CAST(BL_PET,bl), first); break; + case BL_HOM: status_calc_homunculus_(BL_CAST(BL_HOM,bl), first); break; + case BL_MER: status_calc_mercenary_(BL_CAST(BL_MER,bl), first); break; + case BL_ELEM: status_calc_elemental_(BL_CAST(BL_ELEM,bl), first); break; + case BL_NPC: status_calc_npc_(BL_CAST(BL_NPC,bl), first); break; + } + } + + if( bl->type == BL_PET ) + return; // pets are not affected by statuses + + if( first && bl->type == BL_MOB ) + return; // assume there will be no statuses active + + status_calc_bl_main(bl, flag); + + if( first && bl->type == BL_HOM ) + return; // client update handled by caller + + // compare against new values and send client updates + if( bl->type == BL_PC ) + { + TBL_PC* sd = BL_CAST(BL_PC, bl); + if(b_status.str != status->str) + clif_updatestatus(sd,SP_STR); + if(b_status.agi != status->agi) + clif_updatestatus(sd,SP_AGI); + if(b_status.vit != status->vit) + clif_updatestatus(sd,SP_VIT); + if(b_status.int_ != status->int_) + clif_updatestatus(sd,SP_INT); + if(b_status.dex != status->dex) + clif_updatestatus(sd,SP_DEX); + if(b_status.luk != status->luk) + clif_updatestatus(sd,SP_LUK); + if(b_status.hit != status->hit) + clif_updatestatus(sd,SP_HIT); + if(b_status.flee != status->flee) + clif_updatestatus(sd,SP_FLEE1); + if(b_status.amotion != status->amotion) + clif_updatestatus(sd,SP_ASPD); + if(b_status.speed != status->speed) + clif_updatestatus(sd,SP_SPEED); + + if(b_status.batk != status->batk +#ifndef RENEWAL + || b_status.rhw.atk != status->rhw.atk || b_status.lhw.atk != status->lhw.atk +#endif + ) + clif_updatestatus(sd,SP_ATK1); + + if(b_status.def != status->def){ + clif_updatestatus(sd,SP_DEF1); +#ifdef RENEWAL + clif_updatestatus(sd,SP_DEF2); +#endif + } + + if(b_status.rhw.atk2 != status->rhw.atk2 || b_status.lhw.atk2 != status->lhw.atk2 +#ifdef RENEWAL + || b_status.rhw.atk != status->rhw.atk || b_status.lhw.atk != status->lhw.atk +#endif + ) + clif_updatestatus(sd,SP_ATK2); + + if(b_status.def2 != status->def2){ + clif_updatestatus(sd,SP_DEF2); +#ifdef RENEWAL + clif_updatestatus(sd,SP_DEF1); +#endif + } + if(b_status.flee2 != status->flee2) + clif_updatestatus(sd,SP_FLEE2); + if(b_status.cri != status->cri) + clif_updatestatus(sd,SP_CRITICAL); +#ifndef RENEWAL + if(b_status.matk_max != status->matk_max) + clif_updatestatus(sd,SP_MATK1); + if(b_status.matk_min != status->matk_min) + clif_updatestatus(sd,SP_MATK2); +#else + if(b_status.matk_max != status->matk_max || b_status.matk_min != status->matk_min){ + clif_updatestatus(sd,SP_MATK2); + clif_updatestatus(sd,SP_MATK1); + } +#endif + if(b_status.mdef != status->mdef){ + clif_updatestatus(sd,SP_MDEF1); +#ifdef RENEWAL + clif_updatestatus(sd,SP_MDEF2); +#endif + } + if(b_status.mdef2 != status->mdef2){ + clif_updatestatus(sd,SP_MDEF2); +#ifdef RENEWAL + clif_updatestatus(sd,SP_MDEF1); +#endif + } + if(b_status.rhw.range != status->rhw.range) + clif_updatestatus(sd,SP_ATTACKRANGE); + if(b_status.max_hp != status->max_hp) + clif_updatestatus(sd,SP_MAXHP); + if(b_status.max_sp != status->max_sp) + clif_updatestatus(sd,SP_MAXSP); + if(b_status.hp != status->hp) + clif_updatestatus(sd,SP_HP); + if(b_status.sp != status->sp) + clif_updatestatus(sd,SP_SP); + } else if( bl->type == BL_HOM ) { + TBL_HOM* hd = BL_CAST(BL_HOM, bl); + if( hd->master && memcmp(&b_status, status, sizeof(struct status_data)) != 0 ) + clif_hominfo(hd->master,hd,0); + } else if( bl->type == BL_MER ) { + TBL_MER* md = BL_CAST(BL_MER, bl); + if( b_status.rhw.atk != status->rhw.atk || b_status.rhw.atk2 != status->rhw.atk2 ) + clif_mercenary_updatestatus(md->master, SP_ATK1); + if( b_status.matk_max != status->matk_max ) + clif_mercenary_updatestatus(md->master, SP_MATK1); + if( b_status.hit != status->hit ) + clif_mercenary_updatestatus(md->master, SP_HIT); + if( b_status.cri != status->cri ) + clif_mercenary_updatestatus(md->master, SP_CRITICAL); + if( b_status.def != status->def ) + clif_mercenary_updatestatus(md->master, SP_DEF1); + if( b_status.mdef != status->mdef ) + clif_mercenary_updatestatus(md->master, SP_MDEF1); + if( b_status.flee != status->flee ) + clif_mercenary_updatestatus(md->master, SP_MERCFLEE); + if( b_status.amotion != status->amotion ) + clif_mercenary_updatestatus(md->master, SP_ASPD); + if( b_status.max_hp != status->max_hp ) + clif_mercenary_updatestatus(md->master, SP_MAXHP); + if( b_status.max_sp != status->max_sp ) + clif_mercenary_updatestatus(md->master, SP_MAXSP); + if( b_status.hp != status->hp ) + clif_mercenary_updatestatus(md->master, SP_HP); + if( b_status.sp != status->sp ) + clif_mercenary_updatestatus(md->master, SP_SP); + } else if( bl->type == BL_ELEM ) { + TBL_ELEM* ed = BL_CAST(BL_ELEM, bl); + if( b_status.max_hp != status->max_hp ) + clif_elemental_updatestatus(ed->master, SP_MAXHP); + if( b_status.max_sp != status->max_sp ) + clif_elemental_updatestatus(ed->master, SP_MAXSP); + if( b_status.hp != status->hp ) + clif_elemental_updatestatus(ed->master, SP_HP); + if( b_status.sp != status->sp ) + clif_mercenary_updatestatus(ed->master, SP_SP); + } +} + +/*========================================== + * Apply shared stat mods from status changes [DracoRPG] + *------------------------------------------*/ +static unsigned short status_calc_str(struct block_list *bl, struct status_change *sc, int str) +{ + if(!sc || !sc->count) + return cap_value(str,0,USHRT_MAX); + + if(sc->data[SC_HARMONIZE]) { + str -= sc->data[SC_HARMONIZE]->val2; + return (unsigned short)cap_value(str,0,USHRT_MAX); + } + if(sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_HIGH && str < 50) + return 50; + if(sc->data[SC_INCALLSTATUS]) + str += sc->data[SC_INCALLSTATUS]->val1; + if(sc->data[SC_INCSTR]) + str += sc->data[SC_INCSTR]->val1; + if(sc->data[SC_STRFOOD]) + str += sc->data[SC_STRFOOD]->val1; + if(sc->data[SC_FOOD_STR_CASH]) + str += sc->data[SC_FOOD_STR_CASH]->val1; + if(sc->data[SC_BATTLEORDERS]) + str += 5; + if(sc->data[SC_LEADERSHIP]) + str += sc->data[SC_LEADERSHIP]->val1; + if(sc->data[SC_LOUD]) + str += 4; + if(sc->data[SC_TRUESIGHT]) + str += 5; + if(sc->data[SC_SPURT]) + str += 10; + if(sc->data[SC_NEN]) + str += sc->data[SC_NEN]->val1; + if(sc->data[SC_BLESSING]){ + if(sc->data[SC_BLESSING]->val2) + str += sc->data[SC_BLESSING]->val2; + else + str >>= 1; + } + if(sc->data[SC_MARIONETTE]) + str -= ((sc->data[SC_MARIONETTE]->val3)>>16)&0xFF; + if(sc->data[SC_MARIONETTE2]) + str += ((sc->data[SC_MARIONETTE2]->val3)>>16)&0xFF; + if(sc->data[SC_GIANTGROWTH]) + str += 30; + if(sc->data[SC_SAVAGE_STEAK]) + str += sc->data[SC_SAVAGE_STEAK]->val1; + if(sc->data[SC_INSPIRATION]) + str += sc->data[SC_INSPIRATION]->val3; + if(sc->data[SC_STOMACHACHE]) + str -= sc->data[SC_STOMACHACHE]->val1; + if(sc->data[SC_KYOUGAKU]) + str -= sc->data[SC_KYOUGAKU]->val2; + + return (unsigned short)cap_value(str,0,USHRT_MAX); +} + +static unsigned short status_calc_agi(struct block_list *bl, struct status_change *sc, int agi) +{ + if(!sc || !sc->count) + return cap_value(agi,0,USHRT_MAX); + + if(sc->data[SC_HARMONIZE]) { + agi -= sc->data[SC_HARMONIZE]->val2; + return (unsigned short)cap_value(agi,0,USHRT_MAX); + } + if(sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_HIGH && agi < 50) + return 50; + if(sc->data[SC_CONCENTRATE] && !sc->data[SC_QUAGMIRE]) + agi += (agi-sc->data[SC_CONCENTRATE]->val3)*sc->data[SC_CONCENTRATE]->val2/100; + if(sc->data[SC_INCALLSTATUS]) + agi += sc->data[SC_INCALLSTATUS]->val1; + if(sc->data[SC_INCAGI]) + agi += sc->data[SC_INCAGI]->val1; + if(sc->data[SC_AGIFOOD]) + agi += sc->data[SC_AGIFOOD]->val1; + if(sc->data[SC_FOOD_AGI_CASH]) + agi += sc->data[SC_FOOD_AGI_CASH]->val1; + if(sc->data[SC_SOULCOLD]) + agi += sc->data[SC_SOULCOLD]->val1; + if(sc->data[SC_TRUESIGHT]) + agi += 5; + if(sc->data[SC_INCREASEAGI]) + agi += sc->data[SC_INCREASEAGI]->val2; + if(sc->data[SC_INCREASING]) + agi += 4; // added based on skill updates [Reddozen] + if(sc->data[SC_DECREASEAGI]) + agi -= sc->data[SC_DECREASEAGI]->val2; + if(sc->data[SC_QUAGMIRE]) + agi -= sc->data[SC_QUAGMIRE]->val2; + if(sc->data[SC_SUITON] && sc->data[SC_SUITON]->val3) + agi -= sc->data[SC_SUITON]->val2; + if(sc->data[SC_MARIONETTE]) + agi -= ((sc->data[SC_MARIONETTE]->val3)>>8)&0xFF; + if(sc->data[SC_MARIONETTE2]) + agi += ((sc->data[SC_MARIONETTE2]->val3)>>8)&0xFF; + if(sc->data[SC_ADORAMUS]) + agi -= sc->data[SC_ADORAMUS]->val2; + if(sc->data[SC_DROCERA_HERB_STEAMED]) + agi += sc->data[SC_DROCERA_HERB_STEAMED]->val1; + if(sc->data[SC_INSPIRATION]) + agi += sc->data[SC_INSPIRATION]->val3; + if(sc->data[SC_STOMACHACHE]) + agi -= sc->data[SC_STOMACHACHE]->val1; + if(sc->data[SC_KYOUGAKU]) + agi -= sc->data[SC_KYOUGAKU]->val2; + + return (unsigned short)cap_value(agi,0,USHRT_MAX); +} + +static unsigned short status_calc_vit(struct block_list *bl, struct status_change *sc, int vit) +{ + if(!sc || !sc->count) + return cap_value(vit,0,USHRT_MAX); + + if(sc->data[SC_HARMONIZE]) { + vit -= sc->data[SC_HARMONIZE]->val2; + return (unsigned short)cap_value(vit,0,USHRT_MAX); + } + if(sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_HIGH && vit < 50) + return 50; + if(sc->data[SC_INCALLSTATUS]) + vit += sc->data[SC_INCALLSTATUS]->val1; + if(sc->data[SC_INCVIT]) + vit += sc->data[SC_INCVIT]->val1; + if(sc->data[SC_VITFOOD]) + vit += sc->data[SC_VITFOOD]->val1; + if(sc->data[SC_FOOD_VIT_CASH]) + vit += sc->data[SC_FOOD_VIT_CASH]->val1; + if(sc->data[SC_CHANGE]) + vit += sc->data[SC_CHANGE]->val2; + if(sc->data[SC_GLORYWOUNDS]) + vit += sc->data[SC_GLORYWOUNDS]->val1; + if(sc->data[SC_TRUESIGHT]) + vit += 5; + if(sc->data[SC_MARIONETTE]) + vit -= sc->data[SC_MARIONETTE]->val3&0xFF; + if(sc->data[SC_MARIONETTE2]) + vit += sc->data[SC_MARIONETTE2]->val3&0xFF; + if(sc->data[SC_LAUDAAGNUS]) + vit += 4 + sc->data[SC_LAUDAAGNUS]->val1; + if(sc->data[SC_MINOR_BBQ]) + vit += sc->data[SC_MINOR_BBQ]->val1; + if(sc->data[SC_INSPIRATION]) + vit += sc->data[SC_INSPIRATION]->val3; + if(sc->data[SC_STOMACHACHE]) + vit -= sc->data[SC_STOMACHACHE]->val1; + if(sc->data[SC_KYOUGAKU]) + vit -= sc->data[SC_KYOUGAKU]->val2; + + if(sc->data[SC_STRIPARMOR]) + vit -= vit * sc->data[SC_STRIPARMOR]->val2/100; + + return (unsigned short)cap_value(vit,0,USHRT_MAX); +} + +static unsigned short status_calc_int(struct block_list *bl, struct status_change *sc, int int_) +{ + if(!sc || !sc->count) + return cap_value(int_,0,USHRT_MAX); + + if(sc->data[SC_HARMONIZE]) { + int_ -= sc->data[SC_HARMONIZE]->val2; + return (unsigned short)cap_value(int_,0,USHRT_MAX); + } + if(sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_HIGH && int_ < 50) + return 50; + if(sc->data[SC_INCALLSTATUS]) + int_ += sc->data[SC_INCALLSTATUS]->val1; + if(sc->data[SC_INCINT]) + int_ += sc->data[SC_INCINT]->val1; + if(sc->data[SC_INTFOOD]) + int_ += sc->data[SC_INTFOOD]->val1; + if(sc->data[SC_FOOD_INT_CASH]) + int_ += sc->data[SC_FOOD_INT_CASH]->val1; + if(sc->data[SC_CHANGE]) + int_ += sc->data[SC_CHANGE]->val3; + if(sc->data[SC_BATTLEORDERS]) + int_ += 5; + if(sc->data[SC_TRUESIGHT]) + int_ += 5; + if(sc->data[SC_BLESSING]){ + if (sc->data[SC_BLESSING]->val2) + int_ += sc->data[SC_BLESSING]->val2; + else + int_ >>= 1; + } + if(sc->data[SC_NEN]) + int_ += sc->data[SC_NEN]->val1; + if(sc->data[SC_MARIONETTE]) + int_ -= ((sc->data[SC_MARIONETTE]->val4)>>16)&0xFF; + if(sc->data[SC_MARIONETTE2]) + int_ += ((sc->data[SC_MARIONETTE2]->val4)>>16)&0xFF; + if(sc->data[SC_MANDRAGORA]) + int_ -= 5 + 5 * sc->data[SC_MANDRAGORA]->val1; + if(sc->data[SC_COCKTAIL_WARG_BLOOD]) + int_ += sc->data[SC_COCKTAIL_WARG_BLOOD]->val1; + if(sc->data[SC_INSPIRATION]) + int_ += sc->data[SC_INSPIRATION]->val3; + if(sc->data[SC_STOMACHACHE]) + int_ -= sc->data[SC_STOMACHACHE]->val1; + if(sc->data[SC_KYOUGAKU]) + int_ -= sc->data[SC_KYOUGAKU]->val2; + + if(sc->data[SC_STRIPHELM]) + int_ -= int_ * sc->data[SC_STRIPHELM]->val2/100; + if(sc->data[SC__STRIPACCESSORY]) + int_ -= int_ * sc->data[SC__STRIPACCESSORY]->val2 / 100; + + return (unsigned short)cap_value(int_,0,USHRT_MAX); +} + +static unsigned short status_calc_dex(struct block_list *bl, struct status_change *sc, int dex) +{ + if(!sc || !sc->count) + return cap_value(dex,0,USHRT_MAX); + + if(sc->data[SC_HARMONIZE]) { + dex -= sc->data[SC_HARMONIZE]->val2; + return (unsigned short)cap_value(dex,0,USHRT_MAX); + } + if(sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_HIGH && dex < 50) + return 50; + if(sc->data[SC_CONCENTRATE] && !sc->data[SC_QUAGMIRE]) + dex += (dex-sc->data[SC_CONCENTRATE]->val4)*sc->data[SC_CONCENTRATE]->val2/100; + if(sc->data[SC_INCALLSTATUS]) + dex += sc->data[SC_INCALLSTATUS]->val1; + if(sc->data[SC_INCDEX]) + dex += sc->data[SC_INCDEX]->val1; + if(sc->data[SC_DEXFOOD]) + dex += sc->data[SC_DEXFOOD]->val1; + if(sc->data[SC_FOOD_DEX_CASH]) + dex += sc->data[SC_FOOD_DEX_CASH]->val1; + if(sc->data[SC_BATTLEORDERS]) + dex += 5; + if(sc->data[SC_HAWKEYES]) + dex += sc->data[SC_HAWKEYES]->val1; + if(sc->data[SC_TRUESIGHT]) + dex += 5; + if(sc->data[SC_QUAGMIRE]) + dex -= sc->data[SC_QUAGMIRE]->val2; + if(sc->data[SC_BLESSING]){ + if (sc->data[SC_BLESSING]->val2) + dex += sc->data[SC_BLESSING]->val2; + else + dex >>= 1; + } + if(sc->data[SC_INCREASING]) + dex += 4; // added based on skill updates [Reddozen] + if(sc->data[SC_MARIONETTE]) + dex -= ((sc->data[SC_MARIONETTE]->val4)>>8)&0xFF; + if(sc->data[SC_MARIONETTE2]) + dex += ((sc->data[SC_MARIONETTE2]->val4)>>8)&0xFF; + if(sc->data[SC_SIROMA_ICE_TEA]) + dex += sc->data[SC_SIROMA_ICE_TEA]->val1; + if(sc->data[SC_INSPIRATION]) + dex += sc->data[SC_INSPIRATION]->val3; + if(sc->data[SC_STOMACHACHE]) + dex -= sc->data[SC_STOMACHACHE]->val1; + if(sc->data[SC_KYOUGAKU]) + dex -= sc->data[SC_KYOUGAKU]->val2; + + if(sc->data[SC__STRIPACCESSORY]) + dex -= dex * sc->data[SC__STRIPACCESSORY]->val2 / 100; + + return (unsigned short)cap_value(dex,0,USHRT_MAX); +} + +static unsigned short status_calc_luk(struct block_list *bl, struct status_change *sc, int luk) +{ + if(!sc || !sc->count) + return cap_value(luk,0,USHRT_MAX); + + if(sc->data[SC_HARMONIZE]) { + luk -= sc->data[SC_HARMONIZE]->val2; + return (unsigned short)cap_value(luk,0,USHRT_MAX); + } + if(sc->data[SC_CURSE]) + return 0; + if(sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_HIGH && luk < 50) + return 50; + if(sc->data[SC_INCALLSTATUS]) + luk += sc->data[SC_INCALLSTATUS]->val1; + if(sc->data[SC_INCLUK]) + luk += sc->data[SC_INCLUK]->val1; + if(sc->data[SC_LUKFOOD]) + luk += sc->data[SC_LUKFOOD]->val1; + if(sc->data[SC_FOOD_LUK_CASH]) + luk += sc->data[SC_FOOD_LUK_CASH]->val1; + if(sc->data[SC_TRUESIGHT]) + luk += 5; + if(sc->data[SC_GLORIA]) + luk += 30; + if(sc->data[SC_MARIONETTE]) + luk -= sc->data[SC_MARIONETTE]->val4&0xFF; + if(sc->data[SC_MARIONETTE2]) + luk += sc->data[SC_MARIONETTE2]->val4&0xFF; + if(sc->data[SC_PUTTI_TAILS_NOODLES]) + luk += sc->data[SC_PUTTI_TAILS_NOODLES]->val1; + if(sc->data[SC_INSPIRATION]) + luk += sc->data[SC_INSPIRATION]->val3; + if(sc->data[SC_STOMACHACHE]) + luk -= sc->data[SC_STOMACHACHE]->val1; + if(sc->data[SC_KYOUGAKU]) + luk -= sc->data[SC_KYOUGAKU]->val2; + if(sc->data[SC_LAUDARAMUS]) + luk += 4 + sc->data[SC_LAUDARAMUS]->val1; + + if(sc->data[SC__STRIPACCESSORY]) + luk -= luk * sc->data[SC__STRIPACCESSORY]->val2 / 100; + if(sc->data[SC_BANANA_BOMB]) + luk -= luk * sc->data[SC_BANANA_BOMB]->val1 / 100; + + return (unsigned short)cap_value(luk,0,USHRT_MAX); +} + +static unsigned short status_calc_batk(struct block_list *bl, struct status_change *sc, int batk) +{ + if(!sc || !sc->count) + return cap_value(batk,0,USHRT_MAX); + + if(sc->data[SC_ATKPOTION]) + batk += sc->data[SC_ATKPOTION]->val1; + if(sc->data[SC_BATKFOOD]) + batk += sc->data[SC_BATKFOOD]->val1; + if(sc->data[SC_GATLINGFEVER]) + batk += sc->data[SC_GATLINGFEVER]->val3; + if(sc->data[SC_MADNESSCANCEL]) + batk += 100; + if(sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 2) + batk += 50; + if(bl->type == BL_ELEM + && ((sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 1) + || (sc->data[SC_WATER_INSIGNIA] && sc->data[SC_WATER_INSIGNIA]->val1 == 1) + || (sc->data[SC_WIND_INSIGNIA] && sc->data[SC_WIND_INSIGNIA]->val1 == 1) + || (sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 1)) + ) + batk += batk / 5; + if(sc->data[SC_FULL_SWING_K]) + batk += sc->data[SC_FULL_SWING_K]->val1; + if(sc->data[SC_ODINS_POWER]) + batk += 70; + if(sc->data[SC_ASH] && (bl->type==BL_MOB)){ + if(status_get_element(bl) == ELE_WATER) //water type + batk /= 2; + } + if(sc->data[SC_PYROCLASTIC]) + batk += sc->data[SC_PYROCLASTIC]->val2; + if (sc->data[SC_ANGRIFFS_MODUS]) + batk += sc->data[SC_ANGRIFFS_MODUS]->val2; + + if(sc->data[SC_INCATKRATE]) + batk += batk * sc->data[SC_INCATKRATE]->val1/100; + if(sc->data[SC_PROVOKE]) + batk += batk * sc->data[SC_PROVOKE]->val3/100; + if(sc->data[SC_CONCENTRATION]) + batk += batk * sc->data[SC_CONCENTRATION]->val2/100; + if(sc->data[SC_SKE]) + batk += batk * 3; + if(sc->data[SC_BLOODLUST]) + batk += batk * sc->data[SC_BLOODLUST]->val2/100; + if(sc->data[SC_JOINTBEAT] && sc->data[SC_JOINTBEAT]->val2&BREAK_WAIST) + batk -= batk * 25/100; + if(sc->data[SC_CURSE]) + batk -= batk * 25/100; +//Curse shouldn't effect on this? <- Curse OR Bleeding?? +// if(sc->data[SC_BLEEDING]) +// batk -= batk * 25/100; + if(sc->data[SC_FLEET]) + batk += batk * sc->data[SC_FLEET]->val3/100; + if(sc->data[SC__ENERVATION]) + batk -= batk * sc->data[SC__ENERVATION]->val2 / 100; + if(sc->data[SC_RUSHWINDMILL]) + batk += batk * sc->data[SC_RUSHWINDMILL]->val2/100; + if(sc->data[SC_SATURDAYNIGHTFEVER]) + batk += 100 * sc->data[SC_SATURDAYNIGHTFEVER]->val1; + if(sc->data[SC_MELODYOFSINK]) + batk -= batk * sc->data[SC_MELODYOFSINK]->val3/100; + if(sc->data[SC_BEYONDOFWARCRY]) + batk += batk * sc->data[SC_BEYONDOFWARCRY]->val3/100; + if( sc->data[SC_ZANGETSU] ) + batk += batk * sc->data[SC_ZANGETSU]->val2 / 100; + + return (unsigned short)cap_value(batk,0,USHRT_MAX); +} + +static unsigned short status_calc_watk(struct block_list *bl, struct status_change *sc, int watk) +{ + if(!sc || !sc->count) + return cap_value(watk,0,USHRT_MAX); + + if(sc->data[SC_IMPOSITIO]) + watk += sc->data[SC_IMPOSITIO]->val2; + if(sc->data[SC_WATKFOOD]) + watk += sc->data[SC_WATKFOOD]->val1; + if(sc->data[SC_DRUMBATTLE]) + watk += sc->data[SC_DRUMBATTLE]->val2; + if(sc->data[SC_VOLCANO]) + watk += sc->data[SC_VOLCANO]->val2; + if(sc->data[SC_MERC_ATKUP]) + watk += sc->data[SC_MERC_ATKUP]->val2; + if(sc->data[SC_FIGHTINGSPIRIT]) + watk += sc->data[SC_FIGHTINGSPIRIT]->val1; + if(sc->data[SC_STRIKING]) + watk += sc->data[SC_STRIKING]->val2; + if(sc->data[SC_SHIELDSPELL_DEF] && sc->data[SC_SHIELDSPELL_DEF]->val1 == 3) + watk += sc->data[SC_SHIELDSPELL_DEF]->val2; + if(sc->data[SC_INSPIRATION]) + watk += sc->data[SC_INSPIRATION]->val2; + if( sc->data[SC_BANDING] && sc->data[SC_BANDING]->val2 > 0 ) + watk += (10 + 10 * sc->data[SC_BANDING]->val1) * (sc->data[SC_BANDING]->val2); + if( sc->data[SC_TROPIC_OPTION] ) + watk += sc->data[SC_TROPIC_OPTION]->val2; + if( sc->data[SC_HEATER_OPTION] ) + watk += sc->data[SC_HEATER_OPTION]->val2; + if( sc->data[SC_WATER_BARRIER] ) + watk -= sc->data[SC_WATER_BARRIER]->val3; + if( sc->data[SC_PYROTECHNIC_OPTION] ) + watk += sc->data[SC_PYROTECHNIC_OPTION]->val2; + if(sc->data[SC_NIBELUNGEN]) { + if (bl->type != BL_PC) + watk += sc->data[SC_NIBELUNGEN]->val2; + else { + #ifndef RENEWAL + TBL_PC *sd = (TBL_PC*)bl; + int index = sd->equip_index[sd->state.lr_flag?EQI_HAND_L:EQI_HAND_R]; + if(index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->wlv == 4) + #endif + watk += sc->data[SC_NIBELUNGEN]->val2; + } + } + + if(sc->data[SC_INCATKRATE]) + watk += watk * sc->data[SC_INCATKRATE]->val1/100; + if(sc->data[SC_PROVOKE]) + watk += watk * sc->data[SC_PROVOKE]->val3/100; + if(sc->data[SC_CONCENTRATION]) + watk += watk * sc->data[SC_CONCENTRATION]->val2/100; + if(sc->data[SC_SKE]) + watk += watk * 3; + if(sc->data[SC__ENERVATION]) + watk -= watk * sc->data[SC__ENERVATION]->val2 / 100; + if(sc->data[SC_FLEET]) + watk += watk * sc->data[SC_FLEET]->val3/100; + if(sc->data[SC_CURSE]) + watk -= watk * 25/100; + if(sc->data[SC_STRIPWEAPON]) + watk -= watk * sc->data[SC_STRIPWEAPON]->val2/100; + if(sc->data[SC__ENERVATION]) + watk -= watk * sc->data[SC__ENERVATION]->val2 / 100; + if((sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 2) + || (sc->data[SC_WATER_INSIGNIA] && sc->data[SC_WATER_INSIGNIA]->val1 == 2) + || (sc->data[SC_WIND_INSIGNIA] && sc->data[SC_WIND_INSIGNIA]->val1 == 2) + || (sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 2) + ) + watk += watk / 10; + if( sc && sc->data[SC_TIDAL_WEAPON] ) + watk += watk * sc->data[SC_TIDAL_WEAPON]->val2 / 100; + if(sc->data[SC_ANGRIFFS_MODUS]) + watk += watk * sc->data[SC_ANGRIFFS_MODUS]->val2/100; +#ifdef RENEWAL_EDP + if( sc->data[SC_EDP] ) + watk = watk * (100 + sc->data[SC_EDP]->val1 * 80) / 100; +#endif + + return (unsigned short)cap_value(watk,0,USHRT_MAX); +} +#ifdef RENEWAL +static unsigned short status_calc_ematk(struct block_list *bl, struct status_change *sc, int matk) +{ + + if (!sc || !sc->count) + return cap_value(matk,0,USHRT_MAX); + if (sc->data[SC_MATKPOTION]) + matk += sc->data[SC_MATKPOTION]->val1; + if (sc->data[SC_MATKFOOD]) + matk += sc->data[SC_MATKFOOD]->val1; + if(sc->data[SC_MANA_PLUS]) + matk += sc->data[SC_MANA_PLUS]->val1; + if(sc->data[SC_AQUAPLAY_OPTION]) + matk += sc->data[SC_AQUAPLAY_OPTION]->val2; + if(sc->data[SC_CHILLY_AIR_OPTION]) + matk += sc->data[SC_CHILLY_AIR_OPTION]->val2; + if(sc->data[SC_WATER_BARRIER]) + matk -= sc->data[SC_WATER_BARRIER]->val3; + if(sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 3) + matk += 50; + if(sc->data[SC_ODINS_POWER]) + matk += 40 + 30 * sc->data[SC_ODINS_POWER]->val1; //70 lvl1, 100lvl2 + if(sc->data[SC_IZAYOI]) + matk += 50 * sc->data[SC_IZAYOI]->val1; + return (unsigned short)cap_value(matk,0,USHRT_MAX); +} +#endif +static unsigned short status_calc_matk(struct block_list *bl, struct status_change *sc, int matk) +{ + if(!sc || !sc->count) + return cap_value(matk,0,USHRT_MAX); +#ifndef RENEWAL + // take note fixed value first before % modifiers + if (sc->data[SC_MATKPOTION]) + matk += sc->data[SC_MATKPOTION]->val1; + if (sc->data[SC_MATKFOOD]) + matk += sc->data[SC_MATKFOOD]->val1; + if (sc->data[SC_MANA_PLUS]) + matk += sc->data[SC_MANA_PLUS]->val1; + if (sc->data[SC_AQUAPLAY_OPTION]) + matk += sc->data[SC_AQUAPLAY_OPTION]->val2; + if (sc->data[SC_CHILLY_AIR_OPTION]) + matk += sc->data[SC_CHILLY_AIR_OPTION]->val2; + if (sc->data[SC_WATER_BARRIER]) + matk -= sc->data[SC_WATER_BARRIER]->val3; + if (sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 3) + matk += 50; + if (sc->data[SC_ODINS_POWER]) + matk += 40 + 30 * sc->data[SC_ODINS_POWER]->val1; //70 lvl1, 100lvl2 + if (sc->data[SC_IZAYOI]) + matk += 50 * sc->data[SC_IZAYOI]->val1; +#endif + if (sc->data[SC_MAGICPOWER]) + matk += matk * sc->data[SC_MAGICPOWER]->val3/100; + if (sc->data[SC_MINDBREAKER]) + matk += matk * sc->data[SC_MINDBREAKER]->val2/100; + if (sc->data[SC_INCMATKRATE]) + matk += matk * sc->data[SC_INCMATKRATE]->val1/100; + if (sc->data[SC_MOONLITSERENADE]) + matk += matk * sc->data[SC_MOONLITSERENADE]->val2/100; + if (sc->data[SC_MELODYOFSINK]) + matk += matk * sc->data[SC_MELODYOFSINK]->val3/100; + if (sc->data[SC_BEYONDOFWARCRY]) + matk -= matk * sc->data[SC_BEYONDOFWARCRY]->val3/100; + if( sc->data[SC_ZANGETSU] ) + matk += matk * sc->data[SC_ZANGETSU]->val2 / 100; + + return (unsigned short)cap_value(matk,0,USHRT_MAX); +} + +static signed short status_calc_critical(struct block_list *bl, struct status_change *sc, int critical) { + + if(!sc || !sc->count) + return cap_value(critical,10,SHRT_MAX); + + if (sc->data[SC_INCCRI]) + critical += sc->data[SC_INCCRI]->val2; + if (sc->data[SC_EXPLOSIONSPIRITS]) + critical += sc->data[SC_EXPLOSIONSPIRITS]->val2; + if (sc->data[SC_FORTUNE]) + critical += sc->data[SC_FORTUNE]->val2; + if (sc->data[SC_TRUESIGHT]) + critical += sc->data[SC_TRUESIGHT]->val2; + if(sc->data[SC_CLOAKING]) + critical += critical; + if(sc->data[SC_STRIKING]) + critical += sc->data[SC_STRIKING]->val1; +#ifdef RENEWAL + if (sc->data[SC_SPEARQUICKEN]) + critical += 3*sc->data[SC_SPEARQUICKEN]->val1*10; +#endif + + if(sc->data[SC__INVISIBILITY]) + critical += critical * sc->data[SC__INVISIBILITY]->val3 / 100; + if(sc->data[SC__UNLUCKY]) + critical -= critical * sc->data[SC__UNLUCKY]->val2 / 100; + + return (short)cap_value(critical,10,SHRT_MAX); +} + +static signed short status_calc_hit(struct block_list *bl, struct status_change *sc, int hit) +{ + + if(!sc || !sc->count) + return cap_value(hit,1,SHRT_MAX); + + if(sc->data[SC_INCHIT]) + hit += sc->data[SC_INCHIT]->val1; + if(sc->data[SC_HITFOOD]) + hit += sc->data[SC_HITFOOD]->val1; + if(sc->data[SC_TRUESIGHT]) + hit += sc->data[SC_TRUESIGHT]->val3; + if(sc->data[SC_HUMMING]) + hit += sc->data[SC_HUMMING]->val2; + if(sc->data[SC_CONCENTRATION]) + hit += sc->data[SC_CONCENTRATION]->val3; + if(sc->data[SC_INSPIRATION]) + hit += 5 * sc->data[SC_INSPIRATION]->val1; + if(sc->data[SC_ADJUSTMENT]) + hit -= 30; + if(sc->data[SC_INCREASING]) + hit += 20; // RockmanEXE; changed based on updated [Reddozen] + if(sc->data[SC_MERC_HITUP]) + hit += sc->data[SC_MERC_HITUP]->val2; + + if(sc->data[SC_INCHITRATE]) + hit += hit * sc->data[SC_INCHITRATE]->val1/100; + if(sc->data[SC_BLIND]) + hit -= hit * 25/100; + if(sc->data[SC__GROOMY]) + hit -= hit * sc->data[SC__GROOMY]->val3 / 100; + if(sc->data[SC_FEAR]) + hit -= hit * 20 / 100; + if (sc->data[SC_ASH]) + hit /= 2; + + return (short)cap_value(hit,1,SHRT_MAX); +} + +static signed short status_calc_flee(struct block_list *bl, struct status_change *sc, int flee) +{ + if( bl->type == BL_PC ) + { + if( map_flag_gvg(bl->m) ) + flee -= flee * battle_config.gvg_flee_penalty/100; + else if( map[bl->m].flag.battleground ) + flee -= flee * battle_config.bg_flee_penalty/100; + } + + if(!sc || !sc->count) + return cap_value(flee,1,SHRT_MAX); + + if(sc->data[SC_INCFLEE]) + flee += sc->data[SC_INCFLEE]->val1; + if(sc->data[SC_FLEEFOOD]) + flee += sc->data[SC_FLEEFOOD]->val1; + if(sc->data[SC_WHISTLE]) + flee += sc->data[SC_WHISTLE]->val2; + if(sc->data[SC_WINDWALK]) + flee += sc->data[SC_WINDWALK]->val2; + if(sc->data[SC_VIOLENTGALE]) + flee += sc->data[SC_VIOLENTGALE]->val2; + if(sc->data[SC_MOON_COMFORT]) //SG skill [Komurka] + flee += sc->data[SC_MOON_COMFORT]->val2; + if(sc->data[SC_CLOSECONFINE]) + flee += 10; + if (sc->data[SC_ANGRIFFS_MODUS]) + flee -= sc->data[SC_ANGRIFFS_MODUS]->val3; + if (sc->data[SC_OVERED_BOOST]) + flee = max(flee,sc->data[SC_OVERED_BOOST]->val2); + if(sc->data[SC_ADJUSTMENT]) + flee += 30; + if(sc->data[SC_SPEED]) + flee += 10 + sc->data[SC_SPEED]->val1 * 10; + if(sc->data[SC_GATLINGFEVER]) + flee -= sc->data[SC_GATLINGFEVER]->val4; + if(sc->data[SC_PARTYFLEE]) + flee += sc->data[SC_PARTYFLEE]->val1 * 10; + if(sc->data[SC_MERC_FLEEUP]) + flee += sc->data[SC_MERC_FLEEUP]->val2; + if( sc->data[SC_HALLUCINATIONWALK] ) + flee += sc->data[SC_HALLUCINATIONWALK]->val2; + if( sc->data[SC_WATER_BARRIER] ) + flee -= sc->data[SC_WATER_BARRIER]->val3; + if( sc->data[SC_MARSHOFABYSS] ) + flee -= (9 * sc->data[SC_MARSHOFABYSS]->val3 / 10 + sc->data[SC_MARSHOFABYSS]->val2 / 10) * (bl->type == BL_MOB ? 2 : 1); +#ifdef RENEWAL + if( sc->data[SC_SPEARQUICKEN] ) + flee += 2 * sc->data[SC_SPEARQUICKEN]->val1; +#endif + + if(sc->data[SC_INCFLEERATE]) + flee += flee * sc->data[SC_INCFLEERATE]->val1/100; + if(sc->data[SC_SPIDERWEB] && sc->data[SC_SPIDERWEB]->val1) + flee -= flee * 50/100; + if (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) + flee -= flee * 50/100; + if(sc->data[SC_BLIND]) + flee -= flee * 25/100; + if(sc->data[SC_FEAR]) + flee -= flee * 20 / 100; + if(sc->data[SC_PARALYSE]) + flee -= flee * 10 / 100; // 10% Flee reduction + if(sc->data[SC_INFRAREDSCAN]) + flee -= flee * 30 / 100; + if( sc->data[SC__LAZINESS] ) + flee -= flee * sc->data[SC__LAZINESS]->val3 / 100; + if( sc->data[SC_GLOOMYDAY] ) + flee -= flee * sc->data[SC_GLOOMYDAY]->val2 / 100; + if( sc->data[SC_SATURDAYNIGHTFEVER] ) + flee -= flee * (40 + 10 * sc->data[SC_SATURDAYNIGHTFEVER]->val1) / 100; + if( sc->data[SC_WIND_STEP_OPTION] ) + flee += flee * sc->data[SC_WIND_STEP_OPTION]->val2 / 100; + if( sc->data[SC_ZEPHYR] ) + flee += flee * sc->data[SC_ZEPHYR]->val2 / 100; + if(sc->data[SC_ASH] && (bl->type==BL_MOB)){ //mob + if(status_get_element(bl) == ELE_WATER) //water type + flee /= 2; + } + + return (short)cap_value(flee,1,SHRT_MAX); +} + +static signed short status_calc_flee2(struct block_list *bl, struct status_change *sc, int flee2) +{ + if(!sc || !sc->count) + return cap_value(flee2,10,SHRT_MAX); + + if(sc->data[SC_INCFLEE2]) + flee2 += sc->data[SC_INCFLEE2]->val2; + if(sc->data[SC_WHISTLE]) + flee2 += sc->data[SC_WHISTLE]->val3*10; + if(sc->data[SC__UNLUCKY]) + flee2 -= flee2 * sc->data[SC__UNLUCKY]->val2 / 100; + + return (short)cap_value(flee2,10,SHRT_MAX); +} +static defType status_calc_def(struct block_list *bl, struct status_change *sc, int def) { + + if(!sc || !sc->count) + return (defType)cap_value(def,DEFTYPE_MIN,DEFTYPE_MAX); + + if (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) + return 0; + if(sc->data[SC_SKA]) + return sc->data[SC_SKA]->val3; + if(sc->data[SC_BARRIER]) + return 100; + if(sc->data[SC_KEEPING]) + return 90; +#ifndef RENEWAL // does not provide 90 DEF in renewal mode + if(sc->data[SC_STEELBODY]) + return 90; +#endif + + if(sc->data[SC_ARMORCHANGE]) + def += sc->data[SC_ARMORCHANGE]->val2; + if(sc->data[SC_DRUMBATTLE]) + def += sc->data[SC_DRUMBATTLE]->val3; + if(sc->data[SC_DEFENCE]) //[orn] + def += sc->data[SC_DEFENCE]->val2 ; + if(sc->data[SC_INCDEFRATE]) + def += def * sc->data[SC_INCDEFRATE]->val1/100; + if(sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 2) + def += 50; + if(sc->data[SC_ODINS_POWER]) + def -= 20; + if( sc->data[SC_ANGRIFFS_MODUS] ) + def -= 30 + 20 * sc->data[SC_ANGRIFFS_MODUS]->val1; + if(sc->data[SC_STONEHARDSKIN])// Final DEF increase divided by 10 since were using classic (pre-renewal) mechanics. [Rytech] + def += sc->data[SC_STONEHARDSKIN]->val1; + if(sc->data[SC_STONE] && sc->opt1 == OPT1_STONE) + def >>=1; + if(sc->data[SC_FREEZE]) + def >>=1; + if(sc->data[SC_SIGNUMCRUCIS]) + def -= def * sc->data[SC_SIGNUMCRUCIS]->val2/100; + if(sc->data[SC_CONCENTRATION]) + def -= def * sc->data[SC_CONCENTRATION]->val4/100; + if(sc->data[SC_SKE]) + def >>=1; + if(sc->data[SC_PROVOKE] && bl->type != BL_PC) // Provoke doesn't alter player defense-> + def -= def * sc->data[SC_PROVOKE]->val4/100; + if(sc->data[SC_STRIPSHIELD]) + def -= def * sc->data[SC_STRIPSHIELD]->val2/100; + if (sc->data[SC_FLING]) + def -= def * (sc->data[SC_FLING]->val2)/100; + if( sc->data[SC_FREEZING] ) + def -= def * 10 / 100; + if( sc->data[SC_MARSHOFABYSS] ) + def -= def * ( 6 + 6 * sc->data[SC_MARSHOFABYSS]->val3/10 + (bl->type == BL_MOB ? 5 : 3) * sc->data[SC_MARSHOFABYSS]->val2/36 ) / 100; + if( sc->data[SC_ANALYZE] ) + def -= def * ( 14 * sc->data[SC_ANALYZE]->val1 ) / 100; + if( sc->data[SC_FORCEOFVANGUARD] ) + def += def * 2 * sc->data[SC_FORCEOFVANGUARD]->val1 / 100; + if(sc->data[SC_SATURDAYNIGHTFEVER]) + def -= def * (10 + 10 * sc->data[SC_SATURDAYNIGHTFEVER]->val1) / 100; + if(sc->data[SC_EARTHDRIVE]) + def -= def * 25 / 100; + if( sc->data[SC_ROCK_CRUSHER] ) + def -= def * sc->data[SC_ROCK_CRUSHER]->val2 / 100; + if( sc->data[SC_POWER_OF_GAIA] ) + def += def * sc->data[SC_POWER_OF_GAIA]->val2 / 100; + if( sc->data[SC_PRESTIGE] ) + def += def * sc->data[SC_PRESTIGE]->val1 / 100; + if(sc->data[SC_ASH] && (bl->type==BL_MOB)){ + if(status_get_race(bl)==RC_PLANT) + def /= 2; + } + + return (defType)cap_value(def,DEFTYPE_MIN,DEFTYPE_MAX);; +} + +static signed short status_calc_def2(struct block_list *bl, struct status_change *sc, int def2) +{ + if(!sc || !sc->count) +#ifdef RENEWAL + return (short)cap_value(def2,SHRT_MIN,SHRT_MAX); +#else + return (short)cap_value(def2,1,SHRT_MAX); +#endif + + if (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) + return 0; + if(sc->data[SC_ETERNALCHAOS]) + return 0; + if(sc->data[SC_SUN_COMFORT]) + def2 += sc->data[SC_SUN_COMFORT]->val2; + if( sc->data[SC_SHIELDSPELL_REF] && sc->data[SC_SHIELDSPELL_REF]->val1 == 1 ) + def2 += sc->data[SC_SHIELDSPELL_REF]->val2; + if( sc->data[SC_BANDING] && sc->data[SC_BANDING]->val2 > 0 ) + def2 += (5 + sc->data[SC_BANDING]->val1) * (sc->data[SC_BANDING]->val2); + + if(sc->data[SC_ANGELUS]) +#ifdef RENEWAL //in renewal only the VIT stat bonus is boosted by angelus + def2 += status_get_vit(bl) / 2 * sc->data[SC_ANGELUS]->val2/100; +#else + def2 += def2 * sc->data[SC_ANGELUS]->val2/100; +#endif + if(sc->data[SC_CONCENTRATION]) + def2 -= def2 * sc->data[SC_CONCENTRATION]->val4/100; + if(sc->data[SC_POISON]) + def2 -= def2 * 25/100; + if(sc->data[SC_DPOISON]) + def2 -= def2 * 25/100; + if(sc->data[SC_SKE]) + def2 -= def2 * 50/100; + if(sc->data[SC_PROVOKE]) + def2 -= def2 * sc->data[SC_PROVOKE]->val4/100; + if(sc->data[SC_JOINTBEAT]) + def2 -= def2 * ( sc->data[SC_JOINTBEAT]->val2&BREAK_SHOULDER ? 50 : 0 ) / 100 + + def2 * ( sc->data[SC_JOINTBEAT]->val2&BREAK_WAIST ? 25 : 0 ) / 100; + if(sc->data[SC_FLING]) + def2 -= def2 * (sc->data[SC_FLING]->val3)/100; + if( sc->data[SC_FREEZING] ) + def2 -= def2 * 3 / 10; + if(sc->data[SC_ANALYZE]) + def2 -= def2 * ( 14 * sc->data[SC_ANALYZE]->val1 ) / 100; + if( sc->data[SC_ECHOSONG] ) + def2 += def2 * sc->data[SC_ECHOSONG]->val2/100; + if(sc->data[SC_ASH] && (bl->type==BL_MOB)){ + if(status_get_race(bl)==RC_PLANT) + def2 /= 2; + } + if (sc->data[SC_PARALYSIS]) + def2 -= def2 * sc->data[SC_PARALYSIS]->val2 / 100; + +#ifdef RENEWAL + return (short)cap_value(def2,SHRT_MIN,SHRT_MAX); +#else + return (short)cap_value(def2,1,SHRT_MAX); +#endif +} + + +static defType status_calc_mdef(struct block_list *bl, struct status_change *sc, int mdef) { + + if(!sc || !sc->count) + return (defType)cap_value(mdef,DEFTYPE_MIN,DEFTYPE_MAX); + + if (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) + return 0; + if(sc->data[SC_BARRIER]) + return 100; + +#ifndef RENEWAL // no longer provides 90 MDEF in renewal mode + if(sc->data[SC_STEELBODY]) + return 90; +#endif + + if(sc->data[SC_ARMORCHANGE]) + mdef += sc->data[SC_ARMORCHANGE]->val3; + if(sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 3) + mdef += 50; + if(sc->data[SC_ENDURE])// It has been confirmed that eddga card grants 1 MDEF, not 0, not 10, but 1. + mdef += (sc->data[SC_ENDURE]->val4 == 0) ? sc->data[SC_ENDURE]->val1 : 1; + if(sc->data[SC_CONCENTRATION]) + mdef += 1; //Skill info says it adds a fixed 1 Mdef point. + if(sc->data[SC_STONEHARDSKIN])// Final MDEF increase divided by 10 since were using classic (pre-renewal) mechanics. [Rytech] + mdef += sc->data[SC_STONEHARDSKIN]->val1; + if(sc->data[SC_WATER_BARRIER]) + mdef += sc->data[SC_WATER_BARRIER]->val2; + if(sc->data[SC_STONE] && sc->opt1 == OPT1_STONE) + mdef += 25*mdef/100; + if(sc->data[SC_FREEZE]) + mdef += 25*mdef/100; + if( sc->data[SC_MARSHOFABYSS] ) + mdef -= mdef * ( 6 + 6 * sc->data[SC_MARSHOFABYSS]->val3/10 + (bl->type == BL_MOB ? 5 : 3) * sc->data[SC_MARSHOFABYSS]->val2/36 ) / 100; + if(sc->data[SC_ANALYZE]) + mdef -= mdef * ( 14 * sc->data[SC_ANALYZE]->val1 ) / 100; + if(sc->data[SC_SYMPHONYOFLOVER]) + mdef += mdef * sc->data[SC_SYMPHONYOFLOVER]->val2 / 100; + if(sc->data[SC_GT_CHANGE] && sc->data[SC_GT_CHANGE]->val4) + mdef -= mdef * sc->data[SC_GT_CHANGE]->val4 / 100; + if (sc->data[SC_ODINS_POWER]) + mdef -= 20 * sc->data[SC_ODINS_POWER]->val1; + + return (defType)cap_value(mdef,DEFTYPE_MIN,DEFTYPE_MAX); +} + +static signed short status_calc_mdef2(struct block_list *bl, struct status_change *sc, int mdef2) +{ + if(!sc || !sc->count) +#ifdef RENEWAL + return (short)cap_value(mdef2,SHRT_MIN,SHRT_MAX); +#else + return (short)cap_value(mdef2,1,SHRT_MAX); +#endif + + + if (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) + return 0; + if(sc->data[SC_SKA]) + return 90; + if(sc->data[SC_MINDBREAKER]) + mdef2 -= mdef2 * sc->data[SC_MINDBREAKER]->val3/100; + if(sc->data[SC_ANALYZE]) + mdef2 -= mdef2 * ( 14 * sc->data[SC_ANALYZE]->val1 ) / 100; + +#ifdef RENEWAL + return (short)cap_value(mdef2,SHRT_MIN,SHRT_MAX); +#else + return (short)cap_value(mdef2,1,SHRT_MAX); +#endif +} + +static unsigned short status_calc_speed(struct block_list *bl, struct status_change *sc, int speed) +{ + TBL_PC* sd = BL_CAST(BL_PC, bl); + int speed_rate; + + if( sc == NULL ) + return cap_value(speed,10,USHRT_MAX); + + if( sd && sd->ud.skilltimer != INVALID_TIMER && (pc_checkskill(sd,SA_FREECAST) > 0 || sd->ud.skill_id == LG_EXEEDBREAK) ) + { + if( sd->ud.skill_id == LG_EXEEDBREAK ) + speed_rate = 100 + 60 - (sd->ud.skill_lv * 10); + else + speed_rate = 175 - 5 * pc_checkskill(sd,SA_FREECAST); + } + else + { + speed_rate = 100; + + //GetMoveHasteValue2() + { + int val = 0; + + if( sc->data[SC_FUSION] ) + val = 25; + else if( sd ) { + if( pc_isriding(sd) || sd->sc.option&(OPTION_DRAGON|OPTION_MOUNTING) ) + val = 25;//Same bonus + else if( pc_isridingwug(sd) ) + val = 15 + 5 * pc_checkskill(sd, RA_WUGRIDER); + else if( pc_ismadogear(sd) ) { + val = (- 10 * (5 - pc_checkskill(sd,NC_MADOLICENCE))); + if( sc->data[SC_ACCELERATION] ) + val += 25; + } + } + + speed_rate -= val; + } + + //GetMoveSlowValue() + { + int val = 0; + + if( sd && sc->data[SC_HIDING] && pc_checkskill(sd,RG_TUNNELDRIVE) > 0 ) + val = 120 - 6 * pc_checkskill(sd,RG_TUNNELDRIVE); + else + if( sd && sc->data[SC_CHASEWALK] && sc->data[SC_CHASEWALK]->val3 < 0 ) + val = sc->data[SC_CHASEWALK]->val3; + else + { + // Longing for Freedom cancels song/dance penalty + if( sc->data[SC_LONGING] ) + val = max( val, 50 - 10 * sc->data[SC_LONGING]->val1 ); + else + if( sd && sc->data[SC_DANCING] ) + val = max( val, 500 - (40 + 10 * (sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_BARDDANCER)) * pc_checkskill(sd,(sd->status.sex?BA_MUSICALLESSON:DC_DANCINGLESSON)) ); + + if( sc->data[SC_DECREASEAGI] ) + val = max( val, 25 ); + if( sc->data[SC_QUAGMIRE] || sc->data[SC_HALLUCINATIONWALK_POSTDELAY] || (sc->data[SC_GLOOMYDAY] && sc->data[SC_GLOOMYDAY]->val4) ) + val = max( val, 50 ); + if( sc->data[SC_DONTFORGETME] ) + val = max( val, sc->data[SC_DONTFORGETME]->val3 ); + if( sc->data[SC_CURSE] ) + val = max( val, 300 ); + if( sc->data[SC_CHASEWALK] ) + val = max( val, sc->data[SC_CHASEWALK]->val3 ); + if( sc->data[SC_WEDDING] ) + val = max( val, 100 ); + if( sc->data[SC_JOINTBEAT] && sc->data[SC_JOINTBEAT]->val2&(BREAK_ANKLE|BREAK_KNEE) ) + val = max( val, (sc->data[SC_JOINTBEAT]->val2&BREAK_ANKLE ? 50 : 0) + (sc->data[SC_JOINTBEAT]->val2&BREAK_KNEE ? 30 : 0) ); + if( sc->data[SC_CLOAKING] && (sc->data[SC_CLOAKING]->val4&1) == 0 ) + val = max( val, sc->data[SC_CLOAKING]->val1 < 3 ? 300 : 30 - 3 * sc->data[SC_CLOAKING]->val1 ); + if( sc->data[SC_GOSPEL] && sc->data[SC_GOSPEL]->val4 == BCT_ENEMY ) + val = max( val, 75 ); + if( sc->data[SC_SLOWDOWN] ) // Slow Potion + val = max( val, 100 ); + if( sc->data[SC_GATLINGFEVER] ) + val = max( val, 100 ); + if( sc->data[SC_SUITON] ) + val = max( val, sc->data[SC_SUITON]->val3 ); + if( sc->data[SC_SWOO] ) + val = max( val, 300 ); + if( sc->data[SC_FREEZING] ) + val = max( val, 70 ); + if( sc->data[SC_MARSHOFABYSS] ) + val = max( val, 40 + 10 * sc->data[SC_MARSHOFABYSS]->val1 ); + if( sc->data[SC_CAMOUFLAGE] && (sc->data[SC_CAMOUFLAGE]->val3&1) == 0 ) + val = max( val, sc->data[SC_CAMOUFLAGE]->val1 < 3 ? 0 : 25 * (5 - sc->data[SC_CAMOUFLAGE]->val1) ); + if( sc->data[SC__GROOMY] ) + val = max( val, sc->data[SC__GROOMY]->val2); + if( sc->data[SC_STEALTHFIELD_MASTER] ) + val = max( val, 30 ); + if( sc->data[SC_BANDING_DEFENCE] ) + val = max( val, sc->data[SC_BANDING_DEFENCE]->val1 );//+90% walking speed. + if( sc->data[SC_ROCK_CRUSHER_ATK] ) + val = max( val, sc->data[SC_ROCK_CRUSHER_ATK]->val2 ); + if( sc->data[SC_POWER_OF_GAIA] ) + val = max( val, sc->data[SC_POWER_OF_GAIA]->val2 ); + if( sc->data[SC_MELON_BOMB] ) + val = max( val, sc->data[SC_MELON_BOMB]->val1 ); + + if( sd && sd->bonus.speed_rate + sd->bonus.speed_add_rate > 0 ) // permanent item-based speedup + val = max( val, sd->bonus.speed_rate + sd->bonus.speed_add_rate ); + } + + speed_rate += val; + } + + //GetMoveHasteValue1() + { + int val = 0; + + if( sc->data[SC_SPEEDUP1] ) //FIXME: used both by NPC_AGIUP and Speed Potion script + val = max( val, 50 ); + if( sc->data[SC_INCREASEAGI] ) + val = max( val, 25 ); + if( sc->data[SC_WINDWALK] ) + val = max( val, 2 * sc->data[SC_WINDWALK]->val1 ); + if( sc->data[SC_CARTBOOST] ) + val = max( val, 20 ); + if( sd && (sd->class_&MAPID_UPPERMASK) == MAPID_ASSASSIN && pc_checkskill(sd,TF_MISS) > 0 ) + val = max( val, 1 * pc_checkskill(sd,TF_MISS) ); + if( sc->data[SC_CLOAKING] && (sc->data[SC_CLOAKING]->val4&1) == 1 ) + val = max( val, sc->data[SC_CLOAKING]->val1 >= 10 ? 25 : 3 * sc->data[SC_CLOAKING]->val1 - 3 ); + if (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) + val = max( val, 25 ); + if( sc->data[SC_RUN] ) + val = max( val, 55 ); + if( sc->data[SC_AVOID] ) + val = max( val, 10 * sc->data[SC_AVOID]->val1 ); + if( sc->data[SC_INVINCIBLE] && !sc->data[SC_INVINCIBLEOFF] ) + val = max( val, 75 ); + if( sc->data[SC_CLOAKINGEXCEED] ) + val = max( val, sc->data[SC_CLOAKINGEXCEED]->val3); + if( sc->data[SC_HOVERING] ) + val = max( val, 10 ); + if( sc->data[SC_GN_CARTBOOST] ) + val = max( val, sc->data[SC_GN_CARTBOOST]->val2 ); + if( sc->data[SC_SWINGDANCE] ) + val = max( val, sc->data[SC_SWINGDANCE]->val2 ); + if( sc->data[SC_WIND_STEP_OPTION] ) + val = max( val, sc->data[SC_WIND_STEP_OPTION]->val2 ); + + //FIXME: official items use a single bonus for this [ultramage] + if( sc->data[SC_SPEEDUP0] ) // temporary item-based speedup + val = max( val, 25 ); + if( sd && sd->bonus.speed_rate + sd->bonus.speed_add_rate < 0 ) // permanent item-based speedup + val = max( val, -(sd->bonus.speed_rate + sd->bonus.speed_add_rate) ); + + speed_rate -= val; + } + + if( speed_rate < 40 ) + speed_rate = 40; + } + + //GetSpeed() + { + if( sd && pc_iscarton(sd) ) + speed += speed * (50 - 5 * pc_checkskill(sd,MC_PUSHCART)) / 100; + if( sc->data[SC_PARALYSE] ) + speed += speed * 50 / 100; + if( speed_rate != 100 ) + speed = speed * speed_rate / 100; + if( sc->data[SC_STEELBODY] ) + speed = 200; + if( sc->data[SC_DEFENDER] ) + speed = max(speed, 200); + if( sc->data[SC_WALKSPEED] && sc->data[SC_WALKSPEED]->val1 > 0 ) // ChangeSpeed + speed = speed * 100 / sc->data[SC_WALKSPEED]->val1; + } + + return (short)cap_value(speed,10,USHRT_MAX); +} + +#ifdef RENEWAL_ASPD +// flag&1 - fixed value [malufett] +// flag&2 - percentage value +static short status_calc_aspd(struct block_list *bl, struct status_change *sc, short flag) +{ + int i, pots = 0, skills1 = 0, skills2 = 0; + + if(!sc || !sc->count) + return 0; + + if(sc->data[i=SC_ASPDPOTION3] || + sc->data[i=SC_ASPDPOTION2] || + sc->data[i=SC_ASPDPOTION1] || + sc->data[i=SC_ASPDPOTION0]) + pots += sc->data[i]->val1; + + if( !sc->data[SC_QUAGMIRE] ){ + if(sc->data[SC_STAR_COMFORT]) + skills1 = 5; // needs more info + + if(sc->data[SC_TWOHANDQUICKEN] && skills1 < 7) + skills1 = 7; + + if(sc->data[SC_ONEHAND] && skills1 < 7) skills1 = 7; + + if(sc->data[SC_MERC_QUICKEN] && skills1 < 7) // needs more info + skills1 = 7; + + if(sc->data[SC_ADRENALINE2] && skills1 < 6) + skills1 = 6; + + if(sc->data[SC_ADRENALINE] && skills1 < 7) + skills1 = 7; + + if(sc->data[SC_SPEARQUICKEN] && skills1 < 7) + skills1 = 7; + + if(sc->data[SC_GATLINGFEVER] && skills1 < 9) // needs more info + skills1 = 9; + + if(sc->data[SC_FLEET] && skills1 < 5) + skills1 = 5; + + if(sc->data[SC_ASSNCROS] && + skills1 < 5+1*sc->data[SC_ASSNCROS]->val1) // needs more info + { + if (bl->type!=BL_PC) + skills1 = 4+1*sc->data[SC_ASSNCROS]->val1; + else + switch(((TBL_PC*)bl)->status.weapon) + { + case W_BOW: + case W_REVOLVER: + case W_RIFLE: + case W_GATLING: + case W_SHOTGUN: + case W_GRENADE: + break; + default: + skills1 = 5+1*sc->data[SC_ASSNCROS]->val1; + } + } + } + + if((sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) && skills1 < 15) + skills1 = 15; + else if(sc->data[SC_MADNESSCANCEL] && skills1 < 15) // needs more info + skills1 = 15; + + if(sc->data[SC_DONTFORGETME]) + skills2 -= sc->data[SC_DONTFORGETME]->val2; // needs more info + if(sc->data[SC_LONGING]) + skills2 -= sc->data[SC_LONGING]->val2; // needs more info + if(sc->data[SC_STEELBODY]) + skills2 -= 25; + if(sc->data[SC_SKA]) + skills2 -= 25; + if(sc->data[SC_DEFENDER]) + skills2 -= sc->data[SC_DEFENDER]->val4; // needs more info + if(sc->data[SC_GOSPEL] && sc->data[SC_GOSPEL]->val4 == BCT_ENEMY) // needs more info + skills2 -= 25; + if(sc->data[SC_GRAVITATION]) + skills2 -= sc->data[SC_GRAVITATION]->val2; // needs more info + if(sc->data[SC_JOINTBEAT]) { // needs more info + if( sc->data[SC_JOINTBEAT]->val2&BREAK_WRIST ) + skills2 -= 25; + if( sc->data[SC_JOINTBEAT]->val2&BREAK_KNEE ) + skills2 -= 10; + } + if( sc->data[SC_FREEZING] ) + skills2 -= 30; + if( sc->data[SC_HALLUCINATIONWALK_POSTDELAY] ) + skills2 -= 50; + if( sc->data[SC_PARALYSE] ) + skills2 -= 10; + if( sc->data[SC__BODYPAINT] ) + skills2 -= 2 + 5 * sc->data[SC__BODYPAINT]->val1; + if( sc->data[SC__INVISIBILITY] ) + skills2 -= sc->data[SC__INVISIBILITY]->val2 ; + if( sc->data[SC__GROOMY] ) + skills2 -= sc->data[SC__GROOMY]->val2; + if( sc->data[SC_SWINGDANCE] ) + skills2 += sc->data[SC_SWINGDANCE]->val2; + if( sc->data[SC_DANCEWITHWUG] ) + skills2 += sc->data[SC_DANCEWITHWUG]->val3; + if( sc->data[SC_GLOOMYDAY] ) + skills2 -= sc->data[SC_GLOOMYDAY]->val3; + if( sc->data[SC_EARTHDRIVE] ) + skills2 -= 25; + if( sc->data[SC_GT_CHANGE] ) + skills2 += sc->data[SC_GT_CHANGE]->val3; + if( sc->data[SC_MELON_BOMB] ) + skills2 -= sc->data[SC_MELON_BOMB]->val1; + if( sc->data[SC_BOOST500] ) + skills2 += sc->data[SC_BOOST500]->val1; + if( sc->data[SC_EXTRACT_SALAMINE_JUICE] ) + skills2 += sc->data[SC_EXTRACT_SALAMINE_JUICE]->val1; + if( sc->data[SC_INCASPDRATE] ) + skills2 += sc->data[SC_INCASPDRATE]->val1; + + return ( flag&1? (skills1 + pots) : skills2 ); +} +#endif + +static short status_calc_fix_aspd(struct block_list *bl, struct status_change *sc, int aspd) { + if (!sc || !sc->count) + return cap_value(aspd, 0, 2000); + + if (!sc->data[SC_QUAGMIRE]) { + if (sc->data[SC_OVERED_BOOST]) + aspd = 2000 - sc->data[SC_OVERED_BOOST]->val3*10; + } + + if ((sc->data[SC_GUST_OPTION] || sc->data[SC_BLAST_OPTION] + || sc->data[SC_WILD_STORM_OPTION])) + aspd -= 50; // +5 ASPD + if( sc && sc->data[SC_FIGHTINGSPIRIT] && sc->data[SC_FIGHTINGSPIRIT]->val2 ) + aspd -= (bl->type==BL_PC?pc_checkskill((TBL_PC *)bl, RK_RUNEMASTERY):10) / 10 * 40; + + return cap_value(aspd, 0, 2000); // will be recap for proper bl anyway +} + +/// Calculates an object's ASPD modifier (alters the base amotion value). +/// Note that the scale of aspd_rate is 1000 = 100%. +static short status_calc_aspd_rate(struct block_list *bl, struct status_change *sc, int aspd_rate) +{ + int i; + + if(!sc || !sc->count) + return cap_value(aspd_rate,0,SHRT_MAX); + + if( !sc->data[SC_QUAGMIRE] ){ + int max = 0; + if(sc->data[SC_STAR_COMFORT]) + max = sc->data[SC_STAR_COMFORT]->val2; + + if(sc->data[SC_TWOHANDQUICKEN] && + max < sc->data[SC_TWOHANDQUICKEN]->val2) + max = sc->data[SC_TWOHANDQUICKEN]->val2; + + if(sc->data[SC_ONEHAND] && + max < sc->data[SC_ONEHAND]->val2) + max = sc->data[SC_ONEHAND]->val2; + + if(sc->data[SC_MERC_QUICKEN] && + max < sc->data[SC_MERC_QUICKEN]->val2) + max = sc->data[SC_MERC_QUICKEN]->val2; + + if(sc->data[SC_ADRENALINE2] && + max < sc->data[SC_ADRENALINE2]->val3) + max = sc->data[SC_ADRENALINE2]->val3; + + if(sc->data[SC_ADRENALINE] && + max < sc->data[SC_ADRENALINE]->val3) + max = sc->data[SC_ADRENALINE]->val3; + + if(sc->data[SC_SPEARQUICKEN] && + max < sc->data[SC_SPEARQUICKEN]->val2) + max = sc->data[SC_SPEARQUICKEN]->val2; + + if(sc->data[SC_GATLINGFEVER] && + max < sc->data[SC_GATLINGFEVER]->val2) + max = sc->data[SC_GATLINGFEVER]->val2; + + if(sc->data[SC_FLEET] && + max < sc->data[SC_FLEET]->val2) + max = sc->data[SC_FLEET]->val2; + + if(sc->data[SC_ASSNCROS] && + max < sc->data[SC_ASSNCROS]->val2) + { + if (bl->type!=BL_PC) + max = sc->data[SC_ASSNCROS]->val2; + else + switch(((TBL_PC*)bl)->status.weapon) + { + case W_BOW: + case W_REVOLVER: + case W_RIFLE: + case W_GATLING: + case W_SHOTGUN: + case W_GRENADE: + break; + default: + max = sc->data[SC_ASSNCROS]->val2; + } + } + aspd_rate -= max; + + if((sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST])) + aspd_rate -= 300; + else if(sc->data[SC_MADNESSCANCEL]) + aspd_rate -= 200; + } + + if( sc->data[i=SC_ASPDPOTION3] || + sc->data[i=SC_ASPDPOTION2] || + sc->data[i=SC_ASPDPOTION1] || + sc->data[i=SC_ASPDPOTION0] ) + aspd_rate -= sc->data[i]->val2; + + if(sc->data[SC_DONTFORGETME]) + aspd_rate += 10 * sc->data[SC_DONTFORGETME]->val2; + if(sc->data[SC_LONGING]) + aspd_rate += sc->data[SC_LONGING]->val2; + if(sc->data[SC_STEELBODY]) + aspd_rate += 250; + if(sc->data[SC_SKA]) + aspd_rate += 250; + if(sc->data[SC_DEFENDER]) + aspd_rate += sc->data[SC_DEFENDER]->val4; + if(sc->data[SC_GOSPEL] && sc->data[SC_GOSPEL]->val4 == BCT_ENEMY) + aspd_rate += 250; + if(sc->data[SC_GRAVITATION]) + aspd_rate += sc->data[SC_GRAVITATION]->val2; + if(sc->data[SC_JOINTBEAT]) { + if( sc->data[SC_JOINTBEAT]->val2&BREAK_WRIST ) + aspd_rate += 250; + if( sc->data[SC_JOINTBEAT]->val2&BREAK_KNEE ) + aspd_rate += 100; + } + if( sc->data[SC_FREEZING] ) + aspd_rate += 300; + if( sc->data[SC_HALLUCINATIONWALK_POSTDELAY] ) + aspd_rate += 500; + if( sc->data[SC_FIGHTINGSPIRIT] && sc->data[SC_FIGHTINGSPIRIT]->val2 ) + aspd_rate -= sc->data[SC_FIGHTINGSPIRIT]->val2; + if( sc->data[SC_PARALYSE] ) + aspd_rate += 100; + if( sc->data[SC__BODYPAINT] ) + aspd_rate += 200 + 50 * sc->data[SC__BODYPAINT]->val1; + if( sc->data[SC__INVISIBILITY] ) + aspd_rate += sc->data[SC__INVISIBILITY]->val2 * 10 ; + if( sc->data[SC__GROOMY] ) + aspd_rate += sc->data[SC__GROOMY]->val2 * 10; + if( sc->data[SC_SWINGDANCE] ) + aspd_rate -= sc->data[SC_SWINGDANCE]->val2 * 10; + if( sc->data[SC_DANCEWITHWUG] ) + aspd_rate -= sc->data[SC_DANCEWITHWUG]->val3 * 10; + if( sc->data[SC_GLOOMYDAY] ) + aspd_rate += sc->data[SC_GLOOMYDAY]->val3 * 10; + if( sc->data[SC_EARTHDRIVE] ) + aspd_rate += 250; + if( sc->data[SC_GT_CHANGE] ) + aspd_rate -= sc->data[SC_GT_CHANGE]->val3 * 10; + if( sc->data[SC_MELON_BOMB] ) + aspd_rate += sc->data[SC_MELON_BOMB]->val1 * 10; + if( sc->data[SC_BOOST500] ) + aspd_rate -= sc->data[SC_BOOST500]->val1 *10; + if( sc->data[SC_EXTRACT_SALAMINE_JUICE] ) + aspd_rate -= sc->data[SC_EXTRACT_SALAMINE_JUICE]->val1 * 10; + if( sc->data[SC_INCASPDRATE] ) + aspd_rate -= sc->data[SC_INCASPDRATE]->val1 * 10; + if( sc->data[SC_PAIN_KILLER]) + aspd_rate += sc->data[SC_PAIN_KILLER]->val2 * 10; + if( sc->data[SC_GOLDENE_FERSE]) + aspd_rate -= sc->data[SC_GOLDENE_FERSE]->val3 * 10; + + return (short)cap_value(aspd_rate,0,SHRT_MAX); +} + +static unsigned short status_calc_dmotion(struct block_list *bl, struct status_change *sc, int dmotion) +{ + if( !sc || !sc->count || map_flag_gvg(bl->m) || map[bl->m].flag.battleground ) + return cap_value(dmotion,0,USHRT_MAX); + /** + * It has been confirmed on official servers that MvP mobs have no dmotion even without endure + **/ + if( sc->data[SC_ENDURE] || ( bl->type == BL_MOB && (((TBL_MOB*)bl)->status.mode&MD_BOSS) ) ) + return 0; + if( sc->data[SC_CONCENTRATION] ) + return 0; + if( sc->data[SC_RUN] || sc->data[SC_WUGDASH] ) + return 0; + + return (unsigned short)cap_value(dmotion,0,USHRT_MAX); +} + +static unsigned int status_calc_maxhp(struct block_list *bl, struct status_change *sc, uint64 maxhp) +{ + if(!sc || !sc->count) + return (unsigned int)cap_value(maxhp,1,UINT_MAX); + + if(sc->data[SC_INCMHPRATE]) + maxhp += maxhp * sc->data[SC_INCMHPRATE]->val1/100; + if(sc->data[SC_INCMHP]) + maxhp += (sc->data[SC_INCMHP]->val1); + if(sc->data[SC_APPLEIDUN]) + maxhp += maxhp * sc->data[SC_APPLEIDUN]->val2/100; + if(sc->data[SC_DELUGE]) + maxhp += maxhp * sc->data[SC_DELUGE]->val2/100; + if (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST]) + maxhp += maxhp * 2; + if(sc->data[SC_MARIONETTE]) + maxhp -= 1000; + if(sc->data[SC_SOLID_SKIN_OPTION]) + maxhp += 2000;// Fix amount. + if(sc->data[SC_POWER_OF_GAIA]) + maxhp += 3000; + if(sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 2) + maxhp += 500; + + if(sc->data[SC_MERC_HPUP]) + maxhp += maxhp * sc->data[SC_MERC_HPUP]->val2/100; + + if(sc->data[SC_EPICLESIS]) + maxhp += maxhp * 5 * sc->data[SC_EPICLESIS]->val1 / 100; + if(sc->data[SC_VENOMBLEED]) + maxhp -= maxhp * 15 / 100; + if(sc->data[SC__WEAKNESS]) + maxhp -= maxhp * sc->data[SC__WEAKNESS]->val2 / 100; + if(sc->data[SC_LERADSDEW]) + maxhp += maxhp * sc->data[SC_LERADSDEW]->val3 / 100; + if(sc->data[SC_FORCEOFVANGUARD]) + maxhp += maxhp * 3 * sc->data[SC_FORCEOFVANGUARD]->val1 / 100; + if(sc->data[SC_INSPIRATION]) //Custom value. + maxhp += maxhp * 3 * sc->data[SC_INSPIRATION]->val1 / 100; + if(sc->data[SC_RAISINGDRAGON]) + maxhp += maxhp * (2 + sc->data[SC_RAISINGDRAGON]->val1) / 100; + if(sc->data[SC_GT_CHANGE]) // Max HP decrease: [Skill Level x 4] % + maxhp -= maxhp * (4 * sc->data[SC_GT_CHANGE]->val1) / 100; + if(sc->data[SC_GT_REVITALIZE])// Max HP increase: [Skill Level x 2] % + maxhp += maxhp * (2 * sc->data[SC_GT_REVITALIZE]->val1) / 100; + if(sc->data[SC_MUSTLE_M]) + maxhp += maxhp * sc->data[SC_MUSTLE_M]->val1/100; + if(sc->data[SC_MYSTERIOUS_POWDER]) + maxhp -= sc->data[SC_MYSTERIOUS_POWDER]->val1 / 100; + if(sc->data[SC_PETROLOGY_OPTION]) + maxhp += maxhp * sc->data[SC_PETROLOGY_OPTION]->val2 / 100; + if (sc->data[SC_ANGRIFFS_MODUS]) + maxhp += maxhp * 5 * sc->data[SC_ANGRIFFS_MODUS]->val1 /100; + if (sc->data[SC_GOLDENE_FERSE]) + maxhp += maxhp * sc->data[SC_GOLDENE_FERSE]->val2 / 100; + + return (unsigned int)cap_value(maxhp,1,UINT_MAX); +} + +static unsigned int status_calc_maxsp(struct block_list *bl, struct status_change *sc, unsigned int maxsp) +{ + if(!sc || !sc->count) + return cap_value(maxsp,1,UINT_MAX); + + if(sc->data[SC_INCMSPRATE]) + maxsp += maxsp * sc->data[SC_INCMSPRATE]->val1/100; + if(sc->data[SC_INCMSP]) + maxsp += (sc->data[SC_INCMSP]->val1); + if(sc->data[SC_SERVICE4U]) + maxsp += maxsp * sc->data[SC_SERVICE4U]->val2/100; + if(sc->data[SC_MERC_SPUP]) + maxsp += maxsp * sc->data[SC_MERC_SPUP]->val2/100; + if(sc->data[SC_RAISINGDRAGON]) + maxsp += maxsp * (2 + sc->data[SC_RAISINGDRAGON]->val1) / 100; + if(sc->data[SC_LIFE_FORCE_F]) + maxsp += maxsp * sc->data[SC_LIFE_FORCE_F]->val1/100; + if(sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 3) + maxsp += 50; + + return cap_value(maxsp,1,UINT_MAX); +} + +static unsigned char status_calc_element(struct block_list *bl, struct status_change *sc, int element) +{ + if(!sc || !sc->count) + return element; + + if(sc->data[SC_FREEZE]) + return ELE_WATER; + if(sc->data[SC_STONE] && sc->opt1 == OPT1_STONE) + return ELE_EARTH; + if(sc->data[SC_BENEDICTIO]) + return ELE_HOLY; + if(sc->data[SC_CHANGEUNDEAD]) + return ELE_UNDEAD; + if(sc->data[SC_ELEMENTALCHANGE]) + return sc->data[SC_ELEMENTALCHANGE]->val2; + if(sc->data[SC_SHAPESHIFT]) + return sc->data[SC_SHAPESHIFT]->val2; + + return (unsigned char)cap_value(element,0,UCHAR_MAX); +} + +static unsigned char status_calc_element_lv(struct block_list *bl, struct status_change *sc, int lv) +{ + if(!sc || !sc->count) + return lv; + + if(sc->data[SC_FREEZE]) + return 1; + if(sc->data[SC_STONE] && sc->opt1 == OPT1_STONE) + return 1; + if(sc->data[SC_BENEDICTIO]) + return 1; + if(sc->data[SC_CHANGEUNDEAD]) + return 1; + if(sc->data[SC_ELEMENTALCHANGE]) + return sc->data[SC_ELEMENTALCHANGE]->val1; + if(sc->data[SC_SHAPESHIFT]) + return 1; + if(sc->data[SC__INVISIBILITY]) + return 1; + + return (unsigned char)cap_value(lv,1,4); +} + + +unsigned char status_calc_attack_element(struct block_list *bl, struct status_change *sc, int element) +{ + if(!sc || !sc->count) + return element; + if(sc->data[SC_ENCHANTARMS]) + return sc->data[SC_ENCHANTARMS]->val2; + if(sc->data[SC_WATERWEAPON] + || (sc->data[SC_WATER_INSIGNIA] && sc->data[SC_WATER_INSIGNIA]->val1 == 2) ) + return ELE_WATER; + if(sc->data[SC_EARTHWEAPON] + || (sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 2) ) + return ELE_EARTH; + if(sc->data[SC_FIREWEAPON] + || (sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 2) ) + return ELE_FIRE; + if(sc->data[SC_WINDWEAPON] + || (sc->data[SC_WIND_INSIGNIA] && sc->data[SC_WIND_INSIGNIA]->val1 == 2) ) + return ELE_WIND; + if(sc->data[SC_ENCPOISON]) + return ELE_POISON; + if(sc->data[SC_ASPERSIO]) + return ELE_HOLY; + if(sc->data[SC_SHADOWWEAPON]) + return ELE_DARK; + if(sc->data[SC_GHOSTWEAPON] || sc->data[SC__INVISIBILITY]) + return ELE_GHOST; + if(sc->data[SC_TIDAL_WEAPON_OPTION] || sc->data[SC_TIDAL_WEAPON] ) + return ELE_WATER; + if(sc->data[SC_PYROCLASTIC]) + return ELE_FIRE; + return (unsigned char)cap_value(element,0,UCHAR_MAX); +} + +static unsigned short status_calc_mode(struct block_list *bl, struct status_change *sc, int mode) +{ + if(!sc || !sc->count) + return mode; + if(sc->data[SC_MODECHANGE]) { + if (sc->data[SC_MODECHANGE]->val2) + mode = sc->data[SC_MODECHANGE]->val2; //Set mode + if (sc->data[SC_MODECHANGE]->val3) + mode|= sc->data[SC_MODECHANGE]->val3; //Add mode + if (sc->data[SC_MODECHANGE]->val4) + mode&=~sc->data[SC_MODECHANGE]->val4; //Del mode + } + return cap_value(mode,0,USHRT_MAX); +} + +const char* status_get_name(struct block_list *bl) { + nullpo_ret(bl); + switch (bl->type) { + case BL_PC: return ((TBL_PC *)bl)->fakename[0] != '\0' ? ((TBL_PC*)bl)->fakename : ((TBL_PC*)bl)->status.name; + case BL_MOB: return ((TBL_MOB*)bl)->name; + case BL_PET: return ((TBL_PET*)bl)->pet.name; + case BL_HOM: return ((TBL_HOM*)bl)->homunculus.name; + case BL_NPC: return ((TBL_NPC*)bl)->name; + } + return "Unknown"; +} + +/*========================================== + * Get the class of the current bl + * return + * 0 = fail + * class_id = success + *------------------------------------------*/ +int status_get_class(struct block_list *bl) { + nullpo_ret(bl); + switch( bl->type ) { + case BL_PC: return ((TBL_PC*)bl)->status.class_; + case BL_MOB: return ((TBL_MOB*)bl)->vd->class_; //Class used on all code should be the view class of the mob. + case BL_PET: return ((TBL_PET*)bl)->pet.class_; + case BL_HOM: return ((TBL_HOM*)bl)->homunculus.class_; + case BL_MER: return ((TBL_MER*)bl)->mercenary.class_; + case BL_NPC: return ((TBL_NPC*)bl)->class_; + case BL_ELEM: return ((TBL_ELEM*)bl)->elemental.class_; + } + return 0; +} +/*========================================== + * Get the base level of the current bl + * return + * 1 = fail + * level = success + *------------------------------------------*/ +int status_get_lv(struct block_list *bl) { + nullpo_ret(bl); + switch (bl->type) { + case BL_PC: return ((TBL_PC*)bl)->status.base_level; + case BL_MOB: return ((TBL_MOB*)bl)->level; + case BL_PET: return ((TBL_PET*)bl)->pet.level; + case BL_HOM: return ((TBL_HOM*)bl)->homunculus.level; + case BL_MER: return ((TBL_MER*)bl)->db->lv; + case BL_ELEM: return ((TBL_ELEM*)bl)->db->lv; + case BL_NPC: return ((TBL_NPC*)bl)->level; + } + return 1; +} + +struct regen_data *status_get_regen_data(struct block_list *bl) +{ + nullpo_retr(NULL, bl); + switch (bl->type) { + case BL_PC: return &((TBL_PC*)bl)->regen; + case BL_HOM: return &((TBL_HOM*)bl)->regen; + case BL_MER: return &((TBL_MER*)bl)->regen; + case BL_ELEM: return &((TBL_ELEM*)bl)->regen; + default: + return NULL; + } +} + +struct status_data *status_get_status_data(struct block_list *bl) +{ + nullpo_retr(&dummy_status, bl); + + switch (bl->type) { + case BL_PC: return &((TBL_PC*)bl)->battle_status; + case BL_MOB: return &((TBL_MOB*)bl)->status; + case BL_PET: return &((TBL_PET*)bl)->status; + case BL_HOM: return &((TBL_HOM*)bl)->battle_status; + case BL_MER: return &((TBL_MER*)bl)->battle_status; + case BL_ELEM: return &((TBL_ELEM*)bl)->battle_status; + case BL_NPC: return &((TBL_NPC*)bl)->status; + default: + return &dummy_status; + } +} + +struct status_data *status_get_base_status(struct block_list *bl) +{ + nullpo_retr(NULL, bl); + switch (bl->type) { + case BL_PC: return &((TBL_PC*)bl)->base_status; + case BL_MOB: return ((TBL_MOB*)bl)->base_status ? ((TBL_MOB*)bl)->base_status : &((TBL_MOB*)bl)->db->status; + case BL_PET: return &((TBL_PET*)bl)->db->status; + case BL_HOM: return &((TBL_HOM*)bl)->base_status; + case BL_MER: return &((TBL_MER*)bl)->base_status; + case BL_ELEM: return &((TBL_ELEM*)bl)->base_status; + case BL_NPC: return &((TBL_NPC*)bl)->status; + default: + return NULL; + } +} +defType status_get_def(struct block_list *bl) { + struct unit_data *ud; + struct status_data *status = status_get_status_data(bl); + int def = status?status->def:0; + ud = unit_bl2ud(bl); + if (ud && ud->skilltimer != INVALID_TIMER) + def -= def * skill_get_castdef(ud->skill_id)/100; + + return cap_value(def, DEFTYPE_MIN, DEFTYPE_MAX); +} + +unsigned short status_get_speed(struct block_list *bl) +{ + return status_get_status_data(bl)->speed; +} + +int status_get_party_id(struct block_list *bl) { + nullpo_ret(bl); + switch (bl->type) { + case BL_PC: + return ((TBL_PC*)bl)->status.party_id; + case BL_PET: + if (((TBL_PET*)bl)->msd) + return ((TBL_PET*)bl)->msd->status.party_id; + break; + case BL_MOB: { + struct mob_data *md=(TBL_MOB*)bl; + if( md->master_id > 0 ) { + struct map_session_data *msd; + if (md->special_state.ai && (msd = map_id2sd(md->master_id)) != NULL) + return msd->status.party_id; + return -md->master_id; + } + } + break; + case BL_HOM: + if (((TBL_HOM*)bl)->master) + return ((TBL_HOM*)bl)->master->status.party_id; + break; + case BL_MER: + if (((TBL_MER*)bl)->master) + return ((TBL_MER*)bl)->master->status.party_id; + break; + case BL_SKILL: + return ((TBL_SKILL*)bl)->group->party_id; + case BL_ELEM: + if (((TBL_ELEM*)bl)->master) + return ((TBL_ELEM*)bl)->master->status.party_id; + break; + } + return 0; +} + +int status_get_guild_id(struct block_list *bl) { + nullpo_ret(bl); + switch (bl->type) { + case BL_PC: + return ((TBL_PC*)bl)->status.guild_id; + case BL_PET: + if (((TBL_PET*)bl)->msd) + return ((TBL_PET*)bl)->msd->status.guild_id; + break; + case BL_MOB: { + struct map_session_data *msd; + struct mob_data *md = (struct mob_data *)bl; + if (md->guardian_data) //Guardian's guild [Skotlex] + return md->guardian_data->guild_id; + if (md->special_state.ai && (msd = map_id2sd(md->master_id)) != NULL) + return msd->status.guild_id; //Alchemist's mobs [Skotlex] + } + break; + case BL_HOM: + if (((TBL_HOM*)bl)->master) + return ((TBL_HOM*)bl)->master->status.guild_id; + break; + case BL_MER: + if (((TBL_MER*)bl)->master) + return ((TBL_MER*)bl)->master->status.guild_id; + break; + case BL_NPC: + if (((TBL_NPC*)bl)->subtype == SCRIPT) + return ((TBL_NPC*)bl)->u.scr.guild_id; + break; + case BL_SKILL: + return ((TBL_SKILL*)bl)->group->guild_id; + case BL_ELEM: + if (((TBL_ELEM*)bl)->master) + return ((TBL_ELEM*)bl)->master->status.guild_id; + break; + } + return 0; +} + +int status_get_emblem_id(struct block_list *bl) { + nullpo_ret(bl); + switch (bl->type) { + case BL_PC: + return ((TBL_PC*)bl)->guild_emblem_id; + case BL_PET: + if (((TBL_PET*)bl)->msd) + return ((TBL_PET*)bl)->msd->guild_emblem_id; + break; + case BL_MOB: { + struct map_session_data *msd; + struct mob_data *md = (struct mob_data *)bl; + if (md->guardian_data) //Guardian's guild [Skotlex] + return md->guardian_data->emblem_id; + if (md->special_state.ai && (msd = map_id2sd(md->master_id)) != NULL) + return msd->guild_emblem_id; //Alchemist's mobs [Skotlex] + } + break; + case BL_HOM: + if (((TBL_HOM*)bl)->master) + return ((TBL_HOM*)bl)->master->guild_emblem_id; + break; + case BL_MER: + if (((TBL_MER*)bl)->master) + return ((TBL_MER*)bl)->master->guild_emblem_id; + break; + case BL_NPC: + if (((TBL_NPC*)bl)->subtype == SCRIPT && ((TBL_NPC*)bl)->u.scr.guild_id > 0) { + struct guild *g = guild_search(((TBL_NPC*)bl)->u.scr.guild_id); + if (g) + return g->emblem_id; + } + break; + case BL_ELEM: + if (((TBL_ELEM*)bl)->master) + return ((TBL_ELEM*)bl)->master->guild_emblem_id; + break; + } + return 0; +} + +int status_get_mexp(struct block_list *bl) +{ + nullpo_ret(bl); + if(bl->type==BL_MOB) + return ((struct mob_data *)bl)->db->mexp; + if(bl->type==BL_PET) + return ((struct pet_data *)bl)->db->mexp; + return 0; +} +int status_get_race2(struct block_list *bl) +{ + nullpo_ret(bl); + if(bl->type == BL_MOB) + return ((struct mob_data *)bl)->db->race2; + if(bl->type==BL_PET) + return ((struct pet_data *)bl)->db->race2; + return 0; +} + +int status_isdead(struct block_list *bl) +{ + nullpo_ret(bl); + return status_get_status_data(bl)->hp == 0; +} + +int status_isimmune(struct block_list *bl) +{ + struct status_change *sc =status_get_sc(bl); + if (sc && sc->data[SC_HERMODE]) + return 100; + + if (bl->type == BL_PC && + ((TBL_PC*)bl)->special_state.no_magic_damage >= battle_config.gtb_sc_immunity) + return ((TBL_PC*)bl)->special_state.no_magic_damage; + return 0; +} + +struct view_data* status_get_viewdata(struct block_list *bl) +{ + nullpo_retr(NULL, bl); + switch (bl->type) { + case BL_PC: return &((TBL_PC*)bl)->vd; + case BL_MOB: return ((TBL_MOB*)bl)->vd; + case BL_PET: return &((TBL_PET*)bl)->vd; + case BL_NPC: return ((TBL_NPC*)bl)->vd; + case BL_HOM: return ((TBL_HOM*)bl)->vd; + case BL_MER: return ((TBL_MER*)bl)->vd; + case BL_ELEM: return ((TBL_ELEM*)bl)->vd; + } + return NULL; +} + +void status_set_viewdata(struct block_list *bl, int class_) +{ + struct view_data* vd; + nullpo_retv(bl); + if (mobdb_checkid(class_) || mob_is_clone(class_)) + vd = mob_get_viewdata(class_); + else if (npcdb_checkid(class_) || (bl->type == BL_NPC && class_ == WARP_CLASS)) + vd = npc_get_viewdata(class_); + else if (homdb_checkid(class_)) + vd = merc_get_hom_viewdata(class_); + else if (merc_class(class_)) + vd = merc_get_viewdata(class_); + else if (elemental_class(class_)) + vd = elemental_get_viewdata(class_); + else + vd = NULL; + + switch (bl->type) { + case BL_PC: + { + TBL_PC* sd = (TBL_PC*)bl; + if (pcdb_checkid(class_)) { + if (sd->sc.option&OPTION_WEDDING) + class_ = JOB_WEDDING; + else if (sd->sc.option&OPTION_SUMMER) + class_ = JOB_SUMMER; + else if (sd->sc.option&OPTION_XMAS) + class_ = JOB_XMAS; + else if (sd->sc.option&OPTION_RIDING) { + switch (class_) { //Adapt class to a Mounted one. + case JOB_KNIGHT: + class_ = JOB_KNIGHT2; + break; + case JOB_CRUSADER: + class_ = JOB_CRUSADER2; + break; + case JOB_LORD_KNIGHT: + class_ = JOB_LORD_KNIGHT2; + break; + case JOB_PALADIN: + class_ = JOB_PALADIN2; + break; + case JOB_BABY_KNIGHT: + class_ = JOB_BABY_KNIGHT2; + break; + case JOB_BABY_CRUSADER: + class_ = JOB_BABY_CRUSADER2; + break; + } + } + sd->vd.class_ = class_; + clif_get_weapon_view(sd, &sd->vd.weapon, &sd->vd.shield); + sd->vd.head_top = sd->status.head_top; + sd->vd.head_mid = sd->status.head_mid; + sd->vd.head_bottom = sd->status.head_bottom; + sd->vd.hair_style = cap_value(sd->status.hair,0,battle_config.max_hair_style); + sd->vd.hair_color = cap_value(sd->status.hair_color,0,battle_config.max_hair_color); + sd->vd.cloth_color = cap_value(sd->status.clothes_color,0,battle_config.max_cloth_color); + sd->vd.sex = sd->status.sex; + } else if (vd) + memcpy(&sd->vd, vd, sizeof(struct view_data)); + else + ShowError("status_set_viewdata (PC): No view data for class %d\n", class_); + } + break; + case BL_MOB: + { + TBL_MOB* md = (TBL_MOB*)bl; + if (vd) + md->vd = vd; + else + ShowError("status_set_viewdata (MOB): No view data for class %d\n", class_); + } + break; + case BL_PET: + { + TBL_PET* pd = (TBL_PET*)bl; + if (vd) { + memcpy(&pd->vd, vd, sizeof(struct view_data)); + if (!pcdb_checkid(vd->class_)) { + pd->vd.hair_style = battle_config.pet_hair_style; + if(pd->pet.equip) { + pd->vd.head_bottom = itemdb_viewid(pd->pet.equip); + if (!pd->vd.head_bottom) + pd->vd.head_bottom = pd->pet.equip; + } + } + } else + ShowError("status_set_viewdata (PET): No view data for class %d\n", class_); + } + break; + case BL_NPC: + { + TBL_NPC* nd = (TBL_NPC*)bl; + if (vd) + nd->vd = vd; + else + ShowError("status_set_viewdata (NPC): No view data for class %d\n", class_); + } + break; + case BL_HOM: //[blackhole89] + { + struct homun_data *hd = (struct homun_data*)bl; + if (vd) + hd->vd = vd; + else + ShowError("status_set_viewdata (HOMUNCULUS): No view data for class %d\n", class_); + } + break; + case BL_MER: + { + struct mercenary_data *md = (struct mercenary_data*)bl; + if (vd) + md->vd = vd; + else + ShowError("status_set_viewdata (MERCENARY): No view data for class %d\n", class_); + } + break; + case BL_ELEM: + { + struct elemental_data *ed = (struct elemental_data*)bl; + if (vd) + ed->vd = vd; + else + ShowError("status_set_viewdata (ELEMENTAL): No view data for class %d\n", class_); + } + break; + } + vd = status_get_viewdata(bl); + if (vd && vd->cloth_color && ( + (vd->class_==JOB_WEDDING && battle_config.wedding_ignorepalette) + || (vd->class_==JOB_XMAS && battle_config.xmas_ignorepalette) + || (vd->class_==JOB_SUMMER && battle_config.summer_ignorepalette) + )) + vd->cloth_color = 0; +} + +/// Returns the status_change data of bl or NULL if it doesn't exist. +struct status_change *status_get_sc(struct block_list *bl) { + if( bl ) + switch (bl->type) { + case BL_PC: return &((TBL_PC*)bl)->sc; + case BL_MOB: return &((TBL_MOB*)bl)->sc; + case BL_NPC: return &((TBL_NPC*)bl)->sc; + case BL_HOM: return &((TBL_HOM*)bl)->sc; + case BL_MER: return &((TBL_MER*)bl)->sc; + case BL_ELEM: return &((TBL_ELEM*)bl)->sc; + } + return NULL; +} + +void status_change_init(struct block_list *bl) +{ + struct status_change *sc = status_get_sc(bl); + nullpo_retv(sc); + memset(sc, 0, sizeof (struct status_change)); +} + +//Applies SC defense to a given status change. +//Returns the adjusted duration based on flag values. +//the flag values are the same as in status_change_start. +int status_get_sc_def(struct block_list *bl, enum sc_type type, int rate, int tick, int flag) +{ + int sc_def = 0, tick_def = 0; + struct status_data* status; + struct status_change* sc; + struct map_session_data *sd; + + nullpo_ret(bl); + + //Status that are blocked by Golden Thief Bug card or Wand of Hermod + if (status_isimmune(bl)) + switch (type) { + case SC_DECREASEAGI: + case SC_SILENCE: + case SC_COMA: + case SC_INCREASEAGI: + case SC_BLESSING: + case SC_SLOWPOISON: + case SC_IMPOSITIO: + case SC_AETERNA: + case SC_SUFFRAGIUM: + case SC_BENEDICTIO: + case SC_PROVIDENCE: + case SC_KYRIE: + case SC_ASSUMPTIO: + case SC_ANGELUS: + case SC_MAGNIFICAT: + case SC_GLORIA: + case SC_WINDWALK: + case SC_MAGICROD: + case SC_HALLUCINATION: + case SC_STONE: + case SC_QUAGMIRE: + case SC_SUITON: + case SC_SWINGDANCE: + case SC__ENERVATION: + case SC__GROOMY: + case SC__IGNORANCE: + case SC__LAZINESS: + case SC__UNLUCKY: + case SC__WEAKNESS: + case SC__BLOODYLUST: + return 0; + } + + sd = BL_CAST(BL_PC,bl); + status = status_get_status_data(bl); + sc = status_get_sc(bl); + if( sc && !sc->count ) + sc = NULL; + switch (type) { + case SC_STUN: + case SC_POISON: + if( sc && sc->data[SC__UNLUCKY] ) + return tick; + case SC_DPOISON: + case SC_SILENCE: + case SC_BLEEDING: + sc_def = 3 +status->vit; + break; + case SC_SLEEP: + sc_def = 3 +status->int_; + break; + case SC_DEEPSLEEP: + tick_def = status->int_ / 10 + status_get_lv(bl) * 65 / 1000; // Seems to be -1 sec every 10 int and -5% chance every 10 int. + sc_def = 5 * status->int_ /10; + break; + case SC_DECREASEAGI: + case SC_ADORAMUS://Arch Bishop + if (sd) tick>>=1; //Half duration for players. + case SC_STONE: + case SC_FREEZE: + sc_def = 3 +status->mdef; + break; + case SC_CURSE: + //Special property: inmunity when luk is greater than level or zero + if (status->luk > status_get_lv(bl) || status->luk == 0) + return 0; + else + sc_def = 3 +status->luk; + tick_def = status->vit; + break; + case SC_BLIND: + if( sc && sc->data[SC__UNLUCKY] ) + return tick; + sc_def = 3 +(status->vit + status->int_)/2; + break; + case SC_CONFUSION: + sc_def = 3 +(status->str + status->int_)/2; + break; + case SC_ANKLE: + if(status->mode&MD_BOSS) // Lasts 5 times less on bosses + tick /= 5; + sc_def = status->agi / 2; + break; + case SC_MAGICMIRROR: + case SC_ARMORCHANGE: + if (sd) //Duration greatly reduced for players. + tick /= 15; + //No defense against it (buff). + rate -= (status_get_lv(bl) / 5 + status->vit / 4 + status->agi / 10)*100; // Lineal Reduction of Rate + break; + case SC_MARSHOFABYSS: + //5 second (Fixed) + 25 second - {( INT + LUK ) / 20 second } + tick -= (status->int_ + status->luk) / 20 * 1000; + break; + case SC_STASIS: + //5 second (fixed) + { Stasis Skill level * 5 - (Target�s VIT + DEX) / 20 } + tick -= (status->vit + status->dex) / 20 * 1000; + break; + case SC_WHITEIMPRISON: + if( tick == 5000 ) // 100% on caster + break; + if( bl->type == BL_PC ) + tick -= (status_get_lv(bl) / 5 + status->vit / 4 + status->agi / 10)*100; + else + tick -= (status->vit + status->luk) / 20 * 1000; + break; + case SC_BURNING: + // From iROwiki : http://forums.irowiki.org/showpost.php?p=577240&postcount=583 + tick -= 50*status->luk + 60*status->int_ + 170*status->vit; + tick = max(tick,10000); // Minimum Duration 10s. + break; + case SC_FREEZING: + tick -= 1000 * ((status->vit + status->dex) / 20); + tick = max(tick,10000); // Minimum Duration 10s. + break; + case SC_OBLIVIONCURSE: // 100% - (100 - 0.8 x INT) + sc_def = 100 - ( 100 - status->int_* 8 / 10 ); + sc_def = max(sc_def, 5); // minimum of 5% + break; + case SC_BITE: // {(Base Success chance) - (Target's AGI / 4)} + rate -= status->agi*1000/4; + rate = max(rate,50000); // minimum of 50% + break; + case SC_ELECTRICSHOCKER: + if( bl->type == BL_MOB ) + tick -= 1000 * (status->agi/10); + break; + case SC_CRYSTALIZE: + tick -= (1000*(status->vit/10))+(status_get_lv(bl)/50); + break; + case SC_MANDRAGORA: + sc_def = (status->vit+status->luk)/5; + break; + case SC_KYOUGAKU: + tick -= 30*status->int_; + break; + case SC_PARALYSIS: + tick -= 50 * (status->vit + status->luk); //(1000/20); + break; + default: + //Effect that cannot be reduced? Likely a buff. + if (!(rnd()%10000 < rate)) + return 0; + return tick?tick:1; + } + + if (sd) { + + if (battle_config.pc_sc_def_rate != 100) + sc_def = sc_def*battle_config.pc_sc_def_rate/100; + + if (sc_def < battle_config.pc_max_sc_def) + sc_def += (battle_config.pc_max_sc_def - sc_def)* + status->luk/battle_config.pc_luk_sc_def; + else + sc_def = battle_config.pc_max_sc_def; + + if (tick_def) { + if (battle_config.pc_sc_def_rate != 100) + tick_def = tick_def*battle_config.pc_sc_def_rate/100; + } + + } else { + + if (battle_config.mob_sc_def_rate != 100) + sc_def = sc_def*battle_config.mob_sc_def_rate/100; + + if (sc_def < battle_config.mob_max_sc_def) + sc_def += (battle_config.mob_max_sc_def - sc_def)* + status->luk/battle_config.mob_luk_sc_def; + else + sc_def = battle_config.mob_max_sc_def; + + if (tick_def) { + if (battle_config.mob_sc_def_rate != 100) + tick_def = tick_def*battle_config.mob_sc_def_rate/100; + } + } + + if (sc) { + if (sc->data[SC_SCRESIST]) + sc_def += sc->data[SC_SCRESIST]->val1; //Status resist + else if (sc->data[SC_SIEGFRIED]) + sc_def += sc->data[SC_SIEGFRIED]->val3; //Status resistance. + } + + //When no tick def, reduction is the same for both. + if( !tick_def && type != SC_STONE ) //Recent tests show duration of petrify isn't reduced by MDEF. [Inkfish] + tick_def = sc_def; + + //Natural resistance + if (!(flag&8)) { + rate -= rate*sc_def/100; + + //Item resistance (only applies to rate%) + if(sd && SC_COMMON_MIN <= type && type <= SC_COMMON_MAX) + { + if( sd->reseff[type-SC_COMMON_MIN] > 0 ) + rate -= rate*sd->reseff[type-SC_COMMON_MIN]/10000; + if( sd->sc.data[SC_COMMONSC_RESIST] ) + rate -= rate*sd->sc.data[SC_COMMONSC_RESIST]->val1/100; + } + } + if (!(rnd()%10000 < rate)) + return 0; + + //Why would a status start with no duration? Presume it has + //duration defined elsewhere. + if (!tick) return 1; + + //Rate reduction + if (flag&2) + return tick; + + tick -= tick*tick_def/100; + // Changed to 5 seconds according to recent tests [Playtester] + if (type == SC_ANKLE && tick < 5000) + tick = 5000; + return tick<=0?0:tick; +} + +/*========================================== + * Starts a status change. + * 'type' = type, 'val1~4' depend on the type. + * 'rate' = base success rate. 10000 = 100% + * 'tick' is base duration + * 'flag': + * &1: Cannot be avoided (it has to start) + * &2: Tick should not be reduced (by vit, luk, lv, etc) + * &4: sc_data loaded, no value has to be altered. + * &8: rate should not be reduced + *------------------------------------------*/ +int status_change_start(struct block_list* bl,enum sc_type type,int rate,int val1,int val2,int val3,int val4,int tick,int flag) +{ + struct map_session_data *sd = NULL; + struct status_change* sc; + struct status_change_entry* sce; + struct status_data *status; + struct view_data *vd; + int opt_flag, calc_flag, undead_flag, val_flag = 0, tick_time = 0; + bool sc_isnew = true; + + nullpo_ret(bl); + sc = status_get_sc(bl); + status = status_get_status_data(bl); + + if( type <= SC_NONE || type >= SC_MAX ) + { + ShowError("status_change_start: invalid status change (%d)!\n", type); + return 0; + } + + if( !sc ) + return 0; //Unable to receive status changes + + if( status_isdead(bl) && type != SC_NOCHAT ) // SC_NOCHAT should work even on dead characters + return 0; + + if( bl->type == BL_MOB) + { + struct mob_data *md = BL_CAST(BL_MOB,bl); + if(md && (md->class_ == MOBID_EMPERIUM || mob_is_battleground(md)) && type != SC_SAFETYWALL && type != SC_PNEUMA) + return 0; //Emperium/BG Monsters can't be afflicted by status changes + // if(md && mob_is_gvg(md) && status_sc2scb_flag(type)&SCB_MAXHP) + // return 0; //prevent status addinh hp to gvg mob (like bloodylust=hp*3 etc... + } + + if( sc->data[SC_REFRESH] ) { + if( type >= SC_COMMON_MIN && type <= SC_COMMON_MAX) // Confirmed. + return 0; // Immune to status ailements + switch( type ) { + case SC_QUAGMIRE://Tester said it protects against this and decrease agi. + case SC_DECREASEAGI: + case SC_BURNING: + case SC_FREEZING: + //case SC_WHITEIMPRISON://Need confirm. Protected against this in the past. [Rytech] + case SC_MARSHOFABYSS: + case SC_TOXIN: + case SC_PARALYSE: + case SC_VENOMBLEED: + case SC_MAGICMUSHROOM: + case SC_DEATHHURT: + case SC_PYREXIA: + case SC_OBLIVIONCURSE: + case SC_LEECHESEND: + case SC_CRYSTALIZE: ////08/31/2011 - Class Balance Changes + case SC_DEEPSLEEP: + case SC_MANDRAGORA: + return 0; + } + } + else if( sc->data[SC_INSPIRATION] ) { + if( type >= SC_COMMON_MIN && type <= SC_COMMON_MAX ) + return 0; // Immune to status ailements + switch( type ) { + case SC_DEEPSLEEP: + case SC_SATURDAYNIGHTFEVER: + case SC_PYREXIA: + case SC_DEATHHURT: + case SC_MAGICMUSHROOM: + case SC_VENOMBLEED: + case SC_TOXIN: + case SC_OBLIVIONCURSE: + case SC_LEECHESEND: + case SC__ENERVATION: + case SC__GROOMY: + case SC__LAZINESS: + case SC__UNLUCKY: + case SC__WEAKNESS: + case SC__BODYPAINT: + case SC__IGNORANCE: + return 0; + } + } + + sd = BL_CAST(BL_PC, bl); + + //Adjust tick according to status resistances + if( !(flag&(1|4)) ) + { + tick = status_get_sc_def(bl, type, rate, tick, flag); + if( !tick ) return 0; + } + + undead_flag = battle_check_undead(status->race,status->def_ele); + //Check for inmunities / sc fails + switch (type) { + case SC_ANGRIFFS_MODUS: + case SC_GOLDENE_FERSE: + if ((type==SC_GOLDENE_FERSE && sc->data[SC_ANGRIFFS_MODUS]) + || (type==SC_ANGRIFFS_MODUS && sc->data[SC_GOLDENE_FERSE]) + ) + return 0; + case SC_STONE: + if(sc->data[SC_POWER_OF_GAIA]) + return 0; + case SC_FREEZE: + //Undead are immune to Freeze/Stone + if (undead_flag && !(flag&1)) + return 0; + case SC_DEEPSLEEP: + case SC_SLEEP: + case SC_STUN: + case SC_FREEZING: + case SC_CRYSTALIZE: + if (sc->opt1) + return 0; //Cannot override other opt1 status changes. [Skotlex] + if((type == SC_FREEZE || type == SC_FREEZING || type == SC_CRYSTALIZE) && sc->data[SC_WARMER]) + return 0; //Immune to Frozen and Freezing status if under Warmer status. [Jobbie] + break; + + //There all like berserk, do not everlap each other + case SC__BLOODYLUST: + if(!sd) return 0; //should only affect player + case SC_BERSERK: + if (((type == SC_BERSERK) && (sc->data[SC_SATURDAYNIGHTFEVER] || sc->data[SC__BLOODYLUST])) + || ((type == SC__BLOODYLUST) && (sc->data[SC_SATURDAYNIGHTFEVER] || sc->data[SC_BERSERK])) + ) + return 0; + break; + + case SC_BURNING: + if(sc->opt1 || sc->data[SC_FREEZING]) + return 0; + break; + + case SC_SIGNUMCRUCIS: + //Only affects demons and undead element (but not players) + if((!undead_flag && status->race!=RC_DEMON) || bl->type == BL_PC) + return 0; + break; + case SC_AETERNA: + if( (sc->data[SC_STONE] && sc->opt1 == OPT1_STONE) || sc->data[SC_FREEZE] ) + return 0; + break; + case SC_KYRIE: + if (bl->type == BL_MOB) + return 0; + break; + case SC_OVERTHRUST: + if (sc->data[SC_MAXOVERTHRUST]) + return 0; //Overthrust can't take effect if under Max Overthrust. [Skotlex] + case SC_MAXOVERTHRUST: + if( sc->option&OPTION_MADOGEAR ) + return 0;//Overthrust and Overthrust Max cannot be used on Mado Gear [Ind] + break; + case SC_ADRENALINE: + if(sd && !pc_check_weapontype(sd,skill_get_weapontype(BS_ADRENALINE))) + return 0; + if (sc->data[SC_QUAGMIRE] || + sc->data[SC_DECREASEAGI] || + sc->option&OPTION_MADOGEAR //Adrenaline doesn't affect Mado Gear [Ind] + ) + return 0; + break; + case SC_ADRENALINE2: + if(sd && !pc_check_weapontype(sd,skill_get_weapontype(BS_ADRENALINE2))) + return 0; + if (sc->data[SC_QUAGMIRE] || + sc->data[SC_DECREASEAGI] + ) + return 0; + break; + case SC_MAGNIFICAT: + if( sc->option&OPTION_MADOGEAR ) //Mado is immune to magnificat + return 0; + break; + case SC_ONEHAND: + case SC_MERC_QUICKEN: + case SC_TWOHANDQUICKEN: + if(sc->data[SC_DECREASEAGI]) + return 0; + + case SC_INCREASEAGI: + if(sd && pc_issit(sd)){ + pc_setstand(sd); + } + + case SC_CONCENTRATE: + case SC_SPEARQUICKEN: + case SC_TRUESIGHT: + case SC_WINDWALK: + case SC_CARTBOOST: + case SC_ASSNCROS: + if (sc->data[SC_QUAGMIRE]) + return 0; + if(sc->option&OPTION_MADOGEAR) + return 0;//Mado is immune to increase agi, wind walk, cart boost, etc (others above) [Ind] + break; + case SC_CLOAKING: + //Avoid cloaking with no wall and low skill level. [Skotlex] + //Due to the cloaking card, we have to check the wall versus to known + //skill level rather than the used one. [Skotlex] + //if (sd && val1 < 3 && skill_check_cloaking(bl,NULL)) + if( sd && pc_checkskill(sd, AS_CLOAKING) < 3 && !skill_check_cloaking(bl,NULL) ) + return 0; + break; + case SC_MODECHANGE: + { + int mode; + struct status_data *bstatus = status_get_base_status(bl); + if (!bstatus) return 0; + if (sc->data[type]) + { //Pile up with previous values. + if(!val2) val2 = sc->data[type]->val2; + val3 |= sc->data[type]->val3; + val4 |= sc->data[type]->val4; + } + mode = val2?val2:bstatus->mode; //Base mode + if (val4) mode&=~val4; //Del mode + if (val3) mode|= val3; //Add mode + if (mode == bstatus->mode) { //No change. + if (sc->data[type]) //Abort previous status + return status_change_end(bl, type, INVALID_TIMER); + return 0; + } + } + break; + //Strip skills, need to divest something or it fails. + case SC_STRIPWEAPON: + if (sd && !(flag&4)) { //apply sc anyway if loading saved sc_data + int i; + opt_flag = 0; //Reuse to check success condition. + if(sd->bonus.unstripable_equip&EQP_WEAPON) + return 0; + i = sd->equip_index[EQI_HAND_L]; + if (i>=0 && sd->inventory_data[i] && sd->inventory_data[i]->type == IT_WEAPON) { + opt_flag|=1; + pc_unequipitem(sd,i,3); //L-hand weapon + } + + i = sd->equip_index[EQI_HAND_R]; + if (i>=0 && sd->inventory_data[i] && sd->inventory_data[i]->type == IT_WEAPON) { + opt_flag|=2; + pc_unequipitem(sd,i,3); + } + if (!opt_flag) return 0; + } + if (tick == 1) return 1; //Minimal duration: Only strip without causing the SC + break; + case SC_STRIPSHIELD: + if( val2 == 1 ) val2 = 0; //GX effect. Do not take shield off.. + else + if (sd && !(flag&4)) { + int i; + if(sd->bonus.unstripable_equip&EQP_SHIELD) + return 0; + i = sd->equip_index[EQI_HAND_L]; + if ( i < 0 || !sd->inventory_data[i] || sd->inventory_data[i]->type != IT_ARMOR ) + return 0; + pc_unequipitem(sd,i,3); + } + if (tick == 1) return 1; //Minimal duration: Only strip without causing the SC + break; + case SC_STRIPARMOR: + if (sd && !(flag&4)) { + int i; + if(sd->bonus.unstripable_equip&EQP_ARMOR) + return 0; + i = sd->equip_index[EQI_ARMOR]; + if ( i < 0 || !sd->inventory_data[i] ) + return 0; + pc_unequipitem(sd,i,3); + } + if (tick == 1) return 1; //Minimal duration: Only strip without causing the SC + break; + case SC_STRIPHELM: + if (sd && !(flag&4)) { + int i; + if(sd->bonus.unstripable_equip&EQP_HELM) + return 0; + i = sd->equip_index[EQI_HEAD_TOP]; + if ( i < 0 || !sd->inventory_data[i] ) + return 0; + pc_unequipitem(sd,i,3); + } + if (tick == 1) return 1; //Minimal duration: Only strip without causing the SC + break; + case SC_MERC_FLEEUP: + case SC_MERC_ATKUP: + case SC_MERC_HPUP: + case SC_MERC_SPUP: + case SC_MERC_HITUP: + if( bl->type != BL_MER ) + return 0; // Stats only for Mercenaries + break; + case SC_STRFOOD: + if (sc->data[SC_FOOD_STR_CASH] && sc->data[SC_FOOD_STR_CASH]->val1 > val1) + return 0; + break; + case SC_AGIFOOD: + if (sc->data[SC_FOOD_AGI_CASH] && sc->data[SC_FOOD_AGI_CASH]->val1 > val1) + return 0; + break; + case SC_VITFOOD: + if (sc->data[SC_FOOD_VIT_CASH] && sc->data[SC_FOOD_VIT_CASH]->val1 > val1) + return 0; + break; + case SC_INTFOOD: + if (sc->data[SC_FOOD_INT_CASH] && sc->data[SC_FOOD_INT_CASH]->val1 > val1) + return 0; + break; + case SC_DEXFOOD: + if (sc->data[SC_FOOD_DEX_CASH] && sc->data[SC_FOOD_DEX_CASH]->val1 > val1) + return 0; + break; + case SC_LUKFOOD: + if (sc->data[SC_FOOD_LUK_CASH] && sc->data[SC_FOOD_LUK_CASH]->val1 > val1) + return 0; + break; + case SC_FOOD_STR_CASH: + if (sc->data[SC_STRFOOD] && sc->data[SC_STRFOOD]->val1 > val1) + return 0; + break; + case SC_FOOD_AGI_CASH: + if (sc->data[SC_AGIFOOD] && sc->data[SC_AGIFOOD]->val1 > val1) + return 0; + break; + case SC_FOOD_VIT_CASH: + if (sc->data[SC_VITFOOD] && sc->data[SC_VITFOOD]->val1 > val1) + return 0; + break; + case SC_FOOD_INT_CASH: + if (sc->data[SC_INTFOOD] && sc->data[SC_INTFOOD]->val1 > val1) + return 0; + break; + case SC_FOOD_DEX_CASH: + if (sc->data[SC_DEXFOOD] && sc->data[SC_DEXFOOD]->val1 > val1) + return 0; + break; + case SC_FOOD_LUK_CASH: + if (sc->data[SC_LUKFOOD] && sc->data[SC_LUKFOOD]->val1 > val1) + return 0; + break; + case SC_CAMOUFLAGE: + if( sd && pc_checkskill(sd, RA_CAMOUFLAGE) < 3 && !skill_check_camouflage(bl,NULL) ) + return 0; + break; + case SC__STRIPACCESSORY: + if( sd ) { + int i = -1; + if( !(sd->bonus.unstripable_equip&EQI_ACC_L) ) { + i = sd->equip_index[EQI_ACC_L]; + if( i >= 0 && sd->inventory_data[i] && sd->inventory_data[i]->type == IT_ARMOR ) + pc_unequipitem(sd,i,3); //L-Accessory + } if( !(sd->bonus.unstripable_equip&EQI_ACC_R) ) { + i = sd->equip_index[EQI_ACC_R]; + if( i >= 0 && sd->inventory_data[i] && sd->inventory_data[i]->type == IT_ARMOR ) + pc_unequipitem(sd,i,3); //R-Accessory + } + if( i < 0 ) + return 0; + } + if (tick == 1) return 1; //Minimal duration: Only strip without causing the SC + break; + case SC_TOXIN: + case SC_PARALYSE: + case SC_VENOMBLEED: + case SC_MAGICMUSHROOM: + case SC_DEATHHURT: + case SC_PYREXIA: + case SC_OBLIVIONCURSE: + case SC_LEECHESEND: + { // it doesn't stack or even renewed + int i = SC_TOXIN; + for(; i<= SC_LEECHESEND; i++) + if(sc->data[i]) return 0; + } + break; + case SC_SATURDAYNIGHTFEVER: + if (sc->data[SC_BERSERK] || sc->data[SC_INSPIRATION] || sc->data[SC__BLOODYLUST]) + return 0; + break; + } + + //Check for BOSS resistances + if(status->mode&MD_BOSS && !(flag&1)) { + if (type>=SC_COMMON_MIN && type <= SC_COMMON_MAX) + return 0; + switch (type) { + case SC_BLESSING: + case SC_DECREASEAGI: + case SC_PROVOKE: + case SC_COMA: + case SC_GRAVITATION: + case SC_SUITON: + case SC_RICHMANKIM: + case SC_ROKISWEIL: + case SC_FOGWALL: + case SC_FREEZING: + case SC_BURNING: + case SC_MARSHOFABYSS: + case SC_ADORAMUS: + case SC_PARALYSIS: + case SC_DEEPSLEEP: + case SC_CRYSTALIZE: + + // Exploit prevention - kRO Fix + case SC_PYREXIA: + case SC_DEATHHURT: + case SC_TOXIN: + case SC_PARALYSE: + case SC_VENOMBLEED: + case SC_MAGICMUSHROOM: + case SC_OBLIVIONCURSE: + case SC_LEECHESEND: + + // Ranger Effects + case SC_BITE: + case SC_ELECTRICSHOCKER: + case SC_MAGNETICFIELD: + + return 0; + } + } + + //Before overlapping fail, one must check for status cured. + switch (type) { + case SC_BLESSING: + //TO-DO Blessing and Agi up should do 1 damage against players on Undead Status, even on PvM + //but cannot be plagiarized (this requires aegis investigation on packets and official behavior) [Brainstorm] + if ((!undead_flag && status->race!=RC_DEMON) || bl->type == BL_PC) { + status_change_end(bl, SC_CURSE, INVALID_TIMER); + if (sc->data[SC_STONE] && sc->opt1 == OPT1_STONE) + status_change_end(bl, SC_STONE, INVALID_TIMER); + } + break; + case SC_INCREASEAGI: + status_change_end(bl, SC_DECREASEAGI, INVALID_TIMER); + break; + case SC_QUAGMIRE: + status_change_end(bl, SC_CONCENTRATE, INVALID_TIMER); + status_change_end(bl, SC_TRUESIGHT, INVALID_TIMER); + status_change_end(bl, SC_WINDWALK, INVALID_TIMER); + //Also blocks the ones below... + case SC_DECREASEAGI: + status_change_end(bl, SC_CARTBOOST, INVALID_TIMER); + //Also blocks the ones below... + case SC_DONTFORGETME: + status_change_end(bl, SC_INCREASEAGI, INVALID_TIMER); + status_change_end(bl, SC_ADRENALINE, INVALID_TIMER); + status_change_end(bl, SC_ADRENALINE2, INVALID_TIMER); + status_change_end(bl, SC_SPEARQUICKEN, INVALID_TIMER); + status_change_end(bl, SC_TWOHANDQUICKEN, INVALID_TIMER); + status_change_end(bl, SC_ONEHAND, INVALID_TIMER); + status_change_end(bl, SC_MERC_QUICKEN, INVALID_TIMER); + status_change_end(bl, SC_ACCELERATION, INVALID_TIMER); + break; + case SC_ONEHAND: + //Removes the Aspd potion effect, as reported by Vicious. [Skotlex] + status_change_end(bl, SC_ASPDPOTION0, INVALID_TIMER); + status_change_end(bl, SC_ASPDPOTION1, INVALID_TIMER); + status_change_end(bl, SC_ASPDPOTION2, INVALID_TIMER); + status_change_end(bl, SC_ASPDPOTION3, INVALID_TIMER); + break; + case SC_MAXOVERTHRUST: + //Cancels Normal Overthrust. [Skotlex] + status_change_end(bl, SC_OVERTHRUST, INVALID_TIMER); + break; + case SC_KYRIE: + //Cancels Assumptio + status_change_end(bl, SC_ASSUMPTIO, INVALID_TIMER); + break; + case SC_DELUGE: + if (sc->data[SC_FOGWALL] && sc->data[SC_BLIND]) + status_change_end(bl, SC_BLIND, INVALID_TIMER); + break; + case SC_SILENCE: + if (sc->data[SC_GOSPEL] && sc->data[SC_GOSPEL]->val4 == BCT_SELF) + status_change_end(bl, SC_GOSPEL, INVALID_TIMER); + break; + case SC_HIDING: + status_change_end(bl, SC_CLOSECONFINE, INVALID_TIMER); + status_change_end(bl, SC_CLOSECONFINE2, INVALID_TIMER); + break; + case SC__BLOODYLUST: + case SC_BERSERK: + if(battle_config.berserk_cancels_buffs) { + status_change_end(bl, SC_ONEHAND, INVALID_TIMER); + status_change_end(bl, SC_TWOHANDQUICKEN, INVALID_TIMER); + status_change_end(bl, SC_CONCENTRATION, INVALID_TIMER); + status_change_end(bl, SC_PARRYING, INVALID_TIMER); + status_change_end(bl, SC_AURABLADE, INVALID_TIMER); + status_change_end(bl, SC_MERC_QUICKEN, INVALID_TIMER); + } +#ifdef RENEWAL + else { + status_change_end(bl, SC_TWOHANDQUICKEN, INVALID_TIMER); + } +#endif + break; + case SC_ASSUMPTIO: + status_change_end(bl, SC_KYRIE, INVALID_TIMER); + status_change_end(bl, SC_KAITE, INVALID_TIMER); + break; + case SC_KAITE: + status_change_end(bl, SC_ASSUMPTIO, INVALID_TIMER); + break; + case SC_CARTBOOST: + if(sc->data[SC_DECREASEAGI]) + { //Cancel Decrease Agi, but take no further effect [Skotlex] + status_change_end(bl, SC_DECREASEAGI, INVALID_TIMER); + return 0; + } + break; + case SC_FUSION: + status_change_end(bl, SC_SPIRIT, INVALID_TIMER); + break; + case SC_ADJUSTMENT: + status_change_end(bl, SC_MADNESSCANCEL, INVALID_TIMER); + break; + case SC_MADNESSCANCEL: + status_change_end(bl, SC_ADJUSTMENT, INVALID_TIMER); + break; + //NPC_CHANGEUNDEAD will debuff Blessing and Agi Up + case SC_CHANGEUNDEAD: + status_change_end(bl, SC_BLESSING, INVALID_TIMER); + status_change_end(bl, SC_INCREASEAGI, INVALID_TIMER); + break; + case SC_STRFOOD: + status_change_end(bl, SC_FOOD_STR_CASH, INVALID_TIMER); + break; + case SC_AGIFOOD: + status_change_end(bl, SC_FOOD_AGI_CASH, INVALID_TIMER); + break; + case SC_VITFOOD: + status_change_end(bl, SC_FOOD_VIT_CASH, INVALID_TIMER); + break; + case SC_INTFOOD: + status_change_end(bl, SC_FOOD_INT_CASH, INVALID_TIMER); + break; + case SC_DEXFOOD: + status_change_end(bl, SC_FOOD_DEX_CASH, INVALID_TIMER); + break; + case SC_LUKFOOD: + status_change_end(bl, SC_FOOD_LUK_CASH, INVALID_TIMER); + break; + case SC_FOOD_STR_CASH: + status_change_end(bl, SC_STRFOOD, INVALID_TIMER); + break; + case SC_FOOD_AGI_CASH: + status_change_end(bl, SC_AGIFOOD, INVALID_TIMER); + break; + case SC_FOOD_VIT_CASH: + status_change_end(bl, SC_VITFOOD, INVALID_TIMER); + break; + case SC_FOOD_INT_CASH: + status_change_end(bl, SC_INTFOOD, INVALID_TIMER); + break; + case SC_FOOD_DEX_CASH: + status_change_end(bl, SC_DEXFOOD, INVALID_TIMER); + break; + case SC_FOOD_LUK_CASH: + status_change_end(bl, SC_LUKFOOD, INVALID_TIMER); + break; + case SC_FIGHTINGSPIRIT: + status_change_end(bl, type, INVALID_TIMER); // Remove previous one. + break; + case SC_MARSHOFABYSS: + status_change_end(bl, SC_INCAGI, INVALID_TIMER); + status_change_end(bl, SC_WINDWALK, INVALID_TIMER); + status_change_end(bl, SC_ASPDPOTION0, INVALID_TIMER); + status_change_end(bl, SC_ASPDPOTION1, INVALID_TIMER); + status_change_end(bl, SC_ASPDPOTION2, INVALID_TIMER); + status_change_end(bl, SC_ASPDPOTION3, INVALID_TIMER); + break; + case SC_SWINGDANCE: + case SC_SYMPHONYOFLOVER: + case SC_MOONLITSERENADE: + case SC_RUSHWINDMILL: + case SC_ECHOSONG: + case SC_HARMONIZE: //group A doesn't overlap + if (type != SC_SWINGDANCE) status_change_end(bl, SC_SWINGDANCE, INVALID_TIMER); + if (type != SC_SYMPHONYOFLOVER) status_change_end(bl, SC_SYMPHONYOFLOVER, INVALID_TIMER); + if (type != SC_MOONLITSERENADE) status_change_end(bl, SC_MOONLITSERENADE, INVALID_TIMER); + if (type != SC_RUSHWINDMILL) status_change_end(bl, SC_RUSHWINDMILL, INVALID_TIMER); + if (type != SC_ECHOSONG) status_change_end(bl, SC_ECHOSONG, INVALID_TIMER); + if (type != SC_HARMONIZE) status_change_end(bl, SC_HARMONIZE, INVALID_TIMER); + break; + case SC_VOICEOFSIREN: + case SC_DEEPSLEEP: + case SC_GLOOMYDAY: + case SC_SONGOFMANA: + case SC_DANCEWITHWUG: + case SC_SATURDAYNIGHTFEVER: + case SC_LERADSDEW: + case SC_MELODYOFSINK: + case SC_BEYONDOFWARCRY: + case SC_UNLIMITEDHUMMINGVOICE: //group B + if (type != SC_VOICEOFSIREN) status_change_end(bl, SC_VOICEOFSIREN, INVALID_TIMER); + if (type != SC_DEEPSLEEP) status_change_end(bl, SC_DEEPSLEEP, INVALID_TIMER); + if (type != SC_LERADSDEW) status_change_end(bl, SC_LERADSDEW, INVALID_TIMER); + if (type != SC_MELODYOFSINK) status_change_end(bl, SC_MELODYOFSINK, INVALID_TIMER); + if (type != SC_BEYONDOFWARCRY) status_change_end(bl, SC_BEYONDOFWARCRY, INVALID_TIMER); + if (type != SC_UNLIMITEDHUMMINGVOICE) status_change_end(bl, SC_UNLIMITEDHUMMINGVOICE, INVALID_TIMER); + if (type != SC_GLOOMYDAY) { + status_change_end(bl, SC_GLOOMYDAY, INVALID_TIMER); + status_change_end(bl, SC_GLOOMYDAY_SK, INVALID_TIMER); + } + if (type != SC_SONGOFMANA) status_change_end(bl, SC_SONGOFMANA, INVALID_TIMER); + if (type != SC_DANCEWITHWUG) status_change_end(bl, SC_DANCEWITHWUG, INVALID_TIMER); + if (type != SC_SATURDAYNIGHTFEVER) { + if (sc->data[SC_SATURDAYNIGHTFEVER]) { + sc->data[SC_SATURDAYNIGHTFEVER]->val2 = 0; //mark to not lose hp + status_change_end(bl, SC_SATURDAYNIGHTFEVER, INVALID_TIMER); + } + } + break; + case SC_REFLECTSHIELD: + status_change_end(bl, SC_REFLECTDAMAGE, INVALID_TIMER); + break; + case SC_REFLECTDAMAGE: + status_change_end(bl, SC_REFLECTSHIELD, INVALID_TIMER); + break; + case SC_SHIELDSPELL_DEF: + case SC_SHIELDSPELL_MDEF: + case SC_SHIELDSPELL_REF: + status_change_end(bl, SC_MAGNIFICAT, INVALID_TIMER); + if( type != SC_SHIELDSPELL_DEF ) + status_change_end(bl, SC_SHIELDSPELL_DEF, INVALID_TIMER); + if( type != SC_SHIELDSPELL_MDEF ) + status_change_end(bl, SC_SHIELDSPELL_MDEF, INVALID_TIMER); + if( type != SC_SHIELDSPELL_REF ) + status_change_end(bl, SC_SHIELDSPELL_REF, INVALID_TIMER); + break; + case SC_GT_ENERGYGAIN: + case SC_GT_CHANGE: + case SC_GT_REVITALIZE: + if( type != SC_GT_REVITALIZE ) + status_change_end(bl, SC_GT_REVITALIZE, INVALID_TIMER); + if( type != SC_GT_ENERGYGAIN ) + status_change_end(bl, SC_GT_ENERGYGAIN, INVALID_TIMER); + if( type != SC_GT_CHANGE ) + status_change_end(bl, SC_GT_CHANGE, INVALID_TIMER); + break; + case SC_INVINCIBLE: + status_change_end(bl, SC_INVINCIBLEOFF, INVALID_TIMER); + break; + case SC_INVINCIBLEOFF: + status_change_end(bl, SC_INVINCIBLE, INVALID_TIMER); + break; + case SC_MAGICPOWER: + status_change_end(bl, type, INVALID_TIMER); + break; + } + + //Check for overlapping fails + if( (sce = sc->data[type]) ) { + switch( type ) { + case SC_MERC_FLEEUP: + case SC_MERC_ATKUP: + case SC_MERC_HPUP: + case SC_MERC_SPUP: + case SC_MERC_HITUP: + if( sce->val1 > val1 ) + val1 = sce->val1; + break; + case SC_ADRENALINE: + case SC_ADRENALINE2: + case SC_WEAPONPERFECTION: + case SC_OVERTHRUST: + if (sce->val2 > val2) + return 0; + break; + case SC_S_LIFEPOTION: + case SC_L_LIFEPOTION: + case SC_BOSSMAPINFO: + case SC_STUN: + case SC_SLEEP: + case SC_POISON: + case SC_CURSE: + case SC_SILENCE: + case SC_CONFUSION: + case SC_BLIND: + case SC_BLEEDING: + case SC_DPOISON: + case SC_CLOSECONFINE2: //Can't be re-closed in. + case SC_MARIONETTE: + case SC_MARIONETTE2: + case SC_NOCHAT: + case SC_CHANGE: //Otherwise your Hp/Sp would get refilled while still within effect of the last invocation. + case SC__INVISIBILITY: + case SC__ENERVATION: + case SC__GROOMY: + case SC__IGNORANCE: + case SC__LAZINESS: + case SC__WEAKNESS: + case SC__UNLUCKY: + return 0; + case SC_COMBO: + case SC_DANCING: + case SC_DEVOTION: + case SC_ASPDPOTION0: + case SC_ASPDPOTION1: + case SC_ASPDPOTION2: + case SC_ASPDPOTION3: + case SC_ATKPOTION: + case SC_MATKPOTION: + case SC_ENCHANTARMS: + case SC_ARMOR_ELEMENT: + case SC_ARMOR_RESIST: + break; + case SC_GOSPEL: + //Must not override a casting gospel char. + if(sce->val4 == BCT_SELF) + return 0; + if(sce->val1 > val1) + return 1; + break; + case SC_ENDURE: + if(sce->val4 && !val4) + return 1; //Don't let you override infinite endure. + if(sce->val1 > val1) + return 1; + break; + case SC_KAAHI: + //Kaahi overwrites previous level regardless of existing level. + //Delete timer if it exists. + if (sce->val4 != INVALID_TIMER) { + delete_timer(sce->val4,kaahi_heal_timer); + sce->val4 = INVALID_TIMER; + } + break; + case SC_JAILED: + //When a player is already jailed, do not edit the jail data. + val2 = sce->val2; + val3 = sce->val3; + val4 = sce->val4; + break; + case SC_LERADSDEW: + if (sc && (sc->data[SC_BERSERK] || sc->data[SC__BLOODYLUST])) + return 0; + case SC_SHAPESHIFT: + case SC_PROPERTYWALK: + break; + case SC_LEADERSHIP: + case SC_GLORYWOUNDS: + case SC_SOULCOLD: + case SC_HAWKEYES: + if( sce->val4 && !val4 )//you cannot override master guild aura + return 0; + break; + case SC_JOINTBEAT: + val2 |= sce->val2; // stackable ailments + default: + if(sce->val1 > val1) + return 1; //Return true to not mess up skill animations. [Skotlex] + } + } + + vd = status_get_viewdata(bl); + calc_flag = StatusChangeFlagTable[type]; + if(!(flag&4)) //&4 - Do not parse val settings when loading SCs + switch(type) + { + case SC_DECREASEAGI: + case SC_INCREASEAGI: + val2 = 2 + val1; //Agi change + break; + case SC_ENDURE: + val2 = 7; // Hit-count [Celest] + if( !(flag&1) && (bl->type&(BL_PC|BL_MER)) && !map_flag_gvg(bl->m) && !map[bl->m].flag.battleground && !val4 ) + { + struct map_session_data *tsd; + if( sd ) + { + int i; + for( i = 0; i < 5; i++ ) + { + if( sd->devotion[i] && (tsd = map_id2sd(sd->devotion[i])) ) + status_change_start(&tsd->bl, type, 10000, val1, val2, val3, val4, tick, 1); + } + } + else if( bl->type == BL_MER && ((TBL_MER*)bl)->devotion_flag && (tsd = ((TBL_MER*)bl)->master) ) + status_change_start(&tsd->bl, type, 10000, val1, val2, val3, val4, tick, 1); + } + //val4 signals infinite endure (if val4 == 2 it is infinite endure from Berserk) + if( val4 ) + tick = -1; + break; + case SC_AUTOBERSERK: + if (status->hp < status->max_hp>>2 && + (!sc->data[SC_PROVOKE] || sc->data[SC_PROVOKE]->val2==0)) + sc_start4(bl,SC_PROVOKE,100,10,1,0,0,60000); + tick = -1; + break; + case SC_SIGNUMCRUCIS: + val2 = 10 + 4*val1; //Def reduction + tick = -1; + clif_emotion(bl,E_SWT); + break; + case SC_MAXIMIZEPOWER: + tick_time = val2 = tick>0?tick:60000; + tick = -1; // duration sent to the client should be infinite + break; + case SC_EDP: // [Celest] + val2 = val1 + 2; //Chance to Poison enemies. +#ifndef RENEWAL_EDP + val3 = 50*(val1+1); //Damage increase (+50 +50*lv%) +#endif + if( sd )//[Ind] - iROwiki says each level increases its duration by 3 seconds + tick += pc_checkskill(sd,GC_RESEARCHNEWPOISON)*3000; + break; + case SC_POISONREACT: + val2=(val1+1)/2 + val1/10; // Number of counters [Skotlex] + val3=50; // + 5*val1; //Chance to counter. [Skotlex] + break; + case SC_MAGICROD: + val2 = val1*20; //SP gained + break; + case SC_KYRIE: + val2 = (int64)status->max_hp * (val1 * 2 + 10) / 100; //%Max HP to absorb + val3 = (val1 / 2 + 5); //Hits + break; + case SC_MAGICPOWER: + //val1: Skill lv + val2 = 1; //Lasts 1 invocation + val3 = 5*val1; //Matk% increase + val4 = 0; // 0 = ready to be used, 1 = activated and running + break; + case SC_SACRIFICE: + val2 = 5; //Lasts 5 hits + tick = -1; + break; + case SC_ENCPOISON: + val2= 250+50*val1; //Poisoning Chance (2.5+0.5%) in 1/10000 rate + case SC_ASPERSIO: + case SC_FIREWEAPON: + case SC_WATERWEAPON: + case SC_WINDWEAPON: + case SC_EARTHWEAPON: + case SC_SHADOWWEAPON: + case SC_GHOSTWEAPON: + skill_enchant_elemental_end(bl,type); + break; + case SC_ELEMENTALCHANGE: + // val1 : Element Lvl (if called by skill lvl 1, takes random value between 1 and 4) + // val2 : Element (When no element, random one is picked) + // val3 : 0 = called by skill 1 = called by script (fixed level) + if( !val2 ) val2 = rnd()%ELE_MAX; + + if( val1 == 1 && val3 == 0 ) + val1 = 1 + rnd()%4; + else if( val1 > 4 ) + val1 = 4; // Max Level + val3 = 0; // Not need to keep this info. + break; + case SC_PROVIDENCE: + val2=val1*5; //Race/Ele resist + break; + case SC_REFLECTSHIELD: + val2=10+val1*3; // %Dmg reflected + if( !(flag&1) && (bl->type&(BL_PC|BL_MER)) ) + { + struct map_session_data *tsd; + if( sd ) + { + int i; + for( i = 0; i < 5; i++ ) + { + if( sd->devotion[i] && (tsd = map_id2sd(sd->devotion[i])) ) + status_change_start(&tsd->bl, type, 10000, val1, val2, 0, 0, tick, 1); + } + } + else if( bl->type == BL_MER && ((TBL_MER*)bl)->devotion_flag && (tsd = ((TBL_MER*)bl)->master) ) + status_change_start(&tsd->bl, type, 10000, val1, val2, 0, 0, tick, 1); + } + break; + case SC_STRIPWEAPON: + if (!sd) //Watk reduction + val2 = 25; + break; + case SC_STRIPSHIELD: + if (!sd) //Def reduction + val2 = 15; + break; + case SC_STRIPARMOR: + if (!sd) //Vit reduction + val2 = 40; + break; + case SC_STRIPHELM: + if (!sd) //Int reduction + val2 = 40; + break; + case SC_AUTOSPELL: + //Val1 Skill LV of Autospell + //Val2 Skill ID to cast + //Val3 Max Lv to cast + val4 = 5 + val1*2; //Chance of casting + break; + case SC_VOLCANO: + val2 = val1*10; //Watk increase +#ifndef RENEWAL + if (status->def_ele != ELE_FIRE) + val2 = 0; +#endif + break; + case SC_VIOLENTGALE: + val2 = val1*3; //Flee increase + #ifndef RENEWAL + if (status->def_ele != ELE_WIND) + val2 = 0; + #endif + break; + case SC_DELUGE: + val2 = deluge_eff[val1-1]; //HP increase +#ifndef RENEWAL + if(status->def_ele != ELE_WATER) + val2 = 0; +#endif + break; + case SC_SUITON: + if (!val2 || (sd && (sd->class_&MAPID_UPPERMASK) == MAPID_NINJA)) { + //No penalties. + val2 = 0; //Agi penalty + val3 = 0; //Walk speed penalty + break; + } + val3 = 50; + val2 = 3*((val1+1)/3); + if (val1 > 4) val2--; + break; + case SC_ONEHAND: + case SC_TWOHANDQUICKEN: + val2 = 300; + if (val1 > 10) //For boss casted skills [Skotlex] + val2 += 20*(val1-10); + break; + case SC_MERC_QUICKEN: + val2 = 300; + break; +#ifndef RENEWAL + case SC_SPEARQUICKEN: + val2 = 200+10*val1; + break; +#endif + case SC_DANCING: + //val1 : Skill ID + LV + //val2 : Skill Group of the Dance. + //val3 : Brings the skill_lv (merged into val1 here) + //val4 : Partner + if (val1 == CG_MOONLIT) + clif_status_change(bl,SI_MOONLIT,1,tick,0, 0, 0); + val1|= (val3<<16); + val3 = tick/1000; //Tick duration + tick_time = 1000; // [GodLesZ] tick time + break; + case SC_LONGING: + val2 = 500-100*val1; //Aspd penalty. + break; + case SC_EXPLOSIONSPIRITS: + val2 = 75 + 25*val1; //Cri bonus + break; + + case SC_ASPDPOTION0: + case SC_ASPDPOTION1: + case SC_ASPDPOTION2: + case SC_ASPDPOTION3: + val2 = 50*(2+type-SC_ASPDPOTION0); + break; + + case SC_WEDDING: + case SC_XMAS: + case SC_SUMMER: + if (!vd) return 0; + //Store previous values as they could be removed. + val1 = vd->class_; + val2 = vd->weapon; + val3 = vd->shield; + val4 = vd->cloth_color; + unit_stop_attack(bl); + clif_changelook(bl,LOOK_WEAPON,0); + clif_changelook(bl,LOOK_SHIELD,0); + clif_changelook(bl,LOOK_BASE,type==SC_WEDDING?JOB_WEDDING:type==SC_XMAS?JOB_XMAS:JOB_SUMMER); + clif_changelook(bl,LOOK_CLOTHES_COLOR,vd->cloth_color); + break; + case SC_NOCHAT: + // [GodLesZ] FIXME: is this correct? a hardcoded interval of 60sec? what about configuration ?_? + tick = 60000; + val1 = battle_config.manner_system; //Mute filters. + if (sd) + { + clif_changestatus(sd,SP_MANNER,sd->status.manner); + clif_updatestatus(sd,SP_MANNER); + } + break; + + case SC_STONE: + val3 = tick/1000; //Petrified HP-damage iterations. + if(val3 < 1) val3 = 1; + tick = val4; //Petrifying time. + tick = max(tick, 1000); //Min time + calc_flag = 0; //Actual status changes take effect on petrified state. + break; + + case SC_DPOISON: + //Lose 10/15% of your life as long as it doesn't brings life below 25% + if (status->hp > status->max_hp>>2) { + int diff = status->max_hp*(bl->type==BL_PC?10:15)/100; + if (status->hp - diff < status->max_hp>>2) + diff = status->hp - (status->max_hp>>2); + if( val2 && bl->type == BL_MOB ) { + struct block_list* src = map_id2bl(val2); + if( src ) + mob_log_damage((TBL_MOB*)bl,src,diff); + } + status_zap(bl, diff, 0); + } + // fall through + case SC_POISON: + val3 = tick/1000; //Damage iterations + if(val3 < 1) val3 = 1; + tick_time = 1000; // [GodLesZ] tick time + //val4: HP damage + if (bl->type == BL_PC) + val4 = (type == SC_DPOISON) ? 3 + status->max_hp/50 : 3 + status->max_hp*3/200; + else + val4 = (type == SC_DPOISON) ? 3 + status->max_hp/100 : 3 + status->max_hp/200; + + break; + case SC_CONFUSION: + clif_emotion(bl,E_WHAT); + break; + case SC_BLEEDING: + val4 = tick/10000; + if (!val4) val4 = 1; + tick_time = 10000; // [GodLesZ] tick time + break; + case SC_S_LIFEPOTION: + case SC_L_LIFEPOTION: + if( val1 == 0 ) return 0; + // val1 = heal percent/amout + // val2 = seconds between heals + // val4 = total of heals + if( val2 < 1 ) val2 = 1; + if( (val4 = tick/(val2 * 1000)) < 1 ) + val4 = 1; + tick_time = val2 * 1000; // [GodLesZ] tick time + break; + case SC_BOSSMAPINFO: + if( sd != NULL ) + { + struct mob_data *boss_md = map_getmob_boss(bl->m); // Search for Boss on this Map + if( boss_md == NULL || boss_md->bl.prev == NULL ) + { // No MVP on this map - MVP is dead + clif_bossmapinfo(sd->fd, boss_md, 1); + return 0; // No need to start SC + } + val1 = boss_md->bl.id; + if( (val4 = tick/1000) < 1 ) + val4 = 1; + tick_time = 1000; // [GodLesZ] tick time + } + break; + case SC_HIDING: + val2 = tick/1000; + tick_time = 1000; // [GodLesZ] tick time + val3 = 0; // unused, previously speed adjustment + val4 = val1+3; //Seconds before SP substraction happen. + break; + case SC_CHASEWALK: + val2 = tick>0?tick:10000; //Interval at which SP is drained. + val3 = 35 - 5 * val1; //Speed adjustment. + if (sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_ROGUE) + val3 -= 40; + val4 = 10+val1*2; //SP cost. + if (map_flag_gvg(bl->m) || map[bl->m].flag.battleground) val4 *= 5; + break; + case SC_CLOAKING: + if (!sd) //Monsters should be able to walk with no penalties. [Skotlex] + val1 = 10; + tick_time = val2 = tick>0?tick:60000; //SP consumption rate. + tick = -1; // duration sent to the client should be infinite + val3 = 0; // unused, previously walk speed adjustment + //val4&1 signals the presence of a wall. + //val4&2 makes cloak not end on normal attacks [Skotlex] + //val4&4 makes cloak not end on using skills + if (bl->type == BL_PC || (bl->type == BL_MOB && ((TBL_MOB*)bl)->special_state.clone) ) //Standard cloaking. + val4 |= battle_config.pc_cloak_check_type&7; + else + val4 |= battle_config.monster_cloak_check_type&7; + break; + case SC_SIGHT: /* splash status */ + case SC_RUWACH: + case SC_SIGHTBLASTER: + val3 = skill_get_splash(val2, val1); //Val2 should bring the skill-id. + val2 = tick/250; + tick_time = 10; // [GodLesZ] tick time + break; + + //Permanent effects. + case SC_AETERNA: + case SC_MODECHANGE: + case SC_WEIGHT50: + case SC_WEIGHT90: + case SC_BROKENWEAPON: + case SC_BROKENARMOR: + case SC_READYSTORM: + case SC_READYDOWN: + case SC_READYCOUNTER: + case SC_READYTURN: + case SC_DODGE: + case SC_PUSH_CART: + tick = -1; + break; + + case SC_AUTOGUARD: + if( !(flag&1) ) + { + struct map_session_data *tsd; + int i,t; + for( i = val2 = 0; i < val1; i++) + { + t = 5-(i>>1); + val2 += (t < 0)? 1:t; + } + + if( bl->type&(BL_PC|BL_MER) ) + { + if( sd ) + { + for( i = 0; i < 5; i++ ) + { + if( sd->devotion[i] && (tsd = map_id2sd(sd->devotion[i])) ) + status_change_start(&tsd->bl, type, 10000, val1, val2, 0, 0, tick, 1); + } + } + else if( bl->type == BL_MER && ((TBL_MER*)bl)->devotion_flag && (tsd = ((TBL_MER*)bl)->master) ) + status_change_start(&tsd->bl, type, 10000, val1, val2, 0, 0, tick, 1); + } + } + break; + + case SC_DEFENDER: + if (!(flag&1)) + { + val2 = 5 + 15*val1; //Damage reduction + val3 = 0; // unused, previously speed adjustment + val4 = 250 - 50*val1; //Aspd adjustment + + if (sd) + { + struct map_session_data *tsd; + int i; + for (i = 0; i < 5; i++) + { //See if there are devoted characters, and pass the status to them. [Skotlex] + if (sd->devotion[i] && (tsd = map_id2sd(sd->devotion[i]))) + status_change_start(&tsd->bl,type,10000,val1,5+val1*5,val3,val4,tick,1); + } + } + } + break; + + case SC_TENSIONRELAX: + if (sd) { + pc_setsit(sd); + clif_sitting(&sd->bl); + } + val2 = 12; //SP cost + val4 = 10000; //Decrease at 10secs intervals. + val3 = tick/val4; + tick = -1; // duration sent to the client should be infinite + tick_time = val4; // [GodLesZ] tick time + break; + case SC_PARRYING: + val2 = 20 + val1*3; //Block Chance + break; + + case SC_WINDWALK: + val2 = (val1+1)/2; // Flee bonus is 1/1/2/2/3/3/4/4/5/5 + break; + + case SC_JOINTBEAT: + if( val2&BREAK_NECK ) + sc_start(bl,SC_BLEEDING,100,val1,skill_get_time2(status_sc2skill(type),val1)); + break; + + case SC_BERSERK: + if (!sc->data[SC_ENDURE] || !sc->data[SC_ENDURE]->val4) + sc_start4(bl, SC_ENDURE, 100,10,0,0,2, tick); + case SC__BLOODYLUST: + //HP healing is performing after the calc_status call. + //Val2 holds HP penalty + if (!val4) val4 = skill_get_time2(status_sc2skill(type),val1); + if (!val4) val4 = 10000; //Val4 holds damage interval + val3 = tick/val4; //val3 holds skill duration + tick_time = val4; // [GodLesZ] tick time + break; + + case SC_GOSPEL: + if(val4 == BCT_SELF) { // self effect + val2 = tick/10000; + tick_time = 10000; // [GodLesZ] tick time + status_change_clear_buffs(bl,3); //Remove buffs/debuffs + } + break; + + case SC_MARIONETTE: + { + int stat; + + val3 = 0; + val4 = 0; + stat = ( sd ? sd->status.str : status_get_base_status(bl)->str ) / 2; val3 |= cap_value(stat,0,0xFF)<<16; + stat = ( sd ? sd->status.agi : status_get_base_status(bl)->agi ) / 2; val3 |= cap_value(stat,0,0xFF)<<8; + stat = ( sd ? sd->status.vit : status_get_base_status(bl)->vit ) / 2; val3 |= cap_value(stat,0,0xFF); + stat = ( sd ? sd->status.int_: status_get_base_status(bl)->int_) / 2; val4 |= cap_value(stat,0,0xFF)<<16; + stat = ( sd ? sd->status.dex : status_get_base_status(bl)->dex ) / 2; val4 |= cap_value(stat,0,0xFF)<<8; + stat = ( sd ? sd->status.luk : status_get_base_status(bl)->luk ) / 2; val4 |= cap_value(stat,0,0xFF); + break; + } + case SC_MARIONETTE2: + { + int stat,max_stat; + // fetch caster information + struct block_list *pbl = map_id2bl(val1); + struct status_change *psc = pbl?status_get_sc(pbl):NULL; + struct status_change_entry *psce = psc?psc->data[SC_MARIONETTE]:NULL; + // fetch target's stats + struct status_data* status = status_get_status_data(bl); // battle status + + if (!psce) + return 0; + + val3 = 0; + val4 = 0; + max_stat = battle_config.max_parameter; //Cap to 99 (default) + stat = (psce->val3 >>16)&0xFF; stat = min(stat, max_stat - status->str ); val3 |= cap_value(stat,0,0xFF)<<16; + stat = (psce->val3 >> 8)&0xFF; stat = min(stat, max_stat - status->agi ); val3 |= cap_value(stat,0,0xFF)<<8; + stat = (psce->val3 >> 0)&0xFF; stat = min(stat, max_stat - status->vit ); val3 |= cap_value(stat,0,0xFF); + stat = (psce->val4 >>16)&0xFF; stat = min(stat, max_stat - status->int_); val4 |= cap_value(stat,0,0xFF)<<16; + stat = (psce->val4 >> 8)&0xFF; stat = min(stat, max_stat - status->dex ); val4 |= cap_value(stat,0,0xFF)<<8; + stat = (psce->val4 >> 0)&0xFF; stat = min(stat, max_stat - status->luk ); val4 |= cap_value(stat,0,0xFF); + break; + } + case SC_REJECTSWORD: + val2 = 15*val1; //Reflect chance + val3 = 3; //Reflections + tick = -1; + break; + + case SC_MEMORIZE: + val2 = 5; //Memorized casts. + tick = -1; + break; + + case SC_GRAVITATION: + val2 = 50*val1; //aspd reduction + break; + + case SC_REGENERATION: + if (val1 == 1) + val2 = 2; + else + val2 = val1; //HP Regerenation rate: 200% 200% 300% + val3 = val1; //SP Regeneration Rate: 100% 200% 300% + //if val4 comes set, this blocks regen rather than increase it. + break; + + case SC_DEVOTION: + { + struct block_list *d_bl; + struct status_change *d_sc; + + if( (d_bl = map_id2bl(val1)) && (d_sc = status_get_sc(d_bl)) && d_sc->count ) + { // Inherits Status From Source + const enum sc_type types[] = { SC_AUTOGUARD, SC_DEFENDER, SC_REFLECTSHIELD, SC_ENDURE }; + enum sc_type type2; + int i = (map_flag_gvg(bl->m) || map[bl->m].flag.battleground)?2:3; + while( i >= 0 ) + { + type2 = types[i]; + if( d_sc->data[type2] ) + sc_start(bl, type2, 100, d_sc->data[type2]->val1, skill_get_time(status_sc2skill(type2),d_sc->data[type2]->val1)); + i--; + } + } + break; + } + + case SC_COMA: //Coma. Sends a char to 1HP. If val2, do not zap sp + if( val3 && bl->type == BL_MOB ) { + struct block_list* src = map_id2bl(val3); + if( src ) + mob_log_damage((TBL_MOB*)bl,src,status->hp - 1); + } + status_zap(bl, status->hp-1, val2?0:status->sp); + return 1; + break; + case SC_CLOSECONFINE2: + { + struct block_list *src = val2?map_id2bl(val2):NULL; + struct status_change *sc2 = src?status_get_sc(src):NULL; + struct status_change_entry *sce2 = sc2?sc2->data[SC_CLOSECONFINE]:NULL; + if (src && sc2) { + if (!sce2) //Start lock on caster. + sc_start4(src,SC_CLOSECONFINE,100,val1,1,0,0,tick+1000); + else { //Increase count of locked enemies and refresh time. + (sce2->val2)++; + delete_timer(sce2->timer, status_change_timer); + sce2->timer = add_timer(gettick()+tick+1000, status_change_timer, src->id, SC_CLOSECONFINE); + } + } else //Status failed. + return 0; + } + break; + case SC_KAITE: + val2 = 1+val1/5; //Number of bounces: 1 + skill_lv/5 + break; + case SC_KAUPE: + switch (val1) { + case 3: //33*3 + 1 -> 100% + val2++; + case 1: + case 2: //33, 66% + val2 += 33*val1; + val3 = 1; //Dodge 1 attack total. + break; + default: //Custom. For high level mob usage, higher level means more blocks. [Skotlex] + val2 = 100; + val3 = val1-2; + break; + } + break; + + case SC_COMBO: { + //val1: Skill ID + //val2: When given, target (for autotargetting skills) + //val3: When set, this combo time should NOT delay attack/movement + //val3: TK: Last used kick + //val4: TK: Combo time + struct unit_data *ud = unit_bl2ud(bl); + if (ud && !val3) { + tick += 300 * battle_config.combo_delay_rate/100; + ud->attackabletime = gettick()+tick; + unit_set_walkdelay(bl, gettick(), tick, 1); + } + val3 = 0; + val4 = tick; + } + break; + case SC_EARTHSCROLL: + val2 = 11-val1; //Chance to consume: 11-skill_lv% + break; + case SC_RUN: + val4 = gettick(); //Store time at which you started running. + tick = -1; + break; + case SC_KAAHI: + val2 = 200*val1; //HP heal + val3 = 5*val1; //SP cost + val4 = INVALID_TIMER; //Kaahi Timer. + break; + case SC_BLESSING: + if ((!undead_flag && status->race!=RC_DEMON) || bl->type == BL_PC) + val2 = val1; + else + val2 = 0; //0 -> Half stat. + break; + case SC_TRICKDEAD: + if (vd) vd->dead_sit = 1; + tick = -1; + break; + case SC_CONCENTRATE: + val2 = 2 + val1; + if (sd) { //Store the card-bonus data that should not count in the % + val3 = sd->param_bonus[1]; //Agi + val4 = sd->param_bonus[4]; //Dex + } else { + val3 = val4 = 0; + } + break; + case SC_MAXOVERTHRUST: + val2 = 20*val1; //Power increase + break; + case SC_OVERTHRUST: + //val2 holds if it was casted on self, or is bonus received from others + val3 = 5*val1; //Power increase + if(sd && pc_checkskill(sd,BS_HILTBINDING)>0) + tick += tick / 10; + break; + case SC_ADRENALINE2: + case SC_ADRENALINE: + val3 = (val2) ? 300 : 200; // aspd increase + case SC_WEAPONPERFECTION: + if(sd && pc_checkskill(sd,BS_HILTBINDING)>0) + tick += tick / 10; + break; + case SC_CONCENTRATION: + val2 = 5*val1; //Batk/Watk Increase + val3 = 10*val1; //Hit Increase + val4 = 5*val1; //Def reduction + break; + case SC_ANGELUS: + val2 = 5*val1; //def increase + break; + case SC_IMPOSITIO: + val2 = 5*val1; //watk increase + break; + case SC_MELTDOWN: + val2 = 100*val1; //Chance to break weapon + val3 = 70*val1; //Change to break armor + break; + case SC_TRUESIGHT: + val2 = 10*val1; //Critical increase + val3 = 3*val1; //Hit increase + break; + case SC_SUN_COMFORT: + val2 = (status_get_lv(bl) + status->dex + status->luk)/2; //def increase + break; + case SC_MOON_COMFORT: + val2 = (status_get_lv(bl) + status->dex + status->luk)/10; //flee increase + break; + case SC_STAR_COMFORT: + val2 = (status_get_lv(bl) + status->dex + status->luk); //Aspd increase + break; + case SC_QUAGMIRE: + val2 = (sd?5:10)*val1; //Agi/Dex decrease. + break; + + // gs_something1 [Vicious] + case SC_GATLINGFEVER: + val2 = 20*val1; //Aspd increase + val3 = 20+10*val1; //Batk increase + val4 = 5*val1; //Flee decrease + break; + + case SC_FLING: + if (bl->type == BL_PC) + val2 = 0; //No armor reduction to players. + else + val2 = 5*val1; //Def reduction + val3 = 5*val1; //Def2 reduction + break; + case SC_PROVOKE: + //val2 signals autoprovoke. + val3 = 2+3*val1; //Atk increase + val4 = 5+5*val1; //Def reduction. + break; + case SC_AVOID: + //val2 = 10*val1; //Speed change rate. + break; + case SC_DEFENCE: + val2 = 2*val1; //Def bonus + break; + case SC_BLOODLUST: + val2 = 20+10*val1; //Atk rate change. + val3 = 3*val1; //Leech chance + val4 = 20; //Leech percent + break; + case SC_FLEET: + val2 = 30*val1; //Aspd change + val3 = 5+5*val1; //bAtk/wAtk rate change + break; + case SC_MINDBREAKER: + val2 = 20*val1; //matk increase. + val3 = 12*val1; //mdef2 reduction. + break; + case SC_SKA: + val2 = tick/1000; + val3 = rnd()%100; //Def changes randomly every second... + tick_time = 1000; // [GodLesZ] tick time + break; + case SC_JAILED: + //Val1 is duration in minutes. Use INT_MAX to specify 'unlimited' time. + tick = val1>0?1000:250; + if (sd) + { + if (sd->mapindex != val2) + { + int pos = (bl->x&0xFFFF)|(bl->y<<16), //Current Coordinates + map = sd->mapindex; //Current Map + //1. Place in Jail (val2 -> Jail Map, val3 -> x, val4 -> y + pc_setpos(sd,(unsigned short)val2,val3,val4, CLR_TELEPORT); + //2. Set restore point (val3 -> return map, val4 return coords + val3 = map; + val4 = pos; + } else if (!val3 || val3 == sd->mapindex) { //Use save point. + val3 = sd->status.save_point.map; + val4 = (sd->status.save_point.x&0xFFFF) + |(sd->status.save_point.y<<16); + } + } + break; + case SC_UTSUSEMI: + val2=(val1+1)/2; // number of hits blocked + val3=skill_get_blewcount(NJ_UTSUSEMI, val1); //knockback value. + break; + case SC_BUNSINJYUTSU: + val2=(val1+1)/2; // number of hits blocked + break; + case SC_CHANGE: + val2= 30*val1; //Vit increase + val3= 20*val1; //Int increase + break; + case SC_SWOO: + if(status->mode&MD_BOSS) + tick /= 5; //TODO: Reduce skill's duration. But for how long? + break; + case SC_SPIDERWEB: + if( bl->type == BL_PC ) + tick /= 2; + break; + case SC_ARMOR: + //NPC_DEFENDER: + val2 = 80; //Damage reduction + //Attack requirements to be blocked: + val3 = BF_LONG; //Range + val4 = BF_WEAPON|BF_MISC; //Type + break; + case SC_ENCHANTARMS: + //end previous enchants + skill_enchant_elemental_end(bl,type); + //Make sure the received element is valid. + if (val2 >= ELE_MAX) + val2 = val2%ELE_MAX; + else if (val2 < 0) + val2 = rnd()%ELE_MAX; + break; + case SC_CRITICALWOUND: + val2 = 20*val1; //Heal effectiveness decrease + break; + case SC_MAGICMIRROR: + case SC_SLOWCAST: + val2 = 20*val1; //Magic reflection/cast rate + break; + + case SC_ARMORCHANGE: + if (val2 == NPC_ANTIMAGIC) + { //Boost mdef + val2 =-20; + val3 = 20; + } else { //Boost def + val2 = 20; + val3 =-20; + } + val2*=val1; //20% per level + val3*=val1; + break; + case SC_EXPBOOST: + case SC_JEXPBOOST: + if (val1 < 0) + val1 = 0; + break; + case SC_INCFLEE2: + case SC_INCCRI: + val2 = val1*10; //Actual boost (since 100% = 1000) + break; + case SC_SUFFRAGIUM: + val2 = 15 * val1; //Speed cast decrease + break; + case SC_INCHEALRATE: + if (val1 < 1) + val1 = 1; + break; + case SC_HALLUCINATION: + val2 = 5+val1; //Factor by which displayed damage is increased by + break; + case SC_DOUBLECAST: + val2 = 30+10*val1; //Trigger rate + break; + case SC_KAIZEL: + val2 = 10*val1; //% of life to be revived with + break; + // case SC_ARMOR_ELEMENT: + // case SC_ARMOR_RESIST: + // Mod your resistance against elements: + // val1 = water | val2 = earth | val3 = fire | val4 = wind + // break; + //case ????: + //Place here SCs that have no SCB_* data, no skill associated, no ICON + //associated, and yet are not wrong/unknown. [Skotlex] + //break; + + case SC_MERC_FLEEUP: + case SC_MERC_ATKUP: + case SC_MERC_HITUP: + val2 = 15 * val1; + break; + case SC_MERC_HPUP: + case SC_MERC_SPUP: + val2 = 5 * val1; + break; + case SC_REBIRTH: + val2 = 20*val1; //% of life to be revived with + break; + + case SC_MANU_DEF: + case SC_MANU_ATK: + case SC_MANU_MATK: + val2 = 1; // Manuk group + break; + case SC_SPL_DEF: + case SC_SPL_ATK: + case SC_SPL_MATK: + val2 = 2; // Splendide group + break; + /** + * General + **/ + case SC_FEAR: + val2 = 2; + val4 = tick / 1000; + tick_time = 1000; // [GodLesZ] tick time + break; + case SC_BURNING: + val4 = tick / 2000; // Total Ticks to Burn!! + tick_time = 2000; // [GodLesZ] tick time + break; + /** + * Rune Knight + **/ + case SC_DEATHBOUND: + val2 = 500 + 100 * val1; + break; + case SC_FIGHTINGSPIRIT: + val_flag |= 1|2; + break; + case SC_ABUNDANCE: + val4 = tick / 10000; + tick_time = 10000; // [GodLesZ] tick time + break; + case SC_GIANTGROWTH: + val2 = 10; // Triple damage success rate. + break; + /** + * Arch Bishop + **/ + case SC_RENOVATIO: + val4 = tick / 5000; + tick_time = 5000; + break; + case SC_SECRAMENT: + val2 = 10 * val1; + break; + case SC_VENOMIMPRESS: + val2 = 10 * val1; + val_flag |= 1|2; + break; + case SC_POISONINGWEAPON: + val_flag |= 1|2|4; + break; + case SC_WEAPONBLOCKING: + val2 = 10 + 2 * val1; // Chance + val4 = tick / 3000; + tick_time = 3000; // [GodLesZ] tick time + val_flag |= 1|2; + break; + case SC_TOXIN: + val4 = tick / 10000; + tick_time = 10000; // [GodLesZ] tick time + break; + case SC_MAGICMUSHROOM: + val4 = tick / 4000; + tick_time = 4000; // [GodLesZ] tick time + break; + case SC_PYREXIA: + status_change_start(bl,SC_BLIND,10000,val1,0,0,0,30000,11); // Blind status that last for 30 seconds + val4 = tick / 3000; + tick_time = 3000; // [GodLesZ] tick time + break; + case SC_LEECHESEND: + val4 = tick / 1000; + tick_time = 1000; // [GodLesZ] tick time + break; + case SC_OBLIVIONCURSE: + val4 = tick / 3000; + tick_time = 3000; // [GodLesZ] tick time + break; + case SC_ROLLINGCUTTER: + val_flag |= 1; + break; + case SC_CLOAKINGEXCEED: + val2 = ( val1 + 1 ) / 2; // Hits + val3 = 90 + val1 * 10; // Walk speed + val_flag |= 1|2|4; + if (bl->type == BL_PC) + val4 |= battle_config.pc_cloak_check_type&7; + else + val4 |= battle_config.monster_cloak_check_type&7; + tick_time = 1000; // [GodLesZ] tick time + break; + case SC_HALLUCINATIONWALK: + val2 = 50 * val1; // Evasion rate of physical attacks. Flee + val3 = 10 * val1; // Evasion rate of magical attacks. + val_flag |= 1|2|4; + break; + case SC_WHITEIMPRISON: + status_change_end(bl, SC_BURNING, INVALID_TIMER); + status_change_end(bl, SC_FREEZING, INVALID_TIMER); + status_change_end(bl, SC_FREEZE, INVALID_TIMER); + status_change_end(bl, SC_STONE, INVALID_TIMER); + break; + case SC_FREEZING: + status_change_end(bl, SC_BURNING, INVALID_TIMER); + break; + case SC_READING_SB: + // val2 = sp reduction per second + tick_time = 5000; // [GodLesZ] tick time + break; + case SC_SPHERE_1: + case SC_SPHERE_2: + case SC_SPHERE_3: + case SC_SPHERE_4: + case SC_SPHERE_5: + if( !sd ) + return 0; // Should only work on players. + val4 = tick / 1000; + if( val4 < 1 ) + val4 = 1; + tick_time = 1000; // [GodLesZ] tick time + val_flag |= 1; + break; + case SC_SHAPESHIFT: + switch( val1 ) + { + case 1: val2 = ELE_FIRE; break; + case 2: val2 = ELE_EARTH; break; + case 3: val2 = ELE_WIND; break; + case 4: val2 = ELE_WATER; break; + } + break; + case SC_ELECTRICSHOCKER: + case SC_CRYSTALIZE: + case SC_MEIKYOUSISUI: + val4 = tick / 1000; + if( val4 < 1 ) + val4 = 1; + tick_time = 1000; // [GodLesZ] tick time + break; + case SC_CAMOUFLAGE: + val4 = tick/1000; + tick_time = 1000; // [GodLesZ] tick time + break; + case SC_WUGDASH: + val4 = gettick(); //Store time at which you started running. + tick = -1; + break; + case SC__SHADOWFORM: { + struct map_session_data * s_sd = map_id2sd(val2); + if( s_sd ) + s_sd->shadowform_id = bl->id; + val4 = tick / 1000; + val_flag |= 1|2|4; + tick_time = 1000; // [GodLesZ] tick time + } + break; + case SC__STRIPACCESSORY: + if (!sd) + val2 = 20; + break; + case SC__INVISIBILITY: + val2 = 50 - 10 * val1; // ASPD + val3 = 20 * val1; // CRITICAL + val4 = tick / 1000; + tick_time = 1000; // [GodLesZ] tick time + val_flag |= 1|2; + break; + case SC__ENERVATION: + val2 = 20 + 10 * val1; // ATK Reduction + val_flag |= 1|2; + if( sd ) pc_delspiritball(sd,sd->spiritball,0); + break; + case SC__GROOMY: + val2 = 20 + 10 * val1; //ASPD. Need to confirm if Movement Speed reduction is the same. [Jobbie] + val3 = 20 * val1; //HIT + val_flag |= 1|2|4; + if( sd ) + { // Removes Animals + if( pc_isriding(sd) ) pc_setriding(sd, 0); + if( pc_isridingdragon(sd) ) pc_setoption(sd, sd->sc.option&~OPTION_DRAGON); + if( pc_iswug(sd) ) pc_setoption(sd, sd->sc.option&~OPTION_WUG); + if( pc_isridingwug(sd) ) pc_setoption(sd, sd->sc.option&~OPTION_WUGRIDER); + if( pc_isfalcon(sd) ) pc_setoption(sd, sd->sc.option&~OPTION_FALCON); + if( sd->status.pet_id > 0 ) pet_menu(sd, 3); + if( merc_is_hom_active(sd->hd) ) merc_hom_vaporize(sd,1); + if( sd->md ) merc_delete(sd->md,3); + } + break; + case SC__LAZINESS: + val2 = 10 + 10 * val1; // Cast reduction + val3 = 10 * val1; // Flee Reduction + val_flag |= 1|2|4; + break; + case SC__UNLUCKY: + val2 = 10 * val1; // Crit and Flee2 Reduction + val_flag |= 1|2|4; + break; + case SC__WEAKNESS: + val2 = 10 * val1; + val_flag |= 1|2; + // bypasses coating protection and MADO + sc_start(bl,SC_STRIPWEAPON,100,val1,tick); + sc_start(bl,SC_STRIPSHIELD,100,val1,tick); + break; + break; + case SC_GN_CARTBOOST: + if( val1 < 3 ) + val2 = 50; + else if( val1 < 5 ) + val2 = 75; + else + val2 = 100; + break; + case SC_PROPERTYWALK: + val_flag |= 1|2; + val3 = 0; + break; + case SC_WARMER: + status_change_end(bl, SC_FREEZE, INVALID_TIMER); + status_change_end(bl, SC_FREEZING, INVALID_TIMER); + status_change_end(bl, SC_CRYSTALIZE, INVALID_TIMER); + break; + case SC_STRIKING: + val1 = 6 - val1;//spcost = 6 - level (lvl1:5 ... lvl 5: 1) + val4 = tick / 1000; + tick_time = 1000; // [GodLesZ] tick time + break; + case SC_BLOODSUCKER: + val4 = tick / 1000; + tick_time = 1000; // [GodLesZ] tick time + break; + case SC_VACUUM_EXTREME: + tick -= (status->str / 20) * 1000; + val4 = val3 = tick / 100; + tick_time = 100; // [GodLesZ] tick time + break; + case SC_SWINGDANCE: + val2 = 4 * val1; // Walk speed and aspd reduction. + break; + case SC_SYMPHONYOFLOVER: + case SC_RUSHWINDMILL: + case SC_ECHOSONG: + val2 = 6 * val1; + val2 += val3; //Adding 1% * Lesson Bonus + val2 += (int)(val4*2/10); //Adding 0.2% per JobLevel + break; + case SC_MOONLITSERENADE: + val2 = 10 * val1; + break; + case SC_HARMONIZE: + val2 = 5 + 5 * val1; + break; + case SC_VOICEOFSIREN: + val4 = tick / 2000; + tick_time = 2000; // [GodLesZ] tick time + break; + case SC_DEEPSLEEP: + val4 = tick / 2000; + tick_time = 2000; // [GodLesZ] tick time + break; + case SC_SIRCLEOFNATURE: + val2 = 1 + val1; //SP consume + val3 = 40 * val1; //HP recovery + val4 = tick / 1000; + tick_time = 1000; // [GodLesZ] tick time + break; + case SC_SONGOFMANA: + val3 = 10 + (2 * val2); + val4 = tick/3000; + tick_time = 3000; // [GodLesZ] tick time + break; + case SC_SATURDAYNIGHTFEVER: + if (!val4) val4 = skill_get_time2(status_sc2skill(type),val1); + if (!val4) val4 = 3000; + val3 = tick/val4; + tick_time = val4; // [GodLesZ] tick time + break; + case SC_GLOOMYDAY: + val2 = 20 + 5 * val1; // Flee reduction. + val3 = 15 + 5 * val1; // ASPD reduction. + if( sd && rand()%100 < val1 ){ // (Skill Lv) % + val4 = 1; // reduce walk speed by half. + if( pc_isriding(sd) ) pc_setriding(sd, 0); + if( pc_isridingdragon(sd) ) pc_setoption(sd, sd->sc.option&~OPTION_DRAGON); + } + break; + case SC_GLOOMYDAY_SK: + // Random number between [15 ~ (Voice Lesson Skill Level x 5) + (Skill Level x 10)] %. + val2 = 15 + rand()%( (sd?pc_checkskill(sd, WM_LESSON)*5:0) + val1*10 ); + break; + case SC_SITDOWN_FORCE: + case SC_BANANA_BOMB_SITDOWN: + if( sd && !pc_issit(sd) ) + { + pc_setsit(sd); + skill_sit(sd,1); + clif_sitting(bl); + } + break; + case SC_DANCEWITHWUG: + val3 = (5 * val1) + (1 * val2); //Still need official value. + break; + case SC_LERADSDEW: + val3 = (5 * val1) + (1 * val2); + break; + case SC_MELODYOFSINK: + val3 = (5 * val1) + (1 * val2); + break; + case SC_BEYONDOFWARCRY: + val3 = (5 * val1) + (1 * val2); + break; + case SC_UNLIMITEDHUMMINGVOICE: + { + struct unit_data *ud = unit_bl2ud(bl); + if( ud == NULL ) return 0; + ud->state.skillcastcancel = 0; + val3 = 15 - (2 * val2); + } + break; + case SC_REFLECTDAMAGE: + val2 = 15 + 5 * val1; + val3 = (val1==5)?20:(val1+4)*2; // SP consumption + val4 = tick/10000; + tick_time = 10000; // [GodLesZ] tick time + break; + case SC_FORCEOFVANGUARD: // This is not the official way to handle it but I think we should use it. [pakpil] + val2 = 20 + 12 * (val1 - 1); // Chance + val3 = 5 + (2 * val1); // Max rage counters + tick = -1; //endless duration in the client + tick_time = 6000; // [GodLesZ] tick time + val_flag |= 1|2|4; + break; + case SC_EXEEDBREAK: + val1 *= 150; // 150 * skill_lv + if( sd && sd->inventory_data[sd->equip_index[EQI_HAND_R]] ) { // Chars. + val1 += (sd->inventory_data[sd->equip_index[EQI_HAND_R]]->weight/10 * sd->inventory_data[sd->equip_index[EQI_HAND_R]]->wlv * status_get_lv(bl) / 100); + val1 += 15 * (sd ? sd->status.job_level:50) + 100; + } + else // Mobs + val1 += (400 * status_get_lv(bl) / 100) + (15 * (status_get_lv(bl) / 2)); // About 1138% at mob_lvl 99. Is an aproximation to a standard weapon. [pakpil] + break; + case SC_PRESTIGE: // Bassed on suggested formula in iRO Wiki and some test, still need more test. [pakpil] + val2 = ((status->int_ + status->luk) / 6) + 5; // Chance to evade magic damage. + val1 *= 15; // Defence added + if( sd ) + val1 += 10 * pc_checkskill(sd,CR_DEFENDER); + val_flag |= 1|2; + break; + case SC_BANDING: + tick_time = 5000; // [GodLesZ] tick time + val_flag |= 1; + break; + case SC_SHIELDSPELL_DEF: + case SC_SHIELDSPELL_MDEF: + case SC_SHIELDSPELL_REF: + val_flag |= 1|2; + break; + case SC_MAGNETICFIELD: + val3 = tick / 1000; + tick_time = 1000; // [GodLesZ] tick time + break; + case SC_INSPIRATION: + if( sd ) + { + val2 = (40 * val1) + (3 * sd->status.job_level); // ATK bonus + val3 = (sd->status.job_level / 10) * 2 + 12; // All stat bonus + } + val4 = tick / 1000; + tick_time = 1000; // [GodLesZ] tick time + status_change_clear_buffs(bl,3); //Remove buffs/debuffs + break; + case SC_SPELLFIST: + case SC_CURSEDCIRCLE_ATKER: + val_flag |= 1|2|4; + break; + case SC_CRESCENTELBOW: + val2 = 94 + val1; + val_flag |= 1|2; + break; + case SC_LIGHTNINGWALK: // [(Job Level / 2) + (40 + 5 * Skill Level)] % + val1 = (sd?sd->status.job_level:2)/2 + 40 + 5 * val1; + val_flag |= 1; + break; + case SC_RAISINGDRAGON: + val3 = tick / 5000; + tick_time = 5000; // [GodLesZ] tick time + break; + case SC_GT_CHANGE: + {// take note there is no def increase as skill desc says. [malufett] + struct block_list * src; + val3 = status->agi * val1 / 60; // ASPD increase: [(Target AGI x Skill Level) / 60] % + if( (src = map_id2bl(val2)) ) + val4 = ( 200/status_get_int(src) ) * val1;// MDEF decrease: MDEF [(200 / Caster INT) x Skill Level] + } + break; + case SC_GT_REVITALIZE: + {// take note there is no vit,aspd,speed increase as skill desc says. [malufett] + struct block_list * src; + val3 = val1 * 30 + 150; // Natural HP recovery increase: [(Skill Level x 30) + 50] % + if( (src = map_id2bl(val2)) ) // the stat def is not shown in the status window and it is process differently + val4 = ( status_get_vit(src)/4 ) * val1; // STAT DEF increase: [(Caster VIT / 4) x Skill Level] + } + break; + case SC_PYROTECHNIC_OPTION: + val_flag |= 1|2|4; + break; + case SC_HEATER_OPTION: + val2 = 120; // Watk. TODO: Renewal (Atk2) + val3 = 33; // % Increase effects. + val4 = 3; // Change into fire element. + val_flag |= 1|2|4; + break; + case SC_TROPIC_OPTION: + val2 = 180; // Watk. TODO: Renewal (Atk2) + val3 = MG_FIREBOLT; + break; + case SC_AQUAPLAY_OPTION: + val2 = 40; + val_flag |= 1|2|4; + break; + case SC_COOLER_OPTION: + val2 = 80; // % Freezing chance + val3 = 33; // % increased damage + val4 = 1; // Change into water elemet + val_flag |= 1|2|4; + break; + case SC_CHILLY_AIR_OPTION: + val2 = 120; // Matk. TODO: Renewal (Matk1) + val3 = MG_COLDBOLT; + val_flag |= 1|2; + break; + case SC_GUST_OPTION: + val_flag |= 1|2; + break; + case SC_WIND_STEP_OPTION: + val2 = 50; // % Increase speed and flee. + break; + case SC_BLAST_OPTION: + val2 = 20; + val3 = ELE_WIND; + val_flag |= 1|2|4; + break; + case SC_WILD_STORM_OPTION: + val2 = MG_LIGHTNINGBOLT; + val_flag |= 1|2; + break; + case SC_PETROLOGY_OPTION: + val2 = 5; + val3 = 50; + val_flag |= 1|2|4; + break; + case SC_CURSED_SOIL_OPTION: + val2 = 10; + val3 = 33; + val4 = 2; + val_flag |= 1|2|4; + break; + case SC_UPHEAVAL_OPTION: + val2 = WZ_EARTHSPIKE; + val_flag |= 1|2; + break; + case SC_CIRCLE_OF_FIRE_OPTION: + val2 = 300; + val_flag |= 1|2; + break; + case SC_FIRE_CLOAK_OPTION: + case SC_WATER_DROP_OPTION: + case SC_WIND_CURTAIN_OPTION: + case SC_STONE_SHIELD_OPTION: + val2 = 20; // Elemental modifier. Not confirmed. + break; + case SC_CIRCLE_OF_FIRE: + case SC_FIRE_CLOAK: + case SC_WATER_DROP: + case SC_WATER_SCREEN: + case SC_WIND_CURTAIN: + case SC_WIND_STEP: + case SC_STONE_SHIELD: + case SC_SOLID_SKIN: + val2 = 10; + tick_time = 2000; // [GodLesZ] tick time + break; + case SC_WATER_BARRIER: + val2 = 40; // Increasement. Mdef1 ??? + val3 = 20; // Reductions. Atk2, Flee1, Matk1 ???? + val_flag |= 1|2|4; + break; + case SC_ZEPHYR: + val2 = 22; // Flee. + break; + case SC_TIDAL_WEAPON: + val2 = 20; // Increase Elemental's attack. + break; + case SC_ROCK_CRUSHER: + case SC_ROCK_CRUSHER_ATK: + case SC_POWER_OF_GAIA: + val2 = 33; + break; + case SC_MELON_BOMB: + case SC_BANANA_BOMB: + val1 = 15; + break; + case SC_STOMACHACHE: + val2 = 8; // SP consume. + val4 = tick / 10000; + tick_time = 10000; // [GodLesZ] tick time + break; + case SC_KYOUGAKU: + val2 = 2*val1 + rand()%val1; + clif_status_change(bl,SI_ACTIVE_MONSTER_TRANSFORM,1,0,1002,0,0); + break; + case SC_KAGEMUSYA: + val3 = val1 * 2; + case SC_IZAYOI: + val2 = tick/1000; + tick_time = 1000; + break; + case SC_ZANGETSU: + if( (status_get_hp(bl)+status_get_sp(bl)) % 2 == 0) + val2 = status_get_lv(bl) / 2 + 50; + else + val2 -= 50; + break; + case SC_GENSOU: + { + int hp = status_get_hp(bl), lv = 5; + short per = 100 / (status_get_max_hp(bl) / hp); + + if( per <= 15 ) + lv = 1; + else if( per <= 30 ) + lv = 2; + else if( per <= 50 ) + lv = 3; + else if( per <= 75 ) + lv = 4; + if( hp % 2 == 0) + status_heal(bl, hp * (6-lv) * 4 / 100, status_get_sp(bl) * (6-lv) * 3 / 100, 1); + else + status_zap(bl, hp * (lv*4) / 100, status_get_sp(bl) * (lv*3) / 100); + } + break; + case SC_ANGRIFFS_MODUS: + val2 = 50 + 20 * val1; //atk bonus + val3 = 40 + 20 * val1; // Flee reduction. + val4 = tick/1000; // hp/sp reduction timer + tick_time = 1000; + break; + case SC_GOLDENE_FERSE: + val2 = 10 + 10*val1; //max hp bonus + val3 = 6 + 4 * val1; // Aspd Bonus + val4 = 2 + 2 * val1; // Chance of holy attack + break; + case SC_OVERED_BOOST: + val2 = 300 + 40*val1; //flee bonus + val3 = 179 + 2*val1; //aspd bonus + break; + case SC_GRANITIC_ARMOR: + val2 = 2*val1; //dmg reduction + val3 = 6*val1; //dmg on status end + break; + case SC_MAGMA_FLOW: + val2 = 3*val1; //activation chance + break; + case SC_PYROCLASTIC: + val2 += 10*val1; //atk bonus + break; + case SC_PARALYSIS: //[Lighta] need real info + val2 = 2*val1; //def reduction + val3 = 500*val1; //varcast augmentation + break; + case SC_PAIN_KILLER: //[Lighta] need real info + val2 = 2*val1; //aspd reduction % + val3 = 2*val1; //dmg reduction % + if(sc->data[SC_PARALYSIS]) + sc_start(bl, SC_ENDURE, 100, val1, tick); //start endure for same duration + break; + case SC_STYLE_CHANGE: //[Lighta] need real info + tick = -1; + if(val2 == MH_MD_FIGHTING) val2 = MH_MD_GRAPPLING; + else val2 = MH_MD_FIGHTING; + break; + default: + if( calc_flag == SCB_NONE && StatusSkillChangeTable[type] == 0 && StatusIconChangeTable[type] == 0 ) + { //Status change with no calc, no icon, and no skill associated...? + ShowError("UnknownStatusChange [%d]\n", type); + return 0; + } + } + else //Special considerations when loading SC data. + switch( type ) + { + case SC_WEDDING: + case SC_XMAS: + case SC_SUMMER: + clif_changelook(bl,LOOK_WEAPON,0); + clif_changelook(bl,LOOK_SHIELD,0); + clif_changelook(bl,LOOK_BASE,type==SC_WEDDING?JOB_WEDDING:type==SC_XMAS?JOB_XMAS:JOB_SUMMER); + clif_changelook(bl,LOOK_CLOTHES_COLOR,val4); + break; + case SC_KAAHI: + val4 = INVALID_TIMER; + break; + } + + //Those that make you stop attacking/walking.... + switch (type) { + case SC_FREEZE: + case SC_STUN: + case SC_SLEEP: + case SC_STONE: + case SC_DEEPSLEEP: + if (sd && pc_issit(sd)) //Avoid sprite sync problems. + pc_setstand(sd); + case SC_TRICKDEAD: + status_change_end(bl, SC_DANCING, INVALID_TIMER); + // Cancel cast when get status [LuzZza] + if (battle_config.sc_castcancel&bl->type) + unit_skillcastcancel(bl, 0); + case SC_WHITEIMPRISON: + unit_stop_attack(bl); + case SC_STOP: + case SC_CONFUSION: + case SC_CLOSECONFINE: + case SC_CLOSECONFINE2: + case SC_ANKLE: + case SC_SPIDERWEB: + case SC_ELECTRICSHOCKER: + case SC_BITE: + case SC_THORNSTRAP: + case SC__MANHOLE: + case SC_CRYSTALIZE: + case SC_CURSEDCIRCLE_ATKER: + case SC_CURSEDCIRCLE_TARGET: + case SC_FEAR: + case SC_NETHERWORLD: + case SC_MEIKYOUSISUI: + case SC_KYOUGAKU: + case SC_PARALYSIS: + unit_stop_walking(bl,1); + break; + case SC_HIDING: + case SC_CLOAKING: + case SC_CLOAKINGEXCEED: + case SC_CHASEWALK: + case SC_WEIGHT90: + case SC_CAMOUFLAGE: + case SC_VOICEOFSIREN: + unit_stop_attack(bl); + break; + case SC_SILENCE: + if (battle_config.sc_castcancel&bl->type) + unit_skillcastcancel(bl, 0); + break; + } + + // Set option as needed. + opt_flag = 1; + switch(type) + { + //OPT1 + case SC_STONE: sc->opt1 = OPT1_STONEWAIT; break; + case SC_FREEZE: sc->opt1 = OPT1_FREEZE; break; + case SC_STUN: sc->opt1 = OPT1_STUN; break; + case SC_DEEPSLEEP: opt_flag = 0; + case SC_SLEEP: sc->opt1 = OPT1_SLEEP; break; + case SC_BURNING: sc->opt1 = OPT1_BURNING; break; // Burning need this to be showed correctly. [pakpil] + case SC_WHITEIMPRISON: sc->opt1 = OPT1_IMPRISON; break; + case SC_CRYSTALIZE: sc->opt1 = OPT1_CRYSTALIZE; break; + //OPT2 + case SC_POISON: sc->opt2 |= OPT2_POISON; break; + case SC_CURSE: sc->opt2 |= OPT2_CURSE; break; + case SC_SILENCE: sc->opt2 |= OPT2_SILENCE; break; + + case SC_SIGNUMCRUCIS: + sc->opt2 |= OPT2_SIGNUMCRUCIS; + break; + + case SC_BLIND: sc->opt2 |= OPT2_BLIND; break; + case SC_ANGELUS: sc->opt2 |= OPT2_ANGELUS; break; + case SC_BLEEDING: sc->opt2 |= OPT2_BLEEDING; break; + case SC_DPOISON: sc->opt2 |= OPT2_DPOISON; break; + //OPT3 + case SC_TWOHANDQUICKEN: + case SC_ONEHAND: + case SC_SPEARQUICKEN: + case SC_CONCENTRATION: + case SC_MERC_QUICKEN: + sc->opt3 |= OPT3_QUICKEN; + opt_flag = 0; + break; + case SC_MAXOVERTHRUST: + case SC_OVERTHRUST: + case SC_SWOO: //Why does it shares the same opt as Overthrust? Perhaps we'll never know... + sc->opt3 |= OPT3_OVERTHRUST; + opt_flag = 0; + break; + case SC_ENERGYCOAT: + case SC_SKE: + sc->opt3 |= OPT3_ENERGYCOAT; + opt_flag = 0; + break; + case SC_INCATKRATE: + //Simulate Explosion Spirits effect for NPC_POWERUP [Skotlex] + if (bl->type != BL_MOB) { + opt_flag = 0; + break; + } + case SC_EXPLOSIONSPIRITS: + sc->opt3 |= OPT3_EXPLOSIONSPIRITS; + opt_flag = 0; + break; + case SC_STEELBODY: + case SC_SKA: + sc->opt3 |= OPT3_STEELBODY; + opt_flag = 0; + break; + case SC_BLADESTOP: + sc->opt3 |= OPT3_BLADESTOP; + opt_flag = 0; + break; + case SC_AURABLADE: + sc->opt3 |= OPT3_AURABLADE; + opt_flag = 0; + break; + case SC_BERSERK: + opt_flag = 0; +// case SC__BLOODYLUST: + sc->opt3 |= OPT3_BERSERK; + break; +// case ???: // doesn't seem to do anything +// sc->opt3 |= OPT3_LIGHTBLADE; +// opt_flag = 0; +// break; + case SC_DANCING: + if ((val1&0xFFFF) == CG_MOONLIT) + sc->opt3 |= OPT3_MOONLIT; + opt_flag = 0; + break; + case SC_MARIONETTE: + case SC_MARIONETTE2: + sc->opt3 |= OPT3_MARIONETTE; + opt_flag = 0; + break; + case SC_ASSUMPTIO: + sc->opt3 |= OPT3_ASSUMPTIO; + opt_flag = 0; + break; + case SC_WARM: //SG skills [Komurka] + sc->opt3 |= OPT3_WARM; + opt_flag = 0; + break; + case SC_KAITE: + sc->opt3 |= OPT3_KAITE; + opt_flag = 0; + break; + case SC_BUNSINJYUTSU: + sc->opt3 |= OPT3_BUNSIN; + opt_flag = 0; + break; + case SC_SPIRIT: + sc->opt3 |= OPT3_SOULLINK; + opt_flag = 0; + break; + case SC_CHANGEUNDEAD: + sc->opt3 |= OPT3_UNDEAD; + opt_flag = 0; + break; +// case ???: // from DA_CONTRACT (looks like biolab mobs aura) +// sc->opt3 |= OPT3_CONTRACT; +// opt_flag = 0; +// break; + //OPTION + case SC_HIDING: + sc->option |= OPTION_HIDE; + opt_flag = 2; + break; + case SC_CLOAKING: + case SC_CLOAKINGEXCEED: + case SC__INVISIBILITY: + sc->option |= OPTION_CLOAK; + opt_flag = 2; + break; + case SC_CHASEWALK: + sc->option |= OPTION_CHASEWALK|OPTION_CLOAK; + opt_flag = 2; + break; + case SC_SIGHT: + sc->option |= OPTION_SIGHT; + break; + case SC_RUWACH: + sc->option |= OPTION_RUWACH; + break; + case SC_WEDDING: + sc->option |= OPTION_WEDDING; + break; + case SC_XMAS: + sc->option |= OPTION_XMAS; + break; + case SC_SUMMER: + sc->option |= OPTION_SUMMER; + break; + case SC_ORCISH: + sc->option |= OPTION_ORCISH; + break; + case SC_FUSION: + sc->option |= OPTION_FLYING; + break; + default: + opt_flag = 0; + } + + //On Aegis, when turning on a status change, first goes the option packet, then the sc packet. + if(opt_flag) + clif_changeoption(bl); + + if (calc_flag&SCB_DYE) + { //Reset DYE color + if (vd && vd->cloth_color) + { + val4 = vd->cloth_color; + clif_changelook(bl,LOOK_CLOTHES_COLOR,0); + } + calc_flag&=~SCB_DYE; + } + + clif_status_change(bl,StatusIconChangeTable[type],1,tick,(val_flag&1)?val1:1,(val_flag&2)?val2:0,(val_flag&4)?val3:0); + + /** + * used as temporary storage for scs with interval ticks, so that the actual duration is sent to the client first. + **/ + if( tick_time ) + tick = tick_time; + + //Don't trust the previous sce assignment, in case the SC ended somewhere between there and here. + if((sce=sc->data[type])) {// reuse old sc + if( sce->timer != INVALID_TIMER ) + delete_timer(sce->timer, status_change_timer); + sc_isnew = false; + } else {// new sc + ++(sc->count); + sce = sc->data[type] = ers_alloc(sc_data_ers, struct status_change_entry); + } + sce->val1 = val1; + sce->val2 = val2; + sce->val3 = val3; + sce->val4 = val4; + if (tick >= 0) + sce->timer = add_timer(gettick() + tick, status_change_timer, bl->id, type); + else + sce->timer = INVALID_TIMER; //Infinite duration + + if (calc_flag) + status_calc_bl(bl,calc_flag); + + if ( sc_isnew && StatusChangeStateTable[type] ) /* non-zero */ + status_calc_state(bl,sc,( enum scs_flag ) StatusChangeStateTable[type],true); + + + if(sd && sd->pd) + pet_sc_check(sd, type); //Skotlex: Pet Status Effect Healing + + switch (type) { + case SC__BLOODYLUST: + case SC_BERSERK: + if (!(sce->val2)) { //don't heal if already set + status_heal(bl, status->max_hp, 0, 1); //Do not use percent_heal as this healing must override BERSERK's block. + status_set_sp(bl, 0, 0); //Damage all SP + } + sce->val2 = 5 * status->max_hp / 100; + break; + case SC_CHANGE: + status_percent_heal(bl, 100, 100); + break; + case SC_RUN: + { + struct unit_data *ud = unit_bl2ud(bl); + if( ud ) + ud->state.running = unit_run(bl); + } + break; + case SC_BOSSMAPINFO: + clif_bossmapinfo(sd->fd, map_id2boss(sce->val1), 0); // First Message + break; + case SC_MERC_HPUP: + status_percent_heal(bl, 100, 0); // Recover Full HP + break; + case SC_MERC_SPUP: + status_percent_heal(bl, 0, 100); // Recover Full SP + break; + /** + * Ranger + **/ + case SC_WUGDASH: + { + struct unit_data *ud = unit_bl2ud(bl); + if( ud ) + ud->state.running = unit_wugdash(bl, sd); + } + break; + case SC_COMBO: + switch (sce->val1) { + case TK_STORMKICK: + clif_skill_nodamage(bl,bl,TK_READYSTORM,1,1); + break; + case TK_DOWNKICK: + clif_skill_nodamage(bl,bl,TK_READYDOWN,1,1); + break; + case TK_TURNKICK: + clif_skill_nodamage(bl,bl,TK_READYTURN,1,1); + break; + case TK_COUNTER: + clif_skill_nodamage(bl,bl,TK_READYCOUNTER,1,1); + break; + case MO_COMBOFINISH: + case CH_TIGERFIST: + case CH_CHAINCRUSH: + if (sd) + clif_skillinfo(sd,MO_EXTREMITYFIST, INF_SELF_SKILL); + break; + case TK_JUMPKICK: + if (sd) + clif_skillinfo(sd,TK_JUMPKICK, INF_SELF_SKILL); + break; + case MO_TRIPLEATTACK: + if (sd && pc_checkskill(sd, SR_DRAGONCOMBO) > 0) + clif_skillinfo(sd,SR_DRAGONCOMBO, INF_SELF_SKILL); + break; + case SR_FALLENEMPIRE: + if (sd){ + clif_skillinfo(sd,SR_GATEOFHELL, INF_SELF_SKILL); + clif_skillinfo(sd,SR_TIGERCANNON, INF_SELF_SKILL); + } + break; + } + break; + case SC_RAISINGDRAGON: + sce->val2 = status->max_hp / 100;// Officially tested its 1%hp drain. [Jobbie] + break; + } + + if( opt_flag&2 && sd && sd->touching_id ) + npc_touchnext_areanpc(sd,false); // run OnTouch_ on next char in range + + return 1; +} + +/*========================================== + * Ending all status except those listed. + * @TODO maybe usefull for dispel instead reseting a liste there. + * type: + * 0 - PC killed -> Place here statuses that do not dispel on death. + * 1 - If for some reason status_change_end decides to still keep the status when quitting. + * 2 - Do clif + * 3 - Do not remove some permanent/time-independent effects + *------------------------------------------*/ +int status_change_clear(struct block_list* bl, int type) +{ + struct status_change* sc; + int i; + + sc = status_get_sc(bl); + + if (!sc || !sc->count) + return 0; + + for(i = 0; i < SC_MAX; i++) + { + if(!sc->data[i]) + continue; + + if(type == 0) + switch (i) + { //Type 0: PC killed -> Place here statuses that do not dispel on death. + case SC_ELEMENTALCHANGE://Only when its Holy or Dark that it doesn't dispell on death + if( sc->data[i]->val2 != ELE_HOLY && sc->data[i]->val2 != ELE_DARK ) + break; + case SC_WEIGHT50: + case SC_WEIGHT90: + case SC_EDP: + case SC_MELTDOWN: + case SC_XMAS: + case SC_SUMMER: + case SC_NOCHAT: + case SC_FUSION: + case SC_EARTHSCROLL: + case SC_READYSTORM: + case SC_READYDOWN: + case SC_READYCOUNTER: + case SC_READYTURN: + case SC_DODGE: + case SC_JAILED: + case SC_EXPBOOST: + case SC_ITEMBOOST: + case SC_HELLPOWER: + case SC_JEXPBOOST: + case SC_AUTOTRADE: + case SC_WHISTLE: + case SC_ASSNCROS: + case SC_POEMBRAGI: + case SC_APPLEIDUN: + case SC_HUMMING: + case SC_DONTFORGETME: + case SC_FORTUNE: + case SC_SERVICE4U: + case SC_FOOD_STR_CASH: + case SC_FOOD_AGI_CASH: + case SC_FOOD_VIT_CASH: + case SC_FOOD_DEX_CASH: + case SC_FOOD_INT_CASH: + case SC_FOOD_LUK_CASH: + case SC_DEF_RATE: + case SC_MDEF_RATE: + case SC_INCHEALRATE: + case SC_INCFLEE2: + case SC_INCHIT: + case SC_ATKPOTION: + case SC_MATKPOTION: + case SC_S_LIFEPOTION: + case SC_L_LIFEPOTION: + case SC_PUSH_CART: + continue; + + } + + if( type == 3 ) + { + switch (i) + {// TODO: This list may be incomplete + case SC_WEIGHT50: + case SC_WEIGHT90: + case SC_NOCHAT: + case SC_PUSH_CART: + continue; + } + } + + status_change_end(bl, (sc_type)i, INVALID_TIMER); + + if( type == 1 && sc->data[i] ) + { //If for some reason status_change_end decides to still keep the status when quitting. [Skotlex] + (sc->count)--; + if (sc->data[i]->timer != INVALID_TIMER) + delete_timer(sc->data[i]->timer, status_change_timer); + ers_free(sc_data_ers, sc->data[i]); + sc->data[i] = NULL; + } + } + + sc->opt1 = 0; + sc->opt2 = 0; + sc->opt3 = 0; + sc->option &= OPTION_MASK; + + if( type == 0 || type == 2 ) + clif_changeoption(bl); + + return 1; +} + +/*========================================== + * Special condition we want to effectuate, check before ending a status. + *------------------------------------------*/ +int status_change_end_(struct block_list* bl, enum sc_type type, int tid, const char* file, int line) +{ + struct map_session_data *sd; + struct status_change *sc; + struct status_change_entry *sce; + struct status_data *status; + struct view_data *vd; + int opt_flag=0, calc_flag; + + nullpo_ret(bl); + + sc = status_get_sc(bl); + status = status_get_status_data(bl); + + if(type < 0 || type >= SC_MAX || !sc || !(sce = sc->data[type])) + return 0; + + sd = BL_CAST(BL_PC,bl); + + if (sce->timer != tid && tid != INVALID_TIMER) + return 0; + + if (tid == INVALID_TIMER) { + if (type == SC_ENDURE && sce->val4) + //Do not end infinite endure. + return 0; + if (sce->timer != INVALID_TIMER) //Could be a SC with infinite duration + delete_timer(sce->timer,status_change_timer); + if (sc->opt1) + switch (type) { + //"Ugly workaround" [Skotlex] + //delays status change ending so that a skill that sets opt1 fails to + //trigger when it also removed one + case SC_STONE: + sce->val3 = 0; //Petrify time counter. + case SC_FREEZE: + case SC_STUN: + case SC_SLEEP: + if (sce->val1) { + //Removing the 'level' shouldn't affect anything in the code + //since these SC are not affected by it, and it lets us know + //if we have already delayed this attack or not. + sce->val1 = 0; + sce->timer = add_timer(gettick()+10, status_change_timer, bl->id, type); + return 1; + } + } + } + + (sc->count)--; + + if ( StatusChangeStateTable[type] ) + status_calc_state(bl,sc,( enum scs_flag ) StatusChangeStateTable[type],false); + + sc->data[type] = NULL; + + vd = status_get_viewdata(bl); + calc_flag = StatusChangeFlagTable[type]; + switch(type){ + case SC_GRANITIC_ARMOR:{ + int dammage = status->max_hp*sce->val3/100; + if(status->hp < dammage) //to not kill him + dammage = status->hp-1; + status_damage(NULL, bl, dammage,0,0,1); + break; + } + case SC_PYROCLASTIC: + if(bl->type == BL_PC) + skill_break_equip(bl,EQP_WEAPON,10000,BCT_SELF); + break; + case SC_WEDDING: + case SC_XMAS: + case SC_SUMMER: + if (!vd) break; + if (sd) + { //Load data from sd->status.* as the stored values could have changed. + //Must remove OPTION to prevent class being rechanged. + sc->option &= type==SC_WEDDING?~OPTION_WEDDING:type==SC_XMAS?~OPTION_XMAS:~OPTION_SUMMER; + clif_changeoption(&sd->bl); + status_set_viewdata(bl, sd->status.class_); + } else { + vd->class_ = sce->val1; + vd->weapon = sce->val2; + vd->shield = sce->val3; + vd->cloth_color = sce->val4; + } + clif_changelook(bl,LOOK_BASE,vd->class_); + clif_changelook(bl,LOOK_CLOTHES_COLOR,vd->cloth_color); + clif_changelook(bl,LOOK_WEAPON,vd->weapon); + clif_changelook(bl,LOOK_SHIELD,vd->shield); + if(sd) clif_skillinfoblock(sd); + break; + case SC_RUN: + { + struct unit_data *ud = unit_bl2ud(bl); + bool begin_spurt = true; + if (ud) { + if(!ud->state.running) + begin_spurt = false; + ud->state.running = 0; + if (ud->walktimer != INVALID_TIMER) + unit_stop_walking(bl,1); + } + if (begin_spurt && sce->val1 >= 7 && + DIFF_TICK(gettick(), sce->val4) <= 1000 && + (!sd || (sd->weapontype1 == 0 && sd->weapontype2 == 0)) + ) + sc_start(bl,SC_SPURT,100,sce->val1,skill_get_time2(status_sc2skill(type), sce->val1)); + } + break; + case SC_AUTOBERSERK: + if (sc->data[SC_PROVOKE] && sc->data[SC_PROVOKE]->val2 == 1) + status_change_end(bl, SC_PROVOKE, INVALID_TIMER); + break; + + case SC_ENDURE: + case SC_DEFENDER: + case SC_REFLECTSHIELD: + case SC_AUTOGUARD: + { + struct map_session_data *tsd; + if( bl->type == BL_PC ) + { // Clear Status from others + int i; + for( i = 0; i < 5; i++ ) + { + if( sd->devotion[i] && (tsd = map_id2sd(sd->devotion[i])) && tsd->sc.data[type] ) + status_change_end(&tsd->bl, type, INVALID_TIMER); + } + } + else if( bl->type == BL_MER && ((TBL_MER*)bl)->devotion_flag ) + { // Clear Status from Master + tsd = ((TBL_MER*)bl)->master; + if( tsd && tsd->sc.data[type] ) + status_change_end(&tsd->bl, type, INVALID_TIMER); + } + } + break; + case SC_DEVOTION: + { + struct block_list *d_bl = map_id2bl(sce->val1); + if( d_bl ) + { + if( d_bl->type == BL_PC ) + ((TBL_PC*)d_bl)->devotion[sce->val2] = 0; + else if( d_bl->type == BL_MER ) + ((TBL_MER*)d_bl)->devotion_flag = 0; + clif_devotion(d_bl, NULL); + } + + status_change_end(bl, SC_AUTOGUARD, INVALID_TIMER); + status_change_end(bl, SC_DEFENDER, INVALID_TIMER); + status_change_end(bl, SC_REFLECTSHIELD, INVALID_TIMER); + status_change_end(bl, SC_ENDURE, INVALID_TIMER); + } + break; + + case SC_BLADESTOP: + if(sce->val4) + { + int tid = sce->val4; + struct block_list *tbl = map_id2bl(tid); + struct status_change *tsc = status_get_sc(tbl); + sce->val4 = 0; + if(tbl && tsc && tsc->data[SC_BLADESTOP]) + { + tsc->data[SC_BLADESTOP]->val4 = 0; + status_change_end(tbl, SC_BLADESTOP, INVALID_TIMER); + } + clif_bladestop(bl, tid, 0); + } + break; + case SC_DANCING: + { + const char* prevfile = "<unknown>"; + int prevline = 0; + struct map_session_data *dsd; + struct status_change_entry *dsc; + struct skill_unit_group *group; + + if( sd ) + { + if( sd->delunit_prevfile ) + {// initially this is NULL, when a character logs in + prevfile = sd->delunit_prevfile; + prevline = sd->delunit_prevline; + } + else + { + prevfile = "<none>"; + } + sd->delunit_prevfile = file; + sd->delunit_prevline = line; + } + + if(sce->val4 && sce->val4 != BCT_SELF && (dsd=map_id2sd(sce->val4))) + {// end status on partner as well + dsc = dsd->sc.data[SC_DANCING]; + if(dsc) { + + //This will prevent recursive loops. + dsc->val2 = dsc->val4 = 0; + + status_change_end(&dsd->bl, SC_DANCING, INVALID_TIMER); + } + } + + if(sce->val2) + {// erase associated land skill + group = skill_id2group(sce->val2); + + if( group == NULL ) + { + ShowDebug("status_change_end: SC_DANCING is missing skill unit group (val1=%d, val2=%d, val3=%d, val4=%d, timer=%d, tid=%d, char_id=%d, map=%s, x=%d, y=%d, prev=%s:%d, from=%s:%d). Please report this! (#3504)\n", + sce->val1, sce->val2, sce->val3, sce->val4, sce->timer, tid, + sd ? sd->status.char_id : 0, + mapindex_id2name(map_id2index(bl->m)), bl->x, bl->y, + prevfile, prevline, + file, line); + } + + sce->val2 = 0; + skill_delunitgroup(group); + } + + if((sce->val1&0xFFFF) == CG_MOONLIT) + clif_status_change(bl,SI_MOONLIT,0,0,0,0,0); + + status_change_end(bl, SC_LONGING, INVALID_TIMER); + } + break; + case SC_NOCHAT: + if (sd && sd->status.manner < 0 && tid != INVALID_TIMER) + sd->status.manner = 0; + if (sd && tid == INVALID_TIMER) + { + clif_changestatus(sd,SP_MANNER,sd->status.manner); + clif_updatestatus(sd,SP_MANNER); + } + break; + case SC_SPLASHER: + { + struct block_list *src=map_id2bl(sce->val3); + if(src && tid != INVALID_TIMER) + skill_castend_damage_id(src, bl, sce->val2, sce->val1, gettick(), SD_LEVEL ); + } + break; + case SC_CLOSECONFINE2: + { + struct block_list *src = sce->val2?map_id2bl(sce->val2):NULL; + struct status_change *sc2 = src?status_get_sc(src):NULL; + if (src && sc2 && sc2->data[SC_CLOSECONFINE]) { + //If status was already ended, do nothing. + //Decrease count + if (--(sc2->data[SC_CLOSECONFINE]->val1) <= 0) //No more holds, free him up. + status_change_end(src, SC_CLOSECONFINE, INVALID_TIMER); + } + } + case SC_CLOSECONFINE: + if (sce->val2 > 0) { + //Caster has been unlocked... nearby chars need to be unlocked. + int range = 1 + +skill_get_range2(bl, status_sc2skill(type), sce->val1) + +skill_get_range2(bl, TF_BACKSLIDING, 1); //Since most people use this to escape the hold.... + map_foreachinarea(status_change_timer_sub, + bl->m, bl->x-range, bl->y-range, bl->x+range,bl->y+range,BL_CHAR,bl,sce,type,gettick()); + } + break; + case SC_COMBO: + if( sd ) + switch (sce->val1) { + case MO_COMBOFINISH: + case CH_TIGERFIST: + case CH_CHAINCRUSH: + clif_skillinfo(sd, MO_EXTREMITYFIST, 0); + break; + case TK_JUMPKICK: + clif_skillinfo(sd, TK_JUMPKICK, 0); + break; + case MO_TRIPLEATTACK: + if (pc_checkskill(sd, SR_DRAGONCOMBO) > 0) + clif_skillinfo(sd, SR_DRAGONCOMBO, 0); + break; + case SR_FALLENEMPIRE: + clif_skillinfo(sd, SR_GATEOFHELL, 0); + clif_skillinfo(sd, SR_TIGERCANNON, 0); + break; + } + break; + + case SC_MARIONETTE: + case SC_MARIONETTE2: /// Marionette target + if (sce->val1) + { // check for partner and end their marionette status as well + enum sc_type type2 = (type == SC_MARIONETTE) ? SC_MARIONETTE2 : SC_MARIONETTE; + struct block_list *pbl = map_id2bl(sce->val1); + struct status_change* sc2 = pbl?status_get_sc(pbl):NULL; + + if (sc2 && sc2->data[type2]) + { + sc2->data[type2]->val1 = 0; + status_change_end(pbl, type2, INVALID_TIMER); + } + } + break; + + case SC_BERSERK: + case SC_SATURDAYNIGHTFEVER: + //If val2 is removed, no HP penalty (dispelled?) [Skotlex] + if (status->hp > 100 && sce->val2) + status_set_hp(bl, 100, 0); + if(sc->data[SC_ENDURE] && sc->data[SC_ENDURE]->val4 == 2) + { + sc->data[SC_ENDURE]->val4 = 0; + status_change_end(bl, SC_ENDURE, INVALID_TIMER); + } + case SC__BLOODYLUST: + sc_start4(bl, SC_REGENERATION, 100, 10,0,0,(RGN_HP|RGN_SP), skill_get_time(LK_BERSERK, sce->val1)); + if( type == SC_SATURDAYNIGHTFEVER ) //Sit down force of Saturday Night Fever has the duration of only 3 seconds. + sc_start(bl,SC_SITDOWN_FORCE,100,sce->val1,skill_get_time2(WM_SATURDAY_NIGHT_FEVER,sce->val1)); + break; + case SC_GOSPEL: + if (sce->val3) { //Clear the group. + struct skill_unit_group* group = skill_id2group(sce->val3); + sce->val3 = 0; + skill_delunitgroup(group); + } + break; + case SC_HERMODE: + if(sce->val3 == BCT_SELF) + skill_clear_unitgroup(bl); + break; + case SC_BASILICA: //Clear the skill area. [Skotlex] + skill_clear_unitgroup(bl); + break; + case SC_TRICKDEAD: + if (vd) vd->dead_sit = 0; + break; + case SC_WARM: + case SC__MANHOLE: + if (sce->val4) { //Clear the group. + struct skill_unit_group* group = skill_id2group(sce->val4); + sce->val4 = 0; + if( group ) /* might have been cleared before status ended, e.g. land protector */ + skill_delunitgroup(group); + } + break; + case SC_KAAHI: + //Delete timer if it exists. + if (sce->val4 != INVALID_TIMER) + delete_timer(sce->val4,kaahi_heal_timer); + break; + case SC_JAILED: + if(tid == INVALID_TIMER) + break; + //natural expiration. + if(sd && sd->mapindex == sce->val2) + pc_setpos(sd,(unsigned short)sce->val3,sce->val4&0xFFFF, sce->val4>>16, CLR_TELEPORT); + break; //guess hes not in jail :P + case SC_CHANGE: + if (tid == INVALID_TIMER) + break; + // "lose almost all their HP and SP" on natural expiration. + status_set_hp(bl, 10, 0); + status_set_sp(bl, 10, 0); + break; + case SC_AUTOTRADE: + if (tid == INVALID_TIMER) + break; + // Note: vending/buying is closed by unit_remove_map, no + // need to do it here. + map_quit(sd); + // Because map_quit calls status_change_end with tid -1 + // from here it's not neccesary to continue + return 1; + break; + case SC_STOP: + if( sce->val2 ) + { + struct block_list* tbl = map_id2bl(sce->val2); + sce->val2 = 0; + if( tbl && (sc = status_get_sc(tbl)) && sc->data[SC_STOP] && sc->data[SC_STOP]->val2 == bl->id ) + status_change_end(tbl, SC_STOP, INVALID_TIMER); + } + break; + /** + * 3rd Stuff + **/ + case SC_MILLENNIUMSHIELD: + clif_millenniumshield(sd,0); + break; + case SC_HALLUCINATIONWALK: + sc_start(bl,SC_HALLUCINATIONWALK_POSTDELAY,100,sce->val1,skill_get_time2(GC_HALLUCINATIONWALK,sce->val1)); + break; + case SC_WHITEIMPRISON: + { + struct block_list* src = map_id2bl(sce->val2); + if( tid == -1 || !src) + break; // Terminated by Damage + status_fix_damage(src,bl,400*sce->val1,clif_damage(bl,bl,gettick(),0,0,400*sce->val1,0,0,0)); + } + break; + case SC_WUGDASH: + { + struct unit_data *ud = unit_bl2ud(bl); + if (ud) { + ud->state.running = 0; + if (ud->walktimer != -1) + unit_stop_walking(bl,1); + } + } + break; + case SC_ADORAMUS: + status_change_end(bl, SC_BLIND, INVALID_TIMER); + break; + case SC__SHADOWFORM: { + struct map_session_data *s_sd = map_id2sd(sce->val2); + if( !s_sd ) + break; + s_sd->shadowform_id = 0; + } + break; + case SC_SITDOWN_FORCE: + if( sd && pc_issit(sd) ) { + pc_setstand(sd); + clif_standing(bl); + } + break; + case SC_NEUTRALBARRIER_MASTER: + case SC_STEALTHFIELD_MASTER: + if( sce->val2 ) { + struct skill_unit_group* group = skill_id2group(sce->val2); + sce->val2 = 0; + if( group ) /* might have been cleared before status ended, e.g. land protector */ + skill_delunitgroup(group); + } + break; + case SC_BANDING: + if(sce->val4) { + struct skill_unit_group *group = skill_id2group(sce->val4); + sce->val4 = 0; + if( group ) /* might have been cleared before status ended, e.g. land protector */ + skill_delunitgroup(group); + } + break; + case SC_CURSEDCIRCLE_ATKER: + if( sce->val2 ) // used the default area size cause there is a chance the caster could knock back and can't clear the target. + map_foreachinrange(status_change_timer_sub, bl, battle_config.area_size,BL_CHAR, bl, sce, SC_CURSEDCIRCLE_TARGET, gettick()); + break; + case SC_RAISINGDRAGON: + if( sd && sce->val2 && !pc_isdead(sd) ) { + int i; + i = min(sd->spiritball,5); + pc_delspiritball(sd, sd->spiritball, 0); + status_change_end(bl, SC_EXPLOSIONSPIRITS, INVALID_TIMER); + while( i > 0 ) { + pc_addspiritball(sd, skill_get_time(MO_CALLSPIRITS, pc_checkskill(sd,MO_CALLSPIRITS)), 5); + --i; + } + } + break; + case SC_CURSEDCIRCLE_TARGET: + { + struct block_list *src = map_id2bl(sce->val2); + struct status_change *sc = status_get_sc(src); + if( sc && sc->data[SC_CURSEDCIRCLE_ATKER] && --(sc->data[SC_CURSEDCIRCLE_ATKER]->val2) == 0 ){ + status_change_end(src, SC_CURSEDCIRCLE_ATKER, INVALID_TIMER); + clif_bladestop(bl, sce->val2, 0); + } + } + break; + case SC_BLOODSUCKER: + if( sce->val2 ){ + struct block_list *src = map_id2bl(sce->val2); + if(src){ + struct status_change *sc = status_get_sc(src); + sc->bs_counter--; + } + } + break; + case SC_VACUUM_EXTREME: + if(sc && sc->cant.move > 0) sc->cant.move--; + break; + case SC_KYOUGAKU: + clif_status_load(bl, SI_KYOUGAKU, 0); // Avoid client crash + clif_status_load(bl, SI_ACTIVE_MONSTER_TRANSFORM, 0); + break; + case SC_INTRAVISION: + calc_flag = SCB_ALL;/* required for overlapping */ + break; + } + + opt_flag = 1; + switch(type){ + case SC_STONE: + case SC_FREEZE: + case SC_STUN: + case SC_SLEEP: + case SC_DEEPSLEEP: + case SC_BURNING: + case SC_WHITEIMPRISON: + case SC_CRYSTALIZE: + sc->opt1 = 0; + break; + + case SC_POISON: + case SC_CURSE: + case SC_SILENCE: + case SC_BLIND: + sc->opt2 &= ~(1<<(type-SC_POISON)); + break; + case SC_DPOISON: + sc->opt2 &= ~OPT2_DPOISON; + break; + case SC_SIGNUMCRUCIS: + sc->opt2 &= ~OPT2_SIGNUMCRUCIS; + break; + + case SC_HIDING: + sc->option &= ~OPTION_HIDE; + opt_flag|= 2|4; //Check for warp trigger + AoE trigger + break; + case SC_CLOAKING: + case SC_CLOAKINGEXCEED: + case SC__INVISIBILITY: + sc->option &= ~OPTION_CLOAK; + case SC_CAMOUFLAGE: + opt_flag|= 2; + break; + case SC_CHASEWALK: + sc->option &= ~(OPTION_CHASEWALK|OPTION_CLOAK); + opt_flag|= 2; + break; + case SC_SIGHT: + sc->option &= ~OPTION_SIGHT; + break; + case SC_WEDDING: + sc->option &= ~OPTION_WEDDING; + break; + case SC_XMAS: + sc->option &= ~OPTION_XMAS; + break; + case SC_SUMMER: + sc->option &= ~OPTION_SUMMER; + break; + case SC_ORCISH: + sc->option &= ~OPTION_ORCISH; + break; + case SC_RUWACH: + sc->option &= ~OPTION_RUWACH; + break; + case SC_FUSION: + sc->option &= ~OPTION_FLYING; + break; + //opt3 + case SC_TWOHANDQUICKEN: + case SC_ONEHAND: + case SC_SPEARQUICKEN: + case SC_CONCENTRATION: + case SC_MERC_QUICKEN: + sc->opt3 &= ~OPT3_QUICKEN; + opt_flag = 0; + break; + case SC_OVERTHRUST: + case SC_MAXOVERTHRUST: + case SC_SWOO: + sc->opt3 &= ~OPT3_OVERTHRUST; + if( type == SC_SWOO ) + opt_flag = 8; + else + opt_flag = 0; + break; + case SC_ENERGYCOAT: + case SC_SKE: + sc->opt3 &= ~OPT3_ENERGYCOAT; + opt_flag = 0; + break; + case SC_INCATKRATE: //Simulated Explosion spirits effect. + if (bl->type != BL_MOB) + { + opt_flag = 0; + break; + } + case SC_EXPLOSIONSPIRITS: + sc->opt3 &= ~OPT3_EXPLOSIONSPIRITS; + opt_flag = 0; + break; + case SC_STEELBODY: + case SC_SKA: + sc->opt3 &= ~OPT3_STEELBODY; + opt_flag = 0; + break; + case SC_BLADESTOP: + sc->opt3 &= ~OPT3_BLADESTOP; + opt_flag = 0; + break; + case SC_AURABLADE: + sc->opt3 &= ~OPT3_AURABLADE; + opt_flag = 0; + break; + case SC_BERSERK: + opt_flag = 0; +// case SC__BLOODYLUST: + sc->opt3 &= ~OPT3_BERSERK; + break; +// case ???: // doesn't seem to do anything +// sc->opt3 &= ~OPT3_LIGHTBLADE; +// opt_flag = 0; +// break; + case SC_DANCING: + if ((sce->val1&0xFFFF) == CG_MOONLIT) + sc->opt3 &= ~OPT3_MOONLIT; + opt_flag = 0; + break; + case SC_MARIONETTE: + case SC_MARIONETTE2: + sc->opt3 &= ~OPT3_MARIONETTE; + opt_flag = 0; + break; + case SC_ASSUMPTIO: + sc->opt3 &= ~OPT3_ASSUMPTIO; + opt_flag = 0; + break; + case SC_WARM: //SG skills [Komurka] + sc->opt3 &= ~OPT3_WARM; + opt_flag = 0; + break; + case SC_KAITE: + sc->opt3 &= ~OPT3_KAITE; + opt_flag = 0; + break; + case SC_BUNSINJYUTSU: + sc->opt3 &= ~OPT3_BUNSIN; + opt_flag = 0; + break; + case SC_SPIRIT: + sc->opt3 &= ~OPT3_SOULLINK; + opt_flag = 0; + break; + case SC_CHANGEUNDEAD: + sc->opt3 &= ~OPT3_UNDEAD; + opt_flag = 0; + break; +// case ???: // from DA_CONTRACT (looks like biolab mobs aura) +// sc->opt3 &= ~OPT3_CONTRACT; +// opt_flag = 0; +// break; + default: + opt_flag = 0; + } + + if (calc_flag&SCB_DYE) + { //Restore DYE color + if (vd && !vd->cloth_color && sce->val4) + clif_changelook(bl,LOOK_CLOTHES_COLOR,sce->val4); + calc_flag&=~SCB_DYE; + } + + //On Aegis, when turning off a status change, first goes the sc packet, then the option packet. + clif_status_change(bl,StatusIconChangeTable[type],0,0,0,0,0); + + if( opt_flag&8 ) //bugreport:681 + clif_changeoption2(bl); + else if(opt_flag) + clif_changeoption(bl); + + if (calc_flag) + status_calc_bl(bl,calc_flag); + + if(opt_flag&4) //Out of hiding, invoke on place. + skill_unit_move(bl,gettick(),1); + + if(opt_flag&2 && sd && map_getcell(bl->m,bl->x,bl->y,CELL_CHKNPC)) + npc_touch_areanpc(sd,bl->m,bl->x,bl->y); //Trigger on-touch event. + + ers_free(sc_data_ers, sce); + return 1; +} + +int kaahi_heal_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + struct block_list *bl; + struct status_change *sc; + struct status_change_entry *sce; + struct status_data *status; + int hp; + + if(!((bl=map_id2bl(id))&& + (sc=status_get_sc(bl)) && + (sce = sc->data[SC_KAAHI]))) + return 0; + + if(sce->val4 != tid) { + ShowError("kaahi_heal_timer: Timer mismatch: %d != %d\n", tid, sce->val4); + sce->val4 = INVALID_TIMER; + return 0; + } + + status=status_get_status_data(bl); + if(!status_charge(bl, 0, sce->val3)) { + sce->val4 = INVALID_TIMER; + return 0; + } + + hp = status->max_hp - status->hp; + if (hp > sce->val2) + hp = sce->val2; + if (hp) + status_heal(bl, hp, 0, 2); + sce->val4 = INVALID_TIMER; + return 1; +} + +/*========================================== + * For recusive status, like for each 5s we drop sp etc. + * Reseting the end timer. + *------------------------------------------*/ +int status_change_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + enum sc_type type = (sc_type)data; + struct block_list *bl; + struct map_session_data *sd; + struct status_data *status; + struct status_change *sc; + struct status_change_entry *sce; + + bl = map_id2bl(id); + if(!bl) + { + ShowDebug("status_change_timer: Null pointer id: %d data: %d\n", id, data); + return 0; + } + sc = status_get_sc(bl); + status = status_get_status_data(bl); + + if(!(sc && (sce = sc->data[type]))) + { + ShowDebug("status_change_timer: Null pointer id: %d data: %d bl-type: %d\n", id, data, bl->type); + return 0; + } + + if( sce->timer != tid ) + { + ShowError("status_change_timer: Mismatch for type %d: %d != %d (bl id %d)\n",type,tid,sce->timer, bl->id); + return 0; + } + + sd = BL_CAST(BL_PC, bl); + +// set the next timer of the sce (don't assume the status still exists) +#define sc_timer_next(t,f,i,d) \ + if( (sce=sc->data[type]) ) \ + sce->timer = add_timer(t,f,i,d); \ + else \ + ShowError("status_change_timer: Unexpected NULL status change id: %d data: %d\n", id, data) + + switch(type) + { + case SC_MAXIMIZEPOWER: + case SC_CLOAKING: + if(!status_charge(bl, 0, 1)) + break; //Not enough SP to continue. + sc_timer_next(sce->val2+tick, status_change_timer, bl->id, data); + return 0; + + case SC_CHASEWALK: + if(!status_charge(bl, 0, sce->val4)) + break; //Not enough SP to continue. + + if (!sc->data[SC_INCSTR]) { + sc_start(bl, SC_INCSTR,100,1<<(sce->val1-1), + (sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_ROGUE?10:1) //SL bonus -> x10 duration + *skill_get_time2(status_sc2skill(type),sce->val1)); + } + sc_timer_next(sce->val2+tick, status_change_timer, bl->id, data); + return 0; + break; + + case SC_SKA: + if(--(sce->val2)>0){ + sce->val3 = rnd()%100; //Random defense. + sc_timer_next(1000+tick, status_change_timer,bl->id, data); + return 0; + } + break; + + case SC_HIDING: + if(--(sce->val2)>0){ + + if(sce->val2 % sce->val4 == 0 && !status_charge(bl, 0, 1)) + break; //Fail if it's time to substract SP and there isn't. + + sc_timer_next(1000+tick, status_change_timer,bl->id, data); + return 0; + } + break; + + case SC_SIGHT: + case SC_RUWACH: + case SC_SIGHTBLASTER: + if(type == SC_SIGHTBLASTER) + map_foreachinrange( status_change_timer_sub, bl, sce->val3, BL_CHAR|BL_SKILL, bl, sce, type, tick); + else + map_foreachinrange( status_change_timer_sub, bl, sce->val3, BL_CHAR, bl, sce, type, tick); + + if( --(sce->val2)>0 ){ + sce->val4 += 250; // use for Shadow Form 2 seconds checking. + sc_timer_next(250+tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_PROVOKE: + if(sce->val2) { //Auto-provoke (it is ended in status_heal) + sc_timer_next(1000*60+tick,status_change_timer, bl->id, data ); + return 0; + } + break; + + case SC_STONE: + if(sc->opt1 == OPT1_STONEWAIT && sce->val3) { + sce->val4 = 0; + unit_stop_walking(bl,1); + unit_stop_attack(bl); + sc->opt1 = OPT1_STONE; + clif_changeoption(bl); + sc_timer_next(1000+tick,status_change_timer, bl->id, data ); + status_calc_bl(bl, StatusChangeFlagTable[type]); + return 0; + } + if(--(sce->val3) > 0) { + if(++(sce->val4)%5 == 0 && status->hp > status->max_hp/4) + status_percent_damage(NULL, bl, 1, 0, false); + sc_timer_next(1000+tick,status_change_timer, bl->id, data ); + return 0; + } + break; + + case SC_POISON: + if(status->hp <= max(status->max_hp>>2, sce->val4)) //Stop damaging after 25% HP left. + break; + case SC_DPOISON: + if (--(sce->val3) > 0) { + if (!sc->data[SC_SLOWPOISON]) { + if( sce->val2 && bl->type == BL_MOB ) { + struct block_list* src = map_id2bl(sce->val2); + if( src ) + mob_log_damage((TBL_MOB*)bl,src,sce->val4); + } + map_freeblock_lock(); + status_zap(bl, sce->val4, 0); + if (sc->data[type]) { // Check if the status still last ( can be dead since then ). + sc_timer_next(1000 + tick, status_change_timer, bl->id, data ); + } + map_freeblock_unlock(); + } + return 0; + } + break; + + case SC_TENSIONRELAX: + if(status->max_hp > status->hp && --(sce->val3) > 0){ + sc_timer_next(sce->val4+tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_KNOWLEDGE: + if (!sd) break; + if(bl->m == sd->feel_map[0].m || + bl->m == sd->feel_map[1].m || + bl->m == sd->feel_map[2].m) + { //Timeout will be handled by pc_setpos + sce->timer = INVALID_TIMER; + return 0; + } + break; + + case SC_BLEEDING: + if (--(sce->val4) >= 0) { + int hp = rnd()%600 + 200; + map_freeblock_lock(); + status_fix_damage(NULL, bl, sd||hp<status->hp?hp:status->hp-1, 1); + if( sc->data[type] ) { + if( status->hp == 1 ) { + map_freeblock_unlock(); + break; + } + sc_timer_next(10000 + tick, status_change_timer, bl->id, data); + } + map_freeblock_unlock(); + return 0; + } + break; + + case SC_S_LIFEPOTION: + case SC_L_LIFEPOTION: + if( sd && --(sce->val4) >= 0 ) + { + // val1 < 0 = per max% | val1 > 0 = exact amount + int hp = 0; + if( status->hp < status->max_hp ) + hp = (sce->val1 < 0) ? (int)(sd->status.max_hp * -1 * sce->val1 / 100.) : sce->val1 ; + status_heal(bl, hp, 0, 2); + sc_timer_next((sce->val2 * 1000) + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_BOSSMAPINFO: + if( sd && --(sce->val4) >= 0 ) + { + struct mob_data *boss_md = map_id2boss(sce->val1); + if( boss_md && sd->bl.m == boss_md->bl.m ) + { + clif_bossmapinfo(sd->fd, boss_md, 1); // Update X - Y on minimap + if (boss_md->bl.prev != NULL) { + sc_timer_next(5000 + tick, status_change_timer, bl->id, data); + return 0; + } + } + } + break; + + case SC_DANCING: //SP consumption by time of dancing skills + { + int s = 0; + int sp = 1; + if (--sce->val3 <= 0) + break; + switch(sce->val1&0xFFFF){ + case BD_RICHMANKIM: + case BD_DRUMBATTLEFIELD: + case BD_RINGNIBELUNGEN: + case BD_SIEGFRIED: + case BA_DISSONANCE: + case BA_ASSASSINCROSS: + case DC_UGLYDANCE: + s=3; + break; + case BD_LULLABY: + case BD_ETERNALCHAOS: + case BD_ROKISWEIL: + case DC_FORTUNEKISS: + s=4; + break; + case CG_HERMODE: + case BD_INTOABYSS: + case BA_WHISTLE: + case DC_HUMMING: + case BA_POEMBRAGI: + case DC_SERVICEFORYOU: + s=5; + break; + case BA_APPLEIDUN: + #ifdef RENEWAL + s=5; + #else + s=6; + #endif + break; + case CG_MOONLIT: + //Moonlit's cost is 4sp*skill_lv [Skotlex] + sp= 4*(sce->val1>>16); + //Upkeep is also every 10 secs. + case DC_DONTFORGETME: + s=10; + break; + } + if( s != 0 && sce->val3 % s == 0 ) + { + if (sc->data[SC_LONGING]) + sp*= 3; + if (!status_charge(bl, 0, sp)) + break; + } + sc_timer_next(1000+tick, status_change_timer, bl->id, data); + return 0; + } + break; + case SC__BLOODYLUST: + case SC_BERSERK: + // 5% every 10 seconds [DracoRPG] + if( --( sce->val3 ) > 0 && status_charge(bl, sce->val2, 0) && status->hp > 100 ) + { + sc_timer_next(sce->val4+tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_NOCHAT: + if(sd){ + sd->status.manner++; + clif_changestatus(sd,SP_MANNER,sd->status.manner); + clif_updatestatus(sd,SP_MANNER); + if (sd->status.manner < 0) + { //Every 60 seconds your manner goes up by 1 until it gets back to 0. + sc_timer_next(60000+tick, status_change_timer, bl->id, data); + return 0; + } + } + break; + + case SC_SPLASHER: + // custom Venom Splasher countdown timer + //if (sce->val4 % 1000 == 0) { + // char timer[10]; + // snprintf (timer, 10, "%d", sce->val4/1000); + // clif_message(bl, timer); + //} + if((sce->val4 -= 500) > 0) { + sc_timer_next(500 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_MARIONETTE: + case SC_MARIONETTE2: + { + struct block_list *pbl = map_id2bl(sce->val1); + if( pbl && check_distance_bl(bl, pbl, 7) ) + { + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + } + } + break; + + case SC_GOSPEL: + if(sce->val4 == BCT_SELF && --(sce->val2) > 0) + { + int hp, sp; + hp = (sce->val1 > 5) ? 45 : 30; + sp = (sce->val1 > 5) ? 35 : 20; + if(!status_charge(bl, hp, sp)) + break; + sc_timer_next(10000+tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_JAILED: + if(sce->val1 == INT_MAX || --(sce->val1) > 0) + { + sc_timer_next(60000+tick, status_change_timer, bl->id,data); + return 0; + } + break; + + case SC_BLIND: + if(sc->data[SC_FOGWALL]) + { //Blind lasts forever while you are standing on the fog. + sc_timer_next(5000+tick, status_change_timer, bl->id, data); + return 0; + } + break; + case SC_ABUNDANCE: + if(--(sce->val4) > 0) { + status_heal(bl,0,60,0); + sc_timer_next(10000+tick, status_change_timer, bl->id, data); + } + break; + + case SC_PYREXIA: + if( --(sce->val4) >= 0 ) { + map_freeblock_lock(); + clif_damage(bl,bl,tick,status_get_amotion(bl),status_get_dmotion(bl)+500,100,0,0,0); + status_fix_damage(NULL,bl,100,0); + if( sc->data[type] ) { + sc_timer_next(3000+tick,status_change_timer,bl->id,data); + } + map_freeblock_unlock(); + return 0; + } + break; + + case SC_LEECHESEND: + if( --(sce->val4) >= 0 ) { + int damage = status->max_hp/100; // {Target VIT x (New Poison Research Skill Level - 3)} + (Target HP/100) + damage += status->vit * (sce->val1 - 3); + unit_skillcastcancel(bl,2); + map_freeblock_lock(); + status_damage(bl, bl, damage, 0, clif_damage(bl,bl,tick,status_get_amotion(bl),status_get_dmotion(bl)+500,damage,1,0,0), 1); + if( sc->data[type] ) { + sc_timer_next(1000 + tick, status_change_timer, bl->id, data ); + } + map_freeblock_unlock(); + return 0; + } + break; + + case SC_MAGICMUSHROOM: + if( --(sce->val4) >= 0 ) { + bool flag = 0; + int damage = status->max_hp * 3 / 100; + if( status->hp <= damage ) + damage = status->hp - 1; // Cannot Kill + + if( damage > 0 ) { // 3% Damage each 4 seconds + map_freeblock_lock(); + status_zap(bl,damage,0); + flag = !sc->data[type]; // Killed? Should not + map_freeblock_unlock(); + } + + if( !flag ) { // Random Skill Cast + if (sd && !pc_issit(sd)) { //can't cast if sit + int mushroom_skill_id = 0, i; + unit_stop_attack(bl); + unit_skillcastcancel(bl,1); + do { + i = rnd() % MAX_SKILL_MAGICMUSHROOM_DB; + mushroom_skill_id = skill_magicmushroom_db[i].skill_id; + } + while( mushroom_skill_id == 0 ); + + switch( skill_get_casttype(mushroom_skill_id) ) { // Magic Mushroom skills are buffs or area damage + case CAST_GROUND: + skill_castend_pos2(bl,bl->x,bl->y,mushroom_skill_id,1,tick,0); + break; + case CAST_NODAMAGE: + skill_castend_nodamage_id(bl,bl,mushroom_skill_id,1,tick,0); + break; + case CAST_DAMAGE: + skill_castend_damage_id(bl,bl,mushroom_skill_id,1,tick,0); + break; + } + } + + clif_emotion(bl,E_HEH); + sc_timer_next(4000+tick,status_change_timer,bl->id,data); + } + return 0; + } + break; + + case SC_TOXIN: + if( --(sce->val4) >= 0 ) + { //Damage is every 10 seconds including 3%sp drain. + map_freeblock_lock(); + clif_damage(bl,bl,tick,status_get_amotion(bl),1,1,0,0,0); + status_damage(NULL, bl, 1, status->max_sp * 3 / 100, 0, 0); //cancel dmg only if cancelable + if( sc->data[type] ) { + sc_timer_next(10000 + tick, status_change_timer, bl->id, data ); + } + map_freeblock_unlock(); + return 0; + } + break; + + case SC_OBLIVIONCURSE: + if( --(sce->val4) >= 0 ) + { + clif_emotion(bl,E_WHAT); + sc_timer_next(3000 + tick, status_change_timer, bl->id, data ); + return 0; + } + break; + + case SC_WEAPONBLOCKING: + if( --(sce->val4) >= 0 ) + { + if( !status_charge(bl,0,3) ) + break; + sc_timer_next(3000+tick,status_change_timer,bl->id,data); + return 0; + } + break; + + case SC_CLOAKINGEXCEED: + if(!status_charge(bl,0,10-sce->val1)) + break; + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + + case SC_RENOVATIO: + if( --(sce->val4) >= 0 ) + { + int heal = status->max_hp * 3 / 100; + if( sc && sc->data[SC_AKAITSUKI] && heal ) + heal = ~heal + 1; + status_heal(bl, heal, 0, 2); + sc_timer_next(5000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_BURNING: + if( --(sce->val4) >= 0 ) + { + struct block_list *src = map_id2bl(sce->val3); + int damage = 1000 + 3 * status_get_max_hp(bl) / 100; // Deals fixed (1000 + 3%*MaxHP) + + map_freeblock_lock(); + clif_damage(bl,bl,tick,0,0,damage,1,9,0); //damage is like endure effect with no walk delay + status_damage(src, bl, damage, 0, 0, 1); + + if( sc->data[type]){ // Target still lives. [LimitLine] + sc_timer_next(2000 + tick, status_change_timer, bl->id, data); + } + map_freeblock_unlock(); + return 0; + } + break; + + case SC_FEAR: + if( --(sce->val4) >= 0 ) + { + if( sce->val2 > 0 ) + sce->val2--; + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_SPHERE_1: + case SC_SPHERE_2: + case SC_SPHERE_3: + case SC_SPHERE_4: + case SC_SPHERE_5: + if( --(sce->val4) >= 0 ) + { + if( !status_charge(bl, 0, 1) ) + break; + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_READING_SB: + if( !status_charge(bl, 0, sce->val2) ){ + int i; + for(i = SC_SPELLBOOK1; i <= SC_MAXSPELLBOOK; i++) // Also remove stored spell as well. + status_change_end(bl, (sc_type)i, INVALID_TIMER); + break; + } + sc_timer_next(5000 + tick, status_change_timer, bl->id, data); + return 0; + + case SC_ELECTRICSHOCKER: + if( --(sce->val4) >= 0 ) + { + status_charge(bl, 0, status->max_sp / 100 * sce->val1 ); + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_CAMOUFLAGE: + if(--(sce->val4) > 0){ + status_charge(bl,0,7 - sce->val1); + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC__REPRODUCE: + if(!status_charge(bl, 0, 1)) + break; + sc_timer_next(1000+tick, status_change_timer, bl->id, data); + return 0; + + case SC__SHADOWFORM: + if( --(sce->val4) >= 0 ) + { + if( !status_charge(bl, 0, sce->val1 - (sce->val1 - 1)) ) + break; + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC__INVISIBILITY: + if( --(sce->val4) >= 0 ) + { + if( !status_charge(bl, 0, (status->sp * 6 - sce->val1) / 100) )// 6% - skill_lv. + break; + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_STRIKING: + if( --(sce->val4) >= 0 ) + { + if( !status_charge(bl,0, sce->val1 ) ) + break; + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + case SC_VACUUM_EXTREME: + if( --(sce->val4) >= 0 ){ + if( !unit_is_walking(bl) && !sce->val2 ){ + sc->cant.move++; + sce->val2 = 1; + } + sc_timer_next(100 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + case SC_BLOODSUCKER: + if( --(sce->val4) >= 0 ) { + struct block_list *src = map_id2bl(sce->val2); + int damage; + if( !src || (src && (status_isdead(src) || src->m != bl->m || distance_bl(src, bl) >= 12)) ) + break; + map_freeblock_lock(); + damage = 200 + 100 * sce->val1 + status_get_int(src); + status_damage(src, bl, damage, 0, clif_damage(bl,bl,tick,status->amotion,status->dmotion+200,damage,1,0,0), 1); + unit_skillcastcancel(bl,1); + if ( sc->data[type] ) { + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + } + map_freeblock_unlock(); + status_heal(src, damage*(5 + 5 * sce->val1)/100, 0, 0); // 5 + 5% per level + return 0; + } + break; + + case SC_VOICEOFSIREN: + if( --(sce->val4) >= 0 ) + { + clif_emotion(bl,E_LV); + sc_timer_next(2000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_DEEPSLEEP: + if( --(sce->val4) >= 0 ) + { // Recovers 1% HP/SP every 2 seconds. + status_heal(bl, status->max_hp / 100, status->max_sp / 100, 2); + sc_timer_next(2000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_SIRCLEOFNATURE: + if( --(sce->val4) >= 0 ) + { + if( !status_charge(bl,0,sce->val2) ) + break; + status_heal(bl, sce->val3, 0, 1); + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_SONGOFMANA: + if( --(sce->val4) >= 0 ) + { + status_heal(bl,0,sce->val3,3); + sc_timer_next(3000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + + case SC_SATURDAYNIGHTFEVER: + // 1% HP/SP drain every val4 seconds [Jobbie] + if( --(sce->val3) >= 0 ) + { + int hp = status->hp / 100; + int sp = status->sp / 100; + if( !status_charge(bl, hp, sp) ) + break; + sc_timer_next(sce->val4+tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_CRYSTALIZE: + if( --(sce->val4) >= 0 ) + { // Drains 2% of HP and 1% of SP every seconds. + if( bl->type != BL_MOB) // doesn't work on mobs + status_charge(bl, status->max_hp * 2 / 100, status->max_sp / 100); + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_FORCEOFVANGUARD: + if( !status_charge(bl,0,20) ) + break; + sc_timer_next(6000 + tick, status_change_timer, bl->id, data); + return 0; + + case SC_BANDING: + if( status_charge(bl, 0, 7 - sce->val1) ) + { + if( sd ) pc_banding(sd, sce->val1); + sc_timer_next(5000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_REFLECTDAMAGE: + if( --(sce->val4) >= 0 ) { + if( !status_charge(bl,0,sce->val3) ) + break; + sc_timer_next(10000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_OVERHEAT_LIMITPOINT: + if( --(sce->val1) > 0 ) { // Cooling + sc_timer_next(30000 + tick, status_change_timer, bl->id, data); + } + break; + + case SC_OVERHEAT: + { + int damage = status->max_hp / 100; // Suggestion 1% each second + if( damage >= status->hp ) damage = status->hp - 1; // Do not kill, just keep you with 1 hp minimum + map_freeblock_lock(); + status_fix_damage(NULL,bl,damage,clif_damage(bl,bl,tick,0,0,damage,0,0,0)); + if( sc->data[type] ) { + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + } + map_freeblock_unlock(); + } + break; + + case SC_MAGNETICFIELD: + { + if( --(sce->val3) <= 0 ) + break; // Time out + if( sce->val2 == bl->id ) + { + if( !status_charge(bl,0,14 + (3 * sce->val1)) ) + break; // No more SP status should end, and in the next second will end for the other affected players + } + else + { + struct block_list *src = map_id2bl(sce->val2); + struct status_change *ssc; + if( !src || (ssc = status_get_sc(src)) == NULL || !ssc->data[SC_MAGNETICFIELD] ) + break; // Source no more under Magnetic Field + } + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + } + break; + + case SC_INSPIRATION: + if(--(sce->val4) >= 0) + { + int hp = status->max_hp * (7-sce->val1) / 100; + int sp = status->max_sp * (9-sce->val1) / 100; + + if( !status_charge(bl,hp,sp) ) break; + + sc_timer_next(1000+tick,status_change_timer,bl->id, data); + return 0; + } + break; + + case SC_RAISINGDRAGON: + // 1% every 5 seconds [Jobbie] + if( --(sce->val3)>0 && status_charge(bl, sce->val2, 0) ) + { + if( !sc->data[type] ) return 0; + sc_timer_next(5000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + + case SC_CIRCLE_OF_FIRE: + case SC_FIRE_CLOAK: + case SC_WATER_DROP: + case SC_WATER_SCREEN: + case SC_WIND_CURTAIN: + case SC_WIND_STEP: + case SC_STONE_SHIELD: + case SC_SOLID_SKIN: + if( !status_charge(bl,0,sce->val2) ){ + struct block_list *s_bl = battle_get_master(bl); + if( s_bl ) + status_change_end(s_bl,type+1,INVALID_TIMER); + status_change_end(bl,type,INVALID_TIMER); + break; + } + sc_timer_next(2000 + tick, status_change_timer, bl->id, data); + return 0; + + case SC_STOMACHACHE: + if( --(sce->val4) > 0 ){ + status_charge(bl,0,sce->val2); // Reduce 8 every 10 seconds. + if( sd && !pc_issit(sd) ) // Force to sit every 10 seconds. + { + pc_stop_walking(sd,1|4); + pc_stop_attack(sd); + pc_setsit(sd); + clif_sitting(bl); + } + sc_timer_next(10000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + case SC_LEADERSHIP: + case SC_GLORYWOUNDS: + case SC_SOULCOLD: + case SC_HAWKEYES: + /* they only end by status_change_end */ + sc_timer_next(600000 + tick, status_change_timer, bl->id, data); + return 0; + case SC_MEIKYOUSISUI: + if( --(sce->val4) > 0 ){ + status_heal(bl, status->max_hp * (sce->val1+1) / 100, status->max_sp * sce->val1 / 100, 0); + sc_timer_next(1000 + tick, status_change_timer, bl->id, data); + return 0; + } + break; + case SC_IZAYOI: + case SC_KAGEMUSYA: + if( --(sce->val2) > 0 ){ + if(!status_charge(bl, 0, 1)) break; + sc_timer_next(1000+tick, status_change_timer, bl->id, data); + return 0; + } + break; + case SC_ANGRIFFS_MODUS: + if(--(sce->val4) >= 0) { //drain hp/sp + if( !status_charge(bl,100,20) ) break; + sc_timer_next(1000+tick,status_change_timer,bl->id, data); + return 0; + } + break; + } + + // default for all non-handled control paths is to end the status + return status_change_end( bl,type,tid ); +#undef sc_timer_next +} + +/*========================================== + * Foreach iteration of repetitive status + *------------------------------------------*/ +int status_change_timer_sub(struct block_list* bl, va_list ap) +{ + struct status_change* tsc; + + struct block_list* src = va_arg(ap,struct block_list*); + struct status_change_entry* sce = va_arg(ap,struct status_change_entry*); + enum sc_type type = (sc_type)va_arg(ap,int); //gcc: enum args get promoted to int + unsigned int tick = va_arg(ap,unsigned int); + + if (status_isdead(bl)) + return 0; + + tsc = status_get_sc(bl); + + switch( type ) { + case SC_SIGHT: /* Reveal hidden ennemy on 3*3 range */ + if( tsc && tsc->data[SC__SHADOWFORM] && (sce && sce->val4 >0 && sce->val4%2000 == 0) && // for every 2 seconds do the checking + rnd()%100 < 100-tsc->data[SC__SHADOWFORM]->val1*10 ) // [100 - (Skill Level x 10)] % + status_change_end(bl, SC__SHADOWFORM, INVALID_TIMER); + case SC_CONCENTRATE: + status_change_end(bl, SC_HIDING, INVALID_TIMER); + status_change_end(bl, SC_CLOAKING, INVALID_TIMER); + status_change_end(bl, SC_CLOAKINGEXCEED, INVALID_TIMER); + status_change_end(bl, SC_CAMOUFLAGE, INVALID_TIMER); + status_change_end(bl, SC__INVISIBILITY, INVALID_TIMER); + break; + case SC_RUWACH: /* Reveal hidden target and deal little dammages if ennemy */ + if (tsc && (tsc->data[SC_HIDING] || tsc->data[SC_CLOAKING] || + tsc->data[SC_CAMOUFLAGE] || tsc->data[SC_CLOAKINGEXCEED] || + tsc->data[SC__INVISIBILITY])) { + status_change_end(bl, SC_HIDING, INVALID_TIMER); + status_change_end(bl, SC_CLOAKING, INVALID_TIMER); + status_change_end(bl, SC_CAMOUFLAGE, INVALID_TIMER); + status_change_end(bl, SC_CLOAKINGEXCEED, INVALID_TIMER); + status_change_end(bl, SC__INVISIBILITY, INVALID_TIMER); + if(battle_check_target( src, bl, BCT_ENEMY ) > 0) + skill_attack(BF_MAGIC,src,src,bl,AL_RUWACH,1,tick,0); + } + if( tsc && tsc->data[SC__SHADOWFORM] && (sce && sce->val4 >0 && sce->val4%2000 == 0) && // for every 2 seconds do the checking + rnd()%100 < 100-tsc->data[SC__SHADOWFORM]->val1*10 ) // [100 - (Skill Level x 10)] % + status_change_end(bl, SC__SHADOWFORM, INVALID_TIMER); + break; + case SC_SIGHTBLASTER: + if (battle_check_target( src, bl, BCT_ENEMY ) > 0 && + status_check_skilluse(src, bl, WZ_SIGHTBLASTER, 2)) + { + skill_attack(BF_MAGIC,src,src,bl,WZ_SIGHTBLASTER,1,tick,0); + if (sce && !(bl->type&BL_SKILL)) //The hit is not counted if it's against a trap + sce->val2 = 0; //This signals it to end. + } + break; + case SC_CLOSECONFINE: + //Lock char has released the hold on everyone... + if (tsc && tsc->data[SC_CLOSECONFINE2] && tsc->data[SC_CLOSECONFINE2]->val2 == src->id) { + tsc->data[SC_CLOSECONFINE2]->val2 = 0; + status_change_end(bl, SC_CLOSECONFINE2, INVALID_TIMER); + } + break; + case SC_CURSEDCIRCLE_TARGET: + if( tsc && tsc->data[SC_CURSEDCIRCLE_TARGET] && tsc->data[SC_CURSEDCIRCLE_TARGET]->val2 == src->id ) { + clif_bladestop(bl, tsc->data[SC_CURSEDCIRCLE_TARGET]->val2, 0); + status_change_end(bl, type, INVALID_TIMER); + } + break; + } + return 0; +} + +/*========================================== + * Clears buffs/debuffs of a character. + * type&1 -> buffs, type&2 -> debuffs + * type&4 -> especific debuffs(implemented with refresh) + *------------------------------------------*/ +int status_change_clear_buffs (struct block_list* bl, int type) +{ + int i; + struct status_change *sc= status_get_sc(bl); + + if (!sc || !sc->count) + return 0; + + if (type&6) //Debuffs + for (i = SC_COMMON_MIN; i <= SC_COMMON_MAX; i++) + status_change_end(bl, (sc_type)i, INVALID_TIMER); + + for( i = SC_COMMON_MAX+1; i < SC_MAX; i++ ) + { + if(!sc->data[i]) + continue; + + switch (i) { + //Stuff that cannot be removed + case SC_WEIGHT50: + case SC_WEIGHT90: + case SC_COMBO: + case SC_SMA: + case SC_DANCING: + case SC_LEADERSHIP: + case SC_GLORYWOUNDS: + case SC_SOULCOLD: + case SC_HAWKEYES: + case SC_GUILDAURA: + case SC_SAFETYWALL: + case SC_PNEUMA: + case SC_NOCHAT: + case SC_JAILED: + case SC_ANKLE: + case SC_BLADESTOP: + case SC_CP_WEAPON: + case SC_CP_SHIELD: + case SC_CP_ARMOR: + case SC_CP_HELM: + case SC_STRFOOD: + case SC_AGIFOOD: + case SC_VITFOOD: + case SC_INTFOOD: + case SC_DEXFOOD: + case SC_LUKFOOD: + case SC_HITFOOD: + case SC_FLEEFOOD: + case SC_BATKFOOD: + case SC_WATKFOOD: + case SC_MATKFOOD: + case SC_FOOD_STR_CASH: + case SC_FOOD_AGI_CASH: + case SC_FOOD_VIT_CASH: + case SC_FOOD_DEX_CASH: + case SC_FOOD_INT_CASH: + case SC_FOOD_LUK_CASH: + case SC_EXPBOOST: + case SC_JEXPBOOST: + case SC_ITEMBOOST: + case SC_ELECTRICSHOCKER: + case SC__MANHOLE: + case SC_GIANTGROWTH: + case SC_MILLENNIUMSHIELD: + case SC_REFRESH: + case SC_STONEHARDSKIN: + case SC_VITALITYACTIVATION: + case SC_FIGHTINGSPIRIT: + case SC_ABUNDANCE: + case SC_CURSEDCIRCLE_ATKER: + case SC_CURSEDCIRCLE_TARGET: + continue; + + //Debuffs that can be removed. + case SC_DEEPSLEEP: + case SC_BURNING: + case SC_FREEZING: + case SC_CRYSTALIZE: + case SC_TOXIN: + case SC_PARALYSE: + case SC_VENOMBLEED: + case SC_MAGICMUSHROOM: + case SC_DEATHHURT: + case SC_PYREXIA: + case SC_OBLIVIONCURSE: + case SC_LEECHESEND: + case SC_MARSHOFABYSS: + case SC_MANDRAGORA: + if(!(type&4)) + continue; + break; + case SC_HALLUCINATION: + case SC_QUAGMIRE: + case SC_SIGNUMCRUCIS: + case SC_DECREASEAGI: + case SC_SLOWDOWN: + case SC_MINDBREAKER: + case SC_WINKCHARM: + case SC_STOP: + case SC_ORCISH: + case SC_STRIPWEAPON: + case SC_STRIPSHIELD: + case SC_STRIPARMOR: + case SC_STRIPHELM: + case SC_BITE: + case SC_ADORAMUS: + case SC_VACUUM_EXTREME: + case SC_FEAR: + case SC_MAGNETICFIELD: + case SC_NETHERWORLD: + if (!(type&2)) + continue; + break; + //The rest are buffs that can be removed. + case SC__BLOODYLUST: + case SC_BERSERK: + case SC_SATURDAYNIGHTFEVER: + if (!(type&1)) + continue; + sc->data[i]->val2 = 0; + break; + default: + if (!(type&1)) + continue; + break; + } + status_change_end(bl, (sc_type)i, INVALID_TIMER); + } + return 0; +} + +int status_change_spread( struct block_list *src, struct block_list *bl ) { + int i, flag = 0; + struct status_change *sc = status_get_sc(src); + const struct TimerData *timer; + unsigned int tick; + struct status_change_data data; + + if( !sc || !sc->count ) + return 0; + + tick = gettick(); + + for( i = SC_COMMON_MIN; i < SC_MAX; i++ ) { + if( !sc->data[i] || i == SC_COMMON_MAX ) + continue; + + switch( i ) { + //Debuffs that can be spreaded. + // NOTE: We'll add/delte SCs when we are able to confirm it. + case SC_CURSE: + case SC_SILENCE: + case SC_CONFUSION: + case SC_BLIND: + case SC_NOCHAT: + case SC_HALLUCINATION: + case SC_SIGNUMCRUCIS: + case SC_DECREASEAGI: + case SC_SLOWDOWN: + case SC_MINDBREAKER: + case SC_WINKCHARM: + case SC_STOP: + case SC_ORCISH: + //case SC_STRIPWEAPON://Omg I got infected and had the urge to strip myself physically. + //case SC_STRIPSHIELD://No this is stupid and shouldnt be spreadable at all. + //case SC_STRIPARMOR:// Disabled until I can confirm if it does or not. [Rytech] + //case SC_STRIPHELM: + //case SC__STRIPACCESSORY: + case SC_BITE: + case SC_FREEZING: + case SC_VENOMBLEED: + case SC_DEATHHURT: + case SC_PARALYSE: + if( sc->data[i]->timer != INVALID_TIMER ) { + timer = get_timer(sc->data[i]->timer); + if (timer == NULL || timer->func != status_change_timer || DIFF_TICK(timer->tick,tick) < 0) + continue; + data.tick = DIFF_TICK(timer->tick,tick); + } else + data.tick = INVALID_TIMER; + break; + // Special cases + case SC_POISON: + case SC_DPOISON: + data.tick = sc->data[i]->val3 * 1000; + break; + case SC_FEAR: + case SC_LEECHESEND: + data.tick = sc->data[i]->val4 * 1000; + break; + case SC_BURNING: + data.tick = sc->data[i]->val4 * 2000; + break; + case SC_PYREXIA: + case SC_OBLIVIONCURSE: + data.tick = sc->data[i]->val4 * 3000; + break; + case SC_MAGICMUSHROOM: + data.tick = sc->data[i]->val4 * 4000; + break; + case SC_TOXIN: + case SC_BLEEDING: + data.tick = sc->data[i]->val4 * 10000; + break; + default: + continue; + break; + } + if( i ){ + data.val1 = sc->data[i]->val1; + data.val2 = sc->data[i]->val2; + data.val3 = sc->data[i]->val3; + data.val4 = sc->data[i]->val4; + status_change_start(bl,(sc_type)i,10000,data.val1,data.val2,data.val3,data.val4,data.tick,1|2|8); + flag = 1; + } + } + + return flag; +} + +//Natural regen related stuff. +static unsigned int natural_heal_prev_tick,natural_heal_diff_tick; +static int status_natural_heal(struct block_list* bl, va_list args) +{ + struct regen_data *regen; + struct status_data *status; + struct status_change *sc; + struct unit_data *ud; + struct view_data *vd = NULL; + struct regen_data_sub *sregen; + struct map_session_data *sd; + int val,rate,bonus = 0,flag; + + regen = status_get_regen_data(bl); + if (!regen) return 0; + status = status_get_status_data(bl); + sc = status_get_sc(bl); + if (sc && !sc->count) + sc = NULL; + sd = BL_CAST(BL_PC,bl); + + flag = regen->flag; + if (flag&RGN_HP && (status->hp >= status->max_hp || regen->state.block&1)) + flag&=~(RGN_HP|RGN_SHP); + if (flag&RGN_SP && (status->sp >= status->max_sp || regen->state.block&2)) + flag&=~(RGN_SP|RGN_SSP); + + if (flag && ( + status_isdead(bl) || + (sc && (sc->option&(OPTION_HIDE|OPTION_CLOAK|OPTION_CHASEWALK) || sc->data[SC__INVISIBILITY])) + )) + flag=0; + + if (sd) { + if (sd->hp_loss.value || sd->sp_loss.value) + pc_bleeding(sd, natural_heal_diff_tick); + if (sd->hp_regen.value || sd->sp_regen.value) + pc_regen(sd, natural_heal_diff_tick); + } + + if(flag&(RGN_SHP|RGN_SSP) && regen->ssregen && + (vd = status_get_viewdata(bl)) && vd->dead_sit == 2) + { //Apply sitting regen bonus. + sregen = regen->ssregen; + if(flag&(RGN_SHP)) + { //Sitting HP regen + val = natural_heal_diff_tick * sregen->rate.hp; + if (regen->state.overweight) + val>>=1; //Half as fast when overweight. + sregen->tick.hp += val; + while(sregen->tick.hp >= (unsigned int)battle_config.natural_heal_skill_interval) + { + sregen->tick.hp -= battle_config.natural_heal_skill_interval; + if(status_heal(bl, sregen->hp, 0, 3) < sregen->hp) + { //Full + flag&=~(RGN_HP|RGN_SHP); + break; + } + } + } + if(flag&(RGN_SSP)) + { //Sitting SP regen + val = natural_heal_diff_tick * sregen->rate.sp; + if (regen->state.overweight) + val>>=1; //Half as fast when overweight. + sregen->tick.sp += val; + while(sregen->tick.sp >= (unsigned int)battle_config.natural_heal_skill_interval) + { + sregen->tick.sp -= battle_config.natural_heal_skill_interval; + if(status_heal(bl, 0, sregen->sp, 3) < sregen->sp) + { //Full + flag&=~(RGN_SP|RGN_SSP); + break; + } + } + } + } + + if (flag && regen->state.overweight) + flag=0; + + ud = unit_bl2ud(bl); + + if (flag&(RGN_HP|RGN_SHP|RGN_SSP) && ud && ud->walktimer != INVALID_TIMER) + { + flag&=~(RGN_SHP|RGN_SSP); + if(!regen->state.walk) + flag&=~RGN_HP; + } + + if (!flag) + return 0; + + if (flag&(RGN_HP|RGN_SP)) + { + if(!vd) vd = status_get_viewdata(bl); + if(vd && vd->dead_sit == 2) + bonus++; + if(regen->state.gc) + bonus++; + } + + //Natural Hp regen + if (flag&RGN_HP) + { + rate = natural_heal_diff_tick*(regen->rate.hp+bonus); + if (ud && ud->walktimer != INVALID_TIMER) + rate/=2; + // Homun HP regen fix (they should regen as if they were sitting (twice as fast) + if(bl->type==BL_HOM) rate *=2; + + regen->tick.hp += rate; + + if(regen->tick.hp >= (unsigned int)battle_config.natural_healhp_interval) + { + val = 0; + do { + val += regen->hp; + regen->tick.hp -= battle_config.natural_healhp_interval; + } while(regen->tick.hp >= (unsigned int)battle_config.natural_healhp_interval); + if (status_heal(bl, val, 0, 1) < val) + flag&=~RGN_SHP; //full. + } + } + + //Natural SP regen + if(flag&RGN_SP) + { + rate = natural_heal_diff_tick*(regen->rate.sp+bonus); + // Homun SP regen fix (they should regen as if they were sitting (twice as fast) + if(bl->type==BL_HOM) rate *=2; + + regen->tick.sp += rate; + + if(regen->tick.sp >= (unsigned int)battle_config.natural_healsp_interval) + { + val = 0; + do { + val += regen->sp; + regen->tick.sp -= battle_config.natural_healsp_interval; + } while(regen->tick.sp >= (unsigned int)battle_config.natural_healsp_interval); + if (status_heal(bl, 0, val, 1) < val) + flag&=~RGN_SSP; //full. + } + } + + if (!regen->sregen) + return flag; + + //Skill regen + sregen = regen->sregen; + + if(flag&RGN_SHP) + { //Skill HP regen + sregen->tick.hp += natural_heal_diff_tick * sregen->rate.hp; + + while(sregen->tick.hp >= (unsigned int)battle_config.natural_heal_skill_interval) + { + sregen->tick.hp -= battle_config.natural_heal_skill_interval; + if(status_heal(bl, sregen->hp, 0, 3) < sregen->hp) + break; //Full + } + } + if(flag&RGN_SSP) + { //Skill SP regen + sregen->tick.sp += natural_heal_diff_tick * sregen->rate.sp; + while(sregen->tick.sp >= (unsigned int)battle_config.natural_heal_skill_interval) + { + val = sregen->sp; + if (sd && sd->state.doridori) { + val*=2; + sd->state.doridori = 0; + if ((rate = pc_checkskill(sd,TK_SPTIME))) + sc_start(bl,status_skill2sc(TK_SPTIME), + 100,rate,skill_get_time(TK_SPTIME, rate)); + if ( + (sd->class_&MAPID_UPPERMASK) == MAPID_STAR_GLADIATOR && + rnd()%10000 < battle_config.sg_angel_skill_ratio + ) { //Angel of the Sun/Moon/Star + clif_feel_hate_reset(sd); + pc_resethate(sd); + pc_resetfeel(sd); + } + } + sregen->tick.sp -= battle_config.natural_heal_skill_interval; + if(status_heal(bl, 0, val, 3) < val) + break; //Full + } + } + return flag; +} + +//Natural heal main timer. +static int status_natural_heal_timer(int tid, unsigned int tick, int id, intptr_t data) +{ + natural_heal_diff_tick = DIFF_TICK(tick,natural_heal_prev_tick); + map_foreachregen(status_natural_heal); + natural_heal_prev_tick = tick; + return 0; +} + +/** + * Get the chance to upgrade a piece of equipment. + * @param wlv The weapon type of the item to refine (see see enum refine_type) + * @param refine The target refine level + * @return The chance to refine the item, in percent (0~100) + **/ +int status_get_refine_chance(enum refine_type wlv, int refine) { + + if ( refine < 0 || refine >= MAX_REFINE) + return 0; + + return refine_info[wlv].chance[refine]; +} + + +/*------------------------------------------ + * DB reading. + * job_db1.txt - weight, hp, sp, aspd + * job_db2.txt - job level stat bonuses + * size_fix.txt - size adjustment table for weapons + * refine_db.txt - refining data table + *------------------------------------------*/ +static bool status_readdb_job1(char* fields[], int columns, int current) +{// Job-specific values (weight, HP, SP, ASPD) + int idx, class_; + unsigned int i; + + class_ = atoi(fields[0]); + + if(!pcdb_checkid(class_)) + { + ShowWarning("status_readdb_job1: Invalid job class %d specified.\n", class_); + return false; + } + idx = pc_class2idx(class_); + + max_weight_base[idx] = atoi(fields[1]); + hp_coefficient[idx] = atoi(fields[2]); + hp_coefficient2[idx] = atoi(fields[3]); + sp_coefficient[idx] = atoi(fields[4]); +#ifdef RENEWAL_ASPD + for(i = 0; i <= MAX_WEAPON_TYPE; i++) +#else + for(i = 0; i < MAX_WEAPON_TYPE; i++) +#endif + { + aspd_base[idx][i] = atoi(fields[i+5]); + } + return true; +} + +static bool status_readdb_job2(char* fields[], int columns, int current) +{ + int idx, class_, i; + + class_ = atoi(fields[0]); + + if(!pcdb_checkid(class_)) + { + ShowWarning("status_readdb_job2: Invalid job class %d specified.\n", class_); + return false; + } + idx = pc_class2idx(class_); + + for(i = 1; i < columns; i++) + { + job_bonus[idx][i-1] = atoi(fields[i]); + } + return true; +} + +static bool status_readdb_sizefix(char* fields[], int columns, int current) +{ + unsigned int i; + + for(i = 0; i < MAX_WEAPON_TYPE; i++) + { + atkmods[current][i] = atoi(fields[i]); + } + return true; +} + +static bool status_readdb_refine(char* fields[], int columns, int current) +{ + int i, bonus_per_level, random_bonus, random_bonus_start_level; + + current = atoi(fields[0]); + + if (current < 0 || current >= REFINE_TYPE_MAX) + return false; + + bonus_per_level = atoi(fields[1]); + random_bonus_start_level = atoi(fields[2]); + random_bonus = atoi(fields[3]); + + for(i = 0; i < MAX_REFINE; i++) + { + char* delim; + + if (!(delim = strchr(fields[4+i], ':'))) + return false; + + *delim = '\0'; + + refine_info[current].chance[i] = atoi(fields[4+i]); + + if (i >= random_bonus_start_level - 1) + refine_info[current].randombonus_max[i] = random_bonus * (i - random_bonus_start_level + 2); + + refine_info[current].bonus[i] = bonus_per_level + atoi(delim+1); + if (i > 0) + refine_info[current].bonus[i] += refine_info[current].bonus[i-1]; + } + return true; +} + +/* +* Read status db +* job1.txt +* job2.txt +* size_fixe.txt +* refine_db.txt +*/ +int status_readdb(void) +{ + int i, j; + + // initialize databases to default + // + + // reset job_db1.txt data + memset(max_weight_base, 0, sizeof(max_weight_base)); + memset(hp_coefficient, 0, sizeof(hp_coefficient)); + memset(hp_coefficient2, 0, sizeof(hp_coefficient2)); + memset(sp_coefficient, 0, sizeof(sp_coefficient)); + memset(aspd_base, 0, sizeof(aspd_base)); + // reset job_db2.txt data + memset(job_bonus,0,sizeof(job_bonus)); // Job-specific stats bonus + + // size_fix.txt + for(i=0;i<ARRAYLENGTH(atkmods);i++) + for(j=0;j<MAX_WEAPON_TYPE;j++) + atkmods[i][j]=100; + + // refine_db.txt + for(i=0;i<ARRAYLENGTH(refine_info);i++) + { + for(j=0;j<MAX_REFINE; j++) + { + refine_info[i].chance[j] = 100; + refine_info[i].bonus[j] = 0; + refine_info[i].randombonus_max[j] = 0; + } + } + + // read databases + // + + +#ifdef RENEWAL_ASPD + sv_readdb(db_path, "re/job_db1.txt", ',', 6+MAX_WEAPON_TYPE, 6+MAX_WEAPON_TYPE, -1, &status_readdb_job1); +#else + sv_readdb(db_path, "pre-re/job_db1.txt", ',', 5+MAX_WEAPON_TYPE, 5+MAX_WEAPON_TYPE, -1, &status_readdb_job1); +#endif + sv_readdb(db_path, "job_db2.txt", ',', 1, 1+MAX_LEVEL, -1, &status_readdb_job2); + sv_readdb(db_path, "size_fix.txt", ',', MAX_WEAPON_TYPE, MAX_WEAPON_TYPE, ARRAYLENGTH(atkmods), &status_readdb_sizefix); + sv_readdb(db_path, DBPATH"refine_db.txt", ',', 4+MAX_REFINE, 4+MAX_REFINE, ARRAYLENGTH(refine_info), &status_readdb_refine); + + return 0; +} + +/*========================================== + * Status db init and destroy. + *------------------------------------------*/ +int do_init_status(void) +{ + add_timer_func_list(status_change_timer,"status_change_timer"); + add_timer_func_list(kaahi_heal_timer,"kaahi_heal_timer"); + add_timer_func_list(status_natural_heal_timer,"status_natural_heal_timer"); + initChangeTables(); + initDummyData(); + status_readdb(); + status_calc_sigma(); + natural_heal_prev_tick = gettick(); + sc_data_ers = ers_new(sizeof(struct status_change_entry),"status.c::sc_data_ers",ERS_OPT_NONE); + add_timer_interval(natural_heal_prev_tick + NATURAL_HEAL_INTERVAL, status_natural_heal_timer, 0, 0, NATURAL_HEAL_INTERVAL); + return 0; +} +void do_final_status(void) +{ + ers_destroy(sc_data_ers); +} -- cgit v1.2.3-60-g2f50