-
Notifications
You must be signed in to change notification settings - Fork 0
/
mapclient.go
3986 lines (3581 loc) · 116 KB
/
mapclient.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
\
########################################################################################
# __ #
# /__ _ #
# \_|(_) #
# _______ _______ _______ _______ _______ _______ __ #
# ( ____ \( )( ___ ) Game ( ____ \ / ___ )( __ ) / \ #
# | ( \/| () () || ( ) | Master's | ( \/ \/ ) || ( ) | \/) ) #
# | | | || || || (___) | Assistant | (____ / )| | / | | | #
# | | ____ | |(_)| || ___ | (Go Port) (_____ \ _/ / | (/ /) | | | #
# | | \_ )| | | || ( ) | ) ) / _/ | / | | | | #
# | (___) || ) ( || ) ( | Mapper /\____) ) _ ( (__/\| (__) | _ __) (_ #
# (_______)|/ \||/ \| Client \______/ (_)\_______/(_______)(_) \____/ #
# #
########################################################################################
*/
//
// Package mapper implements a standard client interface for the mapper service.
//
// This package handles the details of communicating with the
// GMA mapper service communication channel used to keep the mapper
// clients in sync with each other and with the other GMA tools.
//
// A client should establish a connection to the game server by
// calling the Dial method in this package. This function will
// sign on to the server and then enter a loop, sending incoming
// server messages back on the channel(s) established via the
// Subscribe method. Dial returns when the session with the
// server has terminated.
//
// Typically, an application will invoke the Dial method in a
// goroutine. Calling the associated context's cancel function
// will signal that we want to stop talking to the server, resulting
// in the termination of the running Dial method.
//
package mapper
//
// Since there's a fair amount of code below which is logically
// divided up by server message type (sending or receiving), we
// will use large banners to make it easy to scroll quickly
// and visually distinguish each section with ease.
//
import (
"bufio"
"context"
"errors"
"fmt"
"log"
"net"
"regexp"
"runtime"
"strings"
"time"
"github.com/MadScienceZone/go-gma/v5/auth"
"github.com/MadScienceZone/go-gma/v5/dice"
"github.com/MadScienceZone/go-gma/v5/util"
)
// ErrAuthenticationRequired is the error returned when the server requires authentication but we didn't provide any.
var ErrAuthenticationRequired = errors.New("authenticator required for connection")
// ErrAuthenticationFailed is the error returned when our authentication was rejected by the server.
var ErrAuthenticationFailed = errors.New("access denied to server")
// ErrServerProtocolError is the error returned when something fundamental about the server's conversation
// with us is so wrong we can't even deal with the conversation any further.
var ErrServerProtocolError = errors.New("server protocol error; unable to continue")
// ErrRetryConnection is returned when the attempt to establish a connection to the server
// fails in such a way that simply trying again would be the right thing to do.
var ErrRetryConnection = errors.New("please retry the server connection")
//
// Debugging information is enabled by selecting a nummber
// of discrete topics which you want logged as the application
// runs (previous versions used a "level" of verbosity which
// doesn't provide the better granularity this version provides
// to just get the info you want.
//
type DebugFlags uint64
const (
DebugAuth DebugFlags = 1 << iota
DebugBinary
DebugEvents
DebugIO
DebugMessages
DebugMisc
DebugQoS
DebugAll DebugFlags = 0xffffffff
)
//
// DebugFlagNameSlice returns a slice of debug flat names
// corresponding to the bit-encoded flags parameter.
//
func DebugFlagNameSlice(flags DebugFlags) []string {
if flags == 0 {
return nil
}
if flags == DebugAll {
return []string{"all"}
}
var list []string
for _, f := range []struct {
bits DebugFlags
name string
}{
{bits: DebugAuth, name: "auth"},
{bits: DebugBinary, name: "binary"},
{bits: DebugEvents, name: "events"},
{bits: DebugIO, name: "i/o"},
{bits: DebugMessages, name: "messages"},
{bits: DebugMisc, name: "misc"},
{bits: DebugQoS, name: "qos"},
} {
if (flags & f.bits) != 0 {
list = append(list, f.name)
}
}
return list
}
//
// DebugFlagNames returns a string representation of
// the debugging flags (topics) stored in the DebugFlags
// value passed in.
//
func DebugFlagNames(flags DebugFlags) string {
list := DebugFlagNameSlice(flags)
if list == nil {
return "<none>"
}
return "<" + strings.Join(list, ",") + ">"
}
//
// NamedDebugFlags takes a comma-separated list of
// debug flag (topic) names, or a list of individual
// names, or both, and returns the DebugFlags
// value which includes all of them.
//
// If "none" appears in the list, it cancels all previous
// values seen, but subsequent names will add their values
// to the list.
//
func NamedDebugFlags(names ...string) (DebugFlags, error) {
var d DebugFlags
var err error
for _, name := range names {
for _, flag := range strings.Split(name, ",") {
switch flag {
case "":
case "none":
d = 0
case "all":
d = DebugAll
case "auth":
d |= DebugAuth
case "binary":
d |= DebugBinary
case "events":
d |= DebugEvents
case "i/o", "io":
d |= DebugIO
case "messages":
d |= DebugMessages
case "misc":
d |= DebugMisc
case "qos":
d |= DebugQoS
default:
err = fmt.Errorf("invalid debug flag name")
// but keep processing the rest
}
}
}
return d, err
}
//
// Connection describes a connection to the server. These are
// created with NewConnection and then send methods such as
// Subscribe and Dial.
//
type Connection struct {
// If true, we will always try to reconnect to the server if we
// lose our connection.
StayConnected bool
// Do we have an active session now?
signedOn bool
// If nonzero, we will re-try a failing connection this many
// times before giving up on the server. Otherwise we will keep
// trying forever.
Retries uint
// The server's protocol version number.
Protocol int
// The verbosity level of debugging log messages.
DebuggingLevel DebugFlags
// If nonzero, our connection attempts will timeout after the
// specified time interval. Otherwise they will wait indefinitely.
Timeout time.Duration
// The server endpoint, in any form acceptable to the net.Dial
// function.
Endpoint string
// Characters received from the server.
Characters map[string]PlayerToken
// Conditions and their token markings received from the server.
Conditions map[string]StatusMarkerDefinition
// If we received pre-authentication data from the server other than
// definition of characters and condition codes, they are technically
// too early to be valid (the server shouldn't do anything else before
// authenticating), so we'll merely collect them here in case they are
// of interest forensically.
Preamble []string
// Any progress gauges sent by the server will be tracked here as well
// as passed to a channel subscribed to UpdateProgress.
Gauges map[string]*UpdateProgressMessagePayload
// The list of advertised updates to our software
PackageUpdatesAvailable map[string][]PackageVersion
// The last error encountered while communicating with the server.
LastError error
serverConn MapConnection
// The calendar system the server indicated as preferred, if any
CalendarSystem string
// Server overrides to client settings (if non-nil)
ClientSettings *ClientSettingsOverrides
// The context for our session, either one we created in the
// NewConnection function or one we received from the caller.
Context context.Context
// If this is non-nil, we will use this to identify the user
// to the server.
Authenticator *auth.Authenticator
// We will log informational messages here as we work.
Logger *log.Logger
// Server message subscriptions currently in effect.
Subscriptions map[ServerMessage]chan MessagePayload
// Our signal that we're ready for the client to talk.
ReadySignal chan byte
// Some statistics we know about the server
ServerStats struct {
Started time.Time // server startup time
Active time.Time // time of last ping (at connect-time, this is time of last ping sent by server)
ConnectTime time.Time // time server connected (time on the server, for comparison with other server times)
ServerVersion string // the server's version number
}
}
//
// Log writes data to our log destination.
//
func (c *Connection) Log(message ...any) {
if c != nil && c.Logger != nil {
message = append([]any{"[client] "}, message...)
c.Logger.Print(message...)
}
}
//
// Logf writes data to our log destination.
//
func (c *Connection) Logf(format string, data ...any) {
if c != nil && c.Logger != nil {
c.Logger.Printf("[client] "+format, data...)
}
}
//
// IsReady returns true if the connection to the server
// has completed and authentication was successful, so
// the connection is ready for interactive use.
//
func (c *Connection) IsReady() bool {
return c != nil && c.serverConn.IsReady() && c.signedOn
}
//
// WithContext modifies the behavior of the NewConnection function
// by supplying a context for this connection, which may be used to
// signal the Dial method that the connection to the server should
// be terminated.
//
// N.B.: When making the initial TCP connection to the server,
// if there is a timeout value specified via WithTimeout, then
// a hanging connection will terminate when that timer expires,
// regardless of the context. Otherwise, the connection will wait
// indefinitely to complete OR until the context is cancelled.
//
func WithContext(ctx context.Context) func(*Connection) error {
return func(c *Connection) error {
c.Context = ctx
return nil
}
}
//
// WhenReady specifies a channel on which to send a single byte
// when the server login process is complete and the server
// is ready to receive our commands.
//
func WhenReady(ch chan byte) func(*Connection) error {
return func(c *Connection) error {
c.ReadySignal = ch
return nil
}
}
// ConnectionOption is an option to be passed to the NewConnection
// function.
//
type ConnectionOption func(*Connection) error
//
// WithSubscription modifies the behavior of the NewConnection function
// by adding a server message subscription to the connection just as if
// the Subscribe method had been called on the connection value.
//
// For example, this:
// server, err := NewConnection(endpoint,
// WithSubscription(chats, ChatMessage, RollResult),
// WithSubscription(oops, ERROR, UNKNOWN))
// go server.Dial()
// is equivalent to this:
// server, err := NewConnection(endpoint)
// err = server.Subscribe(chats, ChatMessage, RollResult)
// err = server.Subscribe(oops, ERROR, UNKNOWN)
// go server.Dial()
// (Of course, real production code should check the returned error values.)
//
func WithSubscription(ch chan MessagePayload, messages ...ServerMessage) ConnectionOption {
return func(c *Connection) error {
return c.Subscribe(ch, messages...)
}
}
//
// WithAuthenticator modifies the behavior of the NewConnection function
// by adding an authenticator which will be used to identify the client
// to the server. If this option is not given, no attempt will be made
// to authenticate, which is only appropriate for servers which do not
// require authentication. (Which, hopefully, won't be the case anyway.)
//
func WithAuthenticator(a *auth.Authenticator) ConnectionOption {
return func(c *Connection) error {
c.Authenticator = a
return nil
}
}
//
// WithLogger modifies the behavior of the NewConnection function
// by specifying a custom logger instead of the default one for
// the Connection to use during its operations.
//
func WithLogger(l *log.Logger) ConnectionOption {
return func(c *Connection) error {
c.Logger = l
return nil
}
}
//
// WithTimeout modifies the behavior of the NewConnection function
// by specifying the time to allow the Dial method to make the TCP
// connection to the server. After this time expires, the attempt
// is abandoned (but may be retried based on the value of
// WithRetries, if any).
//
// N.B.: When making the initial TCP connection to the server,
// if there is a timeout value specified via WithTimeout, then
// a hanging connection will terminate when that timer expires,
// regardless of the context (although a canceled context will
// stop retry attempts). Otherwise, the connection will wait
// indefinitely to complete OR until the context is cancelled.
//
func WithTimeout(t time.Duration) ConnectionOption {
return func(c *Connection) error {
c.Timeout = t
return nil
}
}
//
// WithRetries modifies the behavior of the NewConnection function
// to indicate how many times the Dial method should try to
// establish a connection to the server before giving up.
//
// Setting this to 0 means to retry infinitely many times.
// The default is to make a single attempt to connect to the
// server.
//
func WithRetries(n uint) ConnectionOption {
return func(c *Connection) error {
c.Retries = n
return nil
}
}
//
// StayConnected modifies the behavior of the NewConnection call so that
// when Dial is called on the new Connection, it will
// continue to try to re-establish connections to the server
// (if enable is true) until it utterly fails in the attempt.
// This is useful in case connections to the server tend to
// get inadvertently dropped, since this will allow the client
// to automatically reconnect and resume operations.
//
// If enable is false (the default), Dial will return as soon
// as the server connection is dropped for any reason.
//
func StayConnected(enable bool) ConnectionOption {
return func(c *Connection) error {
c.StayConnected = enable
return nil
}
}
//
// WithDebugging modifies the behavior of the NewConnection function
// so that the operations of the Connection's interaction with the
// server are logged to varying levels of verbosity.
//
func WithDebugging(flags DebugFlags) ConnectionOption {
return func(c *Connection) error {
c.DebuggingLevel = flags
return nil
}
}
//
// NewConnection creates a new server connection value which can then be used to
// manage our communication with the server.
//
// After the endpoint, you may specify any of the following options
// to define the behavior desired for this connection:
// StayConnected(bool)
// WithAuthenticator(a)
// WithDebugging(level)
// WithContext(ctx)
// WithLogger(l)
// WithRetries(n)
// WithSubscription(ch, msgs...)
// WithTimeout(t)
//
// Example:
// a := NewClientAuthenticator("fred", []byte("sekret"), "some random client")
// ctx, cancel := context.Background()
// defer cancel()
//
// messages := make(chan MessagePayload, 10)
// problems := make(chan MessagePayload, 10)
//
// server, err := NewConnection("mygame.example.org:2323",
// WithAuthenticator(a),
// WithContext(ctx),
// StayConnected(true),
// WithSubscription(messages, ChatMessage, RollResult),
// WithSubscription(problems, ERROR, UNKNOWN))
// if err != nil {
// log.Fatalf("can't reach the server: %v", err)
// }
// go server.Dial()
//
func NewConnection(endpoint string, opts ...ConnectionOption) (Connection, error) {
newCon := Connection{
Context: context.Background(),
Endpoint: endpoint,
Retries: 1,
Logger: log.Default(),
}
newCon.Reset()
newCon.serverConn.debug = newCon.debug
newCon.serverConn.debugf = newCon.debugf
for _, o := range opts {
if err := o(&newCon); err != nil {
return newCon, err
}
}
return newCon, nil
}
// Reset returns an existing Connection object to an appropriate pre-connect state.
func (c *Connection) Reset() {
if c == nil {
return
}
c.PartialReset()
c.Subscriptions = make(map[ServerMessage]chan MessagePayload)
}
// PartialReset returns an existing Connection object with the connection-related values
// reset to their pre-connect state, but leaving other things like the subscription list
// intact.
func (c *Connection) PartialReset() {
if c == nil {
return
}
c.signedOn = false
c.Characters = make(map[string]PlayerToken)
c.Conditions = make(map[string]StatusMarkerDefinition)
c.Gauges = make(map[string]*UpdateProgressMessagePayload)
c.PackageUpdatesAvailable = make(map[string][]PackageVersion)
c.serverConn.sendChan = make(chan string, 16)
c.Preamble = nil
c.ClientSettings = nil
}
//
// Log debugging info at the given level.
//
func (c *Connection) debug(level DebugFlags, msg string) {
if c != nil && (c.DebuggingLevel&level) != 0 {
for i, line := range strings.Split(msg, "\n") {
if line != "" {
c.Logf("DEBUG%s%02d: %s", DebugFlagNames(level), i, line)
}
}
}
}
func (c *Connection) debugf(level DebugFlags, format string, args ...any) {
if c != nil && (c.DebuggingLevel&level) != 0 {
args = append([]any{DebugFlagNames(level)}, args...)
c.Logf("DEBUG%s: "+format, args...)
}
}
//
// Close terminates the connection to the server.
// Note that the Dial function normally closes the connection
// before it returns, so calling this explicitly should not
// normally be necessary.
//
// Calling Close will result in the Dial function stopping
// due to the connection disappearing, but it is better to cancel
// the context being watched by Dial instead.
//
func (c *Connection) Close() {
if c != nil {
c.debug(DebugIO, "Close()")
c.serverConn.Close()
}
}
//
// Subscribe arranges for server messages to be sent to the specified channel
// when they arrive.
//
// If multiple messages are specified, they are all directed to send their payloads
// to the channel, which may use the MessageType method to differentiate what
// kind of payload was sent.
//
// This method may be called multiple times for the same channel, in which case
// the specified message(s) are added to the set which sends to that channel.
//
// If another Subscribe method is called with the same ServerMessage that a
// previous Subscribe mentioned, that will change the subscription for that
// message to go to the new channel instead of the previous one.
//
// Unless subscribed, the following default behaviors are assumed:
// Marco: Auto-reply with Polo
// ERROR: Log a message
// UNKNOWN: Log a message
// If any of these are subscribed to, then the default behavior is NOT taken,
// on the assumption that the code consuming the subscribed events will fully
// handle an appropriate response.
//
// Further, if AddCharacter or UpdateStatusMarker messages are received from
// the server, the Connection struct's Characters and Conditions maps are
// automatically updated (respectively) regardless of whether they are
// subscribed to.
//
// The default behavior for all other incoming server messages is to ignore
// them completely. The client will ask the server not to send any non-subscribed
// messages.
//
// This method may be called on an established connection to change the subscription
// list on the fly.
//
// If the channel is nil, the message(s) are unsubscribed and will not be
// received by the client until subscribed to again.
//
// Example: (error checking not shown for the sake of brevity)
// cm := make(chan MessagePayload, 1)
// service, err := NewConnection(endpoint)
// err = service.Subscribe(cm, ChatMessage)
//
func (c *Connection) Subscribe(ch chan MessagePayload, messages ...ServerMessage) error {
if c == nil {
return fmt.Errorf("nil Connection")
}
for _, m := range messages {
if m >= maximumServerMessage {
return fmt.Errorf("server message ID %v not defined (illegal Subscribe call)", m)
}
if ch == nil {
delete(c.Subscriptions, m)
} else {
c.Subscriptions[m] = ch
}
}
return c.filterSubscriptions()
}
//
// MessagePayload is an interface that includes any kind of message the server will
// send to us.
//
type MessagePayload interface {
MessageType() ServerMessage
RawMessage() string
RawBytes() []byte
}
//
// ServerMessage is an arbitrary code which identifies specific message types that
// we can receive from the server. This value is passed to the Subscribe method
// and returned by the MessageType method. These values are intended for use
// within an actively-running program but are not guaranteed to remain stable across
// new releases of the code, so they should not be stored and re-used by a later
// execution of the client, nor passed to other programs whose definition of these
// values may not agree.
//
type ServerMessage byte
// Despite the warning above, we'll do our best to avoid changing these values
// if at all possible.
//
// ServerMessage values (see the comments accompanying the type definition).
//
const (
Accept ServerMessage = iota
AddCharacter
AddDicePresets
AddImage
AddObjAttributes
AdjustView
Allow
Auth
Challenge
ChatMessage
Clear
ClearChat
ClearFrom
CombatMode
Comment
DefineDicePresets
DefineDicePresetDelegates
Denied
Echo
FilterCoreData
FilterDicePresets
FilterImages
Granted
LoadFrom
LoadArcObject
LoadCircleObject
LoadLineObject
LoadPolygonObject
LoadRectangleObject
LoadSpellAreaOfEffectObject
LoadTextObject
LoadTileObject
Marco
Mark
PlaceSomeone
Polo
Priv
Protocol
QueryCoreData
QueryCoreIndex
QueryDicePresets
QueryImage
QueryPeers
Ready
Redirect
RemoveObjAttributes
RollDice
RollResult
Sync
SyncChat
Toolbar
UpdateClock
UpdateCoreData
UpdateCoreIndex
UpdateDicePresets
UpdateInitiative
UpdateObjAttributes
UpdatePeerList
UpdateProgress
UpdateStatusMarker
UpdateTurn
UpdateVersions
World
UNKNOWN
ERROR
maximumServerMessage
)
var ServerMessageByName = map[string]ServerMessage{
"Accept": Accept,
"AddCharacter": AddCharacter,
"AddDicePresets": AddDicePresets,
"AddImage": AddImage,
"AddObjAttributes": AddObjAttributes,
"AdjustView": AdjustView,
"Allow": Allow,
"Auth": Auth,
"Challenge": Challenge,
"ChatMessage": ChatMessage,
"Clear": Clear,
"ClearChat": ClearChat,
"ClearFrom": ClearFrom,
"CombatMode": CombatMode,
"Comment": Comment,
"DefineDicePresets": DefineDicePresets,
"DefineDicePresetDelegates": DefineDicePresetDelegates,
"Denied": Denied,
"Echo": Echo,
"FilterCoreData": FilterCoreData,
"FilterDicePresets": FilterDicePresets,
"FilterImages": FilterImages,
"Granted": Granted,
"LoadFrom": LoadFrom,
"LoadArcObject": LoadArcObject,
"LoadCircleObject": LoadCircleObject,
"LoadLineObject": LoadLineObject,
"LoadPolygonObject": LoadPolygonObject,
"LoadRectangleObject": LoadRectangleObject,
"LoadSpellAreaOfEffectObject": LoadSpellAreaOfEffectObject,
"LoadTextObject": LoadTextObject,
"LoadTileObject": LoadTileObject,
"Marco": Marco,
"Mark": Mark,
"PlaceSomeone": PlaceSomeone,
"Polo": Polo,
"Priv": Priv,
"Protocol": Protocol,
"QueryCoreData": QueryCoreData,
"QueryCoreIndex": QueryCoreIndex,
"QueryDicePresets": QueryDicePresets,
"QueryImage": QueryImage,
"QueryPeers": QueryPeers,
"Ready": Ready,
"Redirect": Redirect,
"RemoveObjAttributes": RemoveObjAttributes,
"RollDice": RollDice,
"RollResult": RollResult,
"Sync": Sync,
"SyncChat": SyncChat,
"Toolbar": Toolbar,
"UpdateClock": UpdateClock,
"UpdateCoreData": UpdateCoreData,
"UpdateCoreIndex": UpdateCoreIndex,
"UpdateDicePresets": UpdateDicePresets,
"UpdateInitiative": UpdateInitiative,
"UpdateObjAttributes": UpdateObjAttributes,
"UpdatePeerList": UpdatePeerList,
"UpdateProgress": UpdateProgress,
"UpdateStatusMarker": UpdateStatusMarker,
"UpdateTurn": UpdateTurn,
"UpdateVersions": UpdateVersions,
"World": World,
}
//
// BaseMessagePayload is not a payload type that you should ever
// encounter directly, but it is included in all other payload
// types. It holds the bare minimum data for any server message.
//
type BaseMessagePayload struct {
rawMessage string `json:"-"`
messageType ServerMessage `json:"-"`
}
//
// RawMessage returns the raw message received from the server before
// it was parsed out into the MessagePayload the client should arguably
// be looking at instead.
//
// The raw message data may be useful for debugging purposes or other
// low-level poking around, though, so we make it available here.
//
func (p BaseMessagePayload) RawMessage() string { return p.rawMessage }
func (p BaseMessagePayload) RawBytes() []byte { return []byte(p.rawMessage) }
//
// MessageType returns the type of message this MessagePayload represents.
// This value will be the same as the ServerMessage value used for the
// Subscribe function, and may be used with channels which receive multiple
// kinds of messages to differentiate them, like so:
//
// select {
// case p<-messages:
// // This channel may receive a ChatMessage or RollResult.
// switch p.MessageType() {
// case ChatMessage:
// // Do whatever with p.(ChatMessageMessagePayload)
// case RollResult:
// // Do whatever with p.(RollResultMessagePayload)
// default:
// // Something bad happened!
// }
// ...
// }
//
// You can also use a type switch to accomplish the same thing and avoid
// the explicit type assertions:
// select {
// case p<-messages:
// // This channel may receive a ChatMessage or RollResult.
// switch msg := p.(type) {
// case ChatMessageMessagePayload:
// // Do whatever with msg
// case RollResultMessagePayload:
// // Do whatever with msg
// default:
// // Something bad happened!
// }
// ...
// }
//
func (p BaseMessagePayload) MessageType() ServerMessage { return p.messageType }
//
// ErrorMessagePayload describes
// an error which encountered when trying to receive a message.
//
type ErrorMessagePayload struct {
BaseMessagePayload
OriginalMessageType ServerMessage
Error error
}
//
// UnknownMessagePayload describes a server message we received
// but have no idea what it is.
//
type UnknownMessagePayload struct {
BaseMessagePayload
}
//
// ProtocolMessagePayload describes the server's statement of
// what protocol version it implements.
//
type ProtocolMessagePayload struct {
BaseMessagePayload
ProtocolVersion int
}
//
// _ _
// / \ ___ ___ ___ _ __ | |_
// / _ \ / __/ __/ _ \ '_ \| __|
// / ___ \ (_| (_| __/ |_) | |_
// /_/ \_\___\___\___| .__/ \__|
// |_|
//
//
// AcceptMessagePayload holds the information sent by a client requesting
// that the server only send a subset of its possible message types to it.
//
// Clients send this by calling the Subscribe method on their connection.
//
type AcceptMessagePayload struct {
BaseMessagePayload
// Messages is a list of message command words.
Messages []string `json:",omitempty"`
}
//________________________________________________________________________________
// _ _ _ ____ _ _
// / \ __| | __| |/ ___| |__ __ _ _ __ __ _ ___| |_ ___ _ __
// / _ \ / _` |/ _` | | | '_ \ / _` | '__/ _` |/ __| __/ _ \ '__|
// / ___ \ (_| | (_| | |___| | | | (_| | | | (_| | (__| || __/ |
// /_/ \_\__,_|\__,_|\____|_| |_|\__,_|_| \__,_|\___|\__\___|_|
//
//
// AddCharacterMessagePayload holds the information sent by the server's AddCharacter
// message to add a new PC to the party. This is not done for most creatures
// and NPCs encountered; it is for the PCs and significant NPCs who are important
// enough to be treated specially by clients (such as being included in menus).
//
type AddCharacterMessagePayload struct {
BaseMessagePayload
PlayerToken
}
//________________________________________________________________________________
// _ _ _ ___
// / \ __| | __| |_ _|_ __ ___ __ _ __ _ ___
// / _ \ / _` |/ _` || || '_ ` _ \ / _` |/ _` |/ _ \
// / ___ \ (_| | (_| || || | | | | | (_| | (_| | __/
// /_/ \_\__,_|\__,_|___|_| |_| |_|\__,_|\__, |\___|
// |___/
//
// AddImageMessagePayload holds the information sent by the server's AddImage
// message informing the client as to where it can locate an image's data.
//
// Call the AddImage method to send this message out to others if you know
// of an image file they should be aware of.
//
type AddImageMessagePayload struct {
BaseMessagePayload
ImageDefinition
}
//
// AddImage informs the server and peers about an image they can use.
//
func (c *Connection) AddImage(idef ImageDefinition) error {
return c.serverConn.Send(AddImage, idef)
}
// _ _ _ ___ _ _ _ _ _ _ _ _
// / \ __| | __| |/ _ \| |__ (_) / \ | |_| |_ _ __(_) |__ _ _| |_ ___ ___
// / _ \ / _` |/ _` | | | | '_ \| | / _ \| __| __| '__| | '_ \| | | | __/ _ \/ __|
// / ___ \ (_| | (_| | |_| | |_) | |/ ___ \ |_| |_| | | | |_) | |_| | || __/\__ \
// /_/ \_\__,_|\__,_|\___/|_.__// /_/ \_\__|\__|_| |_|_.__/ \__,_|\__\___||___/
// |__/
//
// AddObjAttributesMessagePayload holds the information sent by the server's AddObjAttributes
// message. This tells the client to adjust the multi-value attribute
// of the object with the given ID by adding the new values to it.
//
// Call the AddObjAttributes method to send this message out to other clients.
//
type AddObjAttributesMessagePayload struct {
BaseMessagePayload
ObjID string
AttrName string
Values []string
}
//
// AddObjAttributes informs peers to add a set of string values to the existing
// value of an object attribute. The attribute must be one whose value is a list
// of strings, such as StatusList.
//
func (c *Connection) AddObjAttributes(objID, attrName string, values []string) error {
if c == nil {
return fmt.Errorf("nil Connection")
}
return c.serverConn.Send(AddObjAttributes, AddObjAttributesMessagePayload{
ObjID: objID,
AttrName: attrName,
Values: values,
})
}
// _ _ _ _ __ ___
// / \ __| |(_)_ _ ___| |\ \ / (_) _____ __
// / _ \ / _` || | | | / __| __\ \ / /| |/ _ \ \ /\ / /
// / ___ \ (_| || | |_| \__ \ |_ \ V / | | __/\ V V /
// /_/ \_\__,_|/ |\__,_|___/\__| \_/ |_|\___| \_/\_/
// |__/
//
// AdjustViewMessagePayload holds the information sent by the server's AdjustView
// message. This tells the client to set its viewable area so that its x and y
// scrollbars are at the given proportion of their full range.
//
// Call the AdjustView method to send this message out to other clients.