$ = document
###
substitute = ->
source = arguments.shift()
throw new TypeError "substitute called on null or undefined" if not source?
replace = /%%/g
dest = []
tail = 0
for arg in arguments
break if (match = replace.exec source) is null
dest.push source.substring(tail,match.index), arg
tail = replace.lastIndex
dest.join("") + source.substring(tail)
###
#random = Math.random
randomseed = (seed) ->
m = Math.pow(2,48)
x = (25214903917 * seed + 11) % m
->
x = (25214903917 * x + 11) % m
x / m
random = randomseed(Math.random()*Math.pow(2,48))
Rand = (x,y=0) -> y+Math.floor(random()*x)
RandF = (x=1) -> random()*x
Chance = (x) -> random()<x
Choose = (x) -> x[Math.floor(random()*x.length)]
class DieRoll
constructor: (@dice,@sides,@add=0) ->
roll: ->
res = @add
res += Rand(@sides,1) for [1..@dice]
res
describe: ->
"#{@dice}d#{@sides}+#{@add}"
thaco: ->
Math.floor(100.0 * (@sides-(40-@add)) / @sides)
inc_dice: (inc=1) -> @dice += inc
inc_flat: (inc=1) -> @add += inc
class Terrain
can_walk: true
can_climb: false
can_swim: false
can_fly: true
guard_chance: 0
speed_cost: 1
world_freq: 0
heal_cost: 0
type: "terr"
class ClearTerrain extends Terrain
class PlainTerrain extends ClearTerrain
world_freq: 0.7
type: "p"
class ForestTerrain extends ClearTerrain
can_fly: false
speed_cost: 43
world_freq: 0.004
type: "f"
class MountainTerrain extends Terrain
can_walk: false
can_climb: true
world_freq: 0.055
type: "m"
class WaterTerrain extends Terrain
can_walk: false
can_swim: true
world_freq: 0.04
type: "w"
class NamedTerrain extends Terrain
constructor: ->
@name = ""
class VillageTerrain extends NamedTerrain
world_freq: 0.008
guard_chance: 0.2
heal_cost: 15
type: "v"
class CityTerrain extends NamedTerrain
world_freq: 0.004
guard_chance: 0.5
heal_cost: 5
type: "c"
class RoadTerrain extends Terrain
world_freq: 0.024
type: "r"
class ShrineTerrain extends Terrain
guard_chance: 99
type: "s"
class CampTerrain extends Terrain
constructor: (@monster) ->
value: ->
level = @monster::stat.level
15+3*level*level
state: (level) ->
lvl_delta = level - 2*@monster::stat.level
targ_cnt = Math.min(8, 3 + (@monster::stat.level+lvl_delta)//2)
camp_state = switch
when level > 4 + 3*@monster::stat.level then "dead"
when lvl_delta > 0 then "charge"
when level <= 2 then "asleep"
else "awake"
[camp_state, targ_cnt]
chance: (cnt) ->
lvl_delta = level - 2*@monster::stat.level
targ_cnt = Math.min(8, 3 + (@monster::state.level+lvl_delta)//2)
return false if cnt>=targ_cnt
Chance 0.3+0.04*(targ_cnt-cnt)+0.06*lvl_delta
type: "b"
class TempleTerrain extends Terrain
type: "t"
class Loot
type: "loot"
guard_chance: 0.75
class GoldLoot extends Loot
value: 0
class LittleLoot extends GoldLoot
value: 1
guard_chance: 0.15
type: "g"
class LotsLoot extends GoldLoot
value: 6
type: "l"
class TonsLoot extends GoldLoot
value: 17
type: "t"
class EquipmentLoot extends Loot
add_stat: (stat) ->
sub_stat: (stat) ->
class ExoticLoot extends EquipmentLoot
type: "e"
add_stat: (stat) -> stat.armor += 10
sub_stat: (stat) -> stat.armor -= 10
class HODLoot extends EquipmentLoot
type: "d"
add_stat: (stat) -> stat.damage.inc_flat 10
sub_stat: (stat) ->stat.damage.inc_flat -10
class ShieldLoot extends EquipmentLoot
type: "s"
add_stat: (stat) -> stat.armor += 4
sub_stat: (stat) -> stat.armor -= 4
class MasamuneLoot extends EquipmentLoot
type: "m"
add_stat: (stat) -> stat.damage.inc_flat 4
sub_stat: (stat) ->stat.damage.inc_flat -4
class PotionLoot extends Loot
type: "p"
class Treasure
loot: [LittleLoot,LotsLoot,TonsLoot,ExoticLoot,HODLoot,ShieldLoot,MasamuneLoot,PotionLoot]
gen: ->
sum = 0
val = RandF()
for prob,i in @chance
return @loot[i] if val<(sum += prob)
null
class NoTreasure extends Treasure
chance: [0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00]
class LameTreasure extends Treasure
chance: [0.78,0.10,0.00,0.00,0.00,0.01,0.01,0.10]
class ModerateTreasure extends Treasure
chance: [0.10,0.40,0.20,0.00,0.00,0.05,0.05,0.20]
class RichTreasure extends Treasure
chance: [0.00,0.30,0.50,0.00,0.00,0.05,0.05,0.10]
class ArtifactTreasure extends Treasure
chance: [0.00,0.00,0.00,0.00,0.10,0.30,0.30,0.30]
class SpawnedTreasure extends Treasure
chance: [7/16,1/2,1/16,0,0,0,0,0]
class StartingTreasure extends Treasure
chance: [7/8,15/128,1/128,0,0,0,0,0]
class Monster
stat:
tohit: new DieRoll(1,50,50)
hitpoints: new DieRoll(9,10,10)
damage: new DieRoll(9,10,10)
armor: 0
xp_val: 0
level: 0
view_radsq: 0
treasure_chance: 0.0
treasure_type: NoTreasure
#is_wander: false
#is_guard: false
mobility: ["walk"]
constructor: ->
@hp = @stat.hitpoints.roll()
destroy: (manager,world,player) ->
can_move: (terr) ->
for flag in @stat.mobility
return true if terr['can_'+flag]
false
notify: (monster) ->
false
Move: (world,targ) ->
{x:dx,y:dy} = world.Delta @loc,targ
return true if dx is 0 and dy is 0
dx = dx/Math.abs(dx) if dx isnt 0
dy = dy/Math.abs(dy) if dy isnt 0
if not world.monst_at(@loc.x+dx,@loc.y+dy) and @can_move world.terr_at(@loc.x+dx,@loc.y+dy)
world.put_monst @,@loc.x+dx,@loc.y+dy
else if dx isnt 0 and not world.monst_at(@loc.x,@loc.y+dy) and @can_move world.terr_at(@loc.x,@loc.y+dy)
world.put_monst @,@loc.x,@loc.y+dy
else if dy isnt 0 and not world.monst_at(@loc.x+dx,@loc.y) and @can_move world.terr_at(@loc.x+dx,@loc.y)
world.put_monst @,@loc.x+dx,@loc.y
else
return false
return true
Combat: (monster) ->
return "missed" if monster.Defend @
damage = @Attack monster
return "nodamage" unless damage > 0
monster.Damage @,damage
Defend: (monster) ->
40 > (monster.stat.tohit.roll() - @stat.armor//2)
Attack: (monster) ->
@stat.damage.roll() - monster.stat.armor
Damage: (monster,amount) ->
return @Kill monster if amount >= @hp
@hp -= amount
amount
Kill: (monster) ->
@hp = 0
monster.GiveXP @stat.xp_val if monster instanceof Player
"itdied"
Run: ->
type: "monster"
class Player extends Monster
type: "player"
constructor: ->
@level = 1
@gp = @xp = 0
@hp = @max_hp = Rand(6,5)
@heal_timer = 0
@shrine_time = 0
@quest = null
@questcount = 0
@potions = 0
@equipment = {}
@exotic_loc = null
@turns = 0
@casting = false
@stat = { # overrides prototype
damage: new DieRoll(2,4,1)
tohit: new DieRoll(1,50,15)
armor: 0
mobility: ["walk"]
}
Move: (world,targ) ->
{x:dx,y:dy} = world.Delta @loc,targ
return true if targ.x is @loc.x and targ.y is @loc.y
if not world.monst_at(targ.x,targ.y) and @can_move world.terr_at(targ.x,targ.y)
world.put_monst @,targ.x,targ.y
return true
return false
Kill: (monster) ->
@hp = 0
"youdied"
Run: ->
@turns++
if @heal_timer != 0
@heal_timer--
else if @hp < @max_hp
@heal_timer = 64
@Heal 0.05
return
GiveHP: (hp) ->
@hp = Math.min(@hp+hp, @max_hp)
Heal: (pct) ->
@GiveHP Math.max(@max_hp * pct, 1)
CanHeal: ->
@hp < @max_hp
CanLevel: ->
@level < @GetLevelFromXP()
GetLevelFromXP: ->
xp = @xp
switch
when xp >= 1404 then (Math.sqrt(216*xp-120935)+437)//54
when xp >= 280 then (Math.sqrt(136*xp- 17055)+127)//34
else (Math.sqrt( 64*xp+ 576)- 8)//16
#level = 1
#txp = 16
#while txp <= @xp
# txp += if level>14 then 10*(level-14)+9*(level-6)+8*level+16 else
# if level>6 then 9*(level-6)+8*level+16 else
# 8*level+16
# level++
#level
GiveLevel: ->
new_hp = if @level<12 then Rand(6,2) else Rand(4,1)
@level += 1
if @max_hp < 222
new_hp = 1 if @max_hp >= 200
@max_hp += new_hp
@hp += new_hp
if @level <= 5
@stat.damage.inc_dice()
@stat.damage.inc_flat()
else if @level <= 12
@stat.damage.inc_dice()
else if @level%2 == 1
@stat.damage.inc_dice()
if @level <= 6
@stat.tohit.inc_dice(2)
@stat.tohit.inc_flat()
else if @stat.tohit.add < 30
@stat.tohit.inc_flat()
else if @stat.tohit.add < 35 and @level%2 == 1
@stat.tohit.inc_flat()
@level
GiveXP: (inc) ->
old = @GetLevelFromXP()
@xp += inc
old < @GetLevelFromXP()
Praying: ->
turns_needed = if @level > 8 then 16 else @level*2
if ++@shrine_time > turns_needed
@shrine_time = 0
return true
return false
TakeLoot: (loot) ->
@gp += loot.value
TakeEquipment: (loot) ->
return false if @equipment[loot.type]
if loot instanceof ExoticLoot
equip.sub_stat @stat for t,equip of @equipment when equip instanceof ShieldLoot
loot.add_stat @stat
else if loot instanceof ShieldLoot
loot.add_stat @stat unless @equipment[ExoticLoot::type]
else if loot instanceof HODLoot
equip.sub_stat @stat for t,equip of @equipment when equip instanceof MasamuneLoot
loot.add_stat @stat
else if loot instanceof MasamuneLoot
loot.add_stat @stat unless @equipment[HODLoot::type]
@equipment[loot.type] = loot
true
FoundExotic: (loc) ->
if @level>10 and @exotic_loc and @exotic_loc.x==loc.x and @exotic_loc.y==loc.y
@exotic_loc = null
return true
return false
class CombatMonster extends Monster
constructor: ->
@is_attack = false
@is_flee = false
@is_guard = false
@is_cloud = false
@was_hit = false
@was_damaged = false
@ondeath = null
@hp = @stat.hitpoints.roll()
@targ = null
Defend: (monster) ->
@was_hit = true
super
camp: (x,y,hp_fac=1) ->
@is_guard = @is_cloud = true
@hp *= hp_fac
@targ = x:x,y:y
set_attack: (flag=true) ->
@is_guard = false if flag
@is_attack = flag
set_flee: (flag=true) ->
@is_attack = false if flag
@is_flee = flag
set_guard: (flag=true) ->
@is_guard = flag
clear_damage: ->
@was_damaged = @was_hit = false
notify: (monster) ->
return false if @is_attack or @is_flee
notify_chance =
if monster.type == @type
if @is_cloud then 0.95 else 0.90
else
if @is_cloud or @is_guard then 0 else 0.60-0.15*(@stat.level-monster.stat.level)
if Chance notify_chance then @set_attack true else false
Run: (game,world,player) ->
did_we_enrage = false
player_delta = world.Delta @loc,player.loc
player_dist =
x: Math.abs(player_delta.x)
y: Math.abs(player_delta.y)
if @was_hit
did_we_enrage = world.NotifyNearbyMonsters @,5
if @is_cloud and @was_damaged and @dist_to_camp(world)>10
@set_flee true
else
@set_attack true
@clear_damage
if @is_attack
if player_dist.x<=1 and player_dist.y<=1
game.RunCombat @,player
else if not @is_guard
if Chance 0.02*(player_dist.x+player_dist.y)-0.01
@set_attack false
else
@Move world,player.loc
#if @is_flee
if @is_cloud and not (@is_attack or @is_guard)
dist = @dist_to_camp world
ret_chance = 0.10*dist+0.20
if @is_flee
if dist>4
ret_chance = 1.0
else
@set_flee false
@Move world, if Chance ret_chance then @targ else
x:world.MAPX(@loc.x+Rand(9,-4))
y:world.MAPY(@loc.y+Rand(9,-4))
@do_wander world unless @is_attack or @is_flee or @is_guard
#if @is_guard
if did_we_enrage
game.MonsterEnraged @
else
@LookFor game,player_delta if not (@is_guard or @is_attack or @is_cloud) and Chance 3/16
dist_to_camp: (world) ->
delta = world.Delta @loc,@targ
Math.abs(delta.x)+Math.abs(delta.y)
do_wander: (world) ->
loc = world.NearbyTreasure(@) ?
x: world.MAPX(@loc.x+Rand(9,-4))
y: world.MAPY(@loc.y+Rand(9,-4))
@Move world,loc
@set_guard true if Chance (world.treas_at(loc.x,loc.y) ? world.terr_at(loc.x,loc.y)).guard_chance
LookFor: (game,delta) ->
# inverse square law
net_rad = delta.x*delta.x+delta.y*delta.y
if @stat.view_radsq>net_rad and
Chance 1-net_rad/@stat.view_radsq
@is_attack = true
game.MonsterAlerted @
class GoblinMonster extends CombatMonster
type: "gb"
stat:
tohit: new DieRoll(1,50,2)
hitpoints: new DieRoll(1,6,1)
damage: new DieRoll(1,4,0)
armor: 0
xp_val: 3
level: 1
view_rad: 8
treasure_chance: 0.25
treasure_type: LameTreasure
#is_wander: true
#is_guard: false
mobility: ["walk"]
class HeadlessMonster extends CombatMonster
type: "hd"
stat:
tohit: new DieRoll(1,50,8)
hitpoints: new DieRoll(2,6,4)
damage: new DieRoll(1,8,1)
armor: 1
xp_val: 7
level: 2
view_radsq: 4*4
treasure_chance: 0.40
treasure_type: ModerateTreasure
#is_wander: true
#is_guard: false
mobility: ["walk"]
class BatMonster extends CombatMonster
type: "bt"
stat:
tohit: new DieRoll(1,50,0)
hitpoints: new DieRoll(1,4,0)
damage: new DieRoll(1,3,0)
armor: 0
xp_val: 2
level: 1
view_radsq: 8*8
treasure_chance: 0.0
treasure_type: NoTreasure
#is_wander: true
#is_guard: false
mobility: ["fly"]
class PigMonster extends CombatMonster
type: "pg"
stat:
tohit: new DieRoll(1,50,4)
hitpoints: new DieRoll(4,5,5)
damage: new DieRoll(2,3,0)
armor: 4
xp_val: 9
level: 2
view_radsq: 6*6
treasure_chance: 0.20
treasure_type: LameTreasure
#is_wander: true
#is_guard: false
mobility: ["walk"]
class TrollMonster extends CombatMonster
type: "tr"
stat:
tohit: new DieRoll(1,50,17)
hitpoints: new DieRoll(3,8,4)
damage: new DieRoll(2,8,2)
armor: 2
xp_val: 13
level: 3
view_radsq: 10*10
treasure_chance: 0.50
treasure_type: ModerateTreasure
#is_wander: true
#is_guard: false
mobility: ["walk"]
class GhostMonster extends CombatMonster
type: "gh"
stat:
tohit: new DieRoll(1,50,12)
hitpoints: new DieRoll(2,8,8)
damage: new DieRoll(3,8,1)
armor: 0
xp_val: 15
level: 4
view_radsq: 10*10
treasure_chance: 0.25
treasure_type: ModerateTreasure
#is_wander: true
#is_guard: false
mobility: ["walk","fly"]
class SquidMonster extends CombatMonster
type: "sq"
stat:
tohit: new DieRoll(1,50,20)
hitpoints: new DieRoll(2,8,1)
damage: new DieRoll(3,8,1)
armor: 1
xp_val: 9
level: 2
view_radsq: 6*6
treasure_chance: 0.15
treasure_type: NoTreasure
#is_wander: true
#is_guard: false
mobility: ["swim"]
class DemonMonster extends CombatMonster
type: "dm"
stat:
tohit: new DieRoll(1,50,25)
hitpoints: new DieRoll(5,8,8)
damage: new DieRoll(4,8,1)
armor: 3
xp_val: 28
level: 6
view_radsq: 12*12
treasure_chance: 0.50
treasure_type: RichTreasure
#is_wander: true
#is_guard: false
mobility: ["walk","climb"]
class DragonMonster extends CombatMonster
do_wander: (world) ->
loc =
x: world.MAPX(@loc.x+Rand(9,-4))
y: world.MAPY(@loc.y+Rand(9,-4))
@Move world,loc
type: "dg"
stat:
tohit: new DieRoll(1,60,30)
hitpoints: new DieRoll(6,10,10)
damage: new DieRoll(5,6,4)
armor: 5
xp_val: 75
level: 99
view_radsq: 18*18
treasure_chance: 0.75
treasure_type: ArtifactTreasure
#is_wander: true
#is_guard: false
mobility: ["walk","fly"]
class GazerMonster extends CombatMonster
type: "gz"
stat:
tohit: new DieRoll(1,50,21)
hitpoints: new DieRoll(6,6,1)
damage: new DieRoll(4,4,1)
armor: 2
xp_val: 21
level: 5
view_radsq: 15*15
treasure_chance: 0.15
treasure_type: ModerateTreasure
#is_wander: true
#is_guard: false
mobility: ["fly"]
class ReaperMonster extends CombatMonster
type: "rp"
stat:
tohit: new DieRoll(1,50,24)
hitpoints: new DieRoll(5,8,8)
damage: new DieRoll(3,12,2)
armor: 4
xp_val: 25
level: 5
view_radsq: 10*10
treasure_chance: 0.25
treasure_type: RichTreasure
#is_wander: true
#is_guard: false
mobility: ["walk"]
class MimicMonster extends CombatMonster
constructor: ->
super
@is_guard = true
do_wander: (world) ->
type: "mm"
stat:
tohit: new DieRoll(1,50,17)
hitpoints: new DieRoll(4,8,4)
damage: new DieRoll(4,3,0)
armor: 3
xp_val: 15
level: 3
view_radsq: 6*6
treasure_chance: 0.50
treasure_type: ModerateTreasure
#is_wander: false
#is_guard: true
mobility: ["walk"]
class LurkerMonster extends CombatMonster
type: "lk"
stat:
tohit: new DieRoll(1,50,17)
hitpoints: new DieRoll(7,8,5)
damage: new DieRoll(5,3,1)
armor: 4
xp_val: 19
level: 4
view_radsq: 6*6
treasure_chance: 0.50
treasure_type: ModerateTreasure
#is_wander: true
#is_guard: false
mobility: ["walk"]
class SlimeMonster extends CombatMonster
type: "sl"
stat:
tohit: new DieRoll(1,50,15)
hitpoints: new DieRoll(1,3,1)
damage: new DieRoll(1,2,1)
armor: 0
xp_val: 2
level: 1
view_radsq: 4*4
treasure_chance: 0.0
treasure_type: NoTreasure
#is_wander: true
#is_guard: false
mobility: ["walk"]
destroy: (manager,world,player) ->
if Chance(if player.level>20 then 40 else 20+player.level)
# spawn two slime nearby... need to find 2 locations... one will be us
loc = world.FindSurroundingGoodPlace @,@loc.x,@loc.y
if loc
world.put_monst_pt(manager.Gen(SlimeMonster),loc).set_attack()
world.put_monst_pt(manager.Gen(SlimeMonster),@loc).set_attack()
class MongbatMonster extends CombatMonster
type: "mb"
stat:
tohit: new DieRoll(1,50,11)
hitpoints: new DieRoll(2,10,2)
damage: new DieRoll(3,3,2)
armor: 1
xp_val: 9
level: 3
view_radsq: 12*12
treasure_chance: 0.05
treasure_type: LameTreasure
#is_wander: true
#is_guard: false
mobility: ["fly"]
class TalkingMonster extends Monster
Damage: (monster,amount) ->
# never damage talking monsters, period...
""
class WisepersonMonster extends TalkingMonster
type: "wp"
class SnickersMonster extends TalkingMonster
type: "sn"
class LBMonster extends TalkingMonster
type: "lb"
class MerchantMonster extends TalkingMonster
type: "mc"
class MonsterManager
types: [
Player
GoblinMonster
HeadlessMonster
BatMonster
PigMonster
TrollMonster
GhostMonster
SquidMonster
DemonMonster
DragonMonster
GazerMonster
ReaperMonster
MimicMonster
LurkerMonster
SlimeMonster
MongbatMonster
WisepersonMonster
SnickersMonster
LBMonster
MerchantMonster
]
constructor: ->
@monsters = {}
@count = 0
Remove: (monster,world,player) ->
idx = @monsters[monster.type]?.indexOf(monster)
return null if idx is -1
@monsters[monster.type].splice(idx,1)
world.pt(monster.loc).monster = null
if monster instanceof CombatMonster and Chance monster.stat.treasure_chance
treasure = monster.stat.treasure_type.gen()
treasure = LotsLoot if player.equipment[treasure::type]
world.put_treas_pt new treasure,monster.loc
monster.destroy @,world,player
@count-- if not (monster instanceof Player)
Gen: (monster) ->
list = @monsters[monster::type]
list = @monsters[monster::type] = [] unless list
list.push m = new monster
@count++ if monster isnt Player
m
Count: (monster) ->
@monster[monster::type].length
Choose: (player) ->
targ_lvl = player.level//2
mgen = new DieRoll(6,2,-9+targ_lvl)
roll = Math.min(Math.max(mgen.roll(),1),6)
Choose (M for M in @types when M::stat.level is roll)
Chance: ->
Chance switch
when @count < 30 then 1
when @count < 50 then 1/32
when @count < 100 then 1/64
Camp: (camp,level,world) ->
[camp_state,targ_cnt] = camp.state(level)
{x:x, y:y} = camp.loc
monster = world.monst_at x,y
if monster is null
monster = @Gen camp.monster
monster.camp x,y,2.0
monster.set_guard false if camp_state is "charge"
world.put_monst monster,x,y
else if monster instanceof camp.monster
m.set_guard false if camp_state is "charge" and Chance 0.5
return if camp_state is "asleep" or camp_state is "dead"
cnt = world.CountMonsters camp.monster,x,y,7
blt = 0
lx = [-1,-1, 0, 1, 1, 1, 0,-1]
ly = [ 0,-1,-1,-1, 0, 1, 1, 1]
for j in [0...8]
m = world.monst_at(x+lx[j],y+ly[j])
if m is null
if @Count camp.monster < 90 and camp.chance cnt+blt
monster = @Gen camp.monster
monster.camp world.MAPX(x+lx[j]),world.MAPY(y+ly[j]),1.4
monster.set_guard false if camp_state is "charge"
world.put_monst monster,x+lx[j],y+ly[j]
blt++
if m instanceof camp.monster
m.set_guard false if camp_state is "charge" and Chance 0.5
Run: (game,world,player) ->
for M in @types
continue unless @monsters[M::type]
for monster in @monsters[M::type]
monster.Run game,world,player
Each: (monster, callback) ->
@monsters[monster::type]?.forEach callback
MS_PER_VEL_MOVE = 400
gActions = []
class VelAction
constructor: (dx,dy,steps) ->
@delta = x:dx,y:dy
@frames = steps
@timeout = if steps? then steps*MS_PER_VEL_MOVE
gActions.push @
destroy: ->
idx = gActions.indexOf @
gActions[@type].splice(idx,1) if idx isnt -1
Run: (frametime) ->
if @timeout?
if frametime >= @timeout
@timeout = 0
@loc = null
return true
next = (@timeout -= frametime)//MS_PER_VEL_MOVE
if next != @frames
@frames = next
return true
return false
class MapGenerator
constructor: (@world) ->
Clump: (terr,rad,xscale) ->
ang = RandF(Math.PI)
m0 = Math.cos(ang)
m1 = Math.sin(ang)
m2 = -m1
m3 = m0
xseed = Rand(@world.cols)
yseed = Rand(@world.rows)
for x in [-rad .. rad]
for y in [-rad .. rad]
vx = x+RandF()
vy = y+RandF()
nx = vx*m0 + vy*m1
ny = vx*m2 + vy*m3
nx /= xscale
@world.put_terr new terr, xseed+x, yseed+y if nx*nx+ny*ny < rad*rad
Circle: (terr,rad) ->
@Clump terr,rad,1.0
Linear: (terr,rad) ->
@Clump terr,rad,RandF(1/3)
Random: (terr,rad) ->
(if Chance(0.5) then @Circle else @Linear).call @,terr,rad
class Tile
#width: 12
#height: 12
class WorldMapTile extends Tile
constructor: (x,y)->
@terrain = new PlainTerrain
@terrain.loc = x:x,y:y
@treasure = null
@monster = null
@action = null
class World
MAPX: (x) -> (x+@cols)%@cols
MAPY: (y) -> (y+@rows)%@rows
constructor: (@cols, @rows)->
@map = (new WorldMapTile x,y for y in [1..@rows] for x in [1..@cols])
@locations = {}
gen = new MapGenerator @
gen.Circle ForestTerrain, Rand(5,2) for [1..24]
@FindAPlaceFor new ForestTerrain for [1..80]
gen.Linear MountainTerrain, Rand(6,4) for [1..12]
gen.Random WaterTerrain, Rand(3,3) for [1..3]
for [1..3]
{x:x,y:y} = @Find (x,y) => not (@terr_at(x,y) instanceof NamedTerrain)
dx = Rand(3,-1)
dy = Rand(3,-1)
dx = 1 if dx==0 and dy==0
@NameCity @put_terr(new CityTerrain,x,y), @RandomFantasyName(4,4)
for [1..Rand(18,9)]
x = @MAPX(x+dx)
y = @MAPY(y+dy)
@put_terr new RoadTerrain,x,y unless @terr_at(x,y) instanceof NamedTerrain
x = @MAPX(x+dx)
y = @MAPY(y+dy)
while @terr_at(x,y) instanceof NamedTerrain
x = @MAPX(x+dx)
y = @MAPY(y+dy)
@NameCity @put_terr(new CityTerrain,x,y), @RandomFantasyName(4,4)
@NameCity @FindAPlaceFor(new VillageTerrain), @RandomFantasyName(2,3) for [1..8]
@PlaceShrine MountainTerrain
@PlaceShrine WaterTerrain
dist_pt_to_pt: (la, lb) ->
dx = Math.abs(la.x-lb.x)
dy = Math.abs(la.y-lb.y)
dx = @cols-dx if dx>@cols/2
dy = @rows-dy if dy>@rows/2
{x:dx, y:dy}
at: (x,y) ->
@map[@MAPX(x)][@MAPY(y)]
pt: (loc) ->
@at loc.x,loc.y
terr_at: (x,y) ->
@map[@MAPX(x)][@MAPY(y)].terrain
terr_pt: (loc) ->
@terr_at loc.x,loc.y
monst_at: (x,y) ->
@map[@MAPX(x)][@MAPY(y)].monster
treas_at: (x,y) ->
@map[@MAPX(x)][@MAPY(y)].treasure
put_terr: (t,x,y) ->
x = @MAPX(x)
y = @MAPY(y)
t.loc = x:x,y:y
@map[x][y].terrain = t
put_monst: (t,x,y) ->
x = @MAPX(x)
y = @MAPY(y)
@map[t.loc.x][t.loc.y].monster = null if t.loc and @map[t.loc.x][t.loc.y].monster is t
t.loc = x:x,y:y
@map[x][y].monster = t
put_monst_pt: (t,loc) ->
@put_monst t,loc.x,loc.y
put_treas: (t,x,y) ->
x = @MAPX(x)
y = @MAPY(y)
t.loc = x:x,y:y
@map[x][y].treasure = t
put_treas_pt: (t,loc) ->
@put_treas t,loc.x,loc.y
add_action: (t,x,y,time) ->
x = @MAPX(x)
y = @MAPY(y)
t.loc = x:x,y:y
@map[x][y].action = t
Delta: (l1, l2) ->
x = l2.x-l1.x
y = l2.y-l1.y
hw = @cols//2
hh = @rows//2
x:if x<-hw then x+@cols else if x>hw then x-@cols else x,
y:if y<-hh then y+@rows else if y>hh then y-@rows else y
Each: (callback) ->
for x in [0...@cols]
for y in [0...@rows]
callback(@at(x,y),x,y)
Find: (check) ->
x = Rand(@cols)
y = Rand(@rows)
for lx in [0...@cols]
for ly in [0...@rows]
mx = @MAPX(x+lx)
my = @MAPY(y+ly)
return x:mx, y:my if check mx,my
null
FindAPlaceFor: (terr) ->
{x:x,y:y} = @Find (x,y) => @terr_at(x,y) instanceof ClearTerrain
@put_terr terr,x,y
FindClearRad: (rad, check) ->
@Find (x,y) =>
for i in [-rad..rad]
for j in [-rad..rad]
return false unless check x+i,y+j
return true
FindClearNearTerr: (terr, monster, plain_terr=ClearTerrain, screen=null) ->
for lx in [0...@cols]
for ly in [0...@rows]
{x:x,y:y} = @FindSafeSquare monster, plain_terr, screen
i = Rand(8)
nx = [-1,-1, 0, 1, 1, 1, 0,-1]
ny = [ 0,-1,-1,-1, 0, 1, 1, 1]
for j in [0...8]
return {x:x,y:y} if @terr_at(x+nx[i],y+ny[i]) instanceof terr
i = (i+1)%8
null
FindSafeSquare: (monster,terr,screen) ->
@Find (x,y) =>
tile = @at x,y
return (not terr or tile.terrain instanceof terr) and
not tile.monster and
monster.can_move(tile.terrain) and
not screen?.visible x,y
FindSurroundingGoodPlace: (monster, nx, ny) ->
lx = [-1,-1, 0, 1, 1, 1, 0,-1]
ly = [ 0,-1,-1,-1, 0, 1, 1, 1]
Choose (x:@MAPX(lx[j]+nx),y:@MAPY(ly[j]+ny) for j in [0...8] when monster.can_move @terr_at(lx[j]+nx,ly[j]+ny))
PlaceShrine: (terr) ->
loc = @FindClearRad(2, (x,y)=>@terr_at(x,y) instanceof ClearTerrain) ?
@FindClearRad(1, (x,y)=>@terr_at(x,y) instanceof ClearTerrain)
return @FindAPlaceFor new ShrineTerrain unless loc
@put_terr new terr,x,y for x in [loc.x-1 .. loc.x+1] for y in [loc.y-1 .. loc.y+1]
[dx,dy] = Choose [
[-1, 0]
[ 0,-1]
[ 1, 0]
[ 0, 1]
]
@put_terr new RoadTerrain, loc.x+dx,loc.y+dy
@put_terr new RoadTerrain, loc.x+2*dx,loc.y+2*dy
@put_terr new ShrineTerrain, loc.x,loc.y
RandomFantasyName: (maxLen,minLen) ->
consonant = "bcdfghjklmnpqrstvwxzzk"
vowel = vowel1 = "aeiouyaey"
vowel2 = vowel+"'"
pick = (s) ->
vowel = vowel1 if (c = s.charAt Rand(s.length)) == "'"
c
str = (pick(if i&1 then vowel else consonant) for i in [0...Rand(maxLen,minLen)])
str[0] = str[0].toUpperCase()
str.join ""
NameCity: (terr,name) ->
name = "New "+name if @locations[name]
terr.name = name
@locations[name] = terr
CityNames: (skip="",filter=NamedTerrain) ->
(name for name,terr of @locations when name isnt skip and terr instanceof filter)
PlaceTreasure: (treasure,player,offscreen=true) ->
loc = @FindSafeSquare player,null,if offscreen then @screen else null
@put_treas_pt new treasure, loc
# FIXME deprecated?
CanEnter: (monster,x,y) ->
tile = @at x,y
not tile.monster and monster.can_move tile.terrain
CountMonsters: (monster,x,y,rad) ->
cnt = 0
for i in [-rad..rad]
for j in [-rad..rad]
cnt++ if @monst_at x+i,y+j instanceof monster
cnt
NotifyNearbyMonsters: (us,rad) ->
did_we_enrage = false
for i in [-rad..rad]
for j in [-rad..rad]
monster = @monst_at us.loc.x+i,us.loc.y+j
if monster and monster isnt us
did_we_enrage = true if monster.notify us
did_we_enrage
NearbyTreasure: (monster,rad) ->
for i in [-rad..rad]
for j in [-rad..rad]
tile = @at monster.loc.x+i,monster.loc.y+j
if tile.treasure and monster.can_move tile.terrain
return x:@MAPX(monster.loc.x+i), y:@MAPY(monster.loc.y+j)
null
class OWGame
constructor: (@screen) ->
@status_lines = []
@status_cur_line = 0
@monsters = new MonsterManager
@messageTime = 5000
AddMessage: (msg) ->
@status_lines.push msg
@status_cur_line++
@screen.timeout @messageTime, => @update_messages()
@messageTime += 220
update_messages: ->
@status_cur_line--
@screen.refresh()
Look: (loc) ->
@screen.do_look(false)
tile = @world.pt loc
dist = @world.dist_pt_to_pt loc,@player.loc
if dist.x<2 and dist.y<2
@world.put_treas_pt new ExoticLoot, loc if @player.FoundExotic loc
if tile.monster is @player
@AddMessage gOWString.ow_stats_intro
if @player.quest
@AddMessage gOWString.ow_stats_acquest.substitute @player.stat.armor,@player.quest
else
@AddMessage gOWString.ow_stats_ac.substitute @player.stat.armor
@AddMessage gOWString.ow_stats_dmg.substitute @player.stat.damage.describe()
@AddMessage gOWString.ow_stats_thaco.substitute @player.stat.tohit.thaco()
@screen.refresh()
return
if tile.monster
#if tile.monster instanceof DragonMonster and @player.dragons>=1
@AddMessage gOWString.ow_id_monst.substitute gOWString["ow_monster_"+tile.monster.type]
if tile.treasure
@AddMessage if tile.treasure instanceof GoldLoot then gOWString.ow_id_loot.substitute tile.treasure.value else gOWString["ow_id_loot_"+tile.treasure.type]
if tile.terrain instanceof CampTerrain or (tile.monster is null and tile.treasure is null)
@AddMessage gOWString.ow_id_terr.substitute gOWString["ow_terrain_"+tile.terrain.type]
@AddMessage gOWString.ow_id_city.substitute tile.terrain.name if tile.terrain instanceof NamedTerrain
@AddMessage gOWString.ow_id_camp.substitute gOWString["ow_monster_"+tile.terrain.monster::type] if tile.terrain instanceof CampTerrain
@screen.refresh()
dump_map: ->
for y in [0..@world.rows]
line = ""
for x in [0..@world.cols]
tile = @world.at x,y
code = switch tile.terrain.type
when "f" then "+"
when "m" then "^"
when "w" then "~"
when "v" then "*"
when "c" then "$"
when "r" then "#"
when "s" then "T"
when "p" then "w"
when "t" then "&"
else "."
switch tile.treasure?.type
when "loot" then code="0"
when "l" then code="1"
when "t" then code="2"
code = "E" if @player.exotic_loc.x==x and @player.exotic_loc.y==y
line += code
console.log line
console.log name for name in @world.CityNames()
CreateTheGameSpace: ->
@world = new World 64,64
@player = @monsters.Gen Player
@world.put_monst_pt @player,@world.FindSafeSquare @player
@world.PlaceTreasure StartingTreasure::gen(),@player,false for [1..64]
@treasureTime = 0
#CreateMonsters()
num_squid = 0
for i in [1..120] when Chance(1/3)
monster = @monsters.Choose @player
if monster is SquidMonster
num_squid++
else
@CreateAMonster monster
#CreateTheDragon()
loc = @world.FindClearRad(2,(x,y)=>@world.terr_at(x,y) instanceof MountainTerrain) or
@world.FindClearRad(1,(x,y)=>@world.terr_at(x,y) instanceof MountainTerrain)
return @CreateTheGameSpace unless loc
@CreateAMonsterAt(DragonMonster,loc).set_guard true
@CreateMonsterCamp GoblinMonster,PlainTerrain
@CreateMonsterCamp GoblinMonster,PlainTerrain
@campTime = -8
@player.exotic_loc = @world.FindClearNearTerr WaterTerrain,@player
@AddMessage gOWString.ow_entergame
CreateAMonster: (type, screen=null) ->
monster = @monsters.Gen type
loc = @world.FindSafeSquare monster,null,screen
@world.put_monst monster,loc.x,loc.y
CreateAMonsterAt: (type, loc) ->
monster = @monsters.Gen type
@world.put_monst monster,loc.x,loc.y
CreateMonsterCamp: (type, terr) ->
monster = new type
loc = @world.FindClearNearTerr(terr,monster,PlainTerrain,@screen) or
@world.FindClearNearTerr(terr,monster,PlainTerrain) or
@world.FindSafeSquare(monster,terr,@screen)
return if not loc or @world.pt(loc).monster
@world.put_terr new CampTerrain(type), loc.x,loc.y
monster = @CreateAMonsterAt type,loc
monster.camp loc.x,loc.y,2.0
for [0...type::stat.level//3+2]
nloc = @world.FindSurroundingGoodPlace monster,loc.x,loc.y
@CreateAMonsterAt(type,nloc)?.camp(nloc.x,nloc.y,1.5) if nloc
RunActions: (time) ->
Draw: (time) ->
@RunActions(time)
@screen.refresh()
MonsterEnraged: (monster) ->
@monsterWasEnraged = true
MonsterAlerted: (monster) ->
@monsterWasAlerted = true
RunSim: (time) ->
@messageTime = 5000
@monsterWasEnraged = false
@monsterWasAlerted = false
@monsters.Run @,@world,@player
if @monsterWasEnraged
# Did we enrage?
@AddMessage gOWString.ow_enrage
else if @monsterWasAlerted
# Were we seen?
@AddMessage gOWString.ow_wereseen
# respawn monsters and treasure
@CreateAMonster @monsters.Choose(@player),@screen if @monsters.Chance()
# check monster camps
if @player.turns - @campTime is 16
@world.Each (tile) =>
@monsters.Camp tile.terrain,@player.level,@world if tile.terrain instanceof CampTerrain
@campTime = @player.turns
# spawn new treasure every 32 turns
@SpawnNewTreasure() if @player.turns - @treasureTime is 32
# shrine setup
terrain = @world.terr_pt @player.loc
if terrain instanceof ShrineTerrain
@RunShrine() if @player.CanLevel()
else if @player.shrine_time > 0
@AddMessage gOWString.ow_stoppray if @player.CanLevel()
@player.shrine_time = 0
# if im in a town or village, do healing, check quests
if terrain instanceof VillageTerrain or terrain instanceof CityTerrain
@check_healing_area terrain.heal_cost if @player.CanHeal()
@DoQuest @world.pt @player.loc
else if terrain instanceof CampTerrain
@GivePlayerXP terrain.value()
@AddMessage gOWString.ow_destroycamp
@world.pt(@player.loc).terrain = new PlainTerrain
else if terrain instanceof TempleTerrain
@RunTemple() if false
# crazy dragon fun
#TODO
# give more hp over time
if @player.turns % 256 == 0
@player.max_hp++ if @player.max_hp < 180
@AddMessage gOWString.ow_moreturn
@Draw time
RunCombat: (attacker,defender) ->
switch damage = attacker.Combat defender
when "missed"
@AddMessage gOWString.ow_missed.substitute gOWString["ow_monster_"+attacker.type]
when "nodamage"
@AddMessage gOWString.ow_nodamage.substitute gOWString["ow_monster_"+attacker.type]
when "itdied"
@AddMessage gOWString.ow_itdied.substitute gOWString["ow_monster_"+defender.type]
@monsters.Remove defender,@world,@player
when "youdied"
@AddMessage gOWString.ow_youdied.substitute gOWString["ow_monster_"+attacker.type]
@monsters.Remove @player,@world,@player
else
@AddMessage gOWString.ow_damage.substitute gOWString["ow_monster_"+attacker.type],damage
check_healing_area: (cost) ->
if @player.gp >= cost
@player.Heal 0.10
@player.gp -= cost
@AddMessage gOWString.ow_payheal.substitute cost
else
@AddMessage gOWString.ow_nocash.substitute cost
RunShrine: ->
@AddMessage gOWString.ow_startpray if @player.shrine_time == 0
@AddMessage gOWString.ow_keeppray if @player.shrine_time&1 == 1
@GivePlayerLevel() if @player.Praying()
RunTemple: ->
@AddMessage gOWString.ow_temple
@GivePlayerXP 222
DoQuest: (tile) ->
if @player.quest is null
return unless tile.terrain instanceof CityTerrain
name = Choose @world.CityNames tile.terrain.name, if Chance(0.25) then CityTerrain else VillageTerrain
@player.quest = name
@AddMessage gOWString.ow_getquest.substitute name
else
return unless @player.quest == tile.terrain.name
@AddMessage gOWString.ow_doquest
@GivePlayerXP Math.min(@player.questcount*(2+@player.level//10)+5, 20+2*@player.level)
@player.quest = null
@player.questcount++
MovePlayer: (dir) ->
targ =
x: @player.loc.x+dir.x
y: @player.loc.y+dir.y
tile = @world.pt targ
if tile.monster
if tile.monster instanceof TalkingMonster
tile.monster.TalkTo @player
else if tile.monster isnt @player
@RunCombat @player,tile.monster
else
if @player.Move @world,targ
@HandleTreasure tile if tile.treasure
else
@AddMessage gOWString.ow_impass
GivePlayerLevel: ->
@player.GiveLevel()
@AddMessage gOWString.ow_newlevel.substitute @player.level
switch @player.level
# when 3, 9
# #CreateMonsterCamp TrollMonster,PlainTerrain
# when 4
# #CreateMonsterCamp HeadlessMonster,PlainTerrain
# when 5
# #CreateTalkingMonster MerchantMonster,RoadTerrain
# when 7
# #CreateTalkingMonster SnickersMonster,CityTerrain
# when 10
# #CreateTalkingMonster WisepersonMonster,WaterTerrain
when 15
@AddMessage gOWString.ow_dragon
@monsters.Each DragonMonster, (dragon) -> dragon.set_attack()
# when 18, 21
# #CreateMonsterCamp DemonMonster,PlainTerrain
GivePlayerXP: (xp) ->
@AddMessage gOWString.ow_gainxp.substitute xp
@AddMessage gOWString.ow_goshrine if @player.GiveXP xp
SpawnNewTreasure: ->
#sharp_die = new DieRoll(4,2)
#if (roll = sharp_die.roll() - 4) >= 2
# roll -= 2
#roll = 1 unless 0 <= roll <= 2
@world.PlaceTreasure SpawnedTreasure::gen(),@player
@treasureTime = @player.turns
HandleTreasure: (tile) ->
if tile.treasure instanceof PotionLoot
@player.potions++
@AddMessage gOWString.ow_find_loot_potion
else if tile.treasure instanceof EquipmentLoot
@AddMessage gOWString["ow_find_loot_"+tile.treasure.type] if @player.TakeEquipment tile.treasure
else if tile.treasure instanceof GoldLoot
@player.TakeLoot tile.treasure
@AddMessage gOWString.ow_gotgold.substitute tile.treasure.value
tile.treasure = null
class OWScreen
constructor: (@cols,@rows,frame,inputClass,gameClass) ->
(@container = $.createElement "div").setAttribute "class","ow-container"
frame.appendChild @container
(@grid = $.createElement "div").setAttribute "class","ow-grid"
@container.appendChild @grid
(@status = $.createElement "div").setAttribute "class","ow-status"
@container.appendChild @status
(@log = $.createElement "div").setAttribute "class","ow-status-log"
@container.appendChild @log
@status.innerHTML = """
<div class="ow-player-stats">
<p class="ow-playstat-health">8 / 8</p>
<p class="ow-playstat-loot">0</p>
<p class="ow-playstat-potion">0</p>
<p class="ow-playstat-level">1</p>
<p class="ow-playstat-exp">0</p>
<p class="ow-playstat-look"></p>
</div>
<div class="ow-messages">
</div>
"""
@status.querySelector(".ow-messages").onclick = => @show_log()
@log.onclick = => @hide_log()
@status.querySelector(".ow-playstat-potion")?.onclick = => @do_potion()
@status.querySelector(".ow-playstat-look")?.onclick = => @do_look(true)
@simStart = Date.now()
@simTime = 0
@game = new gameClass @
@game.CreateTheGameSpace()
@mouseLook = false
@input = new inputClass @grid
@input.on "move", (k) =>
@key k
@input.on "click", (pt) =>
@mouse pt
@refresh()
player_offset: ->
x:(@cols+1)//2 - 1, y:(@rows+1)//2 - 1
visible: (x,y) ->
playerloc = @game.player.loc
Math.abs(x-playerloc.x)<=@player_offset.x and Math.abs(y-playerloc.y)<=@player_offset.y
update_sim: ->
@simTime = Date.now() - @simStart
run_sim: ->
@game.RunSim @simTime
refresh: ->
window.requestAnimationFrame => @draw()
draw: ->
@clear @grid
#{x:ox, y:oy} = @player_offset()
{x:lx, y:ly} = @game.player.loc
lx -= @player_offset().x
ly -= @player_offset().y
@draw_a_tile @game.world.at(fx+lx,fy+ly),fx,fy for fy in [0...@rows] for fx in [0...@cols]
@draw_character_stats @game
@draw_message_lines @game
show_log: ->
msgHtml = ("<p>"+msg+"</p>" for msg in @game.status_lines)
@log.innerHTML = msgHtml.join "\n"
@log.classList.add "ow-show"
@log.lastChild.scrollIntoView()
hide_log: ->
@log.innerHTML = ""
@log.classList.remove "ow-show"
draw_message_lines: (game) ->
num_msgs = game.status_lines.length
cur = num_msgs - game.status_cur_line
msgHtml = ("<p>"+game.status_lines[idx]+"</p>" for idx in [cur...num_msgs])
@status.querySelector(".ow-messages")?.innerHTML = msgHtml.join "\n"
draw_character_stats: (game) ->
@status.querySelector(".ow-playstat-health")?.innerHTML = game.player.hp + " / " + game.player.max_hp
@status.querySelector(".ow-playstat-potion")?.innerHTML = game.player.potions
@status.querySelector(".ow-playstat-loot")?.innerHTML = game.player.gp
@status.querySelector(".ow-playstat-exp")?.innerHTML = game.player.xp
level = ""+game.player.level
level += " / " + game.player.GetLevelFromXP() if game.player.CanLevel()
@status.querySelector(".ow-playstat-level")?.innerHTML = level
draw_a_tile: (tile,x,y) ->
@set_position(wrapper = @create_a_tile(["ow-tile", "ow-tile-terr-"+tile.terrain.type]), x, y)
wrapper.appendChild @create_a_tile(["ow-tile-inner", "ow-tile-monster-"+tile.monster.type]) if tile.monster
wrapper.appendChild @create_a_tile(["ow-tile-inner", "ow-tile-loot-"+tile.treasure.type]) if tile.treasure and not tile.monster
wrapper.appendChild @create_a_tile(["ow-tile-inner", "ow-tile-action-"+tile.action.type]) if tile.action
@grid.appendChild wrapper
create_a_tile: (classes) ->
(tile = $.createElement "div").setAttribute "class",classes.join " "
tile
tile_size: ->
x:@grid.clientWidth//@cols, y:@grid.clientHeight//@rows
set_position: (tile,x,y) ->
#tile.style.left = (x * Tile::width) + "px"
#tile.style.top = (y * Tile::height) + "px"
tile.classList.remove cl for cl in (cl for cl in tile.classList when cl.startsWith "ow-tile-position-")
tile.classList.add "ow-tile-position-#{x}-#{y}"
clear: (div) ->
div.removeChild div.firstChild while div.firstChild
tile_from_point: (pt) ->
{x:tw, y:th} = @tile_size()
{x:px, y:py} = @player_offset()
x = pt.x // tw
y = pt.y // th
{x:@game.player.loc.x-px+x, y:@game.player.loc.y-py+y}
delta_from_point: (pt) ->
{x:tw, y:th} = @tile_size()
{x:px, y:py} = @player_offset()
dx = dy = 0
x = pt.x - Math.floor(tw*(px+0.5))
y = pt.y - Math.floor(th*(py+0.5))
mx = Math.abs(x)
my = Math.abs(y)
dtx = if x>0 then 1 else -1
dty = if y>0 then 1 else -1
if mx<tw*0.5
dy = dty unless my<th*0.5
else
slope = Math.abs(y/x)
if slope>2.0
dy = dty
else if slope<0.5
dx = dtx
else
dx = dtx
dy = dty
x:dx,y:dy
mouse: (pt) ->
return @game.Look @tile_from_point pt if @mouseLook
delta = @delta_from_point pt
@update_sim()
if @game.player.casting
#@game.UpdateVelAction CombatMissileAct,@game.player.loc,delta
else
@game.MovePlayer delta
@run_sim()
key: (which) ->
@update_sim()
@game.MovePlayer switch which
when 0 then {x: 0,y: 0}
when 1 then {x: 1,y: 0}
when 2 then {x: 1,y:-1}
when 3 then {x: 0,y:-1}
when 4 then {x:-1,y:-1}
when 5 then {x:-1,y: 0}
when 6 then {x:-1,y: 1}
when 7 then {x: 0,y: 1}
when 8 then {x: 1,y: 1}
@run_sim()
timeout: (time,cb) ->
window.setTimeout cb,time
do_potion: ->
console.log "potion"
do_look: (active) ->
if @mouseLook = active
@grid.classList.add "look"
else
@grid.classList.remove "look"
do ->
window.OWGame = OWGame
window.OWScreen = OWScreen
window.OWInit = (width, height, container, inputClass, gameClass) ->
window.requestAnimationFrame ->
new OWScreen width,height,container,inputClass,OWGame