local lua_file = nil

--load common functions
lua_file = nuvie_load("common/common.lua"); lua_file();


SFX_BLOCKED = 0
SFX_HIT = 1
SFX_FOUNTAIN = 2
SFX_DEATH = 3
SFX_RUBBER_DUCK = 4
SFX_BROKEN_GLASS = 5
SFX_BELL = 6
SFX_FIRE = 7
SFX_CLOCK = 8
SFX_PROTECTION_FIELD = 9
SFX_WATER_WHEEL = 10
SFX_MISSLE = 11
SFX_EXPLOSION = 12
SFX_ATTACK_SWING = 13
SFX_SUCCESS = 14
SFX_FAILURE = 15
SFX_CORPSER_DRAGGED_UNDER = 16
SFX_CORPSER_REGURGITATE = 17
SFX_CASTING_MAGIC_P1 = 18
SFX_CASTING_MAGIC_P1_2 = 19
SFX_CASTING_MAGIC_P1_3 = 20
SFX_CASTING_MAGIC_P1_4 = 21
SFX_CASTING_MAGIC_P1_5 = 22
SFX_CASTING_MAGIC_P1_6 = 23
SFX_CASTING_MAGIC_P1_7 = 24
SFX_CASTING_MAGIC_P1_8 = 25
SFX_CASTING_MAGIC_P2 = 26
SFX_CASTING_MAGIC_P2_2 = 27
SFX_CASTING_MAGIC_P2_3 = 28
SFX_CASTING_MAGIC_P2_4 = 29
SFX_CASTING_MAGIC_P2_5 = 30
SFX_CASTING_MAGIC_P2_6 = 31
SFX_CASTING_MAGIC_P2_7 = 32
SFX_CASTING_MAGIC_P2_8 = 33
SFX_AVATAR_DEATH = 34
SFX_KAL_LOR = 35
SFX_SLUG_DISSOLVE = 36
SFX_HAIL_STONE = 37

SFX_EARTH_QUAKE = 39

FADE_COLOR_RED = 12
FADE_COLOR_BLUE = 9

TIMER_LIGHT = 0
TIMER_INFRAVISION = 1
TIMER_STORM = 13
TIMER_TIME_STOP = 14
TIMER_ECLIPSE = 15

OBJLIST_OFFSET_VANISH_OBJ = 0x1c13
OBJLIST_OFFSET_MOONSTONES = 0x1c1b
OBJLIST_OFFSET_KEG_TIMER  = 0x1c4b

g_vanish_obj = {["obj_n"] = 0, ["frame_n"] = 0}

g_keg_timer = 0
g_armageddon = false
g_avatar_died = false -- used so we don't keep casting once Avatar is dead
function is_avatar_dead()
	return g_avatar_died
end

--used with triple crossbow and magic wind spells.
g_projectile_offset_tbl = 
{
 {
  4,5,5,5,5,6,6,6,6,6,6,
  4,4,5,5,5,6,6,6,6,6,7,
  4,4,4,5,5,6,6,6,6,7,7,
  4,4,4,4,5,6,6,6,7,7,7,
  4,4,4,4,4,6,6,7,7,7,7,
  4,4,4,4,4,0,0,0,0,0,0,
  3,3,3,3,2,2,0,0,0,0,0,
  3,3,3,2,2,2,1,0,0,0,0,
  3,3,2,2,2,2,1,1,0,0,0,
  3,2,2,2,2,2,1,1,1,0,0,
  2,2,2,2,2,2,1,1,1,1,0
 },
 {
  2,2,2,2,2,2,3,3,3,3,4,
  1,2,2,2,2,2,3,3,3,4,4,
  1,1,2,2,2,2,3,3,4,4,4,
  1,1,1,2,2,2,3,4,4,4,4,
  1,1,1,1,2,2,4,4,4,4,4,
  0,0,0,0,0,0,4,4,4,4,4,
  0,0,0,0,0,6,6,5,5,5,5,
  0,0,0,0,7,6,6,6,5,5,5,
  0,0,0,7,7,6,6,6,6,5,5,
  0,0,7,7,7,6,6,6,6,6,5,
  0,7,7,7,7,6,6,6,6,6,6
 }
}

--moonstone data is loaded from objlist in load_game()
g_moonstone_loc_tbl =
{
 {x=0x3A7, y=0x106, z=0},
 {x=0x1F7, y=0x166, z=0},
 {x=0x9F,  y=0x3AE, z=0},
 {x=0x127, y=0x26,  z=0},
 {x=0x33F, y=0x0A6, z=0},
 {x=0x147, y=0x336, z=0},
 {x=0x17,  y=0x16,  z=1},
 {x=0x397, y=0x3A6, z=0}
} 

