Mobile.swing()

Archive of the older Feature Request Forum Posts
Locked
Bracco
Adept Poster
Posts: 80
Joined: Thu Dec 28, 2006 11:52 am

Mobile.swing()

Post by Bracco »

title is not so clear :P but i mean a method for mobiles that causes an attack with the weapon (not the animation only)(without waiting the delay and/or movement check for projectables), just like when you are in warmode and swing with your weapon...

useful to implement things such as the aos combat abilities (double strike, moving shot etc)
Last edited by Bracco on Sun Jan 14, 2007 8:30 am, edited 1 time in total.
SMJ
Grandmaster Poster
Posts: 113
Joined: Wed May 10, 2006 5:15 pm

Post by SMJ »

Code: Select all

use uo;

function PlayAttackAnimation(byref a_info)
	var attack_anim := ANIM_FIDGET_1; // Default attack anim for NPCs.
	if ( (a_info.cfginfo).Anim )
		// Normal weapons
		var anim_list := GetConfigStringArray(a_info.cfginfo, "Anim");
		attack_anim := anim_list[RandomInt(anim_list.Size())+1];
	elseif ( (a_info.cfginfo).AttackAnimation )
		// NPC intrinsic weapons
		var anim_list := GetConfigStringArray(a_info.cfginfo, "AttackAnimation");
		attack_anim := anim_list[RandomInt(anim_list.Size())+1];
	endif

	PerformAction(a_info.mobile, CInt(attack_anim));

	return attack_anim;
endfunction
Copy-pasted from 096 combatHook.src
Bracco
Adept Poster
Posts: 80
Joined: Thu Dec 28, 2006 11:52 am

Post by Bracco »

SMJ wrote:

Code: Select all

use uo;

function PlayAttackAnimation(byref a_info)
	var attack_anim := ANIM_FIDGET_1; // Default attack anim for NPCs.
	if ( (a_info.cfginfo).Anim )
		// Normal weapons
		var anim_list := GetConfigStringArray(a_info.cfginfo, "Anim");
		attack_anim := anim_list[RandomInt(anim_list.Size())+1];
	elseif ( (a_info.cfginfo).AttackAnimation )
		// NPC intrinsic weapons
		var anim_list := GetConfigStringArray(a_info.cfginfo, "AttackAnimation");
		attack_anim := anim_list[RandomInt(anim_list.Size())+1];
	endif

	PerformAction(a_info.mobile, CInt(attack_anim));

	return attack_anim;
endfunction
Copy-pasted from 096 combatHook.src
hm? i mean a REAL swing, not just the animation...
SMJ
Grandmaster Poster
Posts: 113
Joined: Wed May 10, 2006 5:15 pm

Post by SMJ »

Then add the damage, too? I'm sorry, but this already exists in the form of:

Code: Select all

Attack( byref attacker, byref defender );
And the suggestion would limit, rather than expand, POL's combat aesthetics. Every weapon has 9 attack animations, but the coders decide which ones to utilize; by adding them to a custom exported Attack(byref attacker, byref defender) function.
Bracco
Adept Poster
Posts: 80
Joined: Thu Dec 28, 2006 11:52 am

Post by Bracco »

SMJ wrote:Then add the damage, too? I'm sorry, but this already exists in the form of:

Code: Select all

Attack( byref attacker, byref defender );
And the suggestion would limit, rather than expand, POL's combat aesthetics. Every weapon has 9 attack animations, but the coders decide which ones to utilize; by adding them to a custom exported Attack(byref attacker, byref defender) function.
i dont get it O_o where's that attack function from? from a script?

i need to cause a physical attack, just as when i am hitting someone with my executioner's axe... it can't be done with attackhook/hitscript, i need to be able to actually cause the attack to happen, not wait till the normal one happens
SMJ
Grandmaster Poster
Posts: 113
Joined: Wed May 10, 2006 5:15 pm

Post by SMJ »

That's called "externalizing" and can be very easily done with an include function. All it really means is that you take out all of the contents of a program, stick it in a function, then make a call to it in the function, so that several different scripts can perform identical purposes without copy-pasting too much content.

Code: Select all

program DoStuff(who)
    return DoStuffFunc(who);
endprogram

Code: Select all

function DoStuffFunc(who)
    // Do all the program's functions
endfunction
Bracco
Adept Poster
Posts: 80
Joined: Thu Dec 28, 2006 11:52 am

