-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathserver.lua
1267 lines (1173 loc) · 32.1 KB
/
server.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
-- this requires a plugin that draws the map: https://github.com/warmist/dfhack/tree/twbt_experiments
--[[
Ideas for future:
microtransactions (for rooms and stuff :D) jkjk
assignment to locations (e.g. performance etc)
see if idle location can be used for direct dwarf control
more info about dwarf
possesions (with a way to add/remove them)
rooms
artifacts
weapons etc...
thoughts
maybe add a way to modify thoughts?
cheats and hacks like:
make a dwarf punch someone
teleport for stuck dwarves
arena mode fights (buy stuff for dwarf and see it battle)
]]
local sock=require 'plugins.luasocket'
local map=require 'plugins.screenshot-map'
local utils=require 'utils'
local debug=require 'debug'
local eventful=require 'plugins.eventful'
local jobs=require 'hack.scripts.http.jobs'
local args={...}
local HOST="http://dwarffort.duckdns.org/"
local DEBUG=false
local ADVENTURE=false
local FORTMODE=false
local FPS_LIMIT=50 --limit fps to this so it would not look too fast
local KILL_MONEY=5 --how much money you get from kills
local USE_MONEY=true
local SPECTATE_UNPAUSES=false
local SPAWN_MOBS=15
if DEBUG then
printd=function ( ... )
print(...)
end
else
printd=function ()end
end
if port and (args[1]=="-r" or args[1]=="-s") then
port:close()
port=nil
end
if timeout_looper then
dfhack.timeout_active(timeout_looper,nil)
end
if args[1]=="-s" then
print("Shutting down")
if clients then
for k,v in pairs(clients) do
k:close()
end
end
return
end
local page_data={}
local assets_data={}
function load_page_data()
local files={
'welcome',
'login',
'cookie',
'play',
'del_user',
'unit_select',
'spectate'
}
for i,v in ipairs(files) do
local f=io.open('hack/scripts/http/'..v..'.html','rb')
page_data[v]=f:read('all')
f:close()
end
local assets={
['favicon.ico']='favicon.png',
['map.js']='map.js',
['chat.css']='chat.css',
['style.css']='style.css',
}
for k,v in pairs(assets) do
local f=io.open('hack/scripts/http/'..v,'rb')
assets_data[k]=f:read('all')
f:close()
end
end
local unit_data={}
function find_caste( race_raw,caste_name )
for i,v in ipairs(race_raw.caste) do
if v.caste_id==caste_name then
return i,v
end
end
end
local item_data={}
function parse_item_token( text )
printd("Loading item:"..text)
local t_s,rest=text:match("([^:]*):(.*)")
if t_s==nil then return nil, "Invalid token" end
if df.item_type[t_s]==nil then return nil, "Could not find:"..t_s end
local cost
local rest,cost=rest:match("([^%s]*)%s*(.*)")
if cost==nil or tonumber(cost)==nil then return nil,"Invalid cost" end
cost=tonumber(cost)
local _,obj=utils.linear_index(df.global.world.raws.itemdefs.all,rest,"id")
if obj==nil then return nil,"Invalid subtype:"..rest end
local ret={type=df.item_type[t_s],subtype=obj.subtype,name=rest}
return ret,cost
end
local mat_data={}
function parse_mat_token( text )
printd("Loading mat:"..text)
local t_s,cost=text:match("([^%s]*)%s*(.*)")
if t_s==nil then return nil, "Invalid token" end
local mat=dfhack.matinfo.find(t_s)
if mat==nil then return nil,"Could not find material:"..t_s end
if cost==nil or tonumber(cost)==nil then return nil,"Invalid cost" end
cost=tonumber(cost)
return mat,cost
end
function find_burrow(name)
local b=df.global.ui.burrows.list
for i,v in ipairs(b) do
if v.name==name then
return v
end
end
print("WARN:"..name.." burrow not found")
end
local spawn_burrow
if FORTMODE then
spawn_burrow=find_burrow("SPAWN")
end
local npc_spawn
if SPAWN_MOBS then
npc_spawn=find_burrow("SPAWN_MOBS")
--print("npc_spawn:",npc_spawn)
end
function load_buyables()
--flip the units raws
local raw=df.global.world.raws.creatures.all
local unit_raw={}
for k,v in ipairs(raw) do
unit_raw[v.creature_id]={raw=v,id=k}
end
--load units
do
local f=io.open('hack/scripts/http/unit_list.txt',rb)
local line_num=1
for l in f:lines() do
local race,caste,cost=l:match("([^:]*):(%a*)%s*(.*)")
if race==nil or caste==nil or cost==nil or tonumber(cost)==nil then
print("Error parsing line:",line_num,race,caste,tonumber(cost))
else
if unit_raw[race]==nil then
print("Could not find race:"..race)
else
local race_r=unit_raw[race].raw
local race_id=unit_raw[race].id
local caste_id,caste_raw=find_caste(race_r,caste)
if caste_id==nil then print("Could not find caste:",caste, " for unit race:",race) else
table.insert(unit_data,
{
race_raw=race_r,race_id=race_id,
caste_id=caste_id,caste_raw=caste_raw,
cost=tonumber(cost)
})
end
end
end
line_num=line_num+1
end
end
--load items
do
local f=io.open('hack/scripts/http/equipment.txt',rb)
local line_num=1
for l in f:lines() do
if l:sub(1,1)~="#" then
local item,cost=parse_item_token(l)
if item==nil then
print("Error parsing line:",line_num,"Err:",cost)
else
table.insert(item_data,{type=item.type,subtype=item.subtype,cost=cost,name=item.name})
end
end
line_num=line_num+1
end
end
--load materials
do
local f=io.open('hack/scripts/http/materials.txt',rb)
local line_num=1
for l in f:lines() do
if l:sub(1,1)~="#" then
local mat,cost=parse_mat_token(l)
if mat==nil then
print("Error parsing line:",line_num,"Err:",cost)
else
table.insert(mat_data,{type=mat.type,index=mat.index,subtype=mat.subtype,cost=cost,name=mat.material.state_name.Solid})
end
end
line_num=line_num+1
end
end
end
function fill_page_data( page_text,variables )
function replace_vars( v )
local vname=v:sub(3,-3)
return tostring(variables[vname])
end
return page_text:gsub("(!![^!]+!!)",replace_vars)
end
load_page_data()
load_buyables()
users=users or {Test={name="Test",password="test"}}
unit_used=unit_used or {}
message_log=message_log or {}
port=port or sock.tcp:bind(HOST,6667)
port:setNonblocking()
local clients={}
local pause_countdown=0
function make_redirect(loc)
return "HTTP/1.1 302 Found\nLocation: "..HOST..loc.."\n\n"
end
function make_content(r)
return string.format("HTTP/1.0 200 OK\r\nConnection: Close\r\nContent-Length: %d\r\n\r\n%s",#r,r)
end
function make_json_content( r )
return string.format("HTTP/1.0 200 OK\r\nConnection: Close\r\nContent-Type: application/json\r\nContent-Length: %d\r\n\r\n%s",#r,r)
end
function respond_err() --TODO: actual error page?
return fill_page_data(page_data.welcome,{hostname=HOST})
end
function switch_labor(user,labor,value )
labor=tonumber(labor)
value=tonumber(value)
local u=df.unit.find(user.unit_id)
if u.status.labors[labor]~=nil then
u.status.labors[labor]=value
end
end
function switch_burrow( user,burrow,value )
burrow=tonumber(burrow)
value=tonumber(value)
local u=df.unit.find(user.unit_id)
local b=df.burrow.find(burrow)
if u and b then
dfhack.burrows.setAssignedUnit(b,u,value==1)
end
end
function respond_login()
return page_data.login
end
function respond_cookie(cmd)
if cmd.username==nil or cmd.username=="" then
return "Invalid user"
end
local user=users[cmd.username]
if user==nil then --create new user, if one does not exist
users[cmd.username]={password=cmd.password,name=cmd.username}
print("New user:"..cmd.username)
user=users[cmd.username]
elseif user.password~=cmd.password then --check password
return "Invalid password"
end
user.name=cmd.username
return fill_page_data(page_data.cookie,{username=cmd.username,password=cmd.password}) --set cookies
end
function get_user(cmd, cookies)
if cookies.username==nil or cookies.username=="" or users[cookies.username]==nil or cookies.password~=users[cookies.username].password then
return false,"Invalid login"
end
local user=users[cookies.username]
return user
end
function get_unit( user )
if user.unit_id==nil then
return
end
local t=df.unit.find(user.unit_id)
if t ==nil then
return false,"Sorry, your unit was lost somewhere... :("
end
return t,user.unit_id
end
function respond_play( cmd,cookies )
local user,err=get_user(cmd,cookies)
if not user then return err end
local t,err2=get_unit(user)
if not t then
return nil,make_redirect("new_unit")
end
local w=21
local valid_variables={
size=w,
canvas_w=w*16,
canvas_h=w*16,
}
return fill_page_data(page_data.play,valid_variables)
end
function respond_spectate(cmd,cookies)
local m=df.global.world.map
local w=21
local valid_variables={
size=w,
canvas_w=w*16,
canvas_h=w*16,
start_x=m.x_count//2,
start_y=m.y_count//2,
start_z=m.z_count//2,
}
return fill_page_data(page_data.spectate,valid_variables)
end
function server_unpause()
pause_countdown=10
end
function json_str( v )
if type(v)=="string" then
return '"'..v..'"'
elseif type(v)=="number" then
return v
elseif type(v)=="boolean" then
return v
elseif type(v)=="table" and v._is_array==nil then
return json_pack_obj(v)
elseif type(v)=="table" and v._is_array then
return json_pack_arr(v,v._is_array)
end
end
function json_pack_arr( t,start )
local ret=""
local comma=""
start=start or 1
for i=start,#t-(1-start) do
ret=ret..string.format('%s%s\n',comma,json_str(t[i]))
comma=','
end
return string.format("[%s]",ret)
end
function json_pack_obj( t)
local ret=""
local comma=""
for k,v in pairs(t) do
ret=ret..string.format('%s"%s":%s\n',comma,k,json_str(v))
comma=','
end
return string.format("{%s}",ret)
end
function json_map(x,y,z,w,h)
local m=map.render_map_rect(x,y,z,w,h)
local line=0
local map_string=""
for i=0,#m,4 do
line=line+1
local comma
if i~=#m-3 then --omit last comma
comma=','
else
comma=''
end
map_string=map_string..string.format("[%d, %d, %d, %d]%s",m[i],m[i+1],m[i+2],m[i+3],comma)
if line==w then
line=0
map_string=map_string.."\n"
end
end
return '['..map_string..']'
end
function respond_json_map(cmd,cookies)
local user,err=get_user(cmd,cookies)
if not user then return "{}" end --TODO somehow report error?
local t,err2=get_unit(user)
if not t then return "{}" end --TODO somehow report error?
--valid users unpause game for some time
local delta_z=0
if cmd.dz and tonumber(cmd.dz) then
delta_z=tonumber(cmd.dz)
end
server_unpause()
local w=21
return json_map(t.pos.x-w//2-1,t.pos.y-w//2-1,t.pos.z+delta_z,w,w)
end
function respond_json_map_spectate(cmd,cookies)
local w=21
if not cmd.x or not tonumber(cmd.x) then return "{error='invalid_x'}" end
if not cmd.y or not tonumber(cmd.y) then return "{error='invalid_y'}" end
if not cmd.z or not tonumber(cmd.z) then return "{error='invalid_z'}" end
local x=tonumber(cmd.x)
local y=tonumber(cmd.y)
local z=tonumber(cmd.z)
if SPECTATE_UNPAUSES then
server_unpause()
end
return json_map(x,y,z,w,w)
end
function respond_json_unit_info(cmd,cookies)
local user,err=get_user(cmd,cookies)
if not user then return '{"error":"Invalid user"}' end --TODO somehow report error?
local unit,err2=get_unit(user)
if not unit then return '{"error":"Invalid unit"}' end --TODO somehow report error?
local uname=dfhack.df2utf(dfhack.TranslateName(unit.name))
local prof=dfhack.units.getProfessionName(unit)
local job
if unit.job.current_job then
job=dfhack.job.getName(unit.job.current_job)
else
job=""
end
local ret={}
ret.name=uname
ret.profession=prof
ret.job=job
ret.labors={_is_array=0}
for i,v in ipairs(unit.status.labors) do
--if df.unit_labor.attrs[i].caption~=nil then
--ret.labors[df.unit_labor.attrs[i].caption]=v
--end
ret.labors[i]=v
end
ret.burrows={}
for i,v in ipairs(df.global.ui.burrows.list) do
local in_burrow_state=0
if dfhack.burrows.isAssignedUnit(v,unit) then
in_burrow_state=1
end
ret.burrows[v.name]={id=v.id,name=v.name,state=in_burrow_state}
end
return json_pack_obj(ret)
end
function respond_delete( cmd, cookies )
local user,err=get_user(cmd,cookies)
if not user then return "Invalid user and/or login" end
if not cmd.do_delete then
return "Are you sure you want to <a href='delete?do_delete'> DELETE</a> your account?"
else
if user.unit_id then
unit_used[user.unit_id]=nil
end
users[cookies.username]=nil
return fill_page_data(page_data.del_user,{username=cookies.username})
end
end
function respond_new_unit(cmd,cookies)
return fill_page_data(page_data.unit_select,{use_money=USE_MONEY})
end
function get_valid_unit_pos( burrow )
local x,y,z
if burrow then
for i=1,100 do
local blocks=dfhack.burrows.listBlocks(burrow)
local m_block=blocks[math.random(0,#blocks-1)]
local lx=math.random(0,15)
local ly=math.random(0,15)
if dfhack.burrows.isAssignedBlockTile(burrow,m_block,lx,ly) then
x=lx+m_block.map_pos.x
y=ly+m_block.map_pos.y
z=m_block.map_pos.z
if dfhack.maps.isValidTilePos(x,y,z) then
local attrs = df.tiletype.attrs
local tt=dfhack.maps.getTileType(x,y,z)
local td,to=dfhack.maps.getTileFlags(x,y,z)
if tt and not td.hidden and td.flow_size==0 and attrs[tt].shape==df.tiletype_shape.FLOOR then
return x,y,z
end
end
end
end
else
local mx,my,mz=dfhack.maps.getTileSize()
for i=1,1000 do
x=math.random(0,mx-1)
y=math.random(0,my-1)
z=math.random(0,mz-1)
if dfhack.maps.isValidTilePos(x,y,z) then
local attrs = df.tiletype.attrs
local tt=dfhack.maps.getTileType(x,y,z)
local td,to=dfhack.maps.getTileFlags(x,y,z)
if tt and not td.hidden and td.flow_size==0 and attrs[tt].shape==df.tiletype_shape.FLOOR then
return x,y,z
end
end
end
end
end
function add_item(item_def)
local as=df.global.world.arena_spawn.equipment
as.item_types:insert("#",item_def.type)
as.item_subtypes:insert("#",item_def.subtype)
as.item_materials.mat_type:insert("#",item_def.mat_type)
as.item_materials.mat_index:insert("#",item_def.mat_index)
as.item_counts:insert("#",item_def.count)
end
function clear_items()
local as=df.global.world.arena_spawn.equipment
as.item_types:resize(0)
as.item_subtypes:resize(0)
as.item_materials.mat_type:resize(0)
as.item_materials.mat_index:resize(0)
as.item_counts:resize(0)
end
function respond_actual_new_unit(cmd,cookies)
local user,err=get_user(cmd,cookies)
if not user then return err end
if user.unit_id then --release old one if we have one
unit_used[user.unit_id]=nil
user.unit_id=nil
end
--TODO: this is actually unnecessary if we make javascript send normal id not id,cost
local race
if cmd.race_~=nil then
race=cmd.race_:match("([^%%]+)") --remove "%C<STH>"
end
if race==nil or tonumber(race)==nil or unit_data[tonumber(race)]==nil then
return "Error: invalid race selected"
end
local sum_cost=0
local actual_race=unit_data[tonumber(race)]
sum_cost=sum_cost+actual_race.cost
local x,y,z
if FORTMODE then
x,y,z=get_valid_unit_pos(spawn_burrow)
else
x,y,z=get_valid_unit_pos()
end
if not x then
return "Error: could not find where to place unit"
end
clear_items()
if cmd.items then
for i,v in ipairs(cmd.items) do
local sp=utils.split_string(v,"%%2C")
local item=tonumber(sp[1])
local mat=tonumber(sp[2])
--print(i,item,mat)
if item==nil then
print("Invalid item:"..v)
elseif mat==nil then
print("Invalid mat:"..v)
elseif item_data[item]==nil then
print("Item not found:"..v)
elseif mat_data[mat]==nil then
print("Mat not found:"..v)
else
local item_t=item_data[item]
local mat_t=mat_data[mat]
sum_cost=sum_cost+mat_t.cost*item_t.cost
local count=1
if item_t.type==df.item_type.AMMO then --hack for now...
count=20
end
add_item({type=item_t.type,subtype=item_t.subtype,mat_type=mat_t.type,mat_index=mat_t.index,count=count})
end
end
end
sum_cost=math.floor(sum_cost)
if USE_MONEY then
user.money=user.money or 0
if sum_cost>user.money then
return "Error: not enough money"
else
user.money=user.money-sum_cost
end
end
--str = str:gsub('%W','')
df.global.world.arena_spawn.side=0
local create_unit=dfhack.script_environment('modtools/create-unit')
print("New unit for user:",user.name, " unit race:",actual_race.race_raw.creature_id)
local u_id
if FORTMODE then
u_id=create_unit.createUnitInFortCivAndGroup(actual_race.race_id,actual_race.caste_id,{x,y,z})
else
u_id=create_unit.createUnit(actual_race.race_id,actual_race.caste_id,{x,y,z})
end
if not u_id then
return "Error: failed to create unit"
end
if cmd.unit_name~=nil and cmd.unit_name~="" then
cmd.unit_name=cmd.unit_name:gsub('%W','')
local unit=df.unit.find(u_id)
unit.name.first_name=cmd.unit_name
end
unit_used[u_id]=user
user.unit_id=u_id
df.global.ui.follow_unit=u_id
return nil, make_redirect("play")
end
function dir_signs( dx,dy )
local sx,sy
sx=0
sy=0
if dx>0 then sx=1 end
if dy>0 then sy=1 end
if dx<0 then sx=-1 end
if dy<0 then sy=-1 end
return sx,sy
end
function respond_json_move( cmd,cookies )
local user,err=get_user(cmd,cookies)
if not user then return '{"error":"invalid_login"}' end
local unit,err2=get_unit(user)
if not unit then return '{"error":"invalid_unit"}' end
if unit.flags1.dead then return '{"error":"dead"}' end
if not cmd.dx or not tonumber(cmd.dx) then return "{error='invalid_dx'}" end
if not cmd.dy or not tonumber(cmd.dy) then return "{error='invalid_dy'}" end
local dz=0
if cmd.dz and tonumber(cmd.dz) then dz=tonumber(cmd.dz) end
local dx=tonumber(cmd.dx)
local dy=tonumber(cmd.dy)
local tx=unit.pos.x+dx
local ty=unit.pos.y+dy
unit.idle_area.x=tx
unit.idle_area.y=ty
--unit.idle_area_type=df.unit_station_type.Guard
--unit.idle_area_type=df.unit_station_type.DungeonCommander
unit.idle_area_type=df.unit_station_type.SquadMove
unit.idle_area_threshold=0
if dfhack.maps.isValidTilePos(tx,ty,unit.pos.z) and dz==0 then
local attrs = df.tiletype.attrs
local tt=dfhack.maps.getTileType(tx,ty,unit.pos.z)
local td,to=dfhack.maps.getTileFlags(tx,ty,unit.pos.z)
--printall(attrs[tt])
--print(df.tiletype_shape[attrs[tt].shape])
if attrs[tt].shape==df.tiletype_shape.RAMP_TOP then --down is easy, just move down
unit.idle_area.z=unit.pos.z-1
elseif attrs[tt].shape==df.tiletype_shape.RAMP then --up is harder. Try stepping in same general direction...
local sx,sy=dir_signs(dx,dy)
unit.idle_area.x=unit.idle_area.x+sx
unit.idle_area.y=unit.idle_area.y+sy
unit.idle_area.z=unit.pos.z+1
---???
end
else
unit.idle_area.z=unit.pos.z+dz
end
unit.path.dest={x=unit.idle_area.x,y=unit.idle_area.y,z=unit.idle_area.z}
unit.path.goal=88 --SQUAD STATION
unit.path.path.x:resize(0)
unit.path.path.y:resize(0)
unit.path.path.z:resize(0)
return "{}"
end
function respond_json_unit_list(cmd, cookies)
--{race=race_r,caste=caste_id,caste_raw=caste_raw,cost=tonumber(cost)}
local ret="["
local comma=''
for i,v in ipairs(unit_data) do
ret=ret..string.format('%s\n{"race":"%s","caste":"%s","name":"%s","cost":%d}',comma,v.race_raw.creature_id,v.caste_raw.caste_id,v.race_raw.name[0],v.cost)
comma=','
end
return ret.."]"
end
function respond_json_combat_log(cmd,cookies)
--TODO: could ddos? find might be slow
local C_DEFAULT_LOGSIZE=20
local C_MAX_LOGSIZE=100
local user,err=get_user(cmd,cookies)
if not user then return "{error='invalid_login'}" end
local unit,err2=get_unit(user)
if not unit then return "{error='invalid_unit'}" end
local log=unit.reports.log.Combat
local last_seen
if cmd.last_seen==nil or tonumber(cmd.last_seen)==nil then
last_seen=#log-C_DEFAULT_LOGSIZE
else
last_seen=tonumber(cmd.last_seen)
end
local reports = df.global.world.status.reports
if last_seen<0 then last_seen=0 end
if last_seen>#log or #log-last_seen>C_MAX_LOGSIZE then
last_seen=#log-C_DEFAULT_LOGSIZE
end
--print("Final last_seen:",last_seen)
local ret=string.format('{"current_count":%d,"log":[',#log)
local comma=''
if #log>0 then
for i=last_seen,#log-1 do
local report=df.report.find(log[i])
if report then
local text=report.text
text=text:gsub('"','')
ret=ret..string.format('%s"%s"\n',comma,text)
comma=','
end
end
end
return ret.."]}"
end
function respond_json_materials( cmd,cookies )
local ret="["
local comma=''
for i,v in ipairs(mat_data) do
ret=ret..string.format('%s\n{"name":"%s","cost":%g}',comma,v.name,v.cost)
comma=','
end
return ret.."]"
end
function find_all_items(item_list,pos)
local vec=df.global.world.items.all
local last_min=-1
local ret={}
for i,v in ipairs(item_list) do
local item,found,vector_pos=utils.binsearch(vec,v,'id',nil,last_min,#vec)
if not found then
return ret
end
if not pos then
table.insert(ret,item)
else
if item.pos.x==pos.x and item.pos.y==pos.y and item.pos.z==pos.z then
table.insert(ret,item)
end
end
last_min=vector_pos
end
return ret
end
function list_items( pos )
local td,to=dfhack.maps.getTileFlags(pos)
if not to.item then
return {}
end
local block=dfhack.maps.getTileBlock(pos)
return find_all_items(block.items,pos)
end
function respond_json_look_items( cmd,cookies )
local user,err=get_user(cmd,cookies)
if not user then return '{"error":"invalid_login"}' end
local unit,err2=get_unit(user)
if not unit then return '{"error":"invalid_unit"}' end
local dx,dy,dz
if cmd.dx and tonumber(cmd.dx) then dx=tonumber(cmd.dx) else dx=0 end
if cmd.dy and tonumber(cmd.dy) then dy=tonumber(cmd.dy) else dy=0 end
if cmd.dz and tonumber(cmd.dz) then dz=tonumber(cmd.dz) else dz=0 end
local tx=unit.pos.x+dx
local ty=unit.pos.y+dy
local tz=unit.pos.z+dz
local items=list_items({x=tx,y=ty,z=tz})
local ret={}
for i,v in ipairs(items) do
table.insert(ret,{name=dfhack.items.getDescription(v,0),id=v.id})
end
return json_pack_arr(ret)
end
function respond_json_equip( cmd,cookies )
local user,err=get_user(cmd,cookies)
if not user then return '{"error":"invalid_login"}' end
local unit,err2=get_unit(user)
if not unit then return '{"error":"invalid_unit"}' end
if not cmd.id or not tonumber(cmd.id) then return '{"error":"invalid_item_id"}' end
local item=df.item.find(tonumber(cmd.id))
if not item then return '{"error":"item_not_found"}' end
local ret=jobs.pickup_equipment(unit,item)
if not ret then return '{"error":"failed"}' end
return "{}"
end
function respond_json_haul( cmd,cookies )
local user,err=get_user(cmd,cookies)
if not user then return '{"error":"invalid_login"}' end
local unit,err2=get_unit(user)
if not unit then return '{"error":"invalid_unit"}' end
if not cmd.id or not tonumber(cmd.id) then return '{"error":"invalid_item_id"}' end
local item=df.item.find(tonumber(cmd.id))
if not item then return '{"error":"item_not_found"}' end
local dx=item.pos.x-unit.pos.x
local dy=item.pos.y-unit.pos.y
local dz=item.pos.z-unit.pos.z
if dx ~=0 or dy~=0 or dz~=0 then return '{"error":"item_too_far"}' end
local ret=dfhack.items.moveToInventory(item,unit,0,0)
if not ret then return '{"error":"failed"}' end
return "{}"
end
function respond_json_drop_haul( cmd,cookies )
local user,err=get_user(cmd,cookies)
if not user then return '{"error":"invalid_login"}' end
local unit,err2=get_unit(user)
if not unit then return '{"error":"invalid_unit"}' end
local item
for i,v in ipairs(unit.inventory) do
if v.mode==0 then
item=v
break
end
end
if not item then return '{"error":"no_item_hauled"}' end
local ret=dfhack.items.moveToGround(item.item,copyall(unit.pos))
if not ret then return '{"error":"failed_to_drop"}' end
return "{}"
end
function respond_json_items( cmd,cookies )
local ret="["
local comma=''
for i,v in ipairs(item_data) do
ret=ret..string.format('%s\n{"name":"%s","cost":%g}',comma,v.name,v.cost)
comma=','
end
return ret.."]"
end
function respond_json_user_info( cmd,cookies )
local user,err=get_user(cmd,cookies)
if not user then return "{error='invalid_login'}" end
local info={}
info.money=user.money or 0
return json_pack_obj(info)
end
function respond_json_kills( cmd,cookies )
local ret="["
local comma=''
local kills={}
for i,v in ipairs(df.global.world.incidents.all) do
local killer=v.killer
if killer~=-1 then
kills[killer]=kills[killer] or 0
kills[killer]=kills[killer]+1
end
end
for k,v in pairs(kills) do
local u=df.unit.find(k)
ret=ret..string.format('%s\n{"name":"%s","kills":%d}',comma,u.name.first_name,v)
comma=','
end
return ret.."]"
end
function sanitize(txt)
local replacements = {
['&' ] = '&',
['<' ] = '<',
['>' ] = '>',
['\n'] = '<br/>'
}
return txt
:gsub('[&<>\n]', replacements)
:gsub(' +', function(s) return ' '..(' '):rep(#s-1) end)
end
function encodeURI(str)
if (str) then
str = string.gsub (str, "\n", "\r\n")
str = string.gsub (str, "([^%w ])",
function (c) return string.format ("%%%02X", string.byte(c)) end)
str = string.gsub (str, " ", "+")
end
return str
end
function decodeURI(s)
if(s) then
s = string.gsub(s, '%%(%x%x)',
function (hex) return string.char(tonumber(hex,16)) end )
end
return s
end
function respond_json_message( cmd,cookies )
local user,err=get_user(cmd,cookies)
if not user then return "{error='invalid_login'}" end
local msg=decodeURI(cmd.msg)
msg=msg:sub(1,63)
msg=user.name..":"..sanitize(msg)
table.insert(message_log,msg)
print("CHAT>"..msg)
return "{}"
end
function respond_json_message_log( cmd,cookies )
local C_DEFAULT_LOGSIZE=20
local C_MAX_LOGSIZE=100
local user,err=get_user(cmd,cookies)
if not user then return "{error='invalid_login'}" end
local last_seen
local log = message_log
if cmd.last_seen==nil or tonumber(cmd.last_seen)==nil then
last_seen=#log-C_DEFAULT_LOGSIZE
else
last_seen=tonumber(cmd.last_seen)
end
if last_seen<1 then last_seen=1 end
if last_seen>#log or #log-last_seen>C_MAX_LOGSIZE then
last_seen=#log-C_DEFAULT_LOGSIZE
end
--print("Final last_seen:",last_seen)
last_seen=last_seen+1
local ret=string.format('{"current_count":%d,"log":[',#log)
local comma=''
if #log>0 then
for i=last_seen,#log do
local text=log[i]
if text then
text=text:gsub('"','')
ret=ret..string.format('%s"%s"\n',comma,text)
comma=','
end
end
end
return ret.."]}"
end
function responses(request,cmd,cookies)
--------------------MISC RESPONSES
local asset=assets_data[request]
if asset then
return asset
end
local table_misc={
fake_error=function () error("inside responses") end
}
local tm=table_misc[request]
if tm then
return tm(cmd,cookies)
end
--------------------JSON RESPONSES
local table_json={
map=respond_json_map,
map_spectate=respond_json_map_spectate,
look_items=respond_json_look_items,
--actions
haul_item=respond_json_haul,
drop_hauled_item=respond_json_drop_haul,
equip_item=respond_json_equip,
move_unit=respond_json_move,
--economy
get_unit_list=respond_json_unit_list,
get_materials=respond_json_materials,
get_items=respond_json_items,
get_kills=respond_json_kills,
--info
get_unit_info=respond_json_unit_info,
get_user_info=respond_json_user_info,
--messages