g_show_stealing = config_get_boolean_value("config/ultima6/show_stealing")

-- some common functions

function set_g_show_stealing(stealing)
	g_show_stealing = stealing
end

function dbg(msg_string)
	--io.stderr:write(msg_string)
end

function alignment_is_evil(align)
   if align == ALIGNMENT_EVIL or align == ALIGNMENT_CHAOTIC then return true end
   
   return false
end

function advance_game_time(nturns)
 if nturns == 0 then return end

 coroutine.yield("adv_game_time", nturns);
end

function get_obj_from_inventory(actor)
	local obj = coroutine.yield("inv_obj", actor)
	return obj
end

function get_obj()
	local obj = coroutine.yield("obj")
	return obj
end

function actor_talk(actor)
	coroutine.yield("talk", actor)
	return
end

function get_spell()
	local spell_num = coroutine.yield("spell")
	return spell_num
end

function obj_new(obj_n, frame_n, status, qty, quality, x, y, z)
  local obj = {}
  
  obj["obj_n"] = obj_n or 0
  obj["frame_n"] = frame_n or 0
  obj["status"] = status or 0
  obj["qty"] = qty or 0
  obj["quality"] = quality or 0

  obj["x"] = x or 0
  obj["y"] = y or 0
  obj["z"] = z or 0

  return obj
end

--FIXME need a better way of doing this. Remove need for deprecated setfenv() function.
function run_script(script)
  local t = {};
  setmetatable(t, {__index = _G});
  local body = nuvie_load(script);
  setfenv(body, t);
  body();
end

function look_obj(obj)
   print("Thou dost see " .. obj.look_string);
   local weight = obj.weight; --FIXME this could be a problem if we want to change Lua_number type to int. 
   if weight ~= 0 then
      if obj.qty > 1 and obj.stackable then
         print(". They weigh");
      else
         print(". It weighs");
      end
      
      print(string.format(" %.1f", weight).." stones");
   end

   --FIXME usecode look description should be lua code.
   if usecode_look(obj) then
      print("\n")
      return false
   end
   
   local dmg = weapon_dmg_tbl[obj.obj_n];
   if dmg ~= nil then
      if weight ~= 0 then
         print(" and")
      else
         print(". It")
      end
      
      print(" can do "..dmg.." point")
      if dmg > 1 then print("s") end
      print(" of damage")

   end
   
   local ac = armour_tbl[obj.obj_n]
   if ac ~= nil then
      if weight ~= 0 or dmg ~= 0 then
         print(" and")
      else
         print(". It")
      end
      
      print(" can absorb "..ac.." point")
      if ac > 1 then print("s") end
      print(" of damage")
   end
   
   print(".\n");
   local player_loc = player_get_location();
   if g_show_stealing == true and obj.getable == true and player_loc.z == 0 and obj.ok_to_take == false then
      if math.abs(player_loc.x - obj.x) > 1 or math.abs(player_loc.y - obj.y) > 1 then
        print("PRIVATE PROPERTY\n")
      else
        print("PRIVATE PROPERTY")
      end
   end
   
   return true
end

function player_subtract_karma(k)
	local karma = player_get_karma() - k
	if karma < 0 then karma = 0 end
	
	player_set_karma(karma)
end

function player_add_karma(k)
local karma = player_get_karma() + k
if karma >= 100 then karma = 99 end

player_set_karma(karma)
end

function party_heal()
	for actor in party_members() do
		actor.asleep = false
		actor.poisoned = false
		actor.paralyzed = false	
		actor_remove_charm(actor)
		actor.hp = actor.max_hp
	end
end

function explosion(tile_num, x, y)
	play_sfx(SFX_EXPLOSION)
	return explosion_start(tile_num, x, y)
end

function projectile(tile_num, start_x, start_y, end_x, end_y, speed, spin)

	if spin == nil then spin = 0 end
	
	local rotate_offset = 0
	local src_tile_y_offset = 0
	
	if tile_num == 547 then --spear
		rotate_offset = 45
	elseif tile_num == 566 then --bow
		rotate_offset = 90
		src_tile_y_offset = 4
	elseif tile_num == 567 then --crossbow
		rotate_offset = 90 
		src_tile_y_offset = 3
	end
	
	play_sfx(SFX_MISSLE)
	projectile_anim(tile_num, start_x, start_y, end_x, end_y, speed, false, rotate_offset, spin, src_tile_y_offset)
end

function fade_obj_blue(obj)
  fade_obj(obj, FADE_COLOR_BLUE, 20)
end

