ソースを参照

Squashed a race condition/deadlock buried in client.go

Keelan Lightfoot 8 年 前
コミット
abc1a78757
共有4 個のファイルを変更した104 個の追加80 個の削除を含む
  1. 18
    12
      client.go
  2. 5
    6
      db.go
  3. 66
    53
      execution_context.go
  4. 15
    9
      object.go

+ 18
- 12
client.go ファイルの表示

12
 type Client struct {
12
 type Client struct {
13
 	conn             net.Conn
13
 	conn             net.Conn
14
 	connectionID     int
14
 	connectionID     int
15
+	connected        bool
15
 	playerID         DBRef
16
 	playerID         DBRef
16
 	authenticated    bool
17
 	authenticated    bool
17
-	connected        bool
18
-	reader           *bufio.Reader
19
-	commandChan      chan string
20
 	inbound          chan PlayerEvent
18
 	inbound          chan PlayerEvent
21
 	outbound         chan PlayerEvent
19
 	outbound         chan PlayerEvent
22
 	eventDistributor *EventDistributor
20
 	eventDistributor *EventDistributor
21
+	quit             chan bool
23
 	db               *DB
22
 	db               *DB
24
 	factory          *ObjectFactory
23
 	factory          *ObjectFactory
25
 }
24
 }
28
 	c := new(Client)
27
 	c := new(Client)
29
 	c.conn = conn
28
 	c.conn = conn
30
 	c.connectionID = connectionID
29
 	c.connectionID = connectionID
31
-	c.reader = bufio.NewReader(conn)
32
-	c.authenticated = false
33
 	c.connected = true
30
 	c.connected = true
34
-	c.commandChan = make(chan string)
31
+	c.authenticated = false
35
 	c.inbound = make(chan PlayerEvent)
32
 	c.inbound = make(chan PlayerEvent)
36
 	c.eventDistributor = e
33
 	c.eventDistributor = e
37
 	c.db = w
34
 	c.db = w
44
 
41
 
45
 	log.Print("Received connection from ", c.conn.RemoteAddr())
42
 	log.Print("Received connection from ", c.conn.RemoteAddr())
46
 
43
 
44
+	commandChan := make(chan string)
45
+	dieChan := make(chan bool)
46
+
47
 	go func() {
47
 	go func() {
48
+		reader := bufio.NewReader(c.conn)
49
+
48
 		for c.connected {
50
 		for c.connected {
49
-			message, err := c.reader.ReadString('\n')
51
+
52
+			message, err := reader.ReadString('\n')
50
 			if err != nil {
53
 			if err != nil {
51
-				c.connected = false
52
-				return
54
+				fmt.Println("lost connection")
55
+				dieChan <- true
56
+				break
53
 			}
57
 			}
54
 			message = strings.TrimSpace(message)
58
 			message = strings.TrimSpace(message)
55
 			if len(message) > 0 {
59
 			if len(message) > 0 {
56
-				c.commandChan <- message
60
+				commandChan <- message
57
 			}
61
 			}
58
 		}
62
 		}
59
 	}()
63
 	}()
64
 		select {
68
 		select {
65
 		case m := <-c.inbound:
69
 		case m := <-c.inbound:
66
 			c.handleInboundEvent(m)
70
 			c.handleInboundEvent(m)
67
-		case cmd := <-c.commandChan:
71
+		case cmd := <-commandChan:
68
 			if c.authenticated {
72
 			if c.authenticated {
69
 				c.sendCommand(cmd)
73
 				c.sendCommand(cmd)
70
 			} else {
74
 			} else {
71
 				c.handleLoginPhase(cmd)
75
 				c.handleLoginPhase(cmd)
72
 			}
76
 			}
77
+		case <-dieChan:
78
+			c.connected = false
73
 		}
79
 		}
74
 	}
80
 	}
75
 
81
 
76
 	c.conn.Close()
82
 	c.conn.Close()
77
-
83
+	fmt.Println("conn.Close")
78
 	c.outbound <- PlayerEvent{src: c.playerID, dst: c.playerID, messageType: EventTypeTeardown, connectionID: c.connectionID}
