Files
scummvm-cursorfix/devtools/create_ultima/files/ultima6/scripts/md/combat.lua
2026-02-02 04:50:13 +01:00

530 lines
16 KiB
Lua

-- [objectnum] = range
local range_weapon_tbl = {
[41] = 4,
[42] = 5,
[43] = 4,
[44] = 9,
[45] = 9,
[46] = 9,
[47] = 4,
[48] = 5,
[240] = 6,
[241] = 6,
[129] = 2,
[261] = 2,
[313] = 9,
[40] = 6,
[366] = 3,
[386] = 4,
[364] = 3,
[384] = 2,
}
function get_weapon_range(weapon)
local range = range_weapon_tbl[weapon.obj_n]
if range == nil then
range = 1
end
return range
end
local weapon_dmg_tbl = {
[16] = 30, --bloody saber
[40] = 1, --Cupid's bow and arrows (charms)
[41] = 15, --derringer
[42] = 18, --revolver
[43] = 20, --shotgun
[44] = 30, --rifle
[45] = 30, --Belgian combine
[46] = 45, --elephant gun
[47] = 8, --sling
[48] = 12, --bow
[49] = 15, --hatchet
[50] = 20, --axe
[51] = 10, --ball-peen hammer
[52] = 25, --sledge hammer
[54] = 10, --knife
[55] = 20, --machete
[56] = 25, --saber
[65] = 15, --pick
[66] = 8, --shovel
[67] = 10, --hoe
[68] = 10, --rake
[69] = 15, --pitchfork
[70] = 12, --cultivator
[71] = 20, --scythe
[72] = 10, --saw
[102] = 12, --pry bar
--[109] = 1, --torch
--[110] = 1, --lit torch
--[111] = 1, --candlestick
--[112] = 1, --lit candle
--[113] = 1, --candelabra
--[114] = 1, --lit andelabra
--[115] = 1, --oil lamp
--[116] = 1, --lit oil lamp
--[117] = 1, --lantern
--[118] = 1, --lit lantern
[129] = 60, --weed sprayer -- FIXME: no damage normally. Only effects plants?
--[136] = 1, --tongs
[241] = 20, --heat ray gun
[242] = 10, --freeze ray gun
--[243] = 1, --martian ritual pod knife
[261] = 60, --spray gun -- FIXME: no damage normally. Only effects plants?
[263] = 10, --martian hoe (couldn't be equipped in original)
[264] = 20, --martian scythe (couldn't be equipped in original)
[265] = 15, --martian pitchfork (couldn't be equipped in original)
[266] = 10, --martian rake (couldn't be equipped in original)
[267] = 8, --martian shovel (couldn't be equipped in original)
[313] = 254, --M60 machine gun (scripted to only attack and kill the big bad)
[327] = 15, --martian pick (couldn't be equipped in original)
[401] = 12, --pool cue
}
function get_weapon_damage(weapon)
local dmg
if weapon.luatype == "actor" then
dmg = actor_get_damage(weapon)
else
dmg = weapon_dmg_tbl[weapon.obj_n]
end
if dmg == nil then
dmg = -1
end
return dmg
end
function sub_1B432(attacker, target)
end
function actor_get_weapon(attacker, target)
local int_test = false
local selected_weapon = attacker
if not actor_has_bad_alignment(attacker) then
if math.random(1, 0x1e) <= actor_int_adj(attacker) then
int_test = true
end
end
local range = actor_get_combat_range(attacker, target.x, target.y)
for obj in actor_inventory(actor) do
if get_weapon_damage(obj) > 0 and get_weapon_range(obj) >= range then
if (obj.obj_n ~= 129 and obj.obj_n ~= 261) --OBJ_WEED_SPRAYER, OBJ_SPRAY_GUN
or (obj.quality ~= 0 and obj.invisible)
or (obj.quality == 0 and (is_plant_obj(target) or (target.luatype=="actor" and (is_actor_stat_bit_set(target.obj_n, 7) and not is_actor_stat_bit_set(target.obj_n, 14))))) then
local weapon_requires_int = false
if obj.obj_n == 240 --OBJ_HEAT_RAY_GUN
or obj.obj_n == 241 --OBJ_FREEZE_RAY_GUN
or obj.obj_n == 45 then
weapon_requires_int = true
end
--if int_test == true and weapon_requires_int == true or obj.obj_n == 43 and not sub_1B432(attacker, target) then --OBJ_SHOTGUN
end
end
end
end
function attack_dex_saving_throw(attacker, defender, weapon)
if defender == nil or defender.luatype == "obj" then
return true
end
local attacker_value
if weapon.luatype == "actor" and is_actor_stat_bit_set(weapon.obj_n, 5) then
attacker_value = actor_str_adj(attacker)
else
attacker_value = actor_dex_adj(attacker)
end
local defender_value = actor_dex_adj(defender)
if math.random(1, 30) >= math.floor((defender_value + 30 - attacker_value) / 2) then
return true
end
return false
end
function out_of_ammo(attacker, weapon, print_message) -- untested function
local weapon_obj_n = weapon.obj_n
if ((weapon_obj_n == 41 or weapon_obj_n == 42) and Actor.inv_has_obj_n(attacker, 57) == false) --derringer, revolver, pistol rounds
or (weapon_obj_n == 43 and Actor.inv_has_obj_n(attacker, 58) == false) --shotgun, shotgun shell
or (weapon_obj_n == 44 and Actor.inv_has_obj_n(attacker, 59) == false) --rifle, rifle round
or (weapon_obj_n == 45 and weapon.quality == 0 and (Actor.inv_has_obj_n(attacker, 58) == false or Actor.inv_has_obj_n(attacker, 59) == false)) --belgian combine (combine), shotgun shell, rifle round
or (weapon_obj_n == 45 and weapon.quality == 1 and Actor.inv_has_obj_n(attacker, 59) == false) --belgian combine (rifle), rifle round
or (weapon_obj_n == 45 and weapon.quality == 2 and Actor.inv_has_obj_n(attacker, 58) == false) --belgian combine (shotgun), shotgun shell
or (weapon_obj_n == 46 and Actor.inv_has_obj_n(attacker, 60) == false) --elephant gun, elephant gun round
or (weapon_obj_n == 47 and Actor.inv_has_obj_n(attacker, 63) == false) --sling, sling stone
or ((weapon_obj_n == 240 or weapon_obj_n == 241 or weapon_obj_n == 129 or weapon_obj_n == 261) and weapon.qty == 0) then --heat ray gun, freeze ray gun, weed sprayer, spray gun
if(print_message) then
printl("OUT_OF_AMMUNITION")
play_md_sfx(5)
end
return true
end
if weapon_obj_n == 48 and Actor.inv_has_obj_n(attacker, 64) == false then --bow, arrows
if(print_message) then
printl("OUT_OF_ARROWS")
play_md_sfx(5)
end
return true
end
return false
end
function attack_with_freezeray(actor, target_actor, damage)
if actor_tbl[target_actor.obj_n] ~= nil
and (is_actor_stat_bit_set(target_actor.obj_n, 14) or is_actor_stat_bit_set(target_actor.obj_n, 7)) then
target_actor.paralyzed = true
printfl("ACTOR_PARALYZED", target_actor.name)
if not is_actor_stat_bit_set(target_actor.obj_n, 7)
or is_actor_stat_bit_set(target_actor.obj_n, 14) then
hit_target(target_actor, BLUE_HIT_TILE)
else
actor_take_hit(actor, target_actor, damage, 1)
end
else
printl("IT_HAS_NO_EFFECT")
end
end
function check_ammo(actor, weapon)
local obj_n = weapon.obj_n
if obj_n == 47 and not Actor.inv_has_obj_n(actor, 63) then --OBJ_SLING, OBJ_SLING_STONE
return 1
end
if (obj_n == 41 or obj_n == 42) and not Actor.inv_has_obj_n(actor, 57) then --OBJ_DERRINGER, OBJ_REVOLVER, OBJ_PISTOL_ROUND
return 1
end
if obj_n == 46 and not Actor.inv_has_obj_n(actor, 60) then --OBJ_ELEPHANT_GUN, OBJ_ELEPHANT_GUN_ROUND
return 1
end
if obj_n == 44 and not Actor.inv_has_obj_n(actor, 59) then --OBJ_RIFLE, OBJ_RIFLE_ROUND
return 1
end
if obj_n == 43 and not Actor.inv_has_obj_n(actor, 58) then --OBJ_SHOTGUN, OBJ_SHOTGUN_SHELL
return 1
end
if (obj_n == 129 or obj_n == 261) and weapon.qty ~= 0 then --OBJ_WEED_SPRAYER, OBJ_SPRAY_GUN
return 1
end
return 0
end
function attack_target_with_weapon(actor, target_x, target_y, weapon)
local target_range = actor_get_combat_range(actor, target_x, target_y)
local weapon_range = get_weapon_range(weapon)
if target_range > weapon_range then
return 2 --out of range
end
local ret = check_ammo(actor, weapon)
if ret ~= 0 then
return ret
end
local obj_n = weapon.obj_n
local var_10 = 0
local var_12 = 0
--OBJ_HEAT_RAY_GUN
--OBJ_FREEZE_RAY_GUN
--OBJ_BELGIAN_COMBINE
if obj_n == 240 or obj_n == 241 or obj_n == 45 then
if weapon.quality < 2 then
var_10= 1
end
if weapon.quality ~= 1 then
var_12 = 1
end
if obj_n == 45 then --OBJ_BELGIAN_COMBINE
if var_10 ~= 0 then
if not Actor.inv_has_obj_n(actor, 59) then --OBJ_RIFLE_ROUND
var_10 = 0
end
end
if var_12 ~= 0 then
if not Actor.inv_has_obj_n(actor, 58) then --OBJ_SHOTGUN_SHELL
var_12 = 0
end
end
else
if weapon.qty == 0 then
return 1
end
if weapon.qty < (var_12 * 4) + var_10 then
var_12 = 0
end
end
if var_10 == 0 and var_12 == 0 then
return 1
end
else
if obj_n == 43 then --OBJ_SHOTGUN
var_10 = 0
var_12 = 1
else
var_10 = 1
var_12 = 0
end
end
local damage_mode = 0
if obj_n == 240 then --OBJ_HEAT_RAY_GUN
damage_mode = 3
end
if obj_n == 241 then --OBJ_FREEZE_RAY_GUN
damage_mode = 1
end
g_attack_target = g_selected_obj
if var_10 == 0 then
spread_weapon_damage(actor, target_x, target_y, weapon)
return 0
end
local is_ranged_attack = false
if target_range > 1 then
is_ranged_attack = true
end
if (obj_n >= 40 and obj_n <= 48) --OBJ_CUPIDS_BOW_AND_ARROWS, OBJ_BOW
or obj_n == 129 --OBJ_WEED_SPRAYER
or obj_n == 261 --OBJ_SPRAY_GUN
or obj_n == 240 --OBJ_HEAT_RAY_GUN
or obj_n == 241 --OBJ_FREEZE_RAY_GUN
then
is_ranged_attack = true
end
local damage
if weapon.luatype == "actor" then
damage = actor_get_damage(actor)
if damage == nil then
damage = 1
end
else
damage = get_weapon_damage(weapon)
if damage < 0 then
damage = 1
end
end
local does_damage
local target = find_rockworm_actor(g_attack_target)
if target ~= nil
and target.luatype == "actor"
and obj_n ~= 129 --OBJ_WEED_SPRAYER
and obj_n ~= 261 then --OBJ_SPRAY_GUN
does_damage = attack_dex_saving_throw(actor, target, weapon)
end
if is_ranged_attack then
fire_range_based_weapon(actor, target_x, target_y, weapon)
end
target = find_rockworm_actor(g_attack_target)
if not is_ranged_attack and map_is_on_screen(actor.xyz) then
play_md_sfx(0)
end
if does_damage == nil then
does_damage = true
if target ~= nil
and target.luatype == "actor"
and obj_n ~= 129 --OBJ_WEED_SPRAYER
and obj_n ~= 261 then --OBJ_SPRAY_GUN
does_damage = attack_dex_saving_throw(actor, target, weapon)
end
end
if not does_damage then
play_md_sfx(3)
end
if does_damage
and target ~= nil
and (target.luatype == "obj" or target.actor_num ~= actor.actor_num) then
if obj_n == 241 then --OBJ_FREEZE_RAY_GUN
if target.obj_n == 160 and target.frame_n == 1 then
printl("THE_WATER_FREEZES")
target.frame_n = 2
else
if target.luatype == "actor" then
attack_with_freezeray(actor, target, damage)
end
end
elseif obj_n == 129 or obj_n == 261 then --OBJ_WEED_SPRAYER, OBJ_SPRAY_GUN
if weapon.quality ~= 0 then
if target.luatype == "actor" and target.obj_n == 145 then --OBJ_MONSTER_FOOTPRINTS
target.obj_n = 364 --OBJ_PROTO_MARTIAN
printfl("BECOMES_VISIBLE", target.name)
elseif target.luatype == "obj" and target.invisible then
target.invisible = false
printfl("BECOMES_VISIBLE", target.name)
else
printl("IT_HAS_NO_EFFECT")
end
else
if actor_tbl[target.obj_n] ~= nil
and not is_actor_stat_bit_set(target.obj_n, 14)
and is_actor_stat_bit_set(target.obj_n, 7) then
actor_take_hit(actor, target, damage, 2)
else
printl("IT_HAS_NO_EFFECT")
end
end
elseif obj_n == 40 then --OBJ_CUPIDS_BOW_AND_ARROWS
if target.luatype == "actor" and target.align ~= ALIGNMENT_GOOD then
target.old_align = target.align
target.align = ALIGNMENT_GOOD
target.charmed = true
printfl("ACTOR_CHARMED", target.name)
end
else
actor_take_hit(actor, target, damage, damage_mode)
end
end
if var_12 ~= 0 then
spread_weapon_damage(actor, target_x, target_y, weapon)
end
return 0
end
local spread_weapon_sfx_tbl = {
[0x2b]=8,
[0x2d]=8,
[0xf0]=0xa,
[0xf1]=0xa,
}
local spread_weapon_tile_num_tbl = {
[0x2b]=0x106,
[0x2d]=0x106,
[0xf0]=0x14e,
[0xf1]=0x14f,
}
local spread_weapon_damage_tbl = {
[0x2b]=0x14,
[0x2d]=0x14,
[0xf0]=0x19,
[0xf1]=0xa,
}
function spread_weapon_damage(actor, target_x, target_y, weapon)
if spread_weapon_sfx_tbl[weapon.obj_n] ~= nil then
play_md_sfx(spread_weapon_sfx_tbl[weapon.obj_n])
end
--FIXME spread weapon anim here.
local hit_items = projectile_anim_multi (spread_weapon_tile_num_tbl[weapon.obj_n], actor.x, actor.y, {{x=target_x, y=target_y, z=actor.z}, {x=target_x+1, y=target_y-1, z=actor.z}}, 2, 1, 0)
local k, v
for k,v in pairs(hit_items) do
if weapon.obj_n == 241 then --OBJ_FREEZE_RAY_GUN
if v.obj_n == 160 and v.frame_n == 1 then --OBJ_EMPTY_BUCKET
printl("THE_WATER_FREEZES")
v.frame_n = 2
elseif v.luatype == "actor" then
if math.random(1, 0x2d) > actor_dex_adj(v) then
attack_with_freezeray(actor, v, 10)
else
printfl("ACTOR_DODGES", v.name)
play_md_sfx(3)
end
end
elseif v.luatype == "obj" or math.random(1, 0x2d) > actor_dex_adj(v) then
local dmg_mode = 0
if weapon.obj_n == 240 then --OBJ_HEAT_RAY_GUN
dmg_mode = 3
end
actor_take_hit(actor, v, spread_weapon_damage_tbl[weapon.obj_n], dmg_mode)
else
printfl("ACTOR_DODGES", v.name)
play_md_sfx(3)
end
end
if weapon.obj_n == 43 or weapon.obj_n == 45 then --OBJ_SHOTGUN, OBJ_BELGIAN_COMBINE
Actor.inv_remove_obj_qty(actor, 58, 1) --OBJ_SHOTGUN_SHELL
else
weapon.qty = weapon.qty - 4
if weapon.qty < 0 then
weapon.qty = 0
end
end
end
local projectile_tbl = {
[41]={tile_num=0x103, sfx_id=7, ammo_obj_n=57},
[42]={tile_num=0x103, sfx_id=7, ammo_obj_n=57},
[44]={tile_num=0x103, sfx_id=8, ammo_obj_n=59},
[45]={tile_num=0x103, sfx_id=8, ammo_obj_n=59},
[46]={tile_num=0x103, sfx_id=8, ammo_obj_n=60},
[47]={tile_num=0x23E, sfx_id=6, ammo_obj_n=63},
[48]={tile_num=0x23F, sfx_id=6, ammo_obj_n=64},
[40]={tile_num=0x23F, sfx_id=6, ammo_obj_n=-3},
[240]={tile_num=0x16B, sfx_id=10, ammo_obj_n=-2},
[241]={tile_num=0x16A, sfx_id=10, ammo_obj_n=-2},
[129]={tile_num=0x10A, sfx_id=6, ammo_obj_n=-2},
[261]={tile_num=0x10A, sfx_id=6, ammo_obj_n=-2},
[313]={tile_num=0x10B, sfx_id=50, ammo_obj_n=-3},
[366]={tile_num=0x16F, sfx_id=34, ammo_obj_n=-3},
[386]={tile_num=0x16C, sfx_id=34, ammo_obj_n=-3},
[364]={tile_num=0x16D, sfx_id=6, ammo_obj_n=-3},
[384]={tile_num=0x16E, sfx_id=6, ammo_obj_n=-3},
}
function fire_range_based_weapon(attacker, target_x, target_y, weapon)
local projectile_info = projectile_tbl[weapon.obj_n]
if projectile_info == nil then
projectile_info = {tile_num=weapon.tile_num, sfx_id=0, ammo_obj_n=-1 }
end
play_md_sfx(projectile_info.sfx_id)
projectile_anim(projectile_info.tile_num, attacker.x, attacker.y, target_x, target_y, 4, false, 0)
if projectile_info.ammo_obj_n > 0 then
Actor.inv_remove_obj_qty(attacker, projectile_info.ammo_obj_n, 1)
elseif projectile_info.ammo_obj_n == -2 then
weapon.qty = weapon.qty - 1
elseif projectile_info.ammo_obj_n == -1 and actor_get_combat_range(attacker, target_x, target_y) > 1 then
if is_open_water_at_loc(target_x, target_y, attacker.z) then
Obj.removeFromEngine(weapon)
else
Obj.moveToMap(weapon, target_x, target_y, attacker.z)
end
--FIXME original updated readied weapons here. We might also need to do that.
end
end