Преглед на файлове

Seperated game logic from connection logic so that game logic can be run by objects like they are players.

Keelan Lightfoot преди 8 години
родител
ревизия
817abeb9f8
променени са 6 файла, в които са добавени 1119 реда и са изтрити 797 реда
  1. 21
    707
      client.go
  2. 1
    2
      cmd/funmow/main.go
  3. 75
    17
      db.go
  4. 138
    66
      event_distributor.go
  5. 881
    0
      execution_context.go
  6. 3
    5
      object.go

+ 21
- 707
client.go Целия файл

@@ -5,7 +5,6 @@ import (
5 5
 	"fmt"
6 6
 	"log"
7 7
 	"net"
8
-	"regexp"
9 8
 	"strings"
10 9
 	"unicode"
11 10
 )
@@ -13,13 +12,13 @@ import (
13 12
 type Client struct {
14 13
 	conn             net.Conn
15 14
 	connectionID     int
16
-	player           Object
15
+	playerID         DBRef
17 16
 	authenticated    bool
18 17
 	connected        bool
19 18
 	reader           *bufio.Reader
20 19
 	commandChan      chan string
21 20
 	inbound          chan PlayerEvent
22
-	outbound         EventChanSet
21
+	outbound         chan PlayerEvent
23 22
 	eventDistributor *EventDistributor
24 23
 	db               *DB
25 24
 	factory          *ObjectFactory
@@ -64,66 +63,20 @@ func (c *Client) Run() {
64 63
 	for c.connected {
65 64
 		select {
66 65
 		case m := <-c.inbound:
67
-			inside, _ := c.db.GetParent(c.player.ID)
68
-			switch m.messageType {
69
-			case EventTypeEmit:
70
-				fallthrough
71
-			case EventTypeOEmit:
72
-				if m.audience == inside {
73
-					c.write("%s\n", m.message)
74
-				}
75
-			case EventTypePEmit:
76
-				c.write("%s\n", m.message)
77
-			case EventTypeWall:
78
-				speaker, found := c.db.Fetch(m.src)
79
-				if !found {
80
-					break
81
-				}
82
-				c.write("In the distance, you hear %s bellow out \"%s\"\n", speaker.Name, m.message)
83
-			case EventTypePage:
84
-				speaker, found := c.db.Fetch(m.src)
85
-				if !found {
86
-					break
87
-				}
88
-				c.write("%s pages you: \"%s\"\n", speaker.Name, m.message)
89
-			case EventTypeSay:
90
-				if m.audience == inside {
91
-					if m.src == c.player.ID {
92
-						c.write("You say \"%s\"\n", m.message)
93
-					} else {
94
-						speaker, found := c.db.Fetch(m.src)
95
-						if !found {
96
-							break
97
-						}
98
-						c.write("%s says \"%s\"\n", speaker.Name, m.message)
99
-					}
100
-				}
101
-			case EventTypePose:
102
-				if m.audience == inside {
103
-					if m.src == c.player.ID {
104
-						c.write("%s %s\n", c.player.Name, m.message)
105
-					} else {
106
-						speaker, found := c.db.Fetch(m.src)
107
-						if !found {
108
-							break
109
-						}
110
-						c.write("%s %s\n", speaker.Name, m.message)
111
-					}
112
-				}
113
-			}
66
+			c.handleInboundEvent(m)
114 67
 		case cmd := <-c.commandChan:
115 68
 			if c.authenticated {
116
-				c.handlePlayPhase(cmd)
69
+				c.sendCommand(cmd)
117 70
 			} else {
118 71
 				c.handleLoginPhase(cmd)
119 72
 			}
120 73
 		}
121 74
 	}
122
-	if c.authenticated {
123
-		c.outbound.shutdownChan <- c.connectionID
124
-	}
75
+
125 76
 	c.conn.Close()
126 77
 
78
+	c.outbound <- PlayerEvent{src: c.playerID, dst: c.playerID, messageType: EventTypeTeardown, connectionID: c.connectionID}
79
+
127 80
 	log.Print("Lost connection from ", c.conn.RemoteAddr())
128 81
 
129 82
 }
@@ -151,688 +104,49 @@ func (c *Client) handleLoginPhase(message string) {
151 104
 		fallthrough
152 105
 	case "connect":
153 106
 		if len(fields) == 3 {
154
-			pID, err := c.db.GetPlayerID(fields[1])
107
+			playerID, err := c.db.GetPlayerID(fields[1])
155 108
 			if err != nil {
156 109
 				c.write("Bad username or password.\n")
157 110
 				break
158 111
 			}
159
-			player, found := c.db.Fetch(pID)
160
-			if !found {
161
-				c.write("You appear to be having an existential crisis.\n")
162
-				c.connected = false
163
-				break
164
-			}
165 112
 
113
+			c.playerID = playerID
166 114
 			c.authenticated = true
167
-			c.player = player
168 115
 
169 116
 			// when a player authenticates, the ipc channels get turned on
170 117
 			subReq := EventSubscribeRequest{
171 118
 				connectionID: c.connectionID,
172
-				playerID:     c.player.ID,
119
+				playerID:     c.playerID,
173 120
 				inbound:      c.inbound,
174
-				chanSet:      make(chan EventChanSet),
121
+				outbound:     make(chan chan PlayerEvent),
175 122
 			}
176 123
 
177 124
 			c.outbound = c.eventDistributor.Subscribe(subReq)
178
-
179
-			c.write("Welcome back, %s!\n", fields[1])
180
-
181
-			c.lookCmd("")
182
-
125
+			c.sendCommand("look")
183 126
 		} else {
184 127
 			c.write("What?\n")
185 128
 		}
186 129
 	case "quit":
187
-		c.quitCmd()
130
+		c.connected = false
188 131
 	default:
189 132
 		c.write("What?\n")
190 133
 	}
191 134
 
192 135
 }
193 136
 
194
-func (c *Client) handlePlayPhase(message string) {
195
-
196
-	switch {
197
-	case message == "l":
198
-		c.lookCmd("")
199
-	case message == "look":
200
-		c.lookCmd("")
201
-	case strings.HasPrefix(message, "l "): // look at
202
-		c.lookCmd(strings.TrimPrefix(message, "l "))
203
-	case strings.HasPrefix(message, "look "): // look at
204
-		c.lookCmd(strings.TrimPrefix(message, "look "))
205
-	case strings.HasPrefix(message, "ex "):
206
-		c.examineCmd(strings.TrimPrefix(message, "ex "))
207
-	case strings.HasPrefix(message, "examine "):
208
-		c.examineCmd(strings.TrimPrefix(message, "examine "))
209
-	case strings.HasPrefix(message, "\""):
210
-		c.sayCmd(strings.TrimPrefix(message, "\""))
211
-	case strings.HasPrefix(message, "say "):
212
-		c.sayCmd(strings.TrimPrefix(message, "say "))
213
-	case strings.HasPrefix(message, ":"):
214
-		c.poseCmd(strings.TrimPrefix(message, ":"))
215
-	case strings.HasPrefix(message, "pose "):
216
-		c.poseCmd(strings.TrimPrefix(message, "pose "))
217
-	case message == "i":
218
-		c.inventoryCmd()
219
-	case message == "inventory":
220
-		c.inventoryCmd()
221
-	case strings.HasPrefix(message, "get "):
222
-		c.getCmd(strings.TrimPrefix(message, "get "))
223
-	case strings.HasPrefix(message, "drop "):
224
-		c.dropCmd(strings.TrimPrefix(message, "drop "))
225
-	case strings.HasPrefix(message, "enter "):
226
-		c.enterCmd(strings.TrimPrefix(message, "enter "))
227
-	case message == "leave":
228
-		c.leaveCmd()
229
-	case message == "quit":
230
-		c.quitCmd()
231
-	case message == "WHO":
232
-		c.whoCmd()
233
-	case strings.HasPrefix(message, "@create "):
234
-		c.createCmd(strings.TrimPrefix(message, "@create "))
235
-	case strings.HasPrefix(message, "@dig "):
236
-		c.digCmd(message)
237
-	case strings.HasPrefix(message, "@open "):
238
-		c.openCmd(message)
239
-	case strings.HasPrefix(message, "@name "):
240
-		c.nameCmd(message)
241
-	case strings.HasPrefix(message, "@desc "):
242
-		c.descCmd(message)
243
-	case strings.HasPrefix(message, "@tel "):
244
-		c.telCmd(strings.TrimPrefix(message, "@tel "))
245
-	case strings.HasPrefix(message, "@dump "):
246
-		c.dumpCmd(strings.TrimPrefix(message, "@dump "))
247
-	case strings.HasPrefix(message, "@destroy "):
248
-		c.destroyCmd(strings.TrimPrefix(message, "@destroy "))
249
-	default:
250
-		if !c.goCmd(message) {
251
-			c.write("What?\n")
252
-		}
253
-	}
254
-
255
-}
256
-
257
-func (c *Client) lookCmd(at string) {
258
-
259
-	roomID, found := c.db.GetParent(c.player.ID)
260
-	room, found := c.db.Fetch(roomID)
261
-	if !found {
262
-		c.write("True Limbo (#NaN)\nThere's nothing to see here. You are inside an object that doesn't exist.\n")
263
-		return
264
-	}
265
-
266
-	if len(at) > 0 {
267
-		object, matchType := room.MatchLinkNames(at, c.player.ID, false).ExactlyOne()
268
-		switch matchType {
269
-		case MatchOne:
270
-			c.write("%s\n%s\n", object.DetailedName(), object.Description)
271
-		case MatchNone:
272
-			c.write("I don't see that here.\n")
273
-		case MatchMany:
274
-			c.write("I don't now which one you're trying to look at.\n")
275
-		}
276
-	} else {
277
-		c.write("%s\n%s\n", room.DetailedName(), room.Description)
278
-		c.lookLinks(&room, "thing", "Things:")
279
-		c.lookLinks(&room, "player", "Players:")
280
-		exits := room.GetLinkNames("exit", nil)
281
-		if len(exits) > 0 {
282
-			c.write("Exits:\n")
283
-			for _, e := range exits {
284
-				aliases := strings.Split(e, ";")
285
-				c.write("%s ", aliases[0])
286
-			}
287
-			c.write("\n")
288
-		}
289
-	}
290
-
291
-}
292
-
293
-func (c *Client) objectName(id DBRef) string {
294
-	o, found := c.db.Fetch(id)
295
-	if found {
296
-		return o.DetailedName()
297
-	} else {
298
-		return fmt.Sprintf("MISSING OBJECT (#%d)", id)
299
-	}
300
-}
301
-
302
-func (c *Client) examineCmd(at string) {
303
-
304
-	roomID, found := c.db.GetParent(c.player.ID)
305
-	room, found := c.db.Fetch(roomID)
306
-	if !found {
307
-		return
308
-	}
309
-
310
-	object, matchType := room.MatchLinkNames(at, c.player.ID, false).ExactlyOne()
311
-
312
-	switch matchType {
313
-	case MatchOne:
314
-
315
-		objectParentID, _ := c.db.GetParent(object.ID)
316
-
317
-		c.write("%s\n", object.DetailedName())
318
-		c.write("ID: %d\n", object.ID)
319
-		c.write("Type: %s\n", object.Type)
320
-		c.write("@name: %s\n", object.Name)
321
-		c.write("@desc: %s\n", object.Description)
322
-		c.write("Inside: %s\n", c.objectName(objectParentID))
323
-		c.write("Next: %s\n", c.objectName(object.Next))
324
-		c.write("Owner: %s\n", c.objectName(object.Owner))
325
-
326
-		inventory := object.GetLinkNames("*", nil)
327
-		if len(inventory) > 0 {
328
-			c.write("Contents:\n %s\n", strings.Join(inventory, "\n "))
329
-		}
330
-	case MatchNone:
331
-		c.write("I don't see that here.\n")
332
-	case MatchMany:
333
-		c.write("I don't know which one you're trying to examine.\n")
334
-	}
335
-
336
-}
337
-
338
-func (c *Client) lookLinks(o *Object, linkType string, pretty string) {
339
-
340
-	linknames := o.GetLinkNames(linkType, DBRefList{c.player.ID})
341
-
342
-	if len(linknames) > 0 {
343
-		c.write("%s\n %s\n", pretty, strings.Join(linknames, "\n "))
344
-	}
345
-
346
-}
347
-
348
-func (c *Client) sayCmd(message string) {
349
-
350
-	inside, _ := c.db.GetParent(c.player.ID)
351
-	c.outbound.messageChan <- PlayerEvent{audience: inside, src: c.player.ID, message: message, messageType: EventTypeSay}
352
-
353
-}
354
-
355
-func (c *Client) poseCmd(message string) {
356
-
357
-	inside, _ := c.db.GetParent(c.player.ID)
358
-	c.outbound.messageChan <- PlayerEvent{audience: inside, src: c.player.ID, dst: c.player.ID, message: message, messageType: EventTypePose}
359
-
360
-}
361
-
362
-func (c *Client) inventoryCmd() {
363
-
364
-	inventory := c.player.GetLinkNames("*", nil)
365
-
366
-	if len(inventory) > 0 {
367
-		c.write("Inventory:\n %s\n", strings.Join(inventory, "\n "))
368
-	} else {
369
-		c.write("You're not carrying anything.\n")
370
-	}
371
-
372
-}
373
-
374
-func (c *Client) getCmd(message string) {
375
-
376
-	roomID, found := c.db.GetParent(c.player.ID)
377
-	room, found := c.db.Fetch(roomID)
378
-	if !found {
379
-		return
380
-	}
381
-
382
-	object, matchType := room.MatchLinkNames(message, c.player.ID, true).ExactlyOne()
383
-
384
-	switch matchType {
385
-	case MatchOne:
386
-		err := c.player.Contains(&object)
387
-		if err != nil {
388
-			return
389
-		}
390
-		c.player.Refresh()
391
-		c.write("You pick up %s.\n", object.Name)
392
-		c.oemit(room.ID, "%s picks up %s.", c.player.Name, object.Name)
393
-	case MatchNone:
394
-		c.write("I don't see that here.\n")
395
-	case MatchMany:
396
-		c.write("I don't know which one.\n")
397
-	}
398
-
399
-}
400
-
401
-func (c *Client) dropCmd(message string) {
402
-
403
-	roomID, found := c.db.GetParent(c.player.ID)
404
-	room, found := c.db.Fetch(roomID)
405
-	if !found {
406
-		return
407
-	}
408
-
409
-	object, matchType := c.player.MatchLinkNames(message, c.player.ID, false).ExactlyOne()
410
-
411
-	switch matchType {
412
-	case MatchOne:
413
-		err := room.Contains(&object)
414
-		if err != nil {
415
-			return
416
-		}
417
-		inside, _ := c.db.GetParent(c.player.ID)
418
-		c.player.Refresh()
419
-		c.write("You drop %s.\n", object.Name)
420
-		c.oemit(inside, "%s drops %s.", c.player.Name, object.Name)
421
-	case MatchNone:
422
-		c.write("You're not carrying that.\n")
423
-	case MatchMany:
424
-		c.write("I don't now which one.\n")
425
-	}
426
-
427
-}
428
-
429
-func (c *Client) enterCmd(message string) {
430
-
431
-	roomID, found := c.db.GetParent(c.player.ID)
432
-	room, found := c.db.Fetch(roomID)
433
-	if !found {
434
-		return
435
-	}
436
-
437
-	object, matchType := room.MatchLinkNames(message, c.player.ID, true).ExactlyOne()
438
-
439
-	switch matchType {
440
-	case MatchOne:
441
-		err := object.Contains(&c.player)
442
-		if err != nil {
443
-			return
444
-		}
445
-		c.player.Refresh()
446
-		c.write("You climb into %s.\n", object.Name)
447
-		c.oemit(room.ID, "%s climbs into %s.", c.player.Name, object.Name)
448
-		c.oemit(object.ID, "%s squeezes into %s with you.", c.player.Name, object.Name)
449
-	case MatchNone:
450
-		c.write("I don't see that here.\n")
451
-	case MatchMany:
452
-		c.write("I don't now which one.\n")
453
-	}
454
-}
455
-
456
-func (c *Client) leaveCmd() {
457
-
458
-	inside, _ := c.db.GetParent(c.player.ID)
459
-	object, found := c.db.Fetch(inside)
460
-	if !found {
461
-		return
462
-	}
463
-	objectInside, _ := c.db.GetParent(inside)
464
-	if objectInside == 0 { // probably trying to 'leave' a room
465
-		c.write("You can't leave here.\n")
466
-		return
467
-	}
468
-
469
-	room, found := c.db.Fetch(objectInside)
470
-	if !found {
471
-		return
472
-	}
473
-
474
-	err := room.Contains(&c.player)
475
-	if err != nil {
476
-		return
477
-	}
478
-
479
-	c.player.Refresh()
480
-
481
-	c.write("You climb out of %s.\n", object.Name)
482
-	c.oemit(object.ID, "%s climbs out of %s.", c.player.Name, object.Name)
483
-	c.oemit(room.ID, "%s climbs out of %s.", c.player.Name, object.Name)
484
-
485
-}
486
-
487
-func (c *Client) quitCmd() {
488
-
489
-	c.write("So long, it's been good to know yah.\n")
490
-	c.connected = false
491
-
492
-}
493
-
494
-func (c *Client) whoCmd() {
495
-	//	onlinePlayers := c.eventDistributor.OnlinePlayers()
496
-
497
-	c.write("Currently Online:\n")
498
-
499
-	//for _, ref := range onlinePlayers {
500
-	//	c.write("%s\n", c.db.GetName(ref))
501
-	//}
502
-}
503
-
504
-func (c *Client) createCmd(message string) {
505
-
506
-	roomID, found := c.db.GetParent(c.player.ID)
507
-	room, found := c.db.Fetch(roomID)
508
-	if !found {
509
-		return
510
-	}
511
-
512
-	o := c.factory.NewThing()
513
-	o.Name = strings.TrimSpace(message)
514
-	o.Owner = c.player.ID
515
-	o.Commit()
516
-
517
-	err := room.Contains(&o)
518
-	if err != nil {
519
-		return
137
+func (c *Client) handleInboundEvent(m PlayerEvent) {
138
+	switch m.messageType {
139
+	case EventTypeOutput:
140
+		c.write("%s\n", m.message)
141
+	case EventTypeQuit:
142
+		c.connected = false
520 143
 	}
521
-
522
-	c.emit(room.ID, "A %s appears out of the ether.", o.Name)
523
-	c.write("%s Created.\n", o.DetailedName())
524
-
525
-}
526
-
527
-func (c *Client) openCmd(input string) {
528
-	// @open <in1;in2;in3;etc>=#<room>,<out1;out2;out3;etc>
529
-
530
-	r, _ := regexp.Compile(`^@open\pZ+([^=]*[^=\pZ]+)\pZ*=#([0-9]+)(?:\pZ*,\pZ*([^,]*[^,\pZ]+)\pZ*)?`)
531
-	params := r.FindStringSubmatch(input)
532
-
533
-	if params == nil {
534
-		return
535
-	}
536
-
537
-	inExit, roomIDStr, outExit := params[1], params[2], params[3]
538
-
539
-	if len(inExit) == 0 || len(roomIDStr) == 0 {
540
-		c.write("Bad command or file name.\n")
541
-		return
542
-	}
543
-
544
-	targetID, _ := NewDBRefFromString(roomIDStr) // this will never fail, the regexp guarantees that
545
-	targetRoom, found := c.db.Fetch(targetID)
546
-	if !found {
547
-		c.write("Target not found.\n")
548
-		return
549
-	}
550
-
551
-	roomID, found := c.db.GetParent(c.player.ID)
552
-	room, found := c.db.Fetch(roomID)
553
-	if !found {
554
-		return
555
-	}
556
-
557
-	toExit := c.factory.NewExit(inExit, targetRoom.ID, c.player.ID)
558
-	toExit.Commit()
559
-	err := room.Contains(&toExit)
560
-	if err != nil {
561
-		return
562
-	}
563
-	c.write("%s Created.\n", toExit.DetailedName())
564
-
565
-	if len(outExit) > 0 {
566
-		fromExit := c.factory.NewExit(outExit, room.ID, c.player.ID)
567
-		fromExit.Commit()
568
-		err = targetRoom.Contains(&fromExit)
569
-		if err != nil {
570
-			return
571
-		}
572
-		c.write("%s Created.\n", fromExit.DetailedName())
573
-	}
574
-
575
-}
576
-
577
-func (c *Client) digCmd(input string) {
578
-	// @dig <Room name>=<in1;in2;in3;etc>,<out1;out2;out3;etc>
579
-	//@dig foo bar  = <F>oo;foo;f,<B>ack;back;b
580
-
581
-	r, _ := regexp.Compile(`^@dig\pZ+([^=]*[^=\pZ]+)(\pZ*=\pZ*(?:([^,]*[^,\pZ]+)\pZ*)?(?:\pZ*,\pZ*([^,]*[^,\pZ]+)\pZ*)?)?`)
582
-	params := r.FindStringSubmatch(input)
583
-	if params == nil {
584
-		return
585
-	}
586
-
587
-	roomName, inExit, outExit := params[1], params[2], params[3]
588
-
589
-	if len(roomName) == 0 {
590
-		c.write("Rooms can't not have names.\n")
591
-		return
592
-	}
593
-
594
-	newRoom := c.factory.NewRoom()
595
-	newRoom.Name = roomName
596
-	newRoom.Owner = c.player.ID
597
-	newRoom.Commit()
598
-
599
-	c.write("%s Created.\n", newRoom.DetailedName())
600
-
601
-	if len(inExit) > 0 || len(outExit) > 0 {
602
-		roomID, found := c.db.GetParent(c.player.ID)
603
-		room, found := c.db.Fetch(roomID)
604
-		if !found {
605
-			return
606
-		}
607
-		if len(inExit) > 0 {
608
-			toExit := c.factory.NewExit(inExit, newRoom.ID, c.player.ID)
609
-			toExit.Commit()
610
-			err := room.Contains(&toExit)
611
-			if err != nil {
612
-				return
613
-			}
614
-			c.write("%s Created.\n", toExit.DetailedName())
615
-		}
616
-		if len(outExit) > 0 {
617
-			fromExit := c.factory.NewExit(outExit, room.ID, c.player.ID)
618
-			fromExit.Commit()
619
-			err := newRoom.Contains(&fromExit)
620
-			if err != nil {
621
-				return
622
-			}
623
-			c.write("%s Created.\n", fromExit.DetailedName())
624
-		}
625
-	}
626
-
627
-}
628
-
629
-func (c *Client) nameCmd(input string) {
630
-
631
-	r, _ := regexp.Compile(`^@name\pZ+([^=]*[^=\pZ]{1})\pZ*=\pZ*(.*)\pZ*$`)
632
-	params := r.FindStringSubmatch(input)
633
-	if params == nil {
634
-		return
635
-	}
636
-
637
-	objectName, name := params[1], params[2]
638
-
639
-	roomID, found := c.db.GetParent(c.player.ID)
640
-	room, found := c.db.Fetch(roomID)
641
-	if !found {
642
-		return
643
-	}
644
-
645
-	candidate, matchType := room.MatchLinkNames(objectName, c.player.ID, false).ExactlyOne()
646
-	switch matchType {
647
-	case MatchOne:
648
-		candidate.Name = name
649
-		candidate.Commit()
650
-		c.write("Name set.\n")
651
-		c.player.Refresh()
652
-	case MatchNone:
653
-		c.write("I don't see that here.\n")
654
-	case MatchMany:
655
-		c.write("I don't now which one.\n")
656
-	}
657
-
658
-}
659
-
660
-func (c *Client) descCmd(input string) {
661
-
662
-	r, _ := regexp.Compile(`^@desc\pZ+([^=]*[^=\pZ]{1})\pZ*=\pZ*(.*)\pZ*$`)
663
-	params := r.FindStringSubmatch(input)
664
-	if params == nil {
665
-		return
666
-	}
667
-
668
-	objectName, description := params[1], params[2]
669
-
670
-	roomID, found := c.db.GetParent(c.player.ID)
671
-	room, found := c.db.Fetch(roomID)
672
-
673
-	if !found {
674
-		return
675
-	}
676
-
677
-	var editObject *Object
678
-	switch objectName {
679
-	case "here":
680
-		editObject = &room
681
-	case "me":
682
-		editObject = &c.player
683
-	default:
684
-		candidate, matchType := room.MatchLinkNames(objectName, c.player.ID, false).ExactlyOne()
685
-		switch matchType {
686
-		case MatchOne:
687
-			editObject = &candidate
688
-		case MatchNone:
689
-			c.write("I don't see that here.\n")
690
-			return
691
-		case MatchMany:
692
-			c.write("I don't now which one.\n")
693
-			return
694
-		}
695
-	}
696
-
697
-	editObject.Description = description
698
-	editObject.Commit()
699
-	c.player.Refresh()
700
-	c.write("Description set.\n")
701
-
702
-}
703
-
704
-func (c *Client) telCmd(destStr string) {
705
-
706
-	dest, err := NewDBRefFromHashRef(destStr)
707
-
708
-	if err != nil {
709
-		c.write("That doesn't look like a DBRef.\n")
710
-		return
711
-	}
712
-
713
-	newRoom, found := c.db.Fetch(dest)
714
-	if !found {
715
-		c.write("That doesn't exist.\n")
716
-		return
717
-	}
718
-
719
-	c.write("You feel an intense wooshing sensation.\n")
720
-	err = newRoom.Contains(&c.player)
721
-	if err != nil {
722
-		return
723
-	}
724
-
725
-	c.player.Refresh()
726
-	c.lookCmd("")
727
-
728
-}
729
-
730
-func (c *Client) dumpCmd(refStr string) {
731
-
732
-	ref, err := NewDBRefFromHashRef(refStr)
733
-
734
-	if err != nil {
735
-		c.write("That doesn't look like a DBRef.\n")
736
-		return
737
-	}
738
-
739
-	obj, found := c.db.Fetch(ref)
740
-	if !found {
741
-		c.write("That doesn't exist.\n")
742
-		return
743
-	}
744
-
745
-	c.write("%s\n", c.db.DumpObject(obj.ID))
746
-
747
-}
748
-
749
-func (c *Client) destroyCmd(message string) {
750
-
751
-	roomID, found := c.db.GetParent(c.player.ID)
752
-	room, found := c.db.Fetch(roomID)
753
-	if !found {
754
-		return
755
-	}
756
-
757
-	object, matchType := room.MatchLinkNames(message, c.player.ID, true).ExactlyOne()
758
-
759
-	switch matchType {
760
-	case MatchOne:
761
-		name := object.DetailedName()
762
-		err := object.Delete()
763
-		if err == nil {
764
-			c.player.Refresh()
765
-			c.write("%s vanishes into thin air.\n", name)
766
-		}
767
-	case MatchNone:
768
-		c.write("I don't see that here.\n")
769
-	case MatchMany:
770
-		c.write("I don't know which one.\n")
771
-	}
772
-
773
-}
774
-
775
-func (c *Client) goCmd(dir string) bool {
776
-
777
-	roomID, found := c.db.GetParent(c.player.ID)
778
-	room, found := c.db.Fetch(roomID)
779
-	if !found {
780
-		return false
781
-	}
782
-
783
-	exit, matchType := room.MatchExitNames(dir).ExactlyOne()
784
-	switch matchType {
785
-	case MatchOne:
786
-		if exit.Next.Valid() {
787
-			newRoom, found := c.db.Fetch(exit.Next)
788
-			if !found {
789
-				c.write("That exit appears to be broken!\n")
790
-				return true
791
-			}
792
-			err := newRoom.Contains(&c.player)
793
-			if err != nil {
794
-				return false
795
-			}
796
-			c.player.Refresh()
797
-			c.write("You head towards %s.\n", newRoom.Name)
798
-			c.oemit(room.ID, "%s leaves the room.", c.player.Name)
799
-			c.oemit(newRoom.ID, "%s enters the room.", c.player.Name)
800
-			return true
801
-		}
802
-	case MatchNone:
803
-		return false
804
-	case MatchMany:
805
-		c.write("Ambiguous exit names are ambiguous.\n")
806
-		return true
807
-	}
808
-
809
-	return false
810
-
811
-}
812
-
813
-func (c *Client) oemit(audience DBRef, format string, a ...interface{}) {
814
-
815
-	message := fmt.Sprintf(format, a...)
816
-	c.outbound.messageChan <- PlayerEvent{audience: audience, src: c.player.ID, dst: c.player.ID, message: message, messageType: EventTypeOEmit}
817
-
818 144
 }
819 145
 
820 146
 func (c *Client) write(format string, a ...interface{}) {
821
-
822 147
 	fmt.Fprintf(c.conn, format, a...)
823
-
824 148
 }
825 149
 
826
-func (c *Client) pemit(audience DBRef, format string, a ...interface{}) {
827
-
828
-	message := fmt.Sprintf(format, a...)
829
-	c.outbound.messageChan <- PlayerEvent{audience: audience, src: c.player.ID, dst: c.player.ID, message: message, messageType: EventTypePEmit}
830
-
831
-}
832
-
833
-func (c *Client) emit(audience DBRef, format string, a ...interface{}) {
834
-
835
-	message := fmt.Sprintf(format, a...)
836
-	c.outbound.messageChan <- PlayerEvent{audience: audience, src: c.player.ID, dst: c.player.ID, message: message, messageType: EventTypeEmit}
837
-
150
+func (c *Client) sendCommand(message string) {
151
+	c.outbound <- PlayerEvent{src: c.playerID, dst: c.playerID, message: message, messageType: EventTypeCommand, connectionID: c.connectionID}
838 152
 }

+ 1
- 2
cmd/funmow/main.go Целия файл

@@ -32,7 +32,7 @@ func main() {
32 32
 
33 33
 	log.Print("Starting event distributor...")
34 34
 
35
-	e := funmow.NewEventDistributor()
35
+	e := funmow.NewEventDistributor(db)
36 36
 	go e.Run()
37 37
 
38 38
 	log.Print("Waiting for connections...")
@@ -51,7 +51,6 @@ func seedDB(db *funmow.DB) {
51 51
 	f := funmow.NewObjectFactory(db)
52 52
 
53 53
 	keelan, _ := db.CreatePlayer("keelan")
54
-
55 54
 	dan, _ := db.CreatePlayer("dan")
56 55
 
57 56
 	outside := f.NewObject()

+ 75
- 17
db.go Целия файл

@@ -10,13 +10,17 @@ import (
10 10
 	"strconv"
11 11
 )
12 12
 
13
+const (
14
+	DEBUG = false
15
+)
16
+
13 17
 type DBRef int
14 18
 
15 19
 func NewDBRefFromHashRef(v string) (DBRef, error) {
16 20
 	var destInt int
17
-	_, err := fmt.Sscanf(v, "#%d", &destInt)
21
+	n, err := fmt.Sscanf(v, "#%d", &destInt)
18 22
 
19
-	if err != nil {
23
+	if err != nil || n == 0 {
20 24
 		return 0, err
21 25
 	}
22 26
 
@@ -140,14 +144,29 @@ func (s *DB) Allocate() (DBRef, error) {
140 144
 }
141 145
 
142 146
 func (s *DB) Fetch(r DBRef) (Object, bool) { // this has become simply an alias for RetrieveObject
147
+
143 148
 	return s.RetrieveObject(r)
144 149
 }
145 150
 
146 151
 func (s *DB) DumpObject(r DBRef) string {
147 152
 	var dump string
148 153
 	s.db.View(func(tx *bolt.Tx) error {
149
-		b := tx.Bucket([]byte("object"))
150
-		dump = string(b.Get(r.Key()))
154
+		objectBucket := tx.Bucket([]byte("object"))
155
+		childBucket := tx.Bucket([]byte("child"))
156
+		parentBucket := tx.Bucket([]byte("parent"))
157
+
158
+		objectDump := string(objectBucket.Get(r.Key()))
159
+
160
+		intermediate := make([]string, 0)
161
+		children := s.txGetChildren(childBucket, r)
162
+		for childID, childType := range children {
163
+			intermediate = append(intermediate, fmt.Sprintf("#%d (%s)", childID, childType))
164
+		}
165
+
166
+		parent, hasParent := s.txGetParent(parentBucket, r)
167
+
168
+		dump = fmt.Sprintf("Object: %s\nHas Parent: %t\nParent ID: #%d\nChildren: %s", objectDump, hasParent, parent, intermediate)
169
+
151 170
 		return nil
152 171
 	})
153 172
 	return dump
@@ -205,37 +224,52 @@ func (s *DB) Delete(objectID DBRef) error {
205 224
 		if err != nil {
206 225
 			return err
207 226
 		}
227
+
228
+		if DEBUG {
229
+			fmt.Printf("Delete name: id: %d\n", objectID)
230
+		}
231
+
208 232
 		return nil
209 233
 	})
210 234
 }
211 235
 
212
-func (s *DB) SetPlayerID(playername string, id DBRef) error {
236
+func (s *DB) SetPlayerID(name string, id DBRef) error {
213 237
 
214 238
 	err := s.db.Update(func(tx *bolt.Tx) error {
215 239
 		b := tx.Bucket([]byte("player"))
216 240
 		if b == nil {
217 241
 			return errors.New("Player bucket not found")
218 242
 		}
219
-		return b.Put([]byte(playername), id.Key())
243
+		return b.Put([]byte(name), id.Key())
220 244
 	})
245
+
246
+	if DEBUG {
247
+		fmt.Printf("SetPlayerID name: %s id: %d\n", name, id)
248
+	}
249
+
221 250
 	return err
222 251
 
223 252
 }
224 253
 
225
-func (s *DB) GetPlayerID(playername string) (DBRef, error) {
254
+func (s *DB) GetPlayerID(name string) (DBRef, error) {
226 255
 	var id DBRef
227 256
 	err := s.db.View(func(tx *bolt.Tx) error {
228 257
 		b := tx.Bucket([]byte("player"))
229 258
 		if b == nil {
230 259
 			return errors.New("Player bucket not found")
231 260
 		}
232
-		v := b.Get([]byte(playername))
261
+		v := b.Get([]byte(name))
233 262
 		if v == nil {
234 263
 			return errors.New("Player not found")
235 264
 		}
236 265
 		id = NewDBRefFromKey(v)
237 266
 		return nil
238 267
 	})
268
+
269
+	if DEBUG {
270
+		fmt.Printf("GetPlayerID name: %s id: %d\n", name, id)
271
+	}
272
+
239 273
 	return id, err
240 274
 }
241 275
 
@@ -251,11 +285,15 @@ func (s *DB) CreatePlayer(name string) (DBRef, error) {
251 285
 			return err
252 286
 		}
253 287
 		playerID = DBRef(seq)
254
-		s.txStoreObject(objectBucket, Object{ID: playerID, Name: name}, playerID)
288
+		s.txStoreObject(objectBucket, Object{ID: playerID, Name: name, Type: "player"}, playerID)
255 289
 		err = playerBucket.Put([]byte(name), playerID.Key())
256 290
 		return err
257 291
 	})
258 292
 
293
+	if DEBUG {
294
+		fmt.Printf("CreatePlayer name: %s id: %d\n", name, playerID)
295
+	}
296
+
259 297
 	return playerID, err
260 298
 }
261 299
 
@@ -284,7 +322,11 @@ func (s *DB) txRetrieveObject(b *bolt.Bucket, id DBRef) (Object, bool) {
284 322
 		err := json.Unmarshal(v, &o)
285 323
 		if err == nil {
286 324
 			f = true
287
-			//fmt.Println("txRetrieveObject", id.String(), string(v))
325
+
326
+			if DEBUG {
327
+				fmt.Printf("txRetrieveObject id: %d\n", id)
328
+			}
329
+
288 330
 		}
289 331
 	}
290 332
 	o.db = s // bit ugly but saves a lot of headaches
@@ -316,6 +358,10 @@ func (s *DB) txStoreObject(b *bolt.Bucket, o Object, id DBRef) error {
316 358
 		return err
317 359
 	}
318 360
 
361
+	if DEBUG {
362
+		fmt.Printf("txStoreObject id: %d\n", id)
363
+	}
364
+
319 365
 	return nil
320 366
 
321 367
 }
@@ -359,19 +405,23 @@ func (s *DB) GetParent(src DBRef) (DBRef, bool) {
359 405
 
360 406
 }
361 407
 
362
-func (s *DB) txGetParent(b *bolt.Bucket, src DBRef) (DBRef, bool) {
408
+func (s *DB) txGetParent(parentBucket *bolt.Bucket, childID DBRef) (DBRef, bool) {
363 409
 
364
-	p := DBRef(0)
410
+	parentID := DBRef(0)
365 411
 	f := false
366 412
 
367
-	v := b.Get(src.Key())
413
+	v := parentBucket.Get(childID.Key())
368 414
 	if v != nil {
369
-		p = NewDBRefFromKey(v)
415
+		parentID = NewDBRefFromKey(v)
370 416
 		f = true
371 417
 	}
372 418
 
419
+	if DEBUG {
420
+		fmt.Printf("txGetParent child: %d parent: %d\n", childID, parentID)
421
+	}
422
+
373 423
 	//fmt.Printf("txGetParent src: %d found: %t parent: %d\n", src, f, p)
374
-	return p, f
424
+	return parentID, f
375 425
 
376 426
 }
377 427
 
@@ -454,7 +504,9 @@ func (s *DB) txLink(childBucket *bolt.Bucket, parentBucket *bolt.Bucket, parentI
454 504
 		return err
455 505
 	}
456 506
 
457
-	//fmt.Printf("txLink %d is now inside %d\n", childID, parentID)
507
+	if DEBUG {
508
+		fmt.Printf("txLink parent: %d child: %d\n", parentID, childID)
509
+	}
458 510
 
459 511
 	return nil
460 512
 
@@ -481,12 +533,18 @@ func (s *DB) txUnlink(childBucket *bolt.Bucket, parentBucket *bolt.Bucket, paren
481 533
 		return err
482 534
 	}
483 535
 
484
-	//fmt.Printf("txUnlink %d has been removed from %d\n", childID, parentID)
536
+	if DEBUG {
537
+		fmt.Printf("txUnlink parent: %d child: %d\n", parentID, childID)
538
+	}
485 539
 
486 540
 	return nil
487 541
 
488 542
 }
489 543
 
490 544
 func (s *DB) txDelete(b *bolt.Bucket, id DBRef) error {
545
+	if DEBUG {
546
+		fmt.Printf("txDelete %d\n", id)
547
+	}
548
+
491 549
 	return b.Delete(id.Key())
492 550
 }

+ 138
- 66
event_distributor.go Целия файл

@@ -1,5 +1,7 @@
1 1
 package funmow
2 2
 
3
+import "fmt"
4
+
3 5
 const (
4 6
 	EventTypeEmit  = iota // everyone in the containing object hears it,
5 7
 	EventTypeOEmit        // everyone in the containing object except the player hears it
@@ -8,107 +10,177 @@ const (
8 10
 	EventTypePage
9 11
 	EventTypeSay
10 12
 	EventTypePose
13
+	EventTypeCommand //7
14
+	EventTypeOutput  //8
15
+	EventTypeQuit
16
+	EventTypeTeardown
17
+	EventTypeTeardownComplete
18
+	EventTypeForce
11 19
 )
12 20
 
13 21
 type PlayerEvent struct {
14
-	src         DBRef
15
-	dst         DBRef
16
-	messageType int
17
-	audience    DBRef
18
-	message     string
22
+	connectionID int
23
+	src          DBRef
24
+	dst          DBRef
25
+	messageType  int
26
+	message      string
27
+}
28
+
29
+type playerRegistration struct {
30
+	execContext *ExecutionContext
31
+	connInbound map[int]chan PlayerEvent
32
+	execInbound chan PlayerEvent
33
+}
34
+
35
+type EventSubscribeRequest struct {
36
+	connectionID int
37
+	playerID     DBRef
38
+	inbound      chan PlayerEvent
39
+	outbound     chan chan PlayerEvent
19 40
 }
20 41
 
21 42
 type EventDistributor struct {
22 43
 	subscribeChan chan EventSubscribeRequest
23
-	whoChan       chan chan DBRefList
44
+	db            *DB
45
+	players       map[DBRef]*playerRegistration
24 46
 }
25 47
 
26
-func NewEventDistributor() *EventDistributor {
48
+func NewEventDistributor(db *DB) *EventDistributor {
27 49
 	e := new(EventDistributor)
50
+	e.db = db
28 51
 	e.subscribeChan = make(chan EventSubscribeRequest)
29
-	e.whoChan = make(chan chan DBRefList)
30 52
 	return e
31 53
 }
32 54
 
33
-func (e *EventDistributor) Subscribe(subReq EventSubscribeRequest) EventChanSet {
34
-	e.subscribeChan <- subReq
35
-	outbound := <-subReq.chanSet
55
+func (e *EventDistributor) Subscribe(req EventSubscribeRequest) chan PlayerEvent {
56
+	e.subscribeChan <- req
57
+	outbound := <-req.outbound
36 58
 	return outbound
37 59
 }
38 60
 
39 61
 func (e *EventDistributor) OnlinePlayers() DBRefList {
40
-	replyChan := make(chan DBRefList)
41
-	e.whoChan <- replyChan
42
-	onlinePlayers := <-replyChan
43
-	return onlinePlayers
62
+	//replyChan := make(chan DBRefList)
63
+	//e.whoChan <- replyChan
64
+	//onlinePlayers := <-replyChan
65
+	//return onlinePlayers
66
+	return DBRefList{}
44 67
 }
45 68
 
69
+//[client runs quit command]
70
+//connection -> "quit" -> exec
71
+//exec -> EventTypeQuit -> connection
72
+//[connection stops doing connectiony stuff]
73
+//connection -> EventTypeTeardown -> exec
74
+//[exec stops, kills off goroutines]
75
+//exec -> EventTypeTeardownComplete -> Event Distributor
76
+//Event Distributor cleans up
77
+
78
+//[client dies]
79
+//connection -> EventTypeTeardown -> exec
80
+//[exec stops, kills off goroutines]
81
+//exec -> EventTypeTeardownComplete -> Event Distributor
82
+//Event Distributor cleans up
83
+
46 84
 func (e *EventDistributor) Run() {
47 85
 
48
-	chanSet := EventChanSet{
49
-		messageChan:  make(chan PlayerEvent),
50
-		shutdownChan: make(chan int),
51
-	}
86
+	//ipcChans := make(map[int]playerRegistration)
87
+
88
+	e.players = make(map[DBRef]*playerRegistration)
52 89
 
53
-	ipcChans := make(map[int]playerRegistration)
90
+	outbound := make(chan PlayerEvent)
91
+
92
+	forceContext := NewForceContext(e, e.db, outbound)
93
+	forceInbound := forceContext.StartInboundChannel()
54 94
 
55 95
 	for {
96
+
56 97
 		select {
57 98
 		case sub := <-e.subscribeChan:
58
-			ipcChans[sub.connectionID] = playerRegistration{ID: sub.playerID, inbound: sub.inbound}
59
-			sub.chanSet <- chanSet
60
-		case connectionID := <-chanSet.shutdownChan:
61
-			delete(ipcChans, connectionID)
62
-		case message := <-chanSet.messageChan:
63
-			go func() {
64
-				for _, player := range ipcChans {
65
-					switch message.messageType {
66
-					case EventTypeEmit:
67
-						player.inbound <- message // looks like a wall because this goroutine doesn't know where the player is
68
-					case EventTypeOEmit:
69
-						if message.dst != player.ID {
70
-							player.inbound <- message
71
-						}
72
-					case EventTypePEmit:
73
-						if message.dst == player.ID {
74
-							player.inbound <- message
75
-						}
76
-					case EventTypeWall:
77
-						player.inbound <- message
78
-					case EventTypePage:
79
-						if message.dst == player.ID {
80
-							player.inbound <- message
81
-						}
82
-					case EventTypeSay:
83
-						player.inbound <- message
84
-					case EventTypePose:
85
-						player.inbound <- message
99
+
100
+			if _, exists := e.players[sub.playerID]; !exists {
101
+				c := NewExecutionContext(sub.playerID, e, e.db, outbound)
102
+				e.players[sub.playerID] = &playerRegistration{
103
+					execInbound: c.StartInboundChannel(),
104
+					execContext: c,
105
+					connInbound: make(map[int]chan PlayerEvent),
106
+				}
107
+			}
108
+			reg := e.players[sub.playerID]
109
+			fmt.Println("connection id", sub.connectionID)
110
+			reg.connInbound[sub.connectionID] = sub.inbound
111
+			sub.outbound <- outbound
112
+		case event := <-outbound: // message
113
+			fmt.Printf("received message src:%d dest:%d type: %d\n", event.src, event.dst, event.messageType)
114
+
115
+			destPlayer, validDest := e.players[event.dst]
116
+
117
+			switch event.messageType {
118
+			case EventTypeForce:
119
+				forceInbound <- event
120
+			case EventTypeTeardownComplete:
121
+				if validDest {
122
+					delete(e.players, event.dst)
123
+					fmt.Println("EventTypeTeardownComplete")
124
+				}
125
+			case EventTypeOutput: // from context to connection
126
+				if validDest {
127
+					e.sendToAllConnections(destPlayer.connInbound, event)
128
+				}
129
+				//destPlayer.connInbound <- event
130
+			case EventTypeQuit: // from context to connection
131
+				if validDest {
132
+					e.players[event.dst].connInbound[event.connectionID] <- event
133
+				}
134
+			case EventTypeTeardown: // comes from client when connection is dropped
135
+				fmt.Println("received EventTypeTeardown")
136
+				if validDest {
137
+					close(e.players[event.dst].connInbound[event.connectionID])
138
+					delete(e.players[event.dst].connInbound, event.connectionID)
139
+					fmt.Println("EventTypeTeardown delete", event.connectionID, "len: ", len(e.players[event.dst].connInbound))
140
+					if len(e.players[event.dst].connInbound) == 0 {
141
+						fmt.Println("EventTypeTeardown killing exe")
142
+						close(e.players[event.dst].execInbound) // closing the inbound channel signals the exec to stop
86 143
 					}
87 144
 				}
88
-			}()
89
-		case replyChan := <-e.whoChan:
90
-			onlinePlayers := make(DBRefList, 0)
91
-			for _, player := range ipcChans {
92
-				onlinePlayers = append(onlinePlayers, player.ID)
145
+			case EventTypeCommand: // from connection to context
146
+				if validDest {
147
+					destPlayer.execInbound <- event
148
+				}
149
+			case EventTypePEmit:
150
+				if validDest {
151
+					destPlayer.execInbound <- event
152
+				}
153
+			case EventTypePage:
154
+				if validDest {
155
+					destPlayer.execInbound <- event
156
+				}
157
+			case EventTypeEmit:
158
+				e.broadcastEvent(event, false)
159
+			case EventTypeOEmit:
160
+				e.broadcastEvent(event, true) // fix
161
+			case EventTypeSay:
162
+				e.broadcastEvent(event, true)
163
+			case EventTypePose:
164
+				e.broadcastEvent(event, false)
165
+			case EventTypeWall:
166
+				e.broadcastEvent(event, false)
93 167
 			}
94
-			replyChan <- onlinePlayers
95 168
 		}
96 169
 	}
97 170
 }
98 171
 
99
-type playerRegistration struct {
100
-	ID      DBRef
101
-	inbound chan PlayerEvent
102
-}
172
+func (e *EventDistributor) sendToAllConnections(connections map[int]chan PlayerEvent, event PlayerEvent) {
173
+
174
+	for _, channel := range connections {
175
+		channel <- event
176
+	}
103 177
 
104
-type EventSubscribeRequest struct {
105
-	connectionID int
106
-	playerID     DBRef
107
-	inbound      chan PlayerEvent
108
-	chanSet      chan EventChanSet
109 178
 }
110 179
 
111
-type EventChanSet struct {
112
-	messageChan  chan PlayerEvent
113
-	shutdownChan chan int
180
+func (e *EventDistributor) broadcastEvent(event PlayerEvent, omitSrc bool) {
181
+	for playerID, player := range e.players {
182
+		if !omitSrc || playerID != event.src {
183
+			player.execInbound <- event
184
+		}
185
+	}
114 186
 }

+ 881
- 0
execution_context.go Целия файл

@@ -0,0 +1,881 @@
1
+package funmow
2
+
3
+import (
4
+	"fmt"
5
+	"regexp"
6
+	"strings"
7
+	"time"
8
+)
9
+
10
+type ExecutionContext struct {
11
+	actor            Object
12
+	inbound          chan PlayerEvent
13
+	outbound         chan PlayerEvent
14
+	eventDistributor *EventDistributor
15
+	db               *DB
16
+	factory          *ObjectFactory
17
+	forceContext     bool
18
+}
19
+
20
+func NewForceContext(e *EventDistributor, w *DB, outbound chan PlayerEvent) *ExecutionContext {
21
+
22
+	c := new(ExecutionContext)
23
+
24
+	c.outbound = outbound
25
+	c.eventDistributor = e
26
+	c.db = w
27
+	c.factory = NewObjectFactory(w)
28
+	c.forceContext = true
29
+	return c
30
+}
31
+
32
+func NewExecutionContext(actorID DBRef, e *EventDistributor, w *DB, outbound chan PlayerEvent) *ExecutionContext {
33
+
34
+	c := new(ExecutionContext)
35
+
36
+	actor, found := w.Fetch(actorID)
37
+	if !found {
38
+		return nil
39
+	}
40
+
41
+	c.actor = actor
42
+	c.outbound = outbound
43
+	c.eventDistributor = e
44
+	c.db = w
45
+	c.factory = NewObjectFactory(w)
46
+
47
+	return c
48
+}
49
+
50
+func (c *ExecutionContext) StartInboundChannel() chan PlayerEvent {
51
+	c.inbound = make(chan PlayerEvent)
52
+	inboundBuffer := make(chan PlayerEvent)
53
+	c.outbound = c.outbound
54
+
55
+	go func() {
56
+		//inbound event buffer to protect the event distributor from long running tasks
57
+		// the alternative is to run each inbound event in a separate goroutine
58
+		// but that has potential problems (ie, two emits arrive in order.  the first one
59
+		// requires an extra DB lookup to sort out context. now the second one ends up
60
+		// sending its output event before the first, and the client sees things backwards
61
+		// this isn't just an edge case,  it happens quite frequently.
62
+		// The alternative is to buffer inbound events, which is the safest, as it doesn't
63
+		// impact the eventdistributor.
64
+
65
+		queue := make([]*PlayerEvent, 0)
66
+		running := true
67
+		for running {
68
+			if len(queue) == 0 {
69
+				select {
70
+				case newEvent, ok := <-c.inbound:
71
+					if !ok {
72
+						running = false
73
+						break
74
+					}
75
+					queue = append(queue, &newEvent)
76
+				}
77
+			} else {
78
+				event := queue[0]
79
+				select {
80
+				case newEvent, ok := <-c.inbound:
81
+					if !ok {
82
+						running = false
83
+						break
84
+					}
85
+					queue = append(queue, &newEvent)
86
+				case inboundBuffer <- *event:
87
+					queue = queue[1:]
88
+				}
89
+			}
90
+		}
91
+		// closure of c.inbound is the signal from the event distributor that we need to die.
92
+		// we then close  inboundBuffer to tell the event goroutine to die
93
+		fmt.Println("close(inboundBuffer)")
94
+		close(inboundBuffer)
95
+	}()
96
+
97
+	go func() {
98
+		for {
99
+			event, ok := <-inboundBuffer
100
+			if !ok {
101
+				break
102
+			}
103
+			c.HandleEvent(event)
104
+		}
105
+		// and finally we tell the event distributor to delete us.
106
+		fmt.Println("Sending EventTypeTeardownComplete")
107
+		c.outbound <- PlayerEvent{src: c.actor.ID, dst: c.actor.ID, messageType: EventTypeTeardownComplete}
108
+
109
+	}()
110
+
111
+	return c.inbound
112
+}
113
+
114
+func (c *ExecutionContext) HandleEvent(m PlayerEvent) {
115
+	inside, _ := c.db.GetParent(c.actor.ID)
116
+	switch m.messageType {
117
+	case EventTypeEmit:
118
+		fallthrough
119
+	case EventTypeOEmit:
120
+		if m.dst == inside {
121
+			c.output("%s", m.message)
122
+		}
123
+	case EventTypePEmit:
124
+		c.output("%s", m.message)
125
+	case EventTypeWall:
126
+		speaker, found := c.db.Fetch(m.src)
127
+		if !found {
128
+			break
129
+		}
130
+		c.output("In the distance, you hear %s bellow out \"%s\"", speaker.Name, m.message)
131
+	case EventTypePage:
132
+		speaker, found := c.db.Fetch(m.src)
133
+		if !found {
134
+			break
135
+		}
136
+		c.output("%s pages you: \"%s\"", speaker.Name, m.message)
137
+	case EventTypeSay:
138
+		if m.dst == inside {
139
+			speaker, found := c.db.Fetch(m.src)
140
+			if !found {
141
+				break
142
+			}
143
+			c.output("%s says \"%s\"", speaker.Name, m.message)
144
+		}
145
+	case EventTypePose:
146
+		if m.dst == inside {
147
+			if m.src == c.actor.ID {
148
+				c.output("%s %s", c.actor.Name, m.message)
149
+			} else {
150
+				speaker, found := c.db.Fetch(m.src)
151
+				if !found {
152
+					break
153
+				}
154
+				c.output("%s %s", speaker.Name, m.message)
155
+			}
156
+		}
157
+	case EventTypeForce:
158
+		actor, found := c.db.Fetch(m.dst)
159
+		if !found {
160
+			break
161
+		}
162
+		c.actor = actor
163
+		c.evaluateCommand(m)
164
+	case EventTypeCommand:
165
+		c.evaluateCommand(m)
166
+	}
167
+}
168
+
169
+func (c *ExecutionContext) evaluateCommand(m PlayerEvent) {
170
+	message := m.message
171
+	c.actor.Refresh()
172
+	switch {
173
+	case message == "l":
174
+		c.lookCmd("")
175
+	case message == "look":
176
+		c.lookCmd("")
177
+	case strings.HasPrefix(message, "l "): // look at
178
+		c.lookCmd(strings.TrimPrefix(message, "l "))
179
+	case strings.HasPrefix(message, "look "): // look at
180
+		c.lookCmd(strings.TrimPrefix(message, "look "))
181
+	case strings.HasPrefix(message, "ex "):
182
+		c.examineCmd(strings.TrimPrefix(message, "ex "))
183
+	case strings.HasPrefix(message, "examine "):
184
+		c.examineCmd(strings.TrimPrefix(message, "examine "))
185
+	case strings.HasPrefix(message, "\""):
186
+		c.sayCmd(strings.TrimPrefix(message, "\""))
187
+	case strings.HasPrefix(message, "say "):
188
+		c.sayCmd(strings.TrimPrefix(message, "say "))
189
+	case strings.HasPrefix(message, ":"):
190
+		c.poseCmd(strings.TrimPrefix(message, ":"))
191
+	case strings.HasPrefix(message, "pose "):
192
+		c.poseCmd(strings.TrimPrefix(message, "pose "))
193
+	case message == "i":
194
+		c.inventoryCmd()
195
+	case message == "inventory":
196
+		c.inventoryCmd()
197
+	case strings.HasPrefix(message, "get "):
198
+		c.getCmd(strings.TrimPrefix(message, "get "))
199
+	case strings.HasPrefix(message, "drop "):
200
+		c.dropCmd(strings.TrimPrefix(message, "drop "))
201
+	case strings.HasPrefix(message, "enter "):
202
+		c.enterCmd(strings.TrimPrefix(message, "enter "))
203
+	case message == "leave":
204
+		c.leaveCmd()
205
+	case message == "quit":
206
+		c.quitCmd(m.connectionID)
207
+	case message == "WHO":
208
+		c.whoCmd()
209
+	case message == "HOWLONG":
210
+		time.Sleep(5 * time.Second)
211
+	case strings.HasPrefix(message, "@create "):
212
+		c.createCmd(strings.TrimPrefix(message, "@create "))
213
+	case strings.HasPrefix(message, "@dig "):
214
+		c.digCmd(message)
215
+	case strings.HasPrefix(message, "@open "):
216
+		c.openCmd(message)
217
+	case strings.HasPrefix(message, "@name "):
218
+		c.nameCmd(message)
219
+	case strings.HasPrefix(message, "@desc "):
220
+		c.descCmd(message)
221
+	case strings.HasPrefix(message, "@tel "):
222
+		c.telCmd(strings.TrimPrefix(message, "@tel "))
223
+	case strings.HasPrefix(message, "@dump "):
224
+		c.dumpCmd(strings.TrimPrefix(message, "@dump "))
225
+	case strings.HasPrefix(message, "@destroy "):
226
+		c.destroyCmd(strings.TrimPrefix(message, "@destroy "))
227
+	case strings.HasPrefix(message, "@force "):
228
+		c.forceCmd(message)
229
+	default:
230
+		if !c.goCmd(message) {
231
+			c.output("What?\n")
232
+		}
233
+	}
234
+
235
+}
236
+
237
+func (c *ExecutionContext) lookCmd(at string) {
238
+
239
+	roomID, found := c.db.GetParent(c.actor.ID)
240
+	room, found := c.db.Fetch(roomID)
241
+	if !found {
242
+		c.output("True Limbo (#NaN)\nThere's nothing to see here. You are inside an object that doesn't exist.")
243
+		return
244
+	}
245
+
246
+	if len(at) > 0 {
247
+		object, matchType := room.MatchLinkNames(at, c.actor.ID).ExactlyOne()
248
+		switch matchType {
249
+		case MatchOne:
250
+			c.output("%s\n%s", object.DetailedName(), object.Description)
251
+		case MatchNone:
252
+			c.output("I don't see that here.")
253
+		case MatchMany:
254
+			c.output("I don't now which one you're trying to look at.")
255
+		}
256
+	} else {
257
+		c.output("%s\n%s", room.DetailedName(), room.Description)
258
+		lookLinks := func(linkType string, pretty string) {
259
+			linknames := room.GetLinkNames(linkType, DBRefList{c.actor.ID})
260
+			if len(linknames) > 0 {
261
+				c.output("%s\n %s", pretty, strings.Join(linknames, "\n "))
262
+			}
263
+		}
264
+		lookLinks("thing", "Things:")
265
+		lookLinks("player", "Players:")
266
+		exits := room.GetLinkNames("exit", nil)
267
+		if len(exits) > 0 {
268
+			c.output("Exits:")
269
+			exitList := make([]string, 0)
270
+			for _, e := range exits {
271
+				aliases := strings.Split(e, ";")
272
+				exitList = append(exitList, aliases[0])
273
+			}
274
+			c.output(strings.Join(exitList, " "))
275
+		}
276
+	}
277
+
278
+}
279
+
280
+func (c *ExecutionContext) objectName(id DBRef) string {
281
+	o, found := c.db.Fetch(id)
282
+	if found {
283
+		return o.DetailedName()
284
+	} else {
285
+		return fmt.Sprintf("MISSING OBJECT (#%d)", id)
286
+	}
287
+}
288
+
289
+func (c *ExecutionContext) examineCmd(at string) {
290
+
291
+	roomID, found := c.db.GetParent(c.actor.ID)
292
+	room, found := c.db.Fetch(roomID)
293
+	if !found {
294
+		return
295
+	}
296
+
297
+	object, matchType := room.MatchLinkNames(at, c.actor.ID).ExactlyOne()
298
+
299
+	switch matchType {
300
+	case MatchOne:
301
+
302
+		objectParentID, _ := c.db.GetParent(object.ID)
303
+
304
+		c.output("%s", object.DetailedName())
305
+		c.output("ID: %d", object.ID)
306
+		c.output("Type: %s", object.Type)
307
+		c.output("@name: %s", object.Name)
308
+		c.output("@desc: %s", object.Description)
309
+		c.output("Inside: %s", c.objectName(objectParentID))
310
+		c.output("Next: %s", c.objectName(object.Next))
311
+		c.output("Owner: %s", c.objectName(object.Owner))
312
+
313
+		inventory := object.GetLinkNames("*", nil)
314
+		if len(inventory) > 0 {
315
+			c.output("Contents:\n %s", strings.Join(inventory, "\n "))
316
+		}
317
+	case MatchNone:
318
+		c.output("I don't see that here.")
319
+	case MatchMany:
320
+		c.output("I don't know which one you're trying to examine.")
321
+	}
322
+
323
+}
324
+
325
+func (c *ExecutionContext) forceCmd(input string) {
326
+
327
+	r, _ := regexp.Compile(`^@force\pZ+([^=]*[^=\pZ]{1})\pZ*=\pZ*(.*)\pZ*$`)
328
+	params := r.FindStringSubmatch(input)
329
+	if params == nil {
330
+		return
331
+	}
332
+
333
+	objectName, command := params[1], params[2]
334
+
335
+	roomID, found := c.db.GetParent(c.actor.ID)
336
+	room, found := c.db.Fetch(roomID)
337
+	if !found {
338
+		return
339
+	}
340
+
341
+	objectID, err := NewDBRefFromHashRef(objectName)
342
+	wizard := false
343
+	if err == nil {
344
+		object, found := c.db.Fetch(objectID)
345
+		if !found {
346
+			c.output("I can't force what doesn't exist.")
347
+		}
348
+		if object.Type == "thing" || (object.Type == "player" && wizard) {
349
+			c.outbound <- PlayerEvent{src: c.actor.ID, dst: object.ID, message: command, messageType: EventTypeForce}
350
+		} else {
351
+			c.output("Some things just can't be forced.")
352
+		}
353
+	} else {
354
+
355
+		object, matchType := room.MatchLinkNames(objectName, c.actor.ID).ExactlyOne()
356
+
357
+		switch matchType {
358
+		case MatchOne:
359
+			if object.Type == "thing" || (object.Type == "player" && wizard) {
360
+				c.outbound <- PlayerEvent{src: c.actor.ID, dst: object.ID, message: command, messageType: EventTypeForce}
361
+			} else {
362
+				c.output("Some things just can't be forced.")
363
+			}
364
+		case MatchNone:
365
+			c.output("I don't see that here.")
366
+		case MatchMany:
367
+			c.output("I don't know which one you're trying to examine.")
368
+		}
369
+	}
370
+}
371
+
372
+func (c *ExecutionContext) sayCmd(message string) {
373
+
374
+	inside, _ := c.db.GetParent(c.actor.ID)
375
+	c.output("You say \"%s\"", message)
376
+	c.outbound <- PlayerEvent{src: c.actor.ID, dst: inside, message: message, messageType: EventTypeSay}
377
+
378
+}
379
+
380
+func (c *ExecutionContext) poseCmd(message string) {
381
+
382
+	inside, _ := c.db.GetParent(c.actor.ID)
383
+	c.outbound <- PlayerEvent{src: c.actor.ID, dst: inside, message: message, messageType: EventTypePose}
384
+
385
+}
386
+
387
+func (c *ExecutionContext) inventoryCmd() {
388
+
389
+	inventory := c.actor.GetLinkNames("*", nil)
390
+
391
+	if len(inventory) > 0 {
392
+		c.output("Inventory:\n %s", strings.Join(inventory, "\n "))
393
+	} else {
394
+		c.output("You're not carrying anything.")
395
+	}
396
+
397
+}
398
+
399
+func (c *ExecutionContext) getCmd(message string) {
400
+
401
+	roomID, found := c.db.GetParent(c.actor.ID)
402
+	room, found := c.db.Fetch(roomID)
403
+	if !found {
404
+		return
405
+	}
406
+
407
+	object, matchType := room.MatchLinkNames(message, c.actor.ID).ExactlyOne()
408
+
409
+	switch matchType {
410
+	case MatchOne:
411
+		if object.ID == c.actor.ID {
412
+			c.output("You can't pick yourself up.")
413
+			return
414
+		}
415
+		err := c.actor.Contains(&object)
416
+		if err != nil {
417
+			return
418
+		}
419
+		//c.actor.Refresh()
420
+		c.output("You pick up %s.", object.Name)
421
+		c.pemit(object.ID, "%s picked you up.", c.actor.Name)
422
+		c.oemit(room.ID, "%s picks up %s.", c.actor.Name, object.Name)
423
+	case MatchNone:
424
+		c.output("I don't see that here.")
425
+	case MatchMany:
426
+		c.output("I don't know which one.")
427
+	}
428
+
429
+}
430
+
431
+func (c *ExecutionContext) dropCmd(message string) {
432
+
433
+	roomID, found := c.db.GetParent(c.actor.ID)
434
+	room, found := c.db.Fetch(roomID)
435
+	if !found {
436
+		return
437
+	}
438
+
439
+	object, matchType := c.actor.MatchLinkNames(message, c.actor.ID).ExactlyOne()
440
+
441
+	switch matchType {
442
+	case MatchOne:
443
+		err := room.Contains(&object)
444
+		if err != nil {
445
+			return
446
+		}
447
+		inside, _ := c.db.GetParent(c.actor.ID)
448
+		c.output("You drop %s.", object.Name)
449
+		c.pemit(object.ID, "%s drops you.", c.actor.Name)
450
+		c.oemit(inside, "%s drops %s.", c.actor.Name, object.Name)
451
+	case MatchNone:
452
+		c.output("You're not carrying that.")
453
+	case MatchMany:
454
+		c.output("I don't now which one.")
455
+	}
456
+
457
+}
458
+
459
+func (c *ExecutionContext) enterCmd(message string) {
460
+
461
+	roomID, found := c.db.GetParent(c.actor.ID)
462
+	room, found := c.db.Fetch(roomID)
463
+	if !found {
464
+		return
465
+	}
466
+
467
+	object, matchType := room.MatchLinkNames(message, c.actor.ID).ExactlyOne()
468
+
469
+	switch matchType {
470
+	case MatchOne:
471
+		if object.ID == c.actor.ID {
472
+			c.output("Entering yourself would be a bad idea.")
473
+			return
474
+		}
475
+		err := object.Contains(&c.actor)
476
+		if err != nil {
477
+			return
478
+		}
479
+		c.output("You climb into %s.", object.Name)
480
+		c.oemit(room.ID, "%s climbs into %s.", c.actor.Name, object.Name)
481
+		c.oemit(object.ID, "%s squeezes into %s with you.", c.actor.Name, object.Name)
482
+	case MatchNone:
483
+		c.output("I don't see that here.")
484
+	case MatchMany:
485
+		c.output("I don't now which one.")
486
+	}
487
+}
488
+
489
+func (c *ExecutionContext) leaveCmd() {
490
+
491
+	inside, _ := c.db.GetParent(c.actor.ID)
492
+	object, found := c.db.Fetch(inside)
493
+	if !found {
494
+		return
495
+	}
496
+	objectInside, _ := c.db.GetParent(inside)
497
+	if objectInside == 0 { // probably trying to 'leave' a room
498
+		c.output("You can't leave here.")
499
+		return
500
+	}
501
+
502
+	room, found := c.db.Fetch(objectInside)
503
+	if !found {
504
+		return
505
+	}
506
+
507
+	err := room.Contains(&c.actor)
508
+	if err != nil {
509
+		return
510
+	}
511
+
512
+	//c.actor.Refresh()
513
+
514
+	c.output("You climb out of %s.", object.Name)
515
+	c.oemit(object.ID, "%s climbs out of %s.", c.actor.Name, object.Name)
516
+	c.oemit(room.ID, "%s climbs out of %s.", c.actor.Name, object.Name)
517
+
518
+}
519
+
520
+func (c *ExecutionContext) quitCmd(connectionID int) {
521
+
522
+	c.output("So long, it's been good to know yah.")
523
+	c.outbound <- PlayerEvent{src: c.actor.ID, dst: c.actor.ID, messageType: EventTypeQuit, connectionID: connectionID}
524
+
525
+}
526
+
527
+func (c *ExecutionContext) whoCmd() {
528
+	//	onlinePlayers := c.eventDistributor.OnlinePlayers()
529
+
530
+	c.output("Currently Online:\n")
531
+
532
+	//for _, ref := range onlinePlayers {
533
+	//	c.output("%s\n", c.db.GetName(ref))
534
+	//}
535
+}
536
+
537
+func (c *ExecutionContext) createCmd(message string) {
538
+
539
+	roomID, found := c.db.GetParent(c.actor.ID)
540
+	room, found := c.db.Fetch(roomID)
541
+	if !found {
542
+		return
543
+	}
544
+
545
+	o := c.factory.NewThing()
546
+	o.Name = strings.TrimSpace(message)
547
+	o.Owner = c.actor.ID
548
+	o.Commit()
549
+
550
+	err := room.Contains(&o)
551
+	if err != nil {
552
+		return
553
+	}
554
+
555
+	c.oemit(room.ID, "A %s appears out of the ether.", o.Name)
556
+	c.output("%s Created.", o.DetailedName())
557
+
558
+}
559
+
560
+func (c *ExecutionContext) openCmd(input string) {
561
+	// @open <in1;in2;in3;etc>=#<room>,<out1;out2;out3;etc>
562
+
563
+	r, _ := regexp.Compile(`^@open\pZ+([^=]*[^=\pZ]+)\pZ*=#([0-9]+)(?:\pZ*,\pZ*([^,]*[^,\pZ]+)\pZ*)?`)
564
+	params := r.FindStringSubmatch(input)
565
+
566
+	if params == nil {
567
+		return
568
+	}
569
+
570
+	inExit, roomIDStr, outExit := params[1], params[2], params[3]
571
+
572
+	if len(inExit) == 0 || len(roomIDStr) == 0 {
573
+		c.output("Bad command or file name.")
574
+		return
575
+	}
576
+
577
+	targetID, _ := NewDBRefFromString(roomIDStr) // this will never fail, the regexp guarantees that
578
+	targetRoom, found := c.db.Fetch(targetID)
579
+	if !found {
580
+		c.output("Target not found.")
581
+		return
582
+	}
583
+
584
+	roomID, found := c.db.GetParent(c.actor.ID)
585
+	room, found := c.db.Fetch(roomID)
586
+	if !found {
587
+		return
588
+	}
589
+
590
+	toExit := c.factory.NewExit(inExit, targetRoom.ID, c.actor.ID)
591
+	toExit.Commit()
592
+	err := room.Contains(&toExit)
593
+	if err != nil {
594
+		return
595
+	}
596
+	c.output("%s Created.", toExit.DetailedName())
597
+
598
+	if len(outExit) > 0 {
599
+		fromExit := c.factory.NewExit(outExit, room.ID, c.actor.ID)
600
+		fromExit.Commit()
601
+		err = targetRoom.Contains(&fromExit)
602
+		if err != nil {
603
+			return
604
+		}
605
+		c.output("%s Created.", fromExit.DetailedName())
606
+	}
607
+
608
+}
609
+
610
+func (c *ExecutionContext) digCmd(input string) {
611
+	// @dig <Room name>=<in1;in2;in3;etc>,<out1;out2;out3;etc>
612
+	//@dig foo bar  = <F>oo;foo;f,<B>ack;back;b
613
+
614
+	r, _ := regexp.Compile(`^@dig\pZ+([^=]*[^=\pZ]+)(\pZ*=\pZ*(?:([^,]*[^,\pZ]+)\pZ*)?(?:\pZ*,\pZ*([^,]*[^,\pZ]+)\pZ*)?)?`)
615
+	params := r.FindStringSubmatch(input)
616
+	if params == nil {
617
+		return
618
+	}
619
+
620
+	roomName, inExit, outExit := params[1], params[2], params[3]
621
+
622
+	if len(roomName) == 0 {
623
+		c.output("Rooms can't not have names.")
624
+		return
625
+	}
626
+
627
+	newRoom := c.factory.NewRoom()
628
+	newRoom.Name = roomName
629
+	newRoom.Owner = c.actor.ID
630
+	newRoom.Commit()
631
+
632
+	c.output("%s Created.", newRoom.DetailedName())
633
+
634
+	if len(inExit) > 0 || len(outExit) > 0 {
635
+		roomID, found := c.db.GetParent(c.actor.ID)
636
+		room, found := c.db.Fetch(roomID)
637
+		if !found {
638
+			return
639
+		}
640
+		if len(inExit) > 0 {
641
+			toExit := c.factory.NewExit(inExit, newRoom.ID, c.actor.ID)
642
+			toExit.Commit()
643
+			err := room.Contains(&toExit)
644
+			if err != nil {
645
+				return
646
+			}
647
+			c.output("%s Created.", toExit.DetailedName())
648
+		}
649
+		if len(outExit) > 0 {
650
+			fromExit := c.factory.NewExit(outExit, room.ID, c.actor.ID)
651
+			fromExit.Commit()
652
+			err := newRoom.Contains(&fromExit)
653
+			if err != nil {
654
+				return
655
+			}
656
+			c.output("%s Created.", fromExit.DetailedName())
657
+		}
658
+	}
659
+
660
+}
661
+
662
+func (c *ExecutionContext) nameCmd(input string) {
663
+
664
+	r, _ := regexp.Compile(`^@name\pZ+([^=]*[^=\pZ]{1})\pZ*=\pZ*(.*)\pZ*$`)
665
+	params := r.FindStringSubmatch(input)
666
+	if params == nil {
667
+		return
668
+	}
669
+
670
+	objectName, name := params[1], params[2]
671
+
672
+	roomID, found := c.db.GetParent(c.actor.ID)
673
+	room, found := c.db.Fetch(roomID)
674
+	if !found {
675
+		return
676
+	}
677
+
678
+	candidate, matchType := room.MatchLinkNames(objectName, c.actor.ID).ExactlyOne()
679
+	switch matchType {
680
+	case MatchOne:
681
+		candidate.Name = name
682
+		candidate.Commit()
683
+		c.output("Name set.")
684
+		//c.actor.Refresh()
685
+	case MatchNone:
686
+		c.output("I don't see that here.")
687
+	case MatchMany:
688
+		c.output("I don't now which one.")
689
+	}
690
+
691
+}
692
+
693
+func (c *ExecutionContext) descCmd(input string) {
694
+
695
+	r, _ := regexp.Compile(`^@desc\pZ+([^=]*[^=\pZ]{1})\pZ*=\pZ*(.*)\pZ*$`)
696
+	params := r.FindStringSubmatch(input)
697
+	if params == nil {
698
+		return
699
+	}
700
+
701
+	objectName, description := params[1], params[2]
702
+
703
+	roomID, found := c.db.GetParent(c.actor.ID)
704
+	room, found := c.db.Fetch(roomID)
705
+
706
+	if !found {
707
+		return
708
+	}
709
+
710
+	var editObject *Object
711
+	switch objectName {
712
+	case "here":
713
+		editObject = &room
714
+	case "me":
715
+		editObject = &c.actor
716
+	default:
717
+		candidate, matchType := room.MatchLinkNames(objectName, c.actor.ID).ExactlyOne()
718
+		switch matchType {
719
+		case MatchOne:
720
+			editObject = &candidate
721
+		case MatchNone:
722
+			c.output("I don't see that here.")
723
+			return
724
+		case MatchMany:
725
+			c.output("I don't now which one.")
726
+			return
727
+		}
728
+	}
729
+
730
+	editObject.Description = description
731
+	editObject.Commit()
732
+	//c.actor.Refresh()
733
+	c.output("Description set.")
734
+
735
+}
736
+
737
+func (c *ExecutionContext) telCmd(destStr string) {
738
+
739
+	dest, err := NewDBRefFromHashRef(destStr)
740
+
741
+	if err != nil {
742
+		c.output("That doesn't look like a DBRef.")
743
+		return
744
+	}
745
+
746
+	newRoom, found := c.db.Fetch(dest)
747
+	if !found {
748
+		c.output("That doesn't exist.")
749
+		return
750
+	}
751
+
752
+	c.output("You feel an intense wooshing sensation.")
753
+	err = newRoom.Contains(&c.actor)
754
+	if err != nil {
755
+		return
756
+	}
757
+
758
+	c.actor.Refresh()
759
+	c.lookCmd("")
760
+
761
+}
762
+
763
+func (c *ExecutionContext) dumpCmd(refStr string) {
764
+
765
+	ref, err := NewDBRefFromHashRef(refStr)
766
+
767
+	if err != nil {
768
+		c.output("That doesn't look like a DBRef.")
769
+		return
770
+	}
771
+
772
+	obj, found := c.db.Fetch(ref)
773
+	if !found {
774
+		c.output("That doesn't exist.")
775
+		return
776
+	}
777
+
778
+	c.output("%s", c.db.DumpObject(obj.ID))
779
+
780
+}
781
+
782
+func (c *ExecutionContext) destroyCmd(message string) {
783
+
784
+	roomID, found := c.db.GetParent(c.actor.ID)
785
+	room, found := c.db.Fetch(roomID)
786
+	if !found {
787
+		return
788
+	}
789
+
790
+	object, matchType := room.MatchLinkNames(message, c.actor.ID).ExactlyOne()
791
+
792
+	switch matchType {
793
+	case MatchOne:
794
+		if object.ID == c.actor.ID {
795
+			c.output("There are alternatives to suicide.")
796
+			return
797
+		}
798
+		if object.Type == "player" {
799
+			c.output("I didn't think homicide was your thing.")
800
+			return
801
+		}
802
+		name := object.DetailedName()
803
+		err := object.Delete()
804
+		if err == nil {
805
+			//c.actor.Refresh()
806
+			c.output("%s vanishes into thin air.", name)
807
+		}
808
+	case MatchNone:
809
+		c.output("I don't see that here.")
810
+	case MatchMany:
811
+		c.output("I don't know which one.")
812
+	}
813
+
814
+}
815
+
816
+func (c *ExecutionContext) goCmd(dir string) bool {
817
+
818
+	roomID, found := c.db.GetParent(c.actor.ID)
819
+	room, found := c.db.Fetch(roomID)
820
+	if !found {
821
+		return false
822
+	}
823
+
824
+	exit, matchType := room.MatchExitNames(dir).ExactlyOne()
825
+	switch matchType {
826
+	case MatchOne:
827
+		if exit.Next.Valid() {
828
+			newRoom, found := c.db.Fetch(exit.Next)
829
+			if !found {
830
+				c.output("That exit appears to be broken!")
831
+				return true
832
+			}
833
+			err := newRoom.Contains(&c.actor)
834
+			if err != nil {
835
+				return false
836
+			}
837
+			//c.actor.Refresh()
838
+			c.output("You head towards %s.", newRoom.Name)
839
+			c.oemit(room.ID, "%s leaves the room.", c.actor.Name)
840
+			c.oemit(newRoom.ID, "%s enters the room.", c.actor.Name)
841
+			return true
842
+		}
843
+	case MatchNone:
844
+		return false
845
+	case MatchMany:
846
+		c.output("Ambiguous exit names are ambiguous.")
847
+		return true
848
+	}
849
+
850
+	return false
851
+
852
+}
853
+
854
+func (c *ExecutionContext) oemit(audience DBRef, format string, a ...interface{}) {
855
+
856
+	message := fmt.Sprintf(format, a...)
857
+	c.outbound <- PlayerEvent{src: c.actor.ID, dst: audience, message: message, messageType: EventTypeOEmit}
858
+
859
+}
860
+
861
+func (c *ExecutionContext) output(format string, a ...interface{}) {
862
+
863
+	message := fmt.Sprintf(format, a...)
864
+	if !c.forceContext {
865
+		c.outbound <- PlayerEvent{src: c.actor.ID, dst: c.actor.ID, message: message, messageType: EventTypeOutput}
866
+	}
867
+}
868
+
869
+func (c *ExecutionContext) pemit(target DBRef, format string, a ...interface{}) {
870
+
871
+	message := fmt.Sprintf(format, a...)
872
+	c.outbound <- PlayerEvent{src: c.actor.ID, dst: target, message: message, messageType: EventTypePEmit}
873
+
874
+}
875
+
876
+func (c *ExecutionContext) emit(audience DBRef, format string, a ...interface{}) {
877
+
878
+	message := fmt.Sprintf(format, a...)
879
+	c.outbound <- PlayerEvent{src: c.actor.ID, dst: audience, message: message, messageType: EventTypeEmit}
880
+
881
+}

+ 3
- 5
object.go Целия файл

@@ -99,11 +99,11 @@ func (o *Object) Contains(c *Object) error {
99 99
 	return o.db.Link(o.ID, c.ID, c.Type)
100 100
 }
101 101
 
102
-func (o *Object) MatchLinkNames(matchName string, player DBRef, excludePlayer bool) ObjectList {
102
+func (o *Object) MatchLinkNames(matchName string, player DBRef) ObjectList {
103 103
 	if matchName == "here" {
104 104
 		return ObjectList{*o}
105 105
 	}
106
-	if !excludePlayer && matchName == "me" {
106
+	if matchName == "me" {
107 107
 		p, found := o.db.Fetch(player)
108 108
 		if !found {
109 109
 			return ObjectList{}
@@ -116,9 +116,6 @@ func (o *Object) MatchLinkNames(matchName string, player DBRef, excludePlayer bo
116 116
 	children := o.db.GetChildren(o.ID) // map[DBRef]string
117 117
 
118 118
 	for childID, _ := range children {
119
-		if excludePlayer && player == childID {
120
-			continue
121
-		}
122 119
 		o, found := o.db.Fetch(childID)
123 120
 		if found {
124 121
 			idName := fmt.Sprintf("#%d", o.ID)
@@ -182,6 +179,7 @@ func (o *Object) GetLinkNames(matchType string, exclude DBRefList) []string {
182 179
 		skip := false
183 180
 		for _, excludeID := range exclude {
184 181
 			if excludeID == childID {
182
+				fmt.Println("Skipping ", childID)
185 183
 				skip = true
186 184
 			}
187 185
 		}