function fade_actor_blue(actor)
	Actor.black_fade_effect(actor, FADE_COLOR_BLUE, 20)
end

function fade_obj_out(obj)
	obj.invisible = true
	fade_tile(obj.x, obj.y, obj.z, obj.tile_num)
	obj.invisible = false
end

function fade_obj_in(obj)
	obj.invisible = true
	fade_tile(obj.x, obj.y, obj.z, nil, obj.tile_num)
	obj.invisible = false
end

function fade_actor_in(actor)
	local new_tile_num = actor.tile_num
	actor.visible = false
	fade_tile(actor.x, actor.y, actor.z, nil, new_tile_num)
	actor.visible = true
end

--tile_num, readied location
local g_readiable_objs_tbl = {
[0x200] = 0, [0x201] = 0, [0x202] = 0, [0x203] = 0, [0x204] = 0, [0x205] = 0, [0x206] = 0, [0x207] = 0,

[0x219] = 1, [0x250] = 1, [0x251] = 1, [0x252] = 1, [0x217] = 1, [0x101] = 1,

[0x220] = 2, [0x221] = 2, [0x223] = 2, [0x224] = 2, [0x225] = 2, [0x226] = 2, [0x227] = 2, [0x22A] = 2,
[0x22F] = 2, [0x230] = 2, [0x238] = 2, [0x254] = 2, [0x256] = 2, [0x255] = 2, [0x259] = 2, [0x262] = 2,
[0x263] = 2, [0x264] = 2, [0x270] = 2, [0x271] = 2, [0x272] = 2, [0x273] = 2, [0x274] = 2, [0x275] = 2,
[0x279] = 2, [0x27D] = 2, [0x27E] = 2, [0x27F] = 2, [0x280] = 2, [0x281] = 2, [0x28C] = 2, [0x28E] = 2,
[0x29D] = 2, [0x2A2] = 2, [0x2A3] = 2, [0x2B9] = 2,

[0x210] = 4, [0x211] = 4, [0x212] = 4, [0x213] = 4, [0x214] = 4, [0x215] = 4, [0x216] = 4, [0x218] = 4,
[0x219] = 4, [0x28c] = 4, [0x28e] = 4, [0x29d] = 4, [0x257] = 4,
[0x208] = 5, [0x209] = 5, [0x20a] = 5, [0x20b] = 5, [0x20c] = 5, [0x20d] = 5, [0x20e] = 5, [0x20f] = 5,
[0x222] = 5,

[0x21a] = 7, [0x21b] = 7,

[0x228] = 8, [0x229] = 8, [0x231] = 8, [0x235] = 8, [0x22b] = 8, [0x22c] = 8, [0x22d] = 8, [0x22e] = 8,

[0x258] = 9, [0x37d] = 9, [0x37e] = 9, [0x37f] = 9
}

function actor_is_readiable_obj(actor)
	if g_readiable_objs_tbl[actor.tile_num] ~= nil then
		return true
	end
	
	return false
end

function obj_is_readiable(obj)
	if g_readiable_objs_tbl[obj.tile_num] ~= nil then
		return true
	end

	return false
end

function is_time_stopped()
	if timer_get(TIMER_TIME_STOP) ~= 0 then return true end
	
	return false
end

function load_game()
	objlist_seek(OBJLIST_OFFSET_VANISH_OBJ)
	local tmp_obj_dat = objlist_read2()

	local obj_n = tmp_obj_dat
	local frame_n = 0
	if tmp_obj_dat > 1023 then
		frame_n = tmp_obj_dat - 1023
		obj_n = tmp_obj_dat - frame_n
	end
	
	g_vanish_obj.obj_n = obj_n
	g_vanish_obj.frame_n = frame_n

	--Load moonstone locations.
	objlist_seek(OBJLIST_OFFSET_MOONSTONES)
	
	for i=1,8 do
	
		local x = objlist_read2()
		local y = objlist_read2()
		local z = objlist_read2()
		dbg("moonstone["..i.."] at ("..x..","..y..","..z..")\n")
		g_moonstone_loc_tbl[i] = {x=x, y=y, z=z}
	end

	objlist_seek(OBJLIST_OFFSET_KEG_TIMER)
	g_keg_timer = objlist_read2()
	set_g_armageddon(false)
	g_avatar_died = false
end

function save_game()
	objlist_seek(OBJLIST_OFFSET_VANISH_OBJ)
	
	local tmp_obj_dat = g_vanish_obj.obj_n
	local frame_n = g_vanish_obj.frame_n

	if frame_n > 0 then
		tmp_obj_dat = tmp_obj_dat + (frame_n + 1023)
	end

	objlist_write2(tmp_obj_dat)
	
	--Save moonstone locations
	objlist_seek(OBJLIST_OFFSET_MOONSTONES)
	
	for i=1,8 do
		local loc = g_moonstone_loc_tbl[i]
		objlist_write2(loc.x)
		objlist_write2(loc.y)
		objlist_write2(loc.z)
	end
	
	objlist_seek(OBJLIST_OFFSET_KEG_TIMER)
	objlist_write2(g_keg_timer)
end

function moonstone_set_loc(phase, x, y, z)
	if phase < 1 or phase > 8 then return false end

	g_moonstone_loc_tbl[phase] = {x=x, y=y, z=z}
	return true
end

function moonstone_get_loc(phase)
	if phase < 1 or phase > 8 then return nil end
	
	return g_moonstone_loc_tbl[phase]
end

function update_moongates(show_moongates)
	local i, loc
	for i,loc in ipairs(g_moonstone_loc_tbl) do
		local moongate = map_get_obj(loc.x, loc.y, loc.z, 0x55) --moongate
		if show_moongates == true then
			if moongate == nil and loc.x ~= 0 then
				moongate = Obj.new(0x55, 1)
				Obj.moveToMap(moongate, loc)
			end
		else --hide moongate
			if moongate ~= nil then
				map_remove_obj(moongate)
			end
		end
	end
end

function use_keg(obj)
	if obj.frame_n ~= 0 then
		print("\nNo effect\n")
		return
	end
	
	if g_keg_timer > 0 then
		print("\nNot now\n")
	else
		obj.frame_n = 1
		print("\nPowder lit!\n")
		g_keg_timer = 3
	end
end

function explode_keg()
	--try to find lit keg on the current game map.
	local loc = player_get_location()
	for obj in find_obj(loc.z, 223, 1) do --keg obj, frame_n = 1
		if obj ~= nil then
			explode_obj(obj)
		end
	end
	
	--try to explode lit kegs in the party's inventory
	local party_actor
	for party_actor in party_members() do
		local obj = Actor.inv_get_obj_n(party_actor, 223, 1) --keg with frame_n = 1
		if obj ~= nil then
			explode_obj(obj, party_actor)
		end
	end
end

function explode_obj(obj, actor)
	dbg("Exploding "..obj.name.."\n")
	local x, y, z
	
	if actor ~= nil then
		x = actor.x
		y = actor.y
		z = actor.z
	else
		x = obj.x
		y = obj.y
		z = obj.z
	end
	
	Obj.removeFromEngine(obj)
	
	local hit_items = explosion(0x189, x, y)
	local random = math.random
	local k, v
	for k,v in pairs(hit_items) do
		if v.luatype == "actor" then
			actor_hit(v, random(1, 0x3c))
		end
		if g_avatar_died == true then
			explode_surrounding_objects(x, y, z)
			actor_avatar_death(Actor.get(1))
			return -- don't keep exploding once Avatar is dead
		end
	end
	
	explode_surrounding_objects(x, y, z)
end

function explode_surrounding_objects(x, y, z)	
	--blow up doors and other kegs
	for x = x - 2,x + 2 do
		for y = y - 2,y + 2 do
			local map_obj = map_get_obj(x, y, z, 223)
			if map_obj ~= nil then
				explode_obj(map_obj)
			end
			
			map_obj = map_get_obj(x, y, z, 0x12c) --steel door
			if map_obj == nil or map_obj.frame_n == 0xc then
				map_obj = map_get_obj(x, y, z, 0x129) --oaken door
				if map_obj == nil or map_obj.frame_n == 0xc then
					map_obj = map_get_obj(x, y, z, 0x12a) --windowed door
					if map_obj == nil or map_obj.frame_n == 0xc then
						map_obj = map_get_obj(x, y, z, 0x12b) --cedar door
					end
				end
			end
			
			if map_obj ~= nil and map_obj.frame_n <= 0xc then
				Obj.removeFromEngine(map_obj)
				print("\nThe door is blown up!\n")
			end
		end
	end
end

function set_g_armageddon(val)
	g_armageddon = val
	set_armageddon(val)
end

function create_object_needs_quan(obj_n)
-- obj.stackable is already checked
	return false
end

--load actor functions
actor_load = nuvie_load("u6/actor.lua");
if type(actor_load) == "function" then
   actor_load()
else
   if type(actor_load) == "string" then
      io.stderr:write(actor_load);
   end
end

-- init magic
magic_init = nuvie_load("u6/magic.lua"); magic_init();

-- init usecode
usecode_init = nuvie_load("u6/usecode.lua"); usecode_init();

player_init = nuvie_load("u6/player.lua"); player_init();

