1 import javax
.swing
.text
.html
.HTMLEditorKit
2 import javolution
.util
.FastTable
3 import p2pmud
.NoWrapEditorKit
4 import org
.stringtree
.json
.JSONWriter
5 import org
.stringtree
.json
.JSONReader
6 import javax
.swing
.border
.BevelBorder
7 import org
.jdesktop
.swingx
.JXStatusBar
8 import javax
.swing
.plaf
.FontUIResource
9 import org
.jdesktop
.swingx
.painter
.GlossPainter
11 import org
.jdesktop
.swingx
.border
.DropShadowBorder
12 import groovy
.swing
.SwingXBuilder
15 import static p2pmud
.Tools
.*
16 import static p2pmud
.BasicTools
.*
17 import java
.awt
.event
.ItemEvent
18 import java
.util
.concurrent
.Executors
19 import javax
.swing
.UIManager
20 import p2pmud
.CloudProperties
21 import java
.text
.SimpleDateFormat
22 import rice
.p2p
.commonapi
.IdFactory
23 import rice
.Continuation
25 import p2pmud
.P2PMudFile
26 import p2pmud
.P2PMudPeer
27 import p2pmud
.P2PMudCommandHandler
30 import java
.awt
.Dimension
31 import java
.awt
.Component
32 import javax
.swing
.BoxLayout
33 import javax
.swing
.border
.LineBorder
34 import javax
.swing
.SpringLayout
35 import javax
.swing
.BoxLayout
36 import java
.awt
.BorderLayout
37 import static java
.awt
.BorderLayout
.*
38 import static java
.awt
.GridBagConstraints
.*
41 import javax
.swing
.border
.*
42 import groovy
.swing
.SwingBuilder
43 import net
.miginfocom
.swing
.MigLayout
45 import GroovyFileFilter
55 def pendingCommands
= [:]
56 def sauerCmds
= new SauerCmds(this)
57 def pastryCmds
= new PastryCmds(this)
59 def queueRunTime
= Long
.MAX_VALUE
60 def queueBatchPeriod
= 200
66 def playerObj
= new Player()
72 def mapPrefix
= 'packages/dist/storage'
82 /** cloudProperties is the shared properties object for PLEXUS
83 * its keys are path-strings, representing information organized in
88 def presenceLock
= new Lock('presence')
90 def triggerLambdas
= [:]
92 def receivedCloudPropertiesHooks
= [
93 {updateMyPlayerInfo()}
95 def executor
= Executors
.newSingleThreadExecutor()
97 def playerListeners
= [:]
102 def launchSauerButton
104 def downloadProgressBar
106 def downloadCountField
110 def cotumeUploadField
115 def pendingDownloads
= [] as Set
119 def cloudFields
= [:]
120 def mappingFields
= [:]
121 def pastryFields
= [:]
122 def cachedPlayerLocations
= [:]
123 def cachedLocationDoc
125 def costumeUpdating
= false
126 def costumeUpdateNeeded
= false
129 def
static TIME_STAMP
= new SimpleDateFormat("yyyyMMdd-HHmmsszzz")
130 def
static final PLEXUS_KEY
= "Plexus: main"
131 def
static final WORLDS_KEY
= "Plexus: worlds"
132 def
static final SPHINX_KEY
= "Plexus: sphinx"
134 public static continuation(args
, parent
= null) {
135 Tools
.continuation(args
, parent
)
137 public static void main(String
[] a
) {
139 println
"Usage: Plexus port name pastryArgs"
142 new Plexus()._main(a
)
144 def
static verifySauerdir(dir
) {
145 if (!dir
) return false
147 def subs
= ['data', 'docs', 'packages/base']
150 if (!new File(f
, s
).isDirectory()) {
156 def
static err(msg
, err
) {
157 System
.err
.println(msg
)
159 System
.err
.println
"UNSANITIZED STACK TRACE FOLLOWS..."
160 err
.printStackTrace()
165 println
"TST: $a, $b"
168 if (executorThread
!= Thread
.currentThread()) {
169 stackTrace(new Exception("Not running in executor thread"))
171 assert executorThread
== Thread
.currentThread()
174 * Exec a block of code asynchronously, hence exec() does not return a value from the block.
180 } catch (Exception ex
) {
187 executorThread
= Thread
.currentThread()
190 headless
= LaunchPlexus
.props
.headless
== '1'
192 sauerDir
= System
.getProperty("sauerdir");
193 if (!verifySauerdir(sauerDir
)) {
194 usage("sauerdir must be provided")
196 usage("name must be provided")
198 sauerDir
= new File(sauerDir
)
200 sauerDir
= new File('duh').getAbsoluteFile().getParentFile()
202 plexusDir
= new File(sauerDir
, "packages/plexus")
203 cloudProperties
= new CloudProperties(this, new File(plexusDir
, "cache/$name/cloud.properties"))
204 cloudProperties
.persistentPropertyPattern
= ~
'(map|privateMap|costume)/..*'
205 cloudProperties
.privatePropertyPattern
= ~
'(privateMap)/..*'
206 cacheDir
= new File(plexusDir
, "cache/$name/files")
207 mapDir
= new File(plexusDir
, "cache/$name/maps")
209 def pastStor
= new File(plexusDir
, "cache/$name/PAST")
210 if (LaunchPlexus
.props
.cleanStart
) {
214 System
.setProperty('past.storage', pastStor
.getAbsolutePath())
216 cloudProperties
.setPropertyHooks
[~
'player/..*'] = {key
, value
, oldValue
->
218 def pid
= key
.substring('player/'.length())
219 def pl
= getPlayer(pid
)
221 if (pl
.map
!= mapTopic
.getId().toStringFull()) {
222 removePlayerFromSauerMap(pl
.id
)
223 } else if (oldValue
) {
224 def oldPl
= getPlayer(pid
, oldValue
)
226 if (oldPl
.costume
!= pl
.costume
) {
227 println
"Costume changed. Loading new costume for $pl"
233 cloudProperties
.setPropertyHooks
[~
'map/..*'] = {key
, value
, oldValue
->
234 def map
= getMap(key
.substring('map/'.length()))
237 playerCount
[map
.id
] = 0
239 if (map
.id
== mapTopic?
.getId()?
.toStringFull()) {
240 loadMap(map
.name
, map
.dir
)
243 cloudProperties
.setPropertyHooks
[~
'privateMap/..*'] = {key
, value
, oldValue
->
244 def map
= getMap(key
.substring('privateMap/'.length()))
246 if (!oldValue
&& pastryCmd
) {
247 def player
= getPlayer(pastryCmd
.from
.toStringFull())
249 playerCount
[map
.id
] = 0
250 sauer('private', "tc_msgbox [You received the key to a private world, $map.name: $map.id] [from $player.name ($player.id)]")
252 if (key
== mapTopic?
.getId()?
.toStringFull()) {
253 loadMap(map
.name
, map
.id
)
256 cloudProperties
.removePropertyHooks
[~
'player/..*'] = {key
, value
->
257 println
"CLOUD PROPERTY REMOVE HOOK"
258 removePlayerFromSauerMap(key
.substring('player/'.length()))
260 cloudProperties
.changedPropertyHooks
.add
{
265 def props
= cloudProperties
.properties
268 new ArrayList(props
.keySet()).sort().each
{
269 data
<< ["<b>$it</b>", props
[it
]]
271 showData(cloudFields
.properties
, "CURRENT CLOUD PROPERTIES: ${new Date()}", 2, data
)
274 //PlasticLookAndFeel.setPlasticTheme(new DesertBlue());
276 // UIManager.setLookAndFeel(new Plastic3DLookAndFeel());
277 UIManager
.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
278 } catch (Exception e
) {}
282 cloudProperties
.saveFilter
= {prop
-> !pendingDownloads
.contains(prop
)}
283 cloudProperties
.setPropertyHooks
[~
'map/..*'] = {key
, value
, oldValue
->
284 def map
= getMap(key
.substring('map/'.length()))
286 println
"RECEIVED MAP NOTIFICATION, DOWNLODING..."
287 fetchAndSave("map $map.name", key
, map
.dir
, "maps/$map.dir")
289 cloudProperties
.setPropertyHooks
[~
'costume/..*'] = {key
, value
, oldValue
->
290 def costume
= getCostume(key
.substring('costume/'.length()))
292 println
"RECEIVED MAP NOTIFICATION, DOWNLODING..."
293 fetchAndSave("costume", key
, costume
.id
, "costumes/$costume.id")
296 P2PMudPeer
.verboseLogging
= LaunchPlexus
.props
.verbose_log
== '1'
297 P2PMudPeer
.logFile
= new File(plexusDir
, "cache/$name/plexus.log")
298 if (P2PMudPeer
.verboseLogging
) {
299 P2PMudPeer
.sauerLogFile
= new File(plexusDir
, "cache/$name/sauer.log")
300 if (P2PMudPeer
.sauerLogFile
.exists()) P2PMudPeer
.sauerLogFile
.delete()
307 if (topic
== null && cmd
== null) {
308 id
= id
.toStringFull()
309 transmitRemoveCloudProperty("player/$id")
310 println
"Going to remove '${ids[id]}' from names"
313 cmd
.msgs
.each
{line
->
314 pastryCmds
.invoke(line
)
319 } catch (Exception ex
) {
320 err("Problem executing command: " + cmd
, ex
)
322 } as P2PMudCommandHandler
,
324 if (P2PMudPeer
.test
.getNeighborCount() < 2 && !headless
) {
325 cloudLight
.setIcon(new ImageIcon(getClass().getResource('/resources/disconnected.gif')))
327 //sauer('peers', "peers ${peer.getNeighborCount()}")
330 args
[2..-1] as String
[])
331 } catch (Exception ex
) {
332 System
.err
.println("PROBLEMS CONNECTING TO THE CLOUD. PEER STATE...")
333 System
.err
.println(P2PMudPeer
.test
.routeState())
334 err("Could not connect...", ex
)
336 peer
= P2PMudPeer
.test
337 peerId
= peer
.nodeId
.toStringFull()
338 // println "Node ID: $peerId}"
339 println
"SAVED NODE ID: $LaunchPlexus.props.nodeId"
340 if (!LaunchPlexus
.props
.nodeId
) {
341 LaunchPlexus
.props
.nodeId
= peerId
342 println
"SAVING NEW NODE ID: $LaunchPlexus.props.nodeId"
343 LaunchPlexus
.saveProps()
346 ids
= [(peerId
): 'p0']
347 plexusTopic
= subscribe(peer
.buildId(PLEXUS_KEY
), null)
348 println
"execing init..."
351 cloudLight
.setIcon(new ImageIcon(getClass().getResource('/resources/connected.gif')))
353 if (peer
.node
.getLeafSet().getUniqueCount() == 1) {
364 if ((LaunchPlexus
.props
.sauer_mode ?
: 'launch') == 'launch') launchSauer();
365 if (launchSauerButton
) {
366 launchSauerButton
.enabled
= true
367 start(LaunchPlexus
.props
.sauer_port
)
371 def
showData(field
, header
, cols
, data
, prologue
= null, epilogue
= null) {
372 def buf
= ("" << "<html><body>${prologue ?: ''}<table><tr colspan=\"$cols\" style=\"background-color: rgb(192,192,192)\"><td><div>$header</div></td></tr>\n")
374 def pane
= field
.parent
.parent
376 data
.sort
{a
, b
-> a
[0].compareTo(b
[0])}
378 buf
<< "<tr${count & 1 ? '' : ' style="background
-color
: rgb(192,255,192)"'}>"
379 row
.each
{col
-> buf
<< "<td><div>$col</div></td>"}
383 buf
<< "</table>${epilogue ?: ''}</body></html>"
385 def horiz
= pane
.horizontalScrollBar
.value
386 def vert
= pane
.verticalScrollBar
.value
388 field
.text
= buf
.toString()
390 pane
.horizontalScrollBar
.value
= horiz
391 pane
.verticalScrollBar
.value
= vert
395 def
fetchAndSave(type
, prop
, id
, location
) {
396 pendingDownloads
.add(prop
)
397 showDownloadProgress(0, 16)
398 fetchDir(id
, new File(plexusDir
, "cache/$name/$location"), receiveResult
: {r
->
400 println
"RECEVED ${type.toUpperCase()}, CHECKPOINTING CLOUD PROPS"
401 pendingDownloads
.remove(prop
)
402 cloudProperties
.save()
404 clearDownloadProgress()
406 }, receiveException
: {ex
-> err("Could not fetch data for $type: $id -> ${new File(plexusDir, "cache
/$name
/$location
")}", ex
)})
418 def node
= peer
.nodeId
.toStringFull()
420 transmitRemoveCloudProperty("player/$node")
427 unsubscribe(mapTopic
)
429 unsubscribe(plexusTopic
)
432 println
"destroying node"
436 if (LaunchPlexus
.igd
) {
437 println
"cleaning up UPNP mappings"
438 LaunchPlexus
.cleanMappings()
445 def
buildPlexusGui() {
446 swing
= new SwingXBuilder()
447 gui
= swing
.frame(title
: "PLEXUS [${LaunchPlexus.props.name}]", size
: [500, 500], windowClosing
: {die()}, pack
: true, show
: true) {
448 def makeTitlePainter
= {label
, pos
= null ->
450 mattePainter(fillPaint
: new Color(0x28, 0x26, 0x19))
451 textPainter(text
: label
, font
: new FontUIResource("SansSerif", Font
.BOLD
, 12), fillPaint
: new Color(0xFF, 0x99, 0x00))
452 glossPainter(paint
:new Color(1.0f
,1.0f
,1.0f
,0.2f
), position
: pos ?
: GlossPainter
.GlossPosition
.TOP
)
455 def field
= {lbl
, key
->
457 fields
[key
] = textField(actionPerformed
: {sauerEnt(key
)}, focusLost
: {sauerEnt(key
)}, constraints
: 'wrap, growx')
459 titledPanel(title
: ' ', titlePainter
: makeTitlePainter("PLEXUS [${LaunchPlexus.props.name}]: Killer App of the Future - Here Today!"), border
: new DropShadowBorder(Color
.BLACK
, 15)) {
460 panel(layout
: new MigLayout('fill, ins 0')) {
461 panel(layout
: new MigLayout(), constraints
: '') {
462 label(text
: "Node id: ")
463 label(text
: LaunchPlexus
.props
.nodeId ?
: "none", constraints
: 'wrap, growx')
464 label(text
: "Neighbors: ")
465 panel(layout
: new MigLayout('fill, ins 0'), constraints
: 'spanx,wrap,growx') {
466 button(text
: "Update Neighbor List", actionPerformed
: {updateNeighborList()})
467 neighborField
= label(text
: 'none', constraints
: 'wrap, growx')
469 label(text
: "Command: ")
470 fields
.cmd
= textField(actionPerformed
: {cmd()}, constraints
: 'wrap, growx')
472 panel(layout
: new MigLayout(), constraints
: 'spanx, wrap') {
473 cloudLight
= label(icon
: imageIcon(resource
: '/resources/waiting.gif'), constraints
: 'wrap')
475 tabbedPane(constraints
: 'spanx,grow,wrap') {
476 scrollPane(name
: 'Commands', border
: null) {
478 panel(layout
: new MigLayout('fillx')) {
479 label(text
: 'Generation')
480 panel(layout
: new MigLayout('fill, ins 0'), constraints
: 'growx,wrap') {
481 launchSauerButton
= button(text
: "Launch Sauerbraten", actionPerformed
: {launchSauer()}, enabled
: false)
482 button(text
: "Generate Dungeon", actionPerformed
: {generateDungeon()})
483 button(text
: "Load DF Map", actionPerformed
: {loadDFMap()})
484 panel(constraints
: 'growx,wrap')
486 label(text
: "Current Map: ")
487 mapCombo
= comboBox(editable
: false, actionPerformed
: {
489 if (mapCombo
&& mapCombo
.selectedIndex
> -1) {
490 connectWorld(mapCombo
.selectedIndex
== 0 ?
null : maps
[mapCombo
.selectedIndex
- 1].id
)
493 }, constraints
: 'wrap')
494 label(text
: 'Choose Costume')
495 tumesCombo
= comboBox(editable
: false, actionPerformed
: {
497 if (tumesCombo
&& tumesCombo
.selectedIndex
> -1) {
498 if (tumesCombo
.selectedIndex
) {
499 def tume
= tumes
[tumesCombo
.selectedIndex
- 1]
501 useCostume(tume
.name
, tume
.id
)
507 }, constraints
: 'wrap')
508 label(text
: "Follow player: ")
509 mapPlayersCombo
= comboBox(editable
: false, actionPerformed
: {
511 if (mapPlayersCombo
&& mapPlayersCombo
.selectedIndex
> -1) {
512 followingPlayer
= mapPlayersCombo
.selectedIndex ? mapPlayers
[mapPlayersCombo
.selectedIndex
- 1] : null
513 println
"NOW FOLLOWING: ${followingPlayer?.name}"
514 if (followingPlayer
) {
515 def array
= cachedPlayerLocations
[followingPlayer
.id
]
517 def name
= array
[0], update
= array
[1]
518 playerUpdate(followingPlayer
.id
, name
, update
)
523 }, constraints
: 'wrap')
524 button(text
: 'Upload Costume', actionPerformed
: {
526 if (costumeUploadField
.text
) {
527 pushCostumeDir(costumeUploadField
.text as File
)
531 panel(constraints
: 'growx,wrap', layout
: new MigLayout('fill,ins 0')) {
532 costumeUploadField
= textField(constraints
: 'growx', actionPerformed
: {
533 exec
{pushCostumeDir(costumeUploadField
.text as File
)}
535 button(text
: '...', actionPerformed
: {
537 def file
= chooseFile("Choose a model to upload", costumeUploadField
, "Costumes", "")
548 scrollPane(name
: 'Stats', border
: null) {
550 panel(name
: 'Stats', layout
: new MigLayout('fillx')) {
560 field('roll: ', 'rol')
561 field('pitch: ', 'pit')
562 field('yaw: ', 'yaw')
563 field('strafe: ', 's')
566 field('physics state: ', 'ps')
567 field('max speed: ', 'ms')
568 panel(constraints
: 'growy')
572 panel(name
: 'Cloud', layout
: new MigLayout('fill')) {
573 label(text
: 'Neighbors: ')
574 cloudFields
.neighbors
= label(constraints
: 'growx,wrap')
575 label(text
: 'Cloud Properties', constraints
: 'growx,spanx,wrap')
576 cloudFields
.scrollPane
= scrollPane(constraints
: 'grow,span,wrap', border
: null) {
577 cloudFields
.properties
= textPane(editable
: false, editorKit
: new NoWrapEditorKit())
580 panel(name
: 'ID Mapping', layout
: new MigLayout('fill')) {
581 scrollPane(constraints
: 'grow,span,wrap', border
: null) {
582 mappingFields
.ids
= textPane(editable
: false, editorKit
: new NoWrapEditorKit())
584 scrollPane(constraints
: 'grow,span,wrap', border
: null) {
585 mappingFields
.names
= textPane(editable
: false, editorKit
: new NoWrapEditorKit())
588 panel(name
: 'Cached Locations', layout
: new MigLayout('fill')) {
589 button(text
: 'Update', actionPerformed
: {exec
{updateCachedLocationDoc()}}, constraints
: 'wrap')
590 scrollPane(constraints
: 'grow,span,wrap', border
: null) {
591 cachedLocationDoc
= textPane(editable
: false, editorKit
: new NoWrapEditorKit())
594 panel(name
: 'Pastry', layout
: new MigLayout('fill')) {
595 button(text
: 'Update', actionPerformed
: {exec
{updatePastryDiag()}}, constraints
: 'wrap')
596 scrollPane(constraints
: 'grow,span,wrap', border
: null) {
597 pastryFields
.topics
= textPane(editable
: false, editorKit
: new NoWrapEditorKit())
601 label(minimumSize
: [24,24], text
: ' ', foregroundPainter
: makeTitlePainter('Copyright (C) 2008, TEAM CTHULHU', GlossPainter
.GlossPosition
.BOTTOM
), constraints
: 'growx, spanx, height 24, wrap')
604 statusBar(border
: new BevelBorder(BevelBorder
.LOWERED
)) {
605 downloadPanel
= panel(layout
: new MigLayout('ins 0 2 0 2,fillx'), constraints
: new JXStatusBar
.Constraint(JXStatusBar
.Constraint
.ResizeBehavior
.FILL
)) {
606 label(text
: 'Uploads: ')
607 uploadCountField
= label(text
: '0')
608 label(text
: ' Downloads: ')
609 downloadCountField
= label(text
: '0')
610 label(text
: ' Current ')
611 loadTypeField
= label(text
: 'Upload')
613 downloadProgressBar
= progressBar(constraints
: 'growx', minimum
: 0, maximum
: 100)
618 def
updateCachedLocationDoc() {
619 def buf
= "<html>" << "<body><table>"
621 cachedPlayerLocations
.each
{
622 buf
<< "<tr><td>$it.key</td><td>${it.value[0]}</td><td><div>${it.value[1].join(' ')}</div></td></tr>"
624 buf
<< "</table></body></html>"
625 cachedLocationDoc
.text
= buf
.toString()
627 def
chooseFile(message
, field
, filterName
, filterRegexp
) {
628 def ch
= new JFileChooser();
631 ch
.setSelectedFile(field
.text as File
)
633 ch
.setDialogTitle(message
);
634 ch
.setFileSelectionMode(JFileChooser
.FILES_AND_DIRECTORIES
)
635 ch
.setFileFilter(new GroovyFileFilter(filterName
) {it
.isDirectory() || it
.name
==~ filterRegexp
})
636 def result
= ch
.showOpenDialog(null) == JFileChooser
.APPROVE_OPTION ? ch
.getSelectedFile() : null
638 field
.text
= result
.getAbsolutePath()
642 def
updateNeighborList() {
644 neighborField
.text
= String
.valueOf(peer
.getNeighborCount())
645 } catch (Exception ex
) {}
651 def winderz
= System
.getProperty('os.name').toLowerCase() ==~
/.*windows
.*/
653 for (vars in System
.getenv()) {
654 if (winderz
&& vars
.key
.equalsIgnoreCase('path')) {
655 env
.add("$vars.key=$vars.value;$sauerDir\\bin")
657 env
.add("$vars.key=$vars.value")
661 def commandLine
= sauerExec
.replaceFirst(/\
-l
[^
\s]+/, '') // remove old -llimbo if present
663 commandLine
+= ' -l' + buildMapPath()
664 commandLine
+= " \"-xalias sauerPort $LaunchPlexus.props.sauer_port;alias sauerHost 127.0.0.1\""
665 def t
= new StreamTokenizer(new StringReader(commandLine
))
669 t
.whitespaceChars(0, (int)' ')
670 t
.quoteChar((int)'"')
671 t
.quoteChar((int)"'")
672 while (t
.nextToken() != StreamTokenizer
.TT_EOF
) {
675 env
= env as String
[]
676 println ("Going to exec $commandLine from $sauerDir with env: $env, args: $args")
677 Runtime
.getRuntime().exec(args as String
[], env
, sauerDir
)
682 def result
= 'plexus/dist/limbo/map'
683 def who
= getPlayer(peerId
)
685 if (who
&& who
.map
) {
686 def map
= getMap(who
.map
)
687 if (map
&& map
.dir
) {
688 def dir
= new File(mapDir
, map
.dir
)
689 def mapPath
= subpath(new File(sauerDir
, "packages"), dir
)
691 result
= "$mapPath/map"
697 def ch
= new JFileChooser();
698 ch
.setDialogTitle("Please choose the DF map to load");
699 ch
.setFileSelectionMode(JFileChooser
.FILES_AND_DIRECTORIES
)
700 ch
.setFileFilter(new GroovyFileFilter("DF ASCII MAPS (*.txt)") {it
.isDirectory() || it
.name
.toLowerCase().endsWith(".txt")})
701 if (ch
.showOpenDialog(null) == JFileChooser
.APPROVE_OPTION
) {
703 if (ch
.getSelectedFile().isFile()) {
704 def dir
= ch
.getSelectedFile().getAbsolutePath();
706 def df
= new DFMapBuilder()
707 df
.buildMap(this, dir
)
713 // generate a random dungeon
714 def
generateDungeon() {
715 println ("Going to generate dungeon")
718 sauer('newmap', 'if (= 1 $editing) [ edittoggle ]; tc_allowedit 1; thirdperson 0; newmap; musicvol 0')
719 sauer('models', 'mapmodelreset ; mmodel tc_door ')
722 def dungeon
= new Dungeon(6, 6, 3, 1)
724 dungeon
.generate_maze();
726 def blocks
= dungeon
.convertTo3DBlocks()
728 for (def i
= 0; i
< dungeon
.blockRows
; ++i
) {
729 for (def j
= 0; j
< dungeon
.blockCols
; ++j
) {
734 def wx
= x
- 32, wy
= y
- 32
735 //println "x: $x y: $y"
736 def h
= (b
== ' ' || b
== 'l') ?
2 : 1
739 sauer('secret', "selcube $x $y 430 1 1 $h 32 5; editmat noclip")
741 sauer('delcube', "selcube $x $y 430 1 1 $h 32 5; delcube")
744 sauer('door', "selcube $x $y 430 1 1 1 32 4; ent.yaw p0 180; entdrop 3; newent mapmodel 0 6")
747 sauer('door', "selcube $x $y 430 1 1 1 32 4; ent.yaw p0 90; entdrop 3; newent mapmodel 0 6")
750 sauer('light', "selcube $x $y 450 1 1 1 32 4; entdrop 2; newent light 128 255 255 255")
759 sauer('tex', 'texturereset; setshader stdworld; exec packages/egyptsoc/package.cfg ')
760 sauer("texture", "selcube 0 0 480 2 2 2 512 4; tc_settex 35 1")
761 sauer("texture2", "tc_settex 37 0; selcube 0 0 480 2 2 2 512 5; tc_settex 51 0")
762 sauer("texture3", "selcube 0 0 470 512 512 1 16 5; tc_settex 7 1")
765 for (def i
= 0; i
< dungeon
.blockRows
; ++i
) {
766 for (def j
= 0; j
< dungeon
.blockCols
; ++j
) {
771 def wx
= x
- 32, wy
= y
- 32
774 sauer('wall1', "selcube $wx $wy 430 3 3 1 32 0; tc_settex 56 0")
775 sauer('wall2', "selcube $wx $wy 430 3 3 1 32 1; tc_settex 56 0")
778 sauer('wall1', "selcube $wx $wy 430 3 3 1 32 2; tc_settex 56 0")
779 sauer('wall2', "selcube $wx $wy 430 3 3 1 32 3; tc_settex 56 0")
788 sauer("spawn", "selcube 32 32 416 1 1 1 32 5; ent.yaw p0 135; newent playerstart; tc_respawn p0")
789 sauer('finished', 'remip; calclight 3; tc_allowedit 0; thirdperson 1')
794 //Plexus.bindLevelTrigger(35, 'remotesend levelTrigger 35 $more $data') {println "duh"} remotesend levelTrigger 35
795 def
bindLevelTrigger(trigger
, lambda
) {
796 if (!lambda
) println
"Error! Trigger lambda is null!"
797 trigger
= Integer
.parseInt(trigger
)
798 triggerLambdas
[trigger
] = lambda
799 sauer('trigger', "level_trigger_$trigger = [ remotesend levelTrigger $trigger ]")
802 def
levelTrigger(trigger
) {
803 trigger
= Integer
.parseInt(trigger
)
804 println
"sauer trigger: $trigger"
805 if (triggerLambdas
[trigger
]) triggerLambdas
[trigger
](trigger
)
809 println
"usage: ${getClass().name} port name"
810 println
"System property sauerdir must hold your Sauerbraten distribution directory"
814 // don't enable socket to sauer til after we've joined the ring
815 sauerSocket
= new ServerSocket()
816 sauerSocket
.setReuseAddress(true)
817 //sauerSocket.setSoTimeout(10);
818 sauerSocket
.bind(new java
.net
.InetSocketAddress(Integer
.parseInt(port
)))
819 println ("Sauer socket opened at: $port")
822 while (sauerSocket
!= null) {
824 socket
= sauerSocket
.accept
{
825 println("Got connection from sauerbraten...")
826 output
= it
.getOutputStream()
827 launchSauerButton
.enabled
= false
830 it
.getInputStream().eachLine
{line
->
833 exec
{sauerCmds
.invoke(line
)}
834 } catch (Exception ex
) {
835 err("Problem executing sauer command: " + it
, ex
)
839 } catch (Exception ex
) {}
840 try {it
.shutdownInput()} catch (Exception ex
) {}
841 try {it
.shutdownOutput()} catch (Exception ex
) {}
844 gui
.extendedState
= Frame
.NORMAL
847 launchSauerButton
.enabled
= true
850 } catch (IOException ex
) {}
854 def
sauer(key
, value
) {
856 pendingCommands
[key
] = value
858 def
hasSauerConnection() {
859 socket?
.isConnected()
863 if (!pendingCommands
.isEmpty()) {
864 if (socket?
.isConnected()) {
865 def out
= pendingCommands
.collect
{it
.value
}.join(";") + '\n'
867 if (P2PMudPeer?
.sauerLogFile
) { P2PMudPeer
.sauerLogFile
<< new Date().toString() << ' ' << out
; }
871 } catch (SocketException ex
) {
872 try {socket
.shutdownInput()}catch(Exception ex2
){}
873 try {socket
.shutdownOutput()}catch(Exception ex2
){}
877 pendingCommands
= [:]
880 def
sauerEnt(label
) {
881 if (fields
[label
]?
.text
&& fields
[label
].text
[0]) {
882 def cmd
= "ent.$label ${ids[peerId]} ${fields[label].text}"
890 if (fields
.cmd
.text
&& fields
.cmd
.text
[0]) {
891 def txt
= fields
.cmd
.text
895 sauer('cmd', fields
.cmd
.text
)
900 def
computePlayerInfo() {
902 def who
= getPlayer(peerId
)
903 def costume
= who
&& who
.costume ? who
.costume
: "mrfixit"
904 playerInfo
= "playerinfo p0 \"${who?.guild}\" $costume"
905 println
"pi $playerInfo"
911 "alias p2pname [$name]",
913 "map " + buildMapPath(),
925 def
broadcast(cmds
) {
927 if (peer
) peer
.broadcastCmds(mapTopic
, cmds as String
[])
931 if (peer
) peer
.anycastCmds(mapTopic
, cmds as String
[])
935 if (id
instanceof String
) {
938 if (peer
) peer
.sendCmds(id
, cmds as String
[])
940 def
cvtNewlines(str
) {
941 println
"${str.replaceAll(/\n/, ';')}"
942 return str
.replaceAll(/\n/, ';')
945 "$name-${TIME_STAMP.format(new Date())}"
947 def
loadMap(name
, id
, cont
= null) {
948 def dir
= new File(mapDir
, id
)
950 println
"Loading map: ${id}"
951 if (id
instanceof String
) {
954 fetchDir(id
, dir
, receiveResult
: {result
->
955 def mapPath
= subpath(new File(sauerDir
, "packages"), dir
)
958 if (hasSauerConnection()) {
959 newMapHookBlock
= {cont
.receiveResult(result
)}
960 println
"Retrieved map from PAST: $dir, executing: map [$mapPath/map]"
961 sauer('load', "echo loading new map: [$mapPath/map]; tc_loadmsg [$name]; map [$mapPath/map]")
964 cont
.receiveResult(result
)
967 }, receiveException
: {ex
->
969 cont
.receiveException(ex
)
971 err("Couldn't load map: $id", ex
)
976 mapCombo
.selectedItem
= mapTopic ?
getMap(mapTopic
.getId().toStringFull()).name
: ''
979 def docFile
= new File(plexusDir
, "cache/$name/cloud.properties")
981 if (docFile
.exists()) {
982 cloudProperties
.load()
985 if (LaunchPlexus
.props
.cleanStart
) {
992 peer
.anycastCmds(plexusTopic
, "sendCloudProperties")
994 def
storeFile(cont
, file
, mutable
= false, cacheOverride
= false) {
995 def total
= P2PMudFile
.estimateChunks(file
.length())
999 showUploadProgress(0, 16)
1001 queueIo(cont
, {uploads
--; updateDownloads(); clearUploadProgress()}) {chain
-> peer
.wimpyStoreFile(cacheDir
, file
, {showUploadProgress(chunk
++, total
)}, chain
, mutable
, cacheOverride
)}
1003 def
storeDir(cont
, dir
) {
1005 showUploadProgress(0, 16)
1007 queueIo(cont
, {uploads
--; updateDownloads(); clearUploadProgress()}) {chain
-> P2PMudFile
.storeDir(cacheDir
, dir
, {chunk
, total
-> showUploadProgress(chunk
, total
)}, chain
)}
1009 def
fetchFile(cont
, id
) {
1013 showDownloadProgress(0, 16)
1015 queueIo(cont
, {downloads
--; updateDownloads(); clearDownloadProgress()}) {chain
-> peer
.wimpyGetFile(id
, cacheDir
, {total
-> showDownloadProgress(chunk
++, total
)}, chain
)}
1017 def
fetchDir(cont
, id
, dir
, mutable
= false) {
1019 showDownloadProgress(0, 16)
1021 queueIo(cont
, {downloads
--; updateDownloads(); clearDownloadProgress()}) {chain
-> P2PMudFile
.fetchDir(id
, cacheDir
, dir
, {chunk
, total
-> showDownloadProgress(chunk
, total
)}, chain
, mutable
)}
1023 def
queueIo(cont
, completedBlock
, block
) {
1027 block(ioContinuation(cont
, completedBlock
))
1030 fileQ
.add({println
"EXECUTING QUEUED"; block(ioContinuation(cont
, completedBlock
))})
1033 def
ioContinuation(cont
, completedBlock
) {
1034 continuation(receiveResult
: {r
->
1038 cont
.receiveResult(r
)
1041 }, receiveException
: {e
->
1045 cont
.receiveException(e
)
1056 if (cacheDir
.exists()) {
1059 println
"STORING CACHE"
1060 deleteAll(new File(cacheDir
, 'download'))
1061 cacheDir
.eachFile
{subDir
->
1062 subDir
.eachFile
{file
->
1066 serialContinuations(files
, receiveResult
: {
1067 println
"FINISHED PUSHING CACHE"
1068 // sauer('msg', 'tc_msgbox Ready [Finished pushing cache in PAST]')
1070 }, receiveException
: {
1071 println
"FAILED TO PUSH CACHE"
1072 err("Error pushing cache in PAST", ex
)
1074 println
"STORING FILE: $file"
1075 storeFile(chain
, file
, false, true)
1078 println
"NO CACHE TO STORE"
1081 def
setCloudProperty(key
, value
) {
1082 cloudProperties
[key
] = value
1084 def
transmitSetCloudProperty(key
, value
) {
1086 cloudProperties
[key
] = value
1087 peer
.broadcastCmds(plexusTopic
, ["setCloudProperty $key $value"] as String
[])
1088 println
"BROADCAST PROPERTY: $key=$value"
1090 def
transmitRemoveCloudProperty(key
) {
1092 println
"REMOVING CLOUD PROPERTY"
1093 cloudProperties
.removeProperty(key
)
1095 peer
.broadcastCmds(plexusTopic
, ["removeCloudProperty $key"] as String
[])
1097 println
"BROADCAST REMOVE PROPERTY: $key"
1099 def
removeCloudProperty(key
) {
1101 println
"REMOVING CLOUD PROPERTY"
1102 cloudProperties
.removeProperty(key
)
1104 def
receiveCloudProperties(props
) {
1106 cloudProperties
.setProperties(props
, true)
1107 receivedCloudPropertiesHooks
.each
{it()}
1109 def
getMap(id
, entry
= null) {
1112 def privateMap
= false
1115 entry
= cloudProperties
["map/$id"]
1118 entry
= cloudProperties
["privateMap/$id"]
1122 map
= new JSONReader().read(entry
)
1124 map
.privateMap
= privateMap
1129 name: entry[1..-1].join(' '),
1130 privateMap: privateMap
1136 def
getPlayer(id
, entry
= null) {
1140 entry
= entry ?
: cloudProperties
["player/$id"]
1142 pl
= new JSONReader().read(entry
)
1147 map: entry[0] == 'none' ? null : entry[0],
1148 costume: entry[1] == 'none' ? null : entry[1],
1149 name: entry[2..-1].join(' ')
1155 def
getCostume(id
, entry
= null) {
1159 entry
= cloudProperties
["costume/$id"]
1162 costume
= new JSONReader().read(entry
)
1168 thumb: entry[0] == 'none' ? null : entry[0],
1170 name: entry[2..-1].join(' ')
1176 def
newMapHook(name
) {
1177 println
"newmap: $name"
1179 sauer("delp", "deleteallplayers")
1180 cachedPlayerLocations
.each
{
1181 def pname
= it
.value
[0], update
= it
.value
[1]
1182 def id
= newPlayer(pname
, it
.key
)
1183 sauer("${id}.update", "tc_setinfo $id ${update.join(' ')}")
1188 if (newMapHookBlock
) {
1190 newMapHookBlock
= null
1193 updateMyPlayerInfo()
1194 def groovyScript
= name
==~
'[^/]*' ?
new File(sauerDir
, "packages/base/${name}.groovy") : new File(sauerDir
, "packages/${name}.groovy")
1195 def runScript
= false
1196 if (groovyScript
.exists()) {
1198 def map
= getMap(mapTopic
.getId().toStringFull())
1200 runScript
= name
==~
".*/$map.dir/map"
1202 runScript
= name
==~
'.*/limbo/map(.ogz)?'
1207 sandbox
= new Sandbox(this, [groovyScript
.parent
])
1209 sandbox
.exec(groovyScript
.name
)
1210 } catch (Exception ex
) {
1211 System
.err
.println
"Error while executing map script..."
1216 def
updateMyPlayerInfo() {
1218 def id
= mapTopic?
.getId()?
.toStringFull()
1220 println
"UPDATING PLAYER INFO"
1221 // after we get the players list, send ourselves out
1222 def node
= peer
.nodeId
.toStringFull()
1223 //TODO put tume in here and persist in props
1224 transmitSetCloudProperty("player/$node", new JSONWriter().write([map
: id
, costume
: costume
, name
: name
, guild
: LaunchPlexus
.props
.guild
]))
1227 def
removePlayerFromSauerMap(peerId
) {
1228 def sauerId
= ids
[peerId
]
1229 if (sauerId
&& sauerId
!= 'p0') {
1230 def who
= getPlayer(peerId
)
1233 println
"Going to remove player $who.name from sauer: $sauerId"
1234 sauer('msgplayer', "echo [Player $who.name has left this world.]")
1237 names
.remove(sauerId
)
1238 cachedPlayerLocations
.remove(peerId
)
1243 sauer('delplayer', "deleteplayer $sauerId")
1246 println
"WARNING: can't find id for player: $peerId"
1249 def
newPlayer(name
, node
= pastryCmd
.from
.toStringFull()) {
1250 def who
= getPlayer(node
)
1251 def sauerid
= ids
[node
]
1252 def guild
= who ? who
.guild
: ""
1253 if (!sauerid
) sauerid
= "p$id_index" as String
1256 names
[sauerid
] = node
1258 sauer('prep', "echo [Welcome player $name to this world.]; createplayer $sauerid $name; playerinfo $sauerid \"$guild\"")
1265 def
updateMappingDiag() {
1270 data
<< [it
.key
, it
.value
]
1272 showData(mappingFields
.ids
, "IDs", 2, data
)
1275 data
<< [it
.key
, it
.value
]
1277 showData(mappingFields
.names
, "Names", 2, data
)
1279 def
updatePastryDiag() {
1284 def map
= getMap(it
.id
.toStringFull())
1286 if (it
== plexusTopic
) {
1291 showData(pastryFields
.topics
, "Topics", 2, data
, "", "Neighbors: ${peer.getNeighborCount()}<br>Route State...<br><pre>${peer.routeState()}</pre><br>")
1293 def
updatePlayerList() {
1294 if (!peer?
.nodeId
|| !swing
) return
1295 synchronized (presenceLock
) {
1298 def id
= peer
.nodeId
.toStringFull()
1299 def myMap
= mapTopic ?
getMap(mapTopic
.getId().toStringFull()) : [name
: 'Limbo', id
: '0']
1300 def allPlayers
= [], newMapPlayers
= []
1302 cloudProperties
.each('player/(..*)') {key
, value
, match
->
1303 def pid
= match
.group(1)
1306 def who
= getPlayer(pid
)
1308 if (myMap?
.id
== who
.map
) {
1309 newMapPlayers
.add(who
)
1313 newMapPlayers
.sort
{a
, b
-> a
.name
.compareTo(b
.name
)}
1314 if (newMapPlayers
!= mapPlayers
) {
1315 mapPlayers
= newMapPlayers
1317 def sel
= mapPlayersCombo
.selectedIndex
1319 mapPlayersCombo
.removeAllItems()
1320 mapPlayersCombo
.addItem('')
1321 for (player in mapPlayers
) {
1322 mapPlayersCombo
.addItem(player
.name
)
1324 mapPlayersCombo
.selectedIndex
= sel
1328 dumpPlayersMenu(myMap
, allPlayers
)
1331 def
dumpPlayersMenu(myMap
, allPlayers
) {
1332 println
"DUMPING PLAYERS TO SAUER"
1333 def PlayerGui
= 'alias showpcostume [ guibar; guiimage (concatword "packages/plexus/models/thumbs/" (get $pcostumenames $guirollovername)) $guirolloveraction 4 1 "packages/plexus/dist/tc_logo.jpg"];'
1334 PlayerGui
+= "alias pcostumenames ["
1335 allPlayers
.each( {who
->
1336 def c
= getCostume(who
.costume
)
1337 def ct
= (c
!= null && c
.thumb
) ?
"${c.id}.$c.type" : ''
1338 def map
= (!who
.map
|| who
.map
== 'none') ?
'none' : getMap(who
.map
)?
.name ?
: 'unknown map'
1339 PlayerGui
+= " \"$who.name ($map)\" $ct"
1343 PlayerGui
+= 'newgui Players [ \n guilist [ \n guilist [ \n'
1344 def i
= 0, needClose
= true, last
= allPlayers
.size()
1349 def mymapid
= myMap?
.id
1351 allPlayers
.each( {who
->
1352 def map
= (!who
.map
|| who
.map
== 'none') ?
'none' : getMap(who
.map
)?
.name ?
: 'unknown map'
1354 PlayerGui
+= "guibutton [$who.name ($map)] [alias tc_whisper $who.id; alias selected_player [$who.name]; alias mapIsPrivate $mapIsPrivate; showgui Player]\n"
1356 if (++i
% wrapAfter
== 0) {
1357 PlayerGui
+= '] \n showpcostume \n ] \n'
1360 def s
= Math
.min(i
, last
- 1), e
= Math
.min(i
+ wrapAfter
, last
- 1)
1361 s
= Character
.toUpperCase((allPlayers
[s
].name
[0]) as
char)
1362 e
= Character
.toUpperCase((allPlayers
[e
].name
[0]) as
char)
1363 PlayerGui
+= " guitab [More $s-$e] \n guilist [ \n guilist [ \n"; needClose
= true
1367 if (mymapid
== who
.map
) {
1368 mapTab
+= "guibutton [$who.name] [echo $who.id]\n"
1372 if (cnt
== 0) PlayerGui
+= 'guitext "Sorry, no players are online!"\n'
1373 //PlayerGui += "guibar\n guibutton Close [cleargui]\n"
1374 if (needClose
) PlayerGui
+= '] \n showpcostume \n ] \n'
1377 PlayerGui
+= "guitab $myMap.name\n$mapTab\n"
1378 if (mapCnt
== 0) PlayerGui
+= "guitext [Sorry, no players are connected to $myMap.name!]\n"
1379 println
"MAPCNT: $mapCnt"
1380 PlayerGui
+= "guibar\n guibutton Close [cleargui]\n"
1382 // bump counts to include ourself
1385 PlayerGui
+= "]; peers $cnt; tc_mapcount $mapCnt" // ;tc_loadmsg ${allPlayers ? myMap.name : 'none'}"
1386 sauer('Player', cvtNewlines(PlayerGui
))
1390 def key
= "privateMap/${mapTopic.getId().toStringFull()}"
1391 def value
= cloudProperties
[key
]
1393 peer
.sendCmds(Id
.build(id
), ["setCloudProperty $key $value"] as String
[])
1395 def
updateMapGui() {
1401 cloudProperties
.each('map/(.*)') {key
, value
, match
->
1402 playerCount
[match
.group(1)] = 0
1404 cloudProperties
.each('privateMap/(.*)') {key
, value
, match
->
1405 playerCount
[match
.group(1)] = 0
1407 cloudProperties
.each('player/(.*)') {key
, value
, match
->
1408 def player
= getPlayer(match
.group(1))
1410 if (player
.map
&& (playerCount
[player
.map
] == 0 || playerCount
[player
.map
])) {
1411 playerCount
[player
.map
]++
1416 cloudProperties
.each('map/(.*)') {key
, value
, match
->
1417 def map
= getMap(match
.group(1))
1418 def cnt
= playerCount
[map
.id
]
1419 ents
.add([map
.name
+ " ($cnt)", map
.id
, cnt
, map
.dir
])
1422 ents
.sort
{a
, b
-> a
[0].compareTo(b
[0])}
1424 def mapsGui
= 'alias showmapthumb [ guibar; guiimage (concatword "packages/plexus/cache/' + name
+ '/maps/" (get $mapthumbs $guirollovername) "/map.jpg") $guirolloveraction 4 1 "packages/plexus/dist/tc_logo.jpg"];'
1425 mapsGui
+= "alias mapthumbs ["
1426 for (trip in ents
) {
1427 mapsGui
+= " \"${trip[0]}\" ${trip[3]}"
1430 mapsGui
+= "newgui Worlds [ \n guilist [ \n guilist [ \n"
1431 def i
= 0, needClose
= true, last
= ents
.size()
1433 for (world in ents
) {
1434 mapsGui
+= "guibutton [${world[0]}] [remotesend connectWorld ${world[1]}]\n"
1435 if (++i
% wrapAfter
== 0) {
1436 mapsGui
+= '] \n showmapthumb \n ] \n'
1439 def s
= Math
.min(i
, last
- 1), e
= Math
.min(i
+ wrapAfter
, last
- 1)
1440 s
= Character
.toUpperCase((ents
[s
][0][0]) as
char)
1441 e
= Character
.toUpperCase((ents
[e
][0][0]) as
char)
1442 mapsGui
+= " guitab [More $s-$e] \n guilist [ \n guilist [ \n"; needClose
= true
1446 if (needClose
) mapsGui
+= '] \n showmapthumb \n ] \n'
1447 newMaps
.sort
{a
, b
-> a
.name
.compareTo(b
.name
)}
1448 if (newMaps
!= maps
) {
1451 def sel
= mapCombo
.selectedItem
1453 mapCombo
.removeAllItems()
1454 mapCombo
.addItem('')
1456 mapCombo
.addItem(map
.name
)
1458 mapCombo
.selectedItem
= sel
1461 cloudProperties
.each('privateMap/(.*)') {key
, value
, match
->
1462 def map
= getMap(match
.group(1))
1464 ents
.add([map
.name
, map
.id
, playerCount
[map
.id
]])
1465 privates
.add([map
.name
, map
.id
, playerCount
[map
.id
]])
1468 privates
.sort
{a
, b
-> a
[0].compareTo(b
[0])}
1469 mapsGui
+= "guitab [Private Worlds]\n"
1471 mapsGui
+= "guibutton [${it[0]} (${it[2]})] [remotesend connectWorld ${it[1]}]\n"
1474 mapsGui
+= "]\nnewgui Portals ["
1475 for (world in ents
) {
1476 mapsGui
+= "guibutton [${world[0]} (${world[2]})] [remotesend createPortal ${world[0]} ${world[1]}]\n"
1479 sauer('maps', cvtNewlines(mapsGui
))
1482 def
loadCostume(who
) {
1484 println
"loading costume: $who.costume"
1485 def costume
= getCostume(who
.costume
)
1486 def costumeFile
= new File(plexusDir
, "models/$who.costume")
1488 if (costumeFile
.exists()) {
1489 clothe(who
, who
.costume
)
1491 fetchDir(who
.costume
, new File(plexusDir
, "models/$who.costume"), receiveResult
: {r
->
1492 clothe(who
, who
.costume
)
1493 }, receiveException
: {ex
->
1494 System
.err
.println("Could not fetch data for costume: $who.costume: ex")
1500 def
clothe(who
, costumeDir
) {
1501 println
"Clothing $who.id with costume: $costumeDir"
1502 println
"SENDING: playerinfo ${ids[who.id]} [${who.guild ?: ''}] $costumeDir"
1503 sauer('clothe', "playerinfo ${ids[who.id]} [${who.guild ?: ''}] $costumeDir")
1506 def
pushCostume(name
) {
1507 println
"PUSHING COSTUME: $name"
1508 pushCostumeDir(name
, new File(plexusDir
, "models/$name"))
1510 def
pushCostumeDir(name
= path ?
(path as File
).getName() : null, path
) {
1511 if (!path?
.exists()) {
1512 sauer('err', "tc_msgbox [File costume not found] [Could not find costume in directory $path]")
1515 println
"STORING COSTUME"
1516 storeDir(path
, receiveResult
: {result
->
1517 def fileId
= result
.file
.getId().toStringFull()
1519 def thumb
= result
.properties
['thumb.png']
1523 thumb
= result
.properties
['thumb.jpg']
1526 println
"STORED COSTUME, adding"
1527 transmitSetCloudProperty("costume/$fileId", new JSONWriter().write([thumb
: thumb
, type
: thumb ? type
: null, name
: name
]))
1528 } catch (Exception ex
) {
1529 System
.err
.println
"Error pushing costume..."
1532 }, receiveException
: {ex
->
1533 System
.err
.println("Couldn't store costume in cloud: $path")
1538 def
updateCostumeGui() {
1539 if (costumeUpdating
) {
1540 println
"\n\n@@@\n@@@ QUEUING COSTUME UPDATE"
1541 costumeUpdateNeeded
= true
1544 costumeUpdateNeeded
= false
1546 def costumesDir
= new File(plexusDir
, 'models')
1549 def present
= [] as Set
1551 cloudProperties
.each('costume/(.*)') {key
, value
, match
->
1552 def tume
= getCostume(match
.group(1))
1555 if (tume
.thumb
&& !new File(costumesDir
, "thumbs/${tume.id}.$tume.type").exists()) {
1562 println
"Tumes: $tumes, Needed: $needed"
1565 costumeUpdating
= true
1566 println
"\n\n@@@\n@@@ UPDATE COSTUME GUI"
1567 serialContinuations(needed
, receiveResult
: {files
->
1568 costumeUpdating
= false
1569 if (costumeUpdateNeeded
) {
1570 println
"\n\n@@@\n@@@ CHECKING QUEUED COSTUME UPDATE"
1573 }, receiveException
: {ex
->
1574 System
.err
.println("Error fetching thumbs for costumes: $ex")
1577 //cut out if we have been superceded
1578 fetchFile(continuation(receiveResult
: {file
->
1579 println
"\n\n###\n### SERIAL COSTUME DOWNLOWD: $i"
1580 def thumbFile
= new File(costumesDir
, "thumbs/${tume.id}.${tume.type}")
1582 thumbFile
.getParentFile().mkdirs()
1583 copyFile(file
[0], thumbFile
)
1585 showTumes(tumes
.findAll
{present
.contains(it
)})
1587 chain
.receiveResult(file
)
1589 receiveException
: {ex
->
1590 System
.err
.println
"Error fetching thumb for costume: ${needed[i].name}..."
1591 stackTrace(files
[i
])
1592 }), Id
.build(tume
.thumb
))
1598 def
showTumes(tumes
) {
1601 tumes
.sort
{a
,b
-> a
.name
.compareTo(b
.name
)}
1602 if (tumes
!= this.tumes
) {
1605 def sel
= tumesCombo
.selectedItem
1607 tumesCombo
.removeAllItems()
1608 tumesCombo
.addItem('')
1610 tumesCombo
.addItem(it
.name
)
1612 tumesCombo
.selectedItem
= sel
1615 tumes
.each
{c
-> trips
.add([c
.name
, c
.thumb ?
"${c.id}.$c.type" : '', c
.id
])}
1616 dumpCostumeSelections(trips
)
1618 //varval = [do [result $@arg1]]
1619 //x3 = hello; v = 3; echo (varval (concatword x $v))
1621 * name, thumb path, costume id
1623 def
dumpCostumeSelections(triples
) {
1626 println
"COSTUME SELS: $triples"
1627 guitext
+= 'showcostumesgui = [showgui Costumes];'
1628 guitext
+= 'alias showcostume [ guibar; guiimage (concatword "packages/plexus/models/thumbs/" (get $costumenames [[@@guirollovername]])) $guirolloveraction 4 1 "packages/plexus/dist/tc_logo.jpg"];'
1629 guitext
+= "alias costumenames ["
1630 for (trip in triples
) {
1631 guitext
+= " [${trip[0]}] ${trip[1] ?: '[]'}"
1634 guitext
+= 'newgui Costumes [ \n guilist [ \n guilist [ \n'
1635 def i
= 0, needClose
= true, last
= triples
.size()
1637 for (trip in triples
) {
1638 guitext
+= "guibutton [${trip[0]}] [remotesend useCostume ${trip[0]} ${trip[2]}];"
1639 if (++i
% wrapAfter
== 0) {
1640 guitext
+= '] \n showcostume \n ] \n'
1643 def s
= Math
.min(i
, last
- 1), e
= Math
.min(i
+ wrapAfter
, last
- 1)
1644 s
= Character
.toUpperCase((triples
[s
][0]) as
char)
1645 e
= Character
.toUpperCase((triples
[e
][0]) as
char)
1646 guitext
+= " guitab [More $s-$e] \n guilist [ \n guilist [ \n"; needClose
= true
1650 if (needClose
) guitext
+= '] \n showcostume \n ] \n'
1651 guitext
+= 'guitab "Upload"; guifield costume_push_name [] \n guibutton [Import Costume] [remotesend pushCostume $costume_push_name] \n ] '
1652 sauer('gui', cvtNewlines(guitext
))
1655 def
useCostume(name
, dirId
) {
1656 println
"name $name dir $dirId"
1658 if (costume
!= null) {
1660 updateMyPlayerInfo()
1661 sauer('cost', "playerinfo p0 [${LaunchPlexus.props.guild}] mrfixit")
1667 if (costume
!= dirId
) {
1668 def costumeDir
= new File(plexusDir
, "models/$dirId")
1670 fetchDir(dirId
, costumeDir
, receiveResult
: {
1672 updateMyPlayerInfo()
1673 sauer('cost', "playerinfo p0 [${LaunchPlexus.props.guild}] ${costumeDir.getName()}")
1675 println
"USE COSTUME $name ($costumeDir)"
1677 }, receiveException
: {ex
->
1678 System
.err
.println("Couldn't use costume: $name: ex")
1683 def
selectCostume() {
1684 tumesCombo
.selectedItem
= costume ?
getCostume(costume
)?
.name ?
: '' : ''
1686 def
clearPlayers() {
1687 //println "Going to clear players"
1688 names
= [p0
: peerId
]
1689 ids
= [(peerId
): 'p0']
1692 def
subscribe(topicId
, cont
) {
1693 def topic
= peer
.subscribe(topicId
, cont
)
1700 def
unsubscribe(topic
) {
1701 peer
.unsubscribe(topic
)
1706 def
connectWorld(id
) {
1708 def map
= getMap(id
)
1711 sauer('entry', "tc_msgbox [Couldn't find map] [Unknown map id: $id]")
1712 } else if (map
.id
!= mapTopic?
.getId()?
.toStringFull()) {
1713 cachedPlayerLocations
= [:]
1714 println
"CONNECTING TO WORLD: $map.name ($map.id)"
1717 unsubscribe(mapTopic
)
1719 loadMap(map
.name
, map
.dir
, continuation(receiveResult
: {
1720 subscribe(Id
.build(id
), continuation(receiveResult
: {topic
->
1723 mapIsPrivate
= map
.privateMap
1724 updateMyPlayerInfo()
1726 anycast(["sendPlayerLocations"])
1729 receiveException
: {exception
-> err("Couldn't subscribe to topic: ", exception
)}))
1731 receiveException
: {ex
->
1732 System
.err
.println("Trouble loading map: $ex")
1738 cachedPlayerLocations
= [:]
1739 unsubscribe(mapTopic
)
1741 sauer('limbo', "tc_loadmsg Limbo; map plexus/dist/limbo/map")
1743 updateMyPlayerInfo()
1747 def
receivePlayerLocation(id
, update
) {
1749 def
pushMap(privateMap
, String
... nameArgs
) {
1750 println
"pushMap: [$nameArgs]"
1751 def name
= nameArgs
.join(' ')
1752 def newMap
= name?
.length()
1754 if (newMap
|| mapTopic
) {
1755 def map
= mapTopic ?
getMap(mapTopic
.getId().toStringFull()) : null
1761 def cont
= continuation(receiveResult
: {result
->
1762 def topic
= newMap ? peer
.randomId().toStringFull() : map
.id
1763 def id
= result
.file
.getId().toStringFull()
1765 transmitSetCloudProperty("${privateMap == '1' ? 'privateMap' : 'map'}/$topic", new JSONWriter().write([dir
: id
, name
: name
]))
1767 receiveException
: {ex
->
1769 System
.err
.println
"Error pushing map..."
1774 if (mapname
==~
'plexus/.*/map') {
1776 def mapdir
= new File(sauerDir
, "packages/$mapname").getParentFile()
1779 storeDir(cont
, mapdir
)
1782 def prefix
= (new File(mapname
).parent ?
new File(sauerDir
, "packages/$mapname") : new File(sauerDir
, "packages/base/$mapname")).getAbsolutePath()
1783 def dirmap
= ['map.ogz': new File(prefix
+ ".ogz")]
1784 def thumbJpg
= new File(prefix
+ ".jpg")
1785 def thumbPng
= new File(prefix
+ ".png")
1786 def cfg
= new File(prefix
+ ".cfg")
1787 def groovyPfx
= "${mapname}."
1789 new File(prefix
).parentFile
.eachFileMatch(~
"$mapname\\..*groovy") {
1790 println
"FOUND GROOVY FILE: $it"
1791 dirmap
["map.${it.name.substring(groovyPfx.length())}"] = it
1793 if (thumbJpg
.exists()) {
1794 dirmap
['map.jpg'] = thumbJpg
1796 if (thumbPng
.exists()) {
1797 dirmap
['map.png'] = thumbPng
1800 dirmap
['map.cfg'] = cfg
1803 storeDir(cont
, dirmap
)
1806 sauer('msg', "tc_msgbox [Error] [No current map]")
1809 def
activePortals(ids
) {
1811 portals
.keySet().retainAll(ids
)
1813 if (!portals
.containsKey(id
)) {
1818 def
bindPortal(id
, topic
) {
1820 if (cloudProperties
["map/$topic"]) {
1821 println
"bindPortal: portal_$id = ${getMapName(getMapEntry(topic))}"
1822 sauer('portal', "portal_$id = ${getMapName(getMapEntry(topic))}")
1826 def
firePortal(id
) {
1827 println
"portals: $portals, id: $id, portals[id]: ${portals[id]}"
1829 connectWorld(portals
[id
])
1832 def
createPortal(name
, id
) {
1833 def triggers
= [] as Set
1835 for (def i
= 0; i
< portals
.size(); i
++) {
1836 triggers
.add(31000 + i
)
1838 triggers
.removeAll(portals
.keySet())
1839 def trigger
= triggers
.isEmpty() ?
31000 + portals
.size() : triggers
.iterator().next()
1840 portals
[trigger as String
] = id
1841 println
"createPortal portal_$trigger = $name; portal $trigger"
1842 sauer('portal', "portal_$trigger = $name; portal $trigger")
1846 def
saveGroovyData() {
1847 def cfg
= new File(mapname
==~
'.*/.*' ?
new File(sauerDir
, "packages") : new File(sauerDir
, "packages/base"), mapname
+ ".cfg")
1852 def pos
= txt
=~
'(' + Prep
.MARKER
+ '\n)[^\n]*(\n|$)'
1855 txt
= txt
.substring(0, pos
.start(1)) + txt
.substring(pos
.end(2))
1860 txt
+= "remotesend bindPortals ["
1861 for (portal in portals
) {
1862 txt
+= "$portal.key $portal.value"
1864 txt
+= "];findPortals"
1868 def
playerUpdate(id
, name
, update
) {
1869 cachedPlayerLocations
[id
] = [name
, update
]
1870 if (id
== followingPlayer?
.id
) {
1874 for (def i
= 0; i
< update
.length
; i
+= 2) {
1875 values
[update
[i
]] = update
[i
+ 1]
1877 values
.x
= (Double
.parseDouble(values
.x
) - 20) as String
1878 values
.y
= (Double
.parseDouble(values
.y
) - 20) as String
1881 format
.add(it
.value
)
1883 sauer('follow', "tc_setinfo p0 ${format.join(' ')}")
1885 broadcast(["update $name ${format.join(' ')}"])
1888 def
updateDownloads() {
1890 downloadCountField
.text
= downloads as String
1891 uploadCountField
.text
= uploads as String
1894 def
clearUploadProgress() {
1898 downloadProgressBar
.setValue(0)
1902 sauer('up', 'tc_piechart_image = ""')
1907 def
showUploadProgress(cur
, total
) {
1911 downloadProgressBar
.setMaximum(total
)
1912 downloadProgressBar
.setValue(cur
)
1916 def x
= total
> 0 ? Math
.round(cur
/total
*16.0) : 0
1917 sauer('up', 'tc_piechart_image = "packages/plexus/dist/ul_' + x
+ '.png"')
1922 def
clearDownloadProgress() {
1926 downloadProgressBar
.setValue(0)
1930 sauer('up', 'tc_piechart_image = ""')
1935 def
showDownloadProgress(cur
, total
) {
1939 downloadProgressBar
.setMaximum(total
)
1940 downloadProgressBar
.setValue(cur
)
1944 def x
= total
> 0 ? Math
.round(cur
/total
*16.0) : 0
1945 sauer('up', 'tc_piechart_image = "packages/plexus/dist/dl_' + x
+ '.png"')