84
 	c.outbound <- PlayerEvent{src: c.playerID, dst: c.playerID, messageType: EventTypeTeardown, connectionID: c.connectionID}
79
 
85
 
80
 	log.Print("Lost connection from ", c.conn.RemoteAddr())
86
 	log.Print("Lost connection from ", c.conn.RemoteAddr())

+ 5
- 6
db.go ファイルの表示

230
 	})
230
 	})
231
 }
231
 }
232
 
232
 
233
-
234
 func (s *DB) SetPlayerPassword(name string, oldPassword string, newPassword string) bool {
233
 func (s *DB) SetPlayerPassword(name string, oldPassword string, newPassword string) bool {
235
-	
234
+
236
 	updated := false
235
 	updated := false
237
 
236
 
238
 	s.db.Update(func(tx *bolt.Tx) error {
237
 	s.db.Update(func(tx *bolt.Tx) error {
246
 		if err != nil {
245
 		if err != nil {
247
 			return err
246
 			return err
248
 		}
247
 		}
249
-		
248
+
250
 		if player.Password != oldPassword {
249
 		if player.Password != oldPassword {
251
 			return fmt.Errorf("password mismatch")
250
 			return fmt.Errorf("password mismatch")
252
 		}
251
 		}
253
-		
252
+
254
 		player.Password = newPassword
253
 		player.Password = newPassword
255
-		
254
+
256
 		buf, err := json.Marshal(player)
255
 		buf, err := json.Marshal(player)
257
 		if err != nil {
256
 		if err != nil {
258
 			return err
257
 			return err
371
 	s.db.View(func(tx *bolt.Tx) error {
370
 	s.db.View(func(tx *bolt.Tx) error {
372
 		b := tx.Bucket([]byte("object"))
371
 		b := tx.Bucket([]byte("object"))
373
 		o, f = s.txRetrieveObject(b, id)
372
 		o, f = s.txRetrieveObject(b, id)
374
-		return nil	
373
+		return nil
375
 	})
374
 	})
376
 	return o, f
375
 	return o, f
377
 
376
 

+ 66
- 53
execution_context.go ファイルの表示

8
 )
8
 )
9
 
9
 
10
 const (
10
 const (
11
-	MatchContext = 			1 << iota
12
-	MatchGlobalDBRef = 		1 << iota
13
-	MatchGlobalPlayer = 	1 << iota
14
-	MatchInventory = 		1 << iota
15
-	MatchRelative =  	    1 << iota
16
-	MatchExitAliases = 		1 << iota
17
-	MatchExitType = 		1 << iota
18
-	MatchRoomType = 		1 << iota
19
-	MatchPlayerType = 		1 << iota
20
-	MatchThingType = 		1 << iota
21
-	MatchAnyType = 			MatchExitType|MatchRoomType|MatchPlayerType|MatchThingType
22
-	MatchStuffTypes =       MatchExitType|MatchRoomType|MatchThingType
23
-
11
+	MatchContext      = 1 << iota
12
+	MatchGlobalDBRef  = 1 << iota
13
+	MatchGlobalPlayer = 1 << iota
14
+	MatchInventory    = 1 << iota
15
+	MatchRelative     = 1 << iota
16
+	MatchExitAliases  = 1 << iota
17
+	MatchExitType     = 1 << iota
18
+	MatchRoomType     = 1 << iota
19
+	MatchPlayerType   = 1 << iota
20
+	MatchThingType    = 1 << iota
21
+	MatchAnyType      = MatchExitType | MatchRoomType | MatchPlayerType | MatchThingType
22
+	MatchStuffTypes   = MatchExitType | MatchRoomType | MatchThingType
24
 )
23
 )
25
 
24
 
26
 type ExecutionContext struct {
25
 type ExecutionContext struct {
291
 		c.output("%s\n%s", object.DetailedName(), object.Description)
290
 		c.output("%s\n%s", object.DetailedName(), object.Description)
292
 	} else {
291
 	} else {
293
 		c.output("%s\n%s", c.context.DetailedName(), c.context.Description)
292
 		c.output("%s\n%s", c.context.DetailedName(), c.context.Description)
294
-		contents := c.context.GetContents(c.actor.ID)
293
+		contents := c.context.GetContents(c.actor.ID, false)
295
 		if len(contents) > 0 {
294
 		if len(contents) > 0 {
296
 			c.output("Contents:\n" + strings.Join(contents, "\n"))
295
 			c.output("Contents:\n" + strings.Join(contents, "\n"))
297
 		}
296
 		}
298
-		exits := c.context.GetExits(true)
297
+		exits := c.context.GetExits(true, false)
299
 		if len(exits) > 0 {
298
 		if len(exits) > 0 {
300
 			c.output("Obvious Exits:\n" + strings.Join(exits, " "))
299
 			c.output("Obvious Exits:\n" + strings.Join(exits, " "))
301
 		}
300
 		}
330
 	c.output("Owner: %s", c.objectName(object.Owner))
329
 	c.output("Owner: %s", c.objectName(object.Owner))
331
 	c.output("Location: %s", c.objectName(containerID))
330
 	c.output("Location: %s", c.objectName(containerID))
332
 
331
 
333
-	contents := object.GetContents(0)
332
+	contents := object.GetContents(0, true)
334
 	if len(contents) > 0 {
333
 	if len(contents) > 0 {
335
 		c.output("Contains:\n%s", strings.Join(contents, "\n"))
334
 		c.output("Contains:\n%s", strings.Join(contents, "\n"))
336
 	}
335
 	}
337
 
336
 
338
-	exits := object.GetExits(false)
337
+	exits := object.GetExits(false, true)
339
 	if len(exits) > 0 {
338
 	if len(exits) > 0 {
340
 		c.output("Exits:\n%s", strings.Join(exits, "\n"))
339
 		c.output("Exits:\n%s", strings.Join(exits, "\n"))
341
 	}
340
 	}
414
 		c.output("That player appears to be offline.")
413
 		c.output("That player appears to be offline.")
415
 		return
414
 		return
416
 	}
415
 	}
417
-	
416
+
418
 	c.output(`You paged %s with "%s".`, candidate.ColorName(), message)
417
 	c.output(`You paged %s with "%s".`, candidate.ColorName(), message)
419
 	c.outbound <- PlayerEvent{src: c.actor.ID, dst: candidate.ID, message: message, messageType: EventTypePage}
418
 	c.outbound <- PlayerEvent{src: c.actor.ID, dst: candidate.ID, message: message, messageType: EventTypePage}
420
-	
419
+
421
 }
420
 }
422
 
421
 
423
 func (c *ExecutionContext) inventoryCmd() {
422
 func (c *ExecutionContext) inventoryCmd() {
424
 
423
 
425
-	inventory := c.actor.GetContents(0)
424
+	inventory := c.actor.GetContents(0, true)
426
 
425
 
427
 	if len(inventory) > 0 {
426
 	if len(inventory) > 0 {
428
 		c.output("Inventory:\n %s", strings.Join(inventory, "\n "))
427
 		c.output("Inventory:\n %s", strings.Join(inventory, "\n "))
451
 		c.output("You can't pick yourself up.")
450
 		c.output("You can't pick yourself up.")
452
 		return
451
 		return
453
 	}
452
 	}
454
-	
453
+
455
 	err := c.actor.Contains(&object)
454
 	err := c.actor.Contains(&object)
456
 	if err != nil {
455
 	if err != nil {
457
 		return
456
 		return
521
 }
520
 }
522
 
521
 
523
 func (c *ExecutionContext) MatchFirst(context DBRef, matchName string, matchType int) (Object, bool) {
522
 func (c *ExecutionContext) MatchFirst(context DBRef, matchName string, matchType int) (Object, bool) {
524
-	
523
+
525
 	wanted := map[string]bool{
524
 	wanted := map[string]bool{
526
-		"player": matchType & MatchPlayerType != 0,
527
-		"exit": matchType & MatchExitType != 0,
528
-		"room": matchType & MatchRoomType != 0,
529
-		"thing": matchType & MatchThingType != 0,
525
+		"player": matchType&MatchPlayerType != 0,
526
+		"exit":   matchType&MatchExitType != 0,
527
+		"room":   matchType&MatchRoomType != 0,
528
+		"thing":  matchType&MatchThingType != 0,
530
 	}
529
 	}
531
-	
532
-	if matchType & MatchRelative != 0 {	
530
+
531
+	if matchType&MatchRelative != 0 {
533
 		if matchName == "here" {
532
 		if matchName == "here" {
534
 			o, found := c.db.Fetch(context)
533
 			o, found := c.db.Fetch(context)
535
 			if found && wanted[o.Type] {
534
 			if found && wanted[o.Type] {
536
 				return o, true
535
 				return o, true
537
 			}
536
 			}
538
 		}
537
 		}
539
-	
538
+
540
 		if matchName == "me" {
539
 		if matchName == "me" {
541
 			if wanted[c.actor.Type] {
540
 			if wanted[c.actor.Type] {
542
 				return c.actor, true
541
 				return c.actor, true
545
 	}
544
 	}
546
 
545
 
547
 	ref, valid := NewDBRefFromHashRef(matchName)
546
 	ref, valid := NewDBRefFromHashRef(matchName)
548
-	
547
+
549
 	if valid {
548
 	if valid {
550
 		o, found := c.db.Fetch(ref)
549
 		o, found := c.db.Fetch(ref)
551
 		if found && wanted[o.Type] {
550
 		if found && wanted[o.Type] {
552
 			container, _ := c.db.GetParent(o.ID)
551
 			container, _ := c.db.GetParent(o.ID)
553
-			if container == context && matchType & MatchContext != 0 {
552
+			if container == context && matchType&MatchContext != 0 {
554
 				return o, true
553
 				return o, true
555
 			}
554
 			}
556
-			if container == c.actor.ID && matchType & MatchInventory != 0 {
555
+			if container == c.actor.ID && matchType&MatchInventory != 0 {
557
 				return o, true
556
 				return o, true
558
 			}
557
 			}
559
-			if matchType & MatchGlobalDBRef != 0 {
558
+			if matchType&MatchGlobalDBRef != 0 {
560
 				return o, true
559
 				return o, true
561
 			}
560
 			}
562
 		}
561
 		}
563
 	}
562
 	}
564
-	
565
 
563
 
566
-	if  wanted["player"] && matchType & MatchGlobalPlayer != 0 {
564
+	if wanted["player"] && matchType&MatchGlobalPlayer != 0 {
567
 		o, found := c.MatchPlayerName(matchName)
565
 		o, found := c.MatchPlayerName(matchName)
568
 		if found {
566
 		if found {
569
 			return o, true
567
 			return o, true
570
 		}
568
 		}
571
 	}
569
 	}
572
 
570
 
573
-	if matchType & MatchContext != 0 {
571
+	if matchType&MatchContext != 0 {
574
 		for childID, _ := range c.db.GetChildren(context) {
572
 		for childID, _ := range c.db.GetChildren(context) {
575
 			o, found := c.db.Fetch(childID)
573
 			o, found := c.db.Fetch(childID)
576
 			if found && wanted[o.Type] {
574
 			if found && wanted[o.Type] {
577
-				if o.Type == "exit" && matchType & MatchExitAliases != 0 {
575
+				if o.Type == "exit" && matchType&MatchExitAliases != 0 {
578
 					aliases := strings.Split(o.Name, ";")
576
 					aliases := strings.Split(o.Name, ";")
579
 					for _, v := range aliases {
577
 					for _, v := range aliases {
580
 						if strings.EqualFold(v, matchName) {
578
 						if strings.EqualFold(v, matchName) {
590
 			}
588
 			}
591
 		}
589
 		}
592
 	}
590
 	}
593
-	
594
-	if matchType & MatchInventory != 0 {
591
+
592
+	if matchType&MatchInventory != 0 {
595
 		for childID, _ := range c.db.GetChildren(c.actor.ID) {
593
 		for childID, _ := range c.db.GetChildren(c.actor.ID) {
596
 			o, found := c.db.Fetch(childID)
594
 			o, found := c.db.Fetch(childID)
597
 			if found && wanted[o.Type] {
595
 			if found && wanted[o.Type] {
603
 			}
601
 			}
604
 		}
602
 		}
605
 	}
603
 	}
606
-	
604
+
607
 	return Object{}, false
605
 	return Object{}, false
608
 
606
 
609
 }
607
 }
648
 		return
646
 		return
649
 	}
647
 	}
650
 
648
 
649
+	if !object.GetFlag("enter_ok") && !c.actor.GetFlag("wizard") {
650
+		c.output("You're not allowed to do that.")
651
+		return
652
+	}
653
+
651
 	err := object.Contains(&c.actor)
654
 	err := object.Contains(&c.actor)
652
 	if err != nil {
655
 	if err != nil {
653
 		return
656
 		return
812
 		c.output("Only players can have passwords.")
815
 		c.output("Only players can have passwords.")
813
 		return
816
 		return
814
 	}
817
 	}
815
-	
818
+
816
 	r, _ := regexp.Compile(`^@password\pZ+([^\pZ]+)\pZ*=\pZ*([^\pZ]+)\pZ*$`)
819
 	r, _ := regexp.Compile(`^@password\pZ+([^\pZ]+)\pZ*=\pZ*([^\pZ]+)\pZ*$`)
817
 	params := r.FindStringSubmatch(input)
820
 	params := r.FindStringSubmatch(input)
818
 	if params == nil {
821
 	if params == nil {
819
 		return
822
 		return
820
 	}
823
 	}
821
-	
824
+
822
 	oldPassword, newPassword := params[1], params[2]
825
 	oldPassword, newPassword := params[1], params[2]
823
 
826
 
824
 	if len(oldPassword) == 0 || len(newPassword) == 0 {
827
 	if len(oldPassword) == 0 || len(newPassword) == 0 {
825
 		c.output("Password can't be blank.")
828
 		c.output("Password can't be blank.")
826
 		return
829
 		return
827
 	}
830
 	}
828
-	
831
+
829
 	changed := c.db.SetPlayerPassword(c.actor.Name, oldPassword, newPassword)
832
 	changed := c.db.SetPlayerPassword(c.actor.Name, oldPassword, newPassword)
830
 	if changed {
833
 	if changed {
831
 		c.output("Password updated.")
834
 		c.output("Password updated.")
832
 	} else {
835
 	} else {
833
 		c.output("Old password is wrong.")
836
 		c.output("Old password is wrong.")
834
 	}
837
 	}
835
-	
838
+
836
 }
839
 }
837
 
840
 
838
 func (c *ExecutionContext) nameCmd(input string) {
841
 func (c *ExecutionContext) nameCmd(input string) {
878
 
881
 
879
 func (c *ExecutionContext) descCmd(input string) {
882
 func (c *ExecutionContext) descCmd(input string) {
880
 
883
 
881
-
882
 	r, _ := regexp.Compile(`^@desc\pZ+([^=]*[^=\pZ]{1})\pZ*=\pZ*(.*)\pZ*$`)
884
 	r, _ := regexp.Compile(`^@desc\pZ+([^=]*[^=\pZ]{1})\pZ*=\pZ*(.*)\pZ*$`)
883
 	params := r.FindStringSubmatch(input)
885
 	params := r.FindStringSubmatch(input)
884
 	if params == nil {
886
 	if params == nil {
887
 
889
 
888
 	objectName, description := params[1], params[2]
890
 	objectName, description := params[1], params[2]
889
 
891
 
890
-
891
 	object, found := c.MatchFirst(c.context.ID, objectName, MatchContext|MatchInventory|MatchRelative|MatchGlobalDBRef|MatchExitAliases|MatchAnyType)
892
 	object, found := c.MatchFirst(c.context.ID, objectName, MatchContext|MatchInventory|MatchRelative|MatchGlobalDBRef|MatchExitAliases|MatchAnyType)
892
 	if !found {
893
 	if !found {
893
 		c.output("I don't see that here.")
894
 		c.output("I don't see that here.")
916
 		return
917
 		return
917
 	}
918
 	}
918
 
919
 
920
+	if object.Owner != c.actor.ID && !c.actor.GetFlag("wizard") {
921
+		c.output("You're not allowed to do that.")
922
+		return
923
+	}
924
+
919
 	object.SetFlag(flag, value)
925
 	object.SetFlag(flag, value)
920
 	object.Commit()
926
 	object.Commit()
921
 
927
 
923
 
929
 
924
 }
930
 }
925
 
931
 
926
-
927
 func (c *ExecutionContext) colorCmd(input string) {
932
 func (c *ExecutionContext) colorCmd(input string) {
928
 
933
 
929
 	r, _ := regexp.Compile(`^@color\pZ+([^=]*[^=\pZ]{1})\pZ*=\pZ*([^\pZ]*)\pZ*$`)
934
 	r, _ := regexp.Compile(`^@color\pZ+([^=]*[^=\pZ]{1})\pZ*=\pZ*([^\pZ]*)\pZ*$`)
941
 		return
946
 		return
942
 	}
947
 	}
943
 
948
 
949
+	if object.Owner != c.actor.ID && !c.actor.GetFlag("wizard") {
950
+		c.output("You're not allowed to do that.")
951
+		return
952
+	}
953
+
944
 	object.SetProp("color", value)
954
 	object.SetProp("color", value)
945
 	object.Commit()
955
 	object.Commit()
946
 
956
 
948
 
958
 
949
 }
959
 }
950
 
960
 
951
-
952
-
953
 func (c *ExecutionContext) telCmd(destStr string) {
961
 func (c *ExecutionContext) telCmd(destStr string) {
954
 
962
 
955
 	var dest Object
963
 	var dest Object
1009
 		c.output("I didn't think homicide was your thing.")
1017
 		c.output("I didn't think homicide was your thing.")
1010
 		return
1018
 		return
1011
 	}
1019
 	}
1020
+
1021
+	if object.Owner != c.actor.ID && !c.actor.GetFlag("wizard") {
1022
+		c.output("You're not allowed to do that.")
1023
+		return
1024
+	}
1025
+
1012
 	name := object.DetailedName()
1026
 	name := object.DetailedName()
1013
 	err := object.Delete()
1027
 	err := object.Delete()
1014
 	if err == nil {
1028
 	if err == nil {
1020
 func (c *ExecutionContext) goCmd(dir string) bool {
1034
 func (c *ExecutionContext) goCmd(dir string) bool {
1021
 
1035
 
1022
 	exit, found := c.MatchFirst(c.context.ID, dir, MatchContext|MatchExitType|MatchExitAliases)
1036
 	exit, found := c.MatchFirst(c.context.ID, dir, MatchContext|MatchExitType|MatchExitAliases)
1023
-	
1037
+
1024
 	if !found {
1038
 	if !found {
1025
 		return false
1039
 		return false
1026
 	}
1040
 	}
1027
-	
1041
+
1028
 	if !exit.Next.Valid() {
1042
 	if !exit.Next.Valid() {
1029
 		return false
1043
 		return false
1030
 	}
1044
 	}
1043
 	c.oemit(c.context.ID, "%s leaves the room.", c.actor.ColorName())
1057
 	c.oemit(c.context.ID, "%s leaves the room.", c.actor.ColorName())
1044
 	c.oemit(newRoom.ID, "%s enters the room.", c.actor.ColorName())
1058
 	c.oemit(newRoom.ID, "%s enters the room.", c.actor.ColorName())
1045
 	return true
1059
 	return true
1046
-	
1047
-	
1060
+
1048
 	return false
1061
 	return false
1049
 
1062
 
1050
 }
1063
 }

+ 15
- 9
object.go ファイルの表示

2
 
2
 
3
 import (
3
 import (
4
 	"fmt"
4
 	"fmt"
5
+	"github.com/mgutz/ansi"
5
 	"sort"
6
 	"sort"
6
 	"strings"
7
 	"strings"
7
-	"github.com/mgutz/ansi"
8
 )
8
 )
9
 
9
 
10
 const (
10
 const (
68
 	o := Object{}
68
 	o := Object{}
69
 	o.ID, _ = f.db.Allocate()
69
 	o.ID, _ = f.db.Allocate()
70
 	o.Type = "room"
70
 	o.Type = "room"
71
-	o.Flags = map[string]bool{"jump_ok": true}
72
 	o.db = f.db
71
 	o.db = f.db
72
+	o.SetFlag("jump_ok", true)
73
 	return o
73
 	return o
74
 }
74
 }
75
 
75
 
78
 	o.ID, _ = f.db.Allocate()
78
 	o.ID, _ = f.db.Allocate()
79
 	o.Type = "thing"
79
 	o.Type = "thing"
80
 	o.db = f.db
80
 	o.db = f.db
81
+	o.SetFlag("enter_ok", true)
81
 	return o
82
 	return o
82
 }
83
 }
83
 
84
 
169
 	return o.Name
170
 	return o.Name
170
 }
171
 }
171
 
172
 
172
-func (o *Object) GetContents(exclude DBRef) []string {
173
+func (o *Object) GetContents(exclude DBRef, showDark bool) []string {
173
 	r := make([]string, 0)
174
 	r := make([]string, 0)
174
 	children := o.db.GetChildren(o.ID)
175
 	children := o.db.GetChildren(o.ID)
175
 	for childID, linkType := range children {
176
 	for childID, linkType := range children {
179
 		if !(linkType == "player" || linkType == "thing") {
180
 		if !(linkType == "player" || linkType == "thing") {
180
 			continue
181
 			continue
181
 		}
182
 		}
182
-		o, found := o.db.Fetch(childID)
183
+		child, found := o.db.Fetch(childID)
183
 		if !found {
184
 		if !found {
184
 			continue
185
 			continue
185
 		}
186
 		}
186
-		if linkType == "player" && !o.GetFlag("online") {
187
+		if linkType == "player" && !child.GetFlag("online") {
188
+			continue
189
+		}
190
+		if child.GetFlag("dark") && !showDark {
187
 			continue
191
 			continue
188
 		}
192
 		}
189
-		r = append(r, o.DetailedName())
193
+		r = append(r, child.DetailedName())
190
 	}
194
 	}
191
 	sort.Strings(r)
195
 	sort.Strings(r)
192
 	return r
196
 	return r
193
 }
197
 }
194
 
198
 
195
-func (o *Object) GetExits(parseAliases bool) []string {
199
+func (o *Object) GetExits(parseAliases bool, showDark bool) []string {
196
 	r := make([]string, 0)
200
 	r := make([]string, 0)
197
 	children := o.db.GetChildren(o.ID)
201
 	children := o.db.GetChildren(o.ID)
198
 	for exitID, linkType := range children {
202
 	for exitID, linkType := range children {
203
 		if !found {
207
 		if !found {
204
 			continue
208
 			continue
205
 		}
209
 		}
210
+		if exit.GetFlag("dark") && !showDark {
211
+			continue
212
+		}
206
 		if parseAliases {
213
 		if parseAliases {
207
 			r = append(r, exit.FirstAlias())
214
 			r = append(r, exit.FirstAlias())
208
 		} else {
215
 		} else {
215
 }
222
 }
216
 
223
 
217
 func (o *Object) FirstAlias() string {
224
 func (o *Object) FirstAlias() string {
218
-	
225
+
219
 	aliases := strings.Split(o.Name, ";")
226
 	aliases := strings.Split(o.Name, ";")
220
 	colour := o.GetProp("color")
227
 	colour := o.GetProp("color")
221
 
228
 
226
 	}
233
 	}
227
 }
234
 }
228
 
235
 
229
-
230
 func (o *Object) SetFlag(flag string, value bool) {
236
 func (o *Object) SetFlag(flag string, value bool) {
231
 	if o.Flags == nil {
237
 	if o.Flags == nil {
232
 		o.Flags = make(map[string]bool)
238
 		o.Flags = make(map[string]bool)