require 'libmh'
class Mounty
def self.dice(d, nd=1)
Array.new(nd) { 1+rand(d) }.inject { |a, b| a+b }
end
# find the dest so that we travel diag from from and arrive at same level
# eg from 000: (0 -> -1 has +1 xy bonus)
# [8, 8, -4] => [5, 5, -4]
# [8, 8, -16] => [8, 8, -16]
# [2, 8, -4] => [2, 5, -4]
def dest_diag(dst, from=pos)
dxy = [(dst[0]-from[0]).abs, (dst[1]-from[1]).abs].max
dxy -= 1 if from[2] == 0
dz = (dst[2] - from[2]).abs
d = dxy-dz
if d > 0
dst = dst.dup
if (dst[0] - from[0]).abs > d
dst[0] -= (dst[0]>from[0]) ? d : -d
end
if (dst[1] - from[1]).abs > d
dst[1] -= (dst[1]>from[1]) ? d : -d
end
end
dst
end
# returns the array of pos in the range from dst (same level)
def dest_range(dst, hzmax, hzmin = 0)
return [dst] if hzmax == 0
ret = []
(hzmin..hzmax).each { |d1|
(-hzmax..hzmax).each { |d2|
ret << dst.dup
ret.last[0] -= d1
ret.last[1] += d2
ret << dst.dup
ret.last[0] += d1
ret.last[1] += d2
ret << dst.dup
ret.last[0] += d2
ret.last[1] -= d1
ret << dst.dup
ret.last[0] += d2
ret.last[1] += d1
}
}
ret.uniq
end
def dist_to_direct(dst, from = pos)
dhz = [ (from[0] - dst[0]).abs, (from[1] - dst[1]).abs ].max
dn = (from[2] - dst[2]).abs
d = dn + [dhz, dn].max
d += 1 if from[2] == 0 and dst[2] < 0
d += 1 if @case_occupee and dst != from and from == pos
d
end
def dist_to_surf(dst, from = pos)
dhz = [ (from[0] - dst[0]).abs, (from[1] - dst[1]).abs ].max
cout = 0
cout += 1 if @case_occupee and dst != from and from == pos
cout += from[2].abs * 2 ; dhz -= from[2].abs # remonte
if dst[2] < 0
cout += 1 + dst[2].abs * 2 ; dhz -= dst[2].abs + 1 # redescend
end
dhz = 0 if dhz < 0
cout + (dhz + 1)/2 # surface
end
# dst = [x, y, n], hz = horizontal distance on the same level
def dist_to(dst, from=pos, hzmax = 0, hzmin = 0)
dest_range(dst, hzmax, hzmin).map { |dst|
[dist_to_surf(dst, from), dist_to_direct(dst, from)].min
}.min
end
def bot_getdest
# TODO mode => nearest_aub ?
d = File.read('dest').delete('[]').split(/[ ,]+/).map { |i| i.to_i }[0, 3]
d.length != 3 ? pos : d
rescue
pos
end
def bot_move_to_dest(dst=bot_getdest, hzmax=0, hzmin=0)
case dst
when Integer; return if not dst = vue.all.find { |a| a.id == dst } || vue.all.find { |a| a.id.to_s =~ /#{dst}/ } ; dst = dst.pos
when String; return if not dst = vue.all.find { |a| a.nom =~ /#{dst}/i } ; dst = dst.pos
end
if self['portation'] and dist_to(dst) > 16 and self[:pa] >= 6 and self[:n] != 0 and vue(0).lieux.empty?
tp(*dst)
elsif self['portation'] and dist_to(dst) > 16 and self[:pa] >= 3 and self[:n] == 0 and vue(2).lieux.empty?
move_to_dest [self[:x] + rand(3)-1, self[:y] + rand(3)-1, -1]
elsif l = vue(0).lieux.first and l.nom =~ /portail de t.*portation/i and pdst = portail(:dest) and dist_to_direct(dst) > dist_to_direct(dst, pdst) + 8
portail :prendre if self[:pa] >= 4
elsif dist_to(dst) > 12 and l = vue.lieux.find { |l| l.dist > 0 and l.nom =~ /portail de t.*portation/i and
dist_to(l.pos) + dist_to_direct(dst, l.pos) <= dist_to(dst) }
move_to_dest l.pos
elsif self['portation'] and dist_to(dst) > 16 and dist_to_surf(dst) < dist_to_direct(dst)
# avoid 0 -> -1 -> 0
move_to_dest dest_diag(dst)
else
move_to_dest dst, hzmax, hzmin
end
end
def nearest_aub
if File.exist? 'aub_list'
aub_list = File.read 'aub_list'
else
aub_list = <
EOL
end
aub_list = parsehtml(aub_list).map { |e| %w[x y z].map { |a| e[a].to_i } }
aub_list.sort_by { |a|
dist_to a
}.first
end
def chargedist
Mounty.portee((self[:pv].to_i + 9) / 10 + self[:reg].to_i) - self[:fatigue].to_i/5
end
def projodist
Mounty.portee(self[:vue_tot])
end
def teamlist(with_self = true)
@teamlist ||=
if File.exist? 'teamlist'
File.read('teamlist').split.map { |i| i.to_i }
else
[self[:mat]]
end
with_self ? @teamlist : (@teamlist - [self[:mat]])
end
MonstreCache = 'monstre_niveau'
MonstreCacheServer = HttpClient.new 'trolls.ratibus.net' # 'trolls.game-host.org'
def load_monstre_cache
return if defined? @monstre_cache
if File.exist? MonstreCache
@monstre_cache = {}
IO.foreach(MonstreCache) { |l|
s = l.chomp.split(';')
@monstre_cache[s[0]] = s[1].to_i if s[1].to_i != 0
}
else
@monstre_cache = nil
end
end
def save_monstre_cache
if @monstre_cache
mc = @monstre_cache.map { |m, n| "#{m};#{n}\n" }.sort.join
if File.read(MonstreCache) != mc
File.open(MonstreCache + '.tmp', 'w') { |fd| fd.puts mc }
File.rename(MonstreCache + '.tmp', MonstreCache)
system('ruby update.rb put ' + MonstreCache) if File.exist? 'update.rb'
end
end
end
def monstre_niveau(nom)
load_monstre_cache
return if not @monstre_cache
return @monstre_cache[nom] if @monstre_cache[nom]
return if not File.writable?(MonstreCache) or File.symlink?(MonstreCache)
# http://trolls.game-host.org/mountyhall/cdm.php?monstre=Ma%EEtre+Squelette+%5BNaissant%5D
req = '/mountyhall/cdm.php?monstre='
req += HttpServer.urlenc nom
begin
p = MonstreCacheServer.get(req, 1).parse
go = false
p.each { |e|
case e.type
when 'String'
if go
niv = e['content'][/ (\d+)$/, 1].to_i
if niv != 0
puts "#{nom} => nv #{niv}"
@monstre_cache[nom] = niv
end
break
end
when 'h1'; go = true
end
}
save_monstre_cache
rescue
puts "get monstre_niveau: error #{$!}, #{$!.message}" rescue nil
end
@monstre_cache[nom]
end
def vue_filt(*a)
v = vue(*a)
v.tresors.delete_if { |e| e.nom =~ /bidouille/i or dist_to(e.pos) > 11 }
v.monstres.delete_if { |e| e.nom =~ /Gowap Appr|Familier|niym/i }
# liste des positions des trolls de la team
teamtrollspos = v.trolls.find_all { |e| teamlist.include? e.id and e.dist != 0 }.map { |e| e.pos } + [self.pos]
trouspos = v.lieux.find_all { |e| e.nom =~ /^Trou de / }.map { |e| e.pos }
# liste des positions des trolls sans trolls de la team sur la case
trollspos = v.trolls.map { |e| e.pos }.uniq - teamtrollspos
v.monstres.delete_if { |e| trollspos.include? e.pos or trouspos.include? e.pos }
v.tresors.delete_if { |e| trouspos.include? e.pos or (e.dist > 0 and e.nom != 'Parchemin' and (trollspos.include? e.pos or teamtrollspos.include? e.pos)) }
v.lieux.delete_if { |e| e.dist > 2 and e.nom =~ /Trou de M.t.orite/i }
# reorder by dist
[:monstres, :trolls, :lieux, :tresors, :champignons].each { |cat|
v.send(cat).replace v.send(cat).sort_by { |e| [dist_to(e.pos), e.n, e.x, e.y, e.id] }
}
v
end
attr_accessor :pos_occ_cb
def move_to_dest(dst, hzmax = 0, hzmin = 0)
case dst
when Integer; return if not dst = vue.all.find { |a| a.id == dst } || vue.all.find { |a| a.id.to_s =~ /#{dst}/ } ; dst = dst.pos
when String; return if not dst = vue.all.find { |a| a.nom =~ /#{dst}/i } ; dst = dst.pos
end
dst = dst.pos if dst.respond_to? :pos
profil
dst = dest_range(dst, hzmax, hzmin).sort_by { |tdst|
[[dist_to_surf(tdst), dist_to_direct(tdst)].min, dist_to_direct(dst, tdst)]
}.first
dx, dy, dn = dst.zip(pos).map { |a, b| a-b }
if dist_to_surf(dst) < dist_to_direct(dst) and self[:n] != 0
dn = -self[:n]
elsif dn < 0 and [dx.abs, dy.abs].max-((self[:n] == 0) ? 1 : 0) > dn.abs
# descend en fin de parcours, pour optimiser les chutes et la surface
dn = 0
elsif dn > 0 and dn < 4 and [dx.abs, dy.abs].max - dn > 3
# evite de remonter a tout prix (la cible risque d'etre redescendue le temps qu'on se rapproche)
dn = 0
end
return unless opt = (pre_move(true) if self[:clair]) || pre_move(false)
mvrel = opt.zip([dx, dy, dn]).map { |can, want| can.sort_by { |c| (c - want).abs }.first }
latt = [dx, dy, dn].map { |c| c.abs }.max
return if mvrel[2] == 0 and latt == dn.abs # evite de gacher des PAs si le changement de niveau est necessaire
if latt > 1 and (dx.abs < latt or dy.abs < latt)
# contournement trolls/monstres
opt[0].delete_if { |can| (dx - can).abs >= latt } ; opt[0] << mvrel[0] if opt[0].empty? # should never happen
opt[1].delete_if { |can| (dy - can).abs >= latt } ; opt[1] << mvrel[1] if opt[1].empty?
mvrel_opt = []
opt[0].each { |canx|
opt[1].each { |cany| mvrel_opt << [canx, cany, mvrel[2]] }
}
st = @http_client.status_save
v = vue(2)
occ = v.anim.map { |e| e.pos }.uniq
occ |= @pos_occ_cb[] if pos_occ_cb
trous = v.lieux.find_all { |e| e.nom =~ /^Trou de / }.map { |e| e.pos }
@http_client.status_restore st
mvrel_opt = mvrel_opt.sort_by { |omvrel|
(dx - omvrel[0]).abs + (dy - omvrel[1]).abs
}
mvrel = mvrel_opt.find { |omvrel|
not (occ+trous).include? pos.zip(omvrel).map { |c, dc| c+dc }
} || mvrel_opt.find { |omvrel|
not trous.include? pos.zip(omvrel).map { |c, dc| c+dc }
} || mvrel
p :dbg_mv_trous, trous, :mvrel, mvrel, :opts, mvrel_opt if not trous.empty?
end
do_move_rel(*mvrel)
end
def amelioration(what = nil)
puts "amelioration #{what}"
p = use_action 6
s = p.parse('Script').first
return false if s['content'] !~ /parent.Contenu.location.href='(.*)';/i
@http_client.get_url_allowed << @http_client.abs_path($1)
@http_client.fetch_next_now
p = get $1
if not what
p.parse.each { |e|
case e.type
when 'form', '/form', 'input'
puts e
when 'String'
puts e['content']
end
}
nil
else
curform = nil
curid = nil
curval = nil
p.parse.each { |e|
case e.type
when 'form'
curform = e['action']
# select the form to post (1st for caracs or 2nd for comps)
# XXX the page has