Post by Bracco »

SMJ wrote:That's called "externalizing" and can be very easily done with an include function. All it really means is that you take out all of the contents of a program, stick it in a function, then make a call to it in the function, so that several different scripts can perform identical purposes without copy-pasting too much content.

Code: Select all

program DoStuff(who)
    return DoStuffFunc(who);
endprogram

Code: Select all

function DoStuffFunc(who)
    // Do all the program's functions
endfunction
hm... i think you totally misunderstood what i'm saying.

i DON'T want to hook the attack, i want to MAKE IT HAPPEN.
also, i know what hooks, exported functions and such things are.

let me explain in a more ground-to-ground way:

i want to implement, for example, AOS combat ability "Double Strike"
what it does is, basically, to attack 2 times in a row, so when your attack lands another one is instantly fired.

how do you do this nowadays? you can't, unless you do it some tricky way, making a fake attack in an hitscript, but maybe, as you say, you have some attackhook/hitscript you want to run before the new attack lands (if it lands)

not to mention that the movement-check for projectable weapons is done by core, so you won't have anything to hook to implement "Moving Shot" (ofc you can make a "fake attack", but it will result in an heavy script to make it work just as the core does)
SMJ
Grandmaster Poster
Posts: 113
Joined: Wed May 10, 2006 5:15 pm

Post by SMJ »

I know exactly what you're saying. You want it to fire twice? Externalize it to a function, and fire it twice. You want it to wait until you would swing normally? Make the command put a cprop on the attacker, and then loop it in the syshook; that way it'll skip the attack delay entirely. I haven't tried using projectiles in a syshook myself, but I don't see why "moving shot" couldn't be done. I don't know exactly how AOS "Moving Shot" works, but if it just means that you use it to shoot while moving; just link a script to it. And if you're worried about a syshook stalling the server, just make the packethook immediately Start_Script another file; and you've solved the problem.

As far as a "heavy script", I don't see where you got that conclusion. 096 AND 097 have a syshook, WOD used a syshook, SxC uses a syshook, I use a syshook, and I have never heard of anyone complaining about ANY of them being slow or heavy.

There is no limit to what you can and cannot do; just a limit to creativity, and what you're willing to try.
Bracco
Adept Poster
Posts: 80
Joined: Thu Dec 28, 2006 11:52 am

Post by Bracco »

SMJ wrote: I don't know exactly how AOS "Moving Shot" works, but if it just means that you use it to shoot while moving; just link a script to it.
meh, lets suppose that i use a script to make a "fake" attack while moving.

i put a start_script in my packethook that fires the "fake attack" script

so i must do something like this

Code: Select all

if( attackhook )
  call hook
  if hook returned 1, exit
endif

if( weapon is projectile )
  try to comsume projectile
  if none, exit
exit

if( weapon is projected )
  play sound and animations
else
  play attack anim
endif

if( combat advancement hook exists )
  call it
endif 

hit_chance = (weapon_attribute + 50.0) / (2.0 * opponent_weapon_attribute + 50.0)

if( random_float(1.0) < hitchance )
  play hit sounds and anims
  damage weapon (1 in 100 chance to lose 1 hp)
  
  damage = random_weapon_die_damage
  damage_multiplier = tactics + 50
  damage_multiplier += strength * 0.2
  damage_multiplier *= 0.01
  damage *= damage_multiplier
  
  if( opponent has shield )
    call parry advancement hook
    
    parry_chance = opponent_parry_skill / 200.0
    if( random_float(1.0) < parry_chance )
      display parry success
      damage -= opponent_shield_ar
    endif
  endif
  
  if( weapon has no hit script )
    choose armor piece hit based on zone coverage percentage
    blocked = armor piece ar + character ar_mod
    absorbed = blocked / 2
    blocked -= absorbed
    absorbed += random_int(blocked+1)
    damage -= absorbed
    if( damage >= 2.0 )
       damage *= 0.5
    endif
    1 in 100 chance for armor piece to lose 1 hp
    play hit animation
    applydamage(damage)
  else
    choose armor piece hit based on zone coverage percentage
    calc base & raw damage (exactly like above if no hit script)
    run hit script
    (Core doesn't damage armor if hitscript, do it in the script)
  endif
else
  play weapon miss sound
endif
that's the core combat pseudocode...
sooo its good to make such a thing in a script that probably will run continuosly during pvp battles?
no its not. moreover, there's really no reason to just make a script clone of an internal core function.

and tbh i dont like filling the scriptbase with intricated, tricky things just to fake out something that it's already been done internally
SMJ
Grandmaster Poster
Posts: 113
Joined: Wed May 10, 2006 5:15 pm

Post by SMJ »

Guess what?

Code: Select all

/* $Id: combatHook.src 788 2006-09-05 01:25:54Z AustinHeilman $
 *
 * NOTES:
 *	Return 0 if you want the core to handle the
 *	combat cycle. Return 1 to say that the cycle is over.
 */

use uo;
use os;
use polsys;
use cfgfile;

include ":attributes:attributes";
include ":brainai:npcUtil";
include ":armor:armorZones";
include ":combat:settings";
include ":damage:damage";
include ":itemutils:itemdesc";
include "include/client";
include "include/facings";

/*
 * Global variables
 * With the way hooks work, these are only set only ONCE
 * and stay the same in every instance the hook gets run.
 */
var g_item_cfg := ReadConfigFile(":*:itemdesc");
var g_settings_cfg := CS_GetSettingsCfgFile();

program Install()
	print("INSTALLING: Combat hook... ");
	return 1;
endprogram

exported function Attack(attacker, defender)
	if ( !g_settings_cfg["Settings"].EnableHook )
		return 0;
	elseif ( !CanAttack(attacker, defender) )
		return 1;
	endif

	var a_info, d_info;
	SetupInfo(attacker, defender, a_info, d_info);

	if ( !DistanceChecks(a_info, d_info) )
		return 1;
	elseif ( !AmmoChecks(a_info, d_info) )
		return 1;
	endif

	FacingChecks(attacker, defender);
	PlayAttackAnimation(a_info);

	var hit_chance := CalcHitDifficulty(a_info, d_info);
	if ( RandomFloat(1.0) < hit_chance )
		var attribute := GetConfigString(a_info.cfginfo, a_info.prefix+"Attribute");
		var gain_flag := GetCombatGainFlags(attacker, defender);
		SkillCheck(attacker, attribute, -1, 0, gain_flag);

		PlayHitSound(a_info, d_info);

		var base_damage := CalcBaseDamage(a_info);
		var raw_damage := base_damage;
		ParryChecks(attacker, defender, raw_damage);

		var armor_hit := GetArmorHit(d_info);
		ArmorChecks(d_info, armor_hit, raw_damage);

		RunWeaponHitScripts(a_info, d_info, armor_hit, base_damage, raw_damage);
		RunArmorHitScripts(a_info, d_info, armor_hit, base_damage, raw_damage);

		ApplyRawDamageEX(d_info.mobile, raw_damage, DMG_FORCED, a_info.mobile);
	else
		PlayMissSound(a_info);
	endif

	return 1;
endfunction

function GetCombatGainFlags(attacker, defender)
	if ( defender.npctemplate )
		return ADV_ALL;
	elseif ( attacker.npctemplate )
		return ADV_ALL;
	elseif ( g_settings_cfg["Settings"].PvPGains )
		return ADV_ALL;
	else
		return ADV_DISABLE;
	endif
endfunction

function CanAttack(attacker, defender)
	// These first two checks are handled by the core.
	//if ( attacker == defender )
	//	return 0;
	//elseif ( !CheckLineOfSight(attacker, defender) )
	//	return 0;
	//
	if ( !attacker.warmode && !g_settings_cfg["Settings"].AutoDefend )
		return 0;
	endif

	return 1;
endfunction

function FacingChecks(attacker, defender)
	if ( g_settings_cfg["Settings"].ForceFacing )
		if ( !IsFacing(attacker, defender.x, defender.y) )
			TurnObjectToward(attacker, defender.x, defender.y);
		endif

		if ( !IsFacing(defender, attacker.x, attacker.y) )
			TurnObjectToward(defender, attacker.x, attacker.y);
		endif
	endif

	return 1;
endfunction

function DistanceChecks(byref a_info, byref d_info)
	// Core handles distance checks before starting the hook.
	return 1;
	/*
	var cur_range := Distance(a_info.mobile, d_info.mobile);
	var max_range := GetConfigInt(a_info.cfginfo, a_info.prefix+"MaxRange");
	if ( max_range == error )
		max_range := 1;
	endif

	if ( cur_range > max_range )
		CombatMsg(a_info.mobile, "Opponent is too far away. ["+max_range+"]", "Dist");
		return 0;
	elseif ( cur_range < CInt(GetConfigInt(a_info.cfginfo, a_info.prefix+"MinRange")) )
		CombatMsg(a_info.mobile, "Opponent is too close.", "Dist");
		return 0;
	else
		return 1;
	endif
	*/
endfunction

function AmmoChecks(byref a_info, byref d_info)
	var ammo_type := CInt((a_info.cfginfo).ProjectileType);
	if ( !ammo_type )
		return 1;
	endif

	if ( ConsumeSubstance((a_info.mobile).backpack, ammo_type, 1) )
		PlaySoundEffect(a_info.mobile, CInt((a_info.cfginfo).ProjectileSound));
		PlayMovingEffect(a_info.mobile, d_info.mobile, (a_info.cfginfo).ProjectileAnim, 10, 0);
		return 1;
	else
		CombatMsg(a_info.mobile, "You do not have any "+GetObjTypeDesc(ammo_type, 1)+"!", "Ammo");
		return 0;
	endif
endfunction

function PlayAttackAnimation(byref a_info)
	var attack_anim := ANIM_FIDGET_1; // Default attack anim for NPCs.
	if ( (a_info.cfginfo).Anim )
		// Normal weapons
		var anim_list := GetConfigStringArray(a_info.cfginfo, "Anim");
		attack_anim := anim_list[RandomInt(anim_list.Size())+1];
	elseif ( (a_info.cfginfo).AttackAnimation )
		// NPC intrinsic weapons
		var anim_list := GetConfigStringArray(a_info.cfginfo, "AttackAnimation");
		attack_anim := anim_list[RandomInt(anim_list.Size())+1];
	endif

	PerformAction(a_info.mobile, CInt(attack_anim));

	return attack_anim;
endfunction

function CalcHitDifficulty(byref a_info, byref d_info)
	//hit_chance = (weapon_attribute + 50.0) / (2.0 * opponent_weapon_attribute + 50.0)
	var a_skill := AP_GetSkill(a_info.mobile, GetConfigString(a_info.cfginfo, a_info.prefix+"Attribute"));
	var d_skill := AP_GetSkill(d_info.mobile, GetConfigString(d_info.cfginfo, d_info.prefix+"Attribute"));

	return ((a_skill + 50.0) / (2.0 * d_skill + 50));
endfunction

function PlayHitSound(byref a_info, byref d_info)
	var hit_sound := GetConfigStringArray(a_info.cfginfo, a_info.prefix+"HitSound");
	hit_sound := hit_sound[RandomInt(hit_sound.Size())+1];
	PlaySoundEffect(a_info.mobile, CInt(hit_sound));

	var damaged_sound;
	if ( (d_info.mobile).npctemplate )
		damaged_sound := GetConfigStringArray(d_info.cfginfo, "DamagedSound");
	else
		case ( (d_info.mobile).gender )
			0:// Male
				damaged_sound := array{341, 342, 343, 345, 346};
				break;
			1://Female
				damaged_sound := array{332, 333, 334, 335, 336};
				break;
		endcase
	endif
	damaged_sound := damaged_sound[RandomInt(damaged_sound.Size())+1];

	PlaySoundEffect(d_info.mobile, CInt(damaged_sound));

	return 1;
endfunction

function PlayMissSound(byref a_info)
	var miss_sound := GetConfigStringArray(a_info.cfginfo, a_info.prefix+"MissSound");
	miss_sound := miss_sound[RandomInt(miss_sound.Size())+1];
	PlaySoundEffect(a_info.mobile, CInt(miss_sound));

	return 1;
endfunction

function CalcBaseDamage(byref a_info)
	var base_dmg := GetConfigString(a_info.cfginfo, a_info.prefix+"Damage");
	base_dmg := RandomDiceRoll(base_dmg);

	var dmg_mult := CDbl(AP_GetSkill(a_info.mobile, TACTICS))+50.0;
	dmg_mult += (CDbl(AP_GetStat(a_info.mobile, STRENGTH)) * 0.2);
	dmg_mult := CDbl(dmg_mult) * 0.01;
	base_dmg *= dmg_mult;

	return CInt(base_dmg);
endfunction

function ParryChecks(byref a_info, byref d_info, byref raw_damage)
	var shield := (d_info.mobile).shield;
	if ( !shield )
		return 0;
	endif

	var parry_elem := g_settings_cfg["Parry"];
	var divisor := CDbl(parry_elem.ParryDivisor);
	var roll := CDbl(parry_elem.ParryRoll);
	var parry_chance := CDbl(AP_GetSkill(a_info.mobile, PARRY)) / divisor;

	if ( RandomFloat(roll) < parry_chance )
		PerformAction(d_info.mobile, ANIM_TWIST_DODGE);
		SendSysMessage(d_info.mobile, "You deflect some damage using your shield.");
		raw_damage -= shield.ar;

		var armor_elem := g_settings_cfg["Armor"];
		if ( RandomInt(100)+1 <= armor_elem.WearChance )
			SendSysMessage(d_info.mobile, shield.desc+" takes some damage.");
			shield.hp -= 1;

			if ( shield.hp <= 1 )
				MoveObjectToLocation(shield, 1, 1, 1, shield.realm, MOVEOBJECT_FORCELOCATION);
				SendSysMessage(d_info.mobile, shield.desc+" has been destroyed.");
				DestroyItem(shield);
			endif
		endif
	endif

	return 1;
endfunction

function GetArmorHit(byref d_info)
	var hit_zone := CS_GetRandomArmorZone();
	var armor_hit := CS_GetEquipmentInArmorZone(d_info.mobile, hit_zone);

	if ( armor_hit.Size() < 1 )
		return 0;
	endif

	var best_armor := 0;
	foreach item in ( armor_hit )
		if ( item.ar > best_armor.ar )
			best_armor := item;
		endif
		SleepMS(2);
	endforeach

	return best_armor;
endfunction

function ArmorChecks(byref d_info, armor_hit, byref raw_damage)
	var blocked := CInt(armor_hit.ar) + (d_info.mobile).ar_mod;
	var absorbed := blocked / 2;
	blocked := blocked - absorbed;
	absorbed := absorbed + RandomInt(blocked+1)+1;
	raw_damage := raw_damage - absorbed;

	if ( raw_damage >= 2.0 )
		raw_damage := raw_damage * 0.5;
	endif
	raw_damage := CInt(raw_damage);

	if ( !armor_hit.IsA(POLCLASS_ARMOR) )
		return 1;
	endif

	var armor_elem := g_settings_cfg["Armor"];
	if ( RandomInt(100)+1 <= armor_elem.WearChance )
		SendSysMessage(d_info.mobile, armor_hit.desc+" takes some damage.");
		armor_hit.hp -= 1;

		if ( armor_hit.hp <= 1 )
			MoveObjectToLocation(armor_hit, 1, 1, 1, armor_hit.realm, MOVEOBJECT_FORCELOCATION);
			SendSysMessage(d_info.mobile, armor_hit.desc+" has been destroyed.");
			DestroyItem(armor_hit);
		endif
	endif

	return 1;
endfunction

function RunWeaponHitScripts(byref a_info, byref d_info, armor_hit, base_damage, raw_damage)
	var weapon_scripts := array{};
	if ( ((a_info.mobile).weapon).intrinsic )
		weapon_scripts := GetObjProperty(a_info.mobile, "HitScripts");
	elseif ( ((a_info.mobile).weapon).IsA(POLCLASS_WEAPON) )
		weapon_scripts := GetObjProperty((a_info.mobile).weapon, "HitScripts");
	endif

	var params := array{a_info.mobile, d_info.mobile, (a_info.mobile).weapon, armor_hit, base_damage, raw_damage};
	foreach hitscript in ( weapon_scripts )
		var script := Start_Script(hitscript, params);
		if ( !script || script.errortext )
			SendSysMessage(a_info.mobile, "*Attacker* Weapon script error starting ["+hitscript+"] :"+script.errortext);
			SendSysMessage(d_info.mobile, "*Attacker* Weapon script error starting ["+hitscript+"] :"+script.errortext);
		endif
		SleepMS(2);
	endforeach

	return 1;
endfunction

function RunArmorHitScripts(byref a_info, byref d_info, armor_hit, base_damage, raw_damage)
	var body_scripts := GetObjProperty(d_info.mobile, "ArmorHitScripts");
	var armor_scripts := GetObjProperty(armor_hit, "ArmorHitScripts");
	if ( !body_scripts )
		body_scripts := array{};
	endif
	if ( !armor_scripts )
		armor_scripts := array{};
	endif

	armor_scripts := armor_scripts + body_scripts;

	var params := array{a_info.mobile, d_info.mobile, (a_info.mobile).weapon, armor_hit, base_damage, raw_damage};
	foreach hitscript in ( armor_scripts )
		var script := Start_Script(hitscript, params);
		if ( !script || script.errortext )
			SendSysMessage(a_info.mobile, "*Defender* Armor script error starting ["+hitscript+"] :"+script.errortext);
			SendSysMessage(d_info.mobile, "*Defender* Armor script error starting ["+hitscript+"] :"+script.errortext);
		endif
		SleepMS(2);
	endforeach

	return 1;
endfunction

function SetupInfo(attacker, defender, byref a_info, byref d_info)
	a_info := struct;
	a_info.+mobile := attacker;
	if ( attacker.IsA(POLCLASS_NPC) && (attacker.weapon).intrinsic )
		a_info.+prefix := "Attack";
		a_info.+cfginfo := NPC_GetNPCConfig(attacker.npctemplate);
	else
		a_info.+prefix := "";
		a_info.+cfginfo := g_item_cfg[(attacker.weapon).objtype];
	endif

	d_info := struct;
	d_info.+mobile := defender;
	if ( defender.IsA(POLCLASS_NPC) && (defender.weapon).intrinsic )
		d_info.+prefix := "Attack";
		d_info.+cfginfo := NPC_GetNPCConfig(defender.npctemplate);
	else
		d_info.+prefix := "";
		d_info.+cfginfo := g_item_cfg[(defender.weapon).objtype];
	endif

	return 1;
endfunction

function CombatMsg(mobile, text, type:="")
	// This is done just to prevent message spam on fast weapons.
	if ( CInt(GetObjProperty(mobile, "#CH-Msg"+type)) < ReadMillisecondClock() )
		SendSysMessage(mobile, text);
		SetObjProperty(mobile, "#CH-Msg"+type, ReadMillisecondClock()+800);
	endif

	return 1;
endfunction
The distro already does it. And nobody's complained. And it really doesn't get that much overhead.

Either apply my advice, or ignore it. I'm never going to use AOS systems; I wasn't particularly impressed with them; but I have seen entire shards pull this kind of thing off, and the distro actually uses it by default. If you get complaints about this being slow, then it's your computer, not POL.
Last edited by SMJ on Sun Jan 14, 2007 9:52 am, edited 1 time in total.
Bracco
Adept Poster
Posts: 80
Joined: Thu Dec 28, 2006 11:52 am

Post by Bracco »

fine, so you're telling there's no reason to have it core side no?

well, i disagree. have you ever seen how such scripts can suck up resources when there are a coulpe hundreds ppl online fighting continuously?

i have, and i didn't like the result

edit: advice what? you just came here and sentenced it itsn't necessary. also you say that its good to use crappy methods, just pump up the hardware (oh, just to point it out, hooking isnt a crappy method, faking an entire existing system is)

btw, enough with discussion. let devs decide if this is worth implementing or not
User avatar
Austin
Former Developer
Posts: 621
Joined: Wed Jan 25, 2006 2:30 am

Post by Austin »

The nice thing about hooked (exported) scripts is that the core caches.. sort of.. remembers the code. This is why they cant be unloaded and reloaded, too.

It just goes through and refreshes the data it is instructed to.. very optimized part of the execution engine. Only one instance of a hook can run at a time but they run so quickly and in their own threads, that they it isn't too big a deal.
MuadDib
Former Developer
Posts: 1091
Joined: Sun Feb 12, 2006 9:50 pm

Post by MuadDib »

Actually for aos combat systems like the double strike, etc, it IS doable without hooking the core combat. To the best of my knowledge about the only thing not doable is the Lightning Strike of Bushido because it enhances your ability to hit the opponent (totally core controlled). The others are modifiers to the successful attack. Others are speed mods, which .delay can be used for this now with core combat without hooking. I have not messed with combat hooks, so don't know personally if you can set the hit percentage chance in the script or not. If so, then even lightning strike is done.

From my personal pvp/pvm on OSI, with all the basic skills, Spellweaving, Bushido, Necro, Chivalry, etc (all cept ninjitsu), including the combat book mods, it's all doable in the hitscripts and so forth without hardly any overhead required at all. Only if you must hook core combat might that make more overhead than desired I would think.
Locked