Browse Source

roleplay: Initial commit

tags/v0.4.0
Daniel Oaks 7 years ago
parent
commit
07e4728c15
8 changed files with 166 additions and 29 deletions
  1. 1
    0
      CHANGELOG.md
  2. 2
    0
      irc/client.go
  3. 16
    0
      irc/commands.go
  4. 20
    0
      irc/help.go
  5. 24
    22
      irc/modes.go
  6. 5
    5
      irc/nickname.go
  7. 94
    0
      irc/roleplay.go
  8. 4
    2
      irc/server.go

+ 1
- 0
CHANGELOG.md View File

@@ -15,6 +15,7 @@ New release of Oragono!
15 15
 * Length of channel mode lists (ban / ban-except / invite-except) is now restricted to the limit in config.
16 16
 * Support `MAXLIST`, `MAXTARGETS`, `MODES`, `TARGMAX` in `RPL_ISUPPORT`.
17 17
 * Added support for IRCv3 capability [`chghost`](http://ircv3.net/specs/extensions/chghost-3.2.html).
18
+* Roleplaying commands, both inside channels and between clients.
18 19
 
19 20
 ### Changed
20 21
 * In the config file, "operator" changed to "opers", and new oper class is required.

+ 2
- 0
irc/client.go View File

@@ -461,6 +461,8 @@ func (client *Client) Send(tags *map[string]ircmsg.TagValue, prefix string, comm
461 461
 	line, err := message.Line()
462 462
 	if err != nil {
463 463
 		// try not to fail quietly - especially useful when running tests, as a note to dig deeper
464
+		// log.Println("Error assembling message:")
465
+		// spew.Dump(message)
464 466
 		message = ircmsg.MakeMessage(nil, client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending")
465 467
 		line, _ := message.Line()
466 468
 		client.socket.Write(line)

+ 16
- 0
irc/commands.go View File

@@ -54,6 +54,10 @@ func (cmd *Command) Run(server *Server, client *Client, msg ircmsg.IrcMessage) b
54 54
 
55 55
 // Commands holds all commands executable by a client connected to us.
56 56
 var Commands = map[string]Command{
57
+	"AMBIANCE": {
58
+		handler:   sceneHandler,
59
+		minParams: 2,
60
+	},
57 61
 	"AUTHENTICATE": {
58 62
 		handler:      authenticateHandler,
59 63
 		usablePreReg: true,
@@ -127,6 +131,14 @@ var Commands = map[string]Command{
127 131
 		handler:   noticeHandler,
128 132
 		minParams: 2,
129 133
 	},
134
+	"NPC": {
135
+		handler:   npcHandler,
136
+		minParams: 3,
137
+	},
138
+	"NPCA": {
139
+		handler:   npcaHandler,
140
+		minParams: 3,
141
+	},
130 142
 	"OPER": {
131 143
 		handler:   operHandler,
132 144
 		minParams: 2,
@@ -161,6 +173,10 @@ var Commands = map[string]Command{
161 173
 		minParams: 2,
162 174
 		oper:      true,
163 175
 	},
176
+	"SCENE": {
177
+		handler:   sceneHandler,
178
+		minParams: 2,
179
+	},
164 180
 	"QUIT": {
165 181
 		handler:      quitHandler,
166 182
 		usablePreReg: true,

+ 20
- 0
irc/help.go View File

@@ -60,6 +60,11 @@ Oragono supports the following user modes:
60 60
 // Help contains the help strings distributed with the IRCd.
61 61
 var Help = map[string]HelpEntry{
62 62
 	// Commands
63
+	"ambiance": {
64
+		text: `AMBIANCE <target> <text to be sent>
65
+
66
+The AMBIANCE command is used to send a scene notification to the given target.`,
67
+	},
63 68
 	"authenticate": {
64 69
 		text: `AUTHENTICATE
65 70
 
@@ -182,6 +187,16 @@ Sets your nickname to the new given one.`,
182 187
 		text: `NOTICE <target>{,<target>} <text to be sent>
183 188
 
184 189
 Sends the text to the given targets as a NOTICE.`,
190
+	},
191
+	"npc": {
192
+		text: `NPC <target> <sourcenick> <text to be sent>
193
+		
194
+The NPC command is used to send a message to the target as the source.`,
195
+	},
196
+	"npca": {
197
+		text: `NPCA <target> <sourcenick> <text to be sent>
198
+		
199
+The NPC command is used to send an action to the target as the source.`,
185 200
 	},
186 201
 	"oper": {
187 202
 		text: `OPER <name> <password>
@@ -219,6 +234,11 @@ Sends the text to the given targets as a PRIVMSG.`,
219 234
 		text: `SANICK <currentnick> <newnick>
220 235
 
221 236
 Gives the given user a new nickname.`,
237
+	},
238
+	"scene": {
239
+		text: `SCENE <target> <text to be sent>
240
+
241
+The SCENE command is used to send a scene notification to the given target.`,
222 242
 	},
223 243
 	"quit": {
224 244
 		text: `QUIT [reason]

+ 24
- 22
irc/modes.go View File

@@ -137,35 +137,37 @@ const (
137 137
 )
138 138
 
139 139
 const (
140
-	Away          UserMode = 'a'
141
-	Invisible     UserMode = 'i'
142
-	LocalOperator UserMode = 'O'
143
-	Operator      UserMode = 'o'
144
-	Restricted    UserMode = 'r'
145
-	ServerNotice  UserMode = 's' // deprecated
146
-	TLS           UserMode = 'Z'
147
-	WallOps       UserMode = 'w'
140
+	Away            UserMode = 'a'
141
+	Invisible       UserMode = 'i'
142
+	LocalOperator   UserMode = 'O'
143
+	Operator        UserMode = 'o'
144
+	Restricted      UserMode = 'r'
145
+	ServerNotice    UserMode = 's' // deprecated
146
+	TLS             UserMode = 'Z'
147
+	UserRoleplaying UserMode = 'E'
148
+	WallOps         UserMode = 'w'
148 149
 )
149 150
 
150 151
 var (
151 152
 	SupportedUserModes = UserModes{
152
-		Away, Invisible, Operator,
153
+		Away, Invisible, Operator, UserRoleplaying,
153 154
 	}
154 155
 	// supportedUserModesString acts as a cache for when we introduce users
155 156
 	supportedUserModesString = SupportedUserModes.String()
156 157
 )
157 158
 
158 159
 const (
159
-	BanMask     ChannelMode = 'b' // arg
160
-	ExceptMask  ChannelMode = 'e' // arg
161
-	InviteMask  ChannelMode = 'I' // arg
162
-	InviteOnly  ChannelMode = 'i' // flag
163
-	Key         ChannelMode = 'k' // flag arg
164
-	Moderated   ChannelMode = 'm' // flag
165
-	NoOutside   ChannelMode = 'n' // flag
166
-	OpOnlyTopic ChannelMode = 't' // flag
167
-	Secret      ChannelMode = 's' // flag
168
-	UserLimit   ChannelMode = 'l' // flag arg
160
+	BanMask         ChannelMode = 'b' // arg
161
+	ChanRoleplaying ChannelMode = 'E' // flag
162
+	ExceptMask      ChannelMode = 'e' // arg
163
+	InviteMask      ChannelMode = 'I' // arg
164
+	InviteOnly      ChannelMode = 'i' // flag
165
+	Key             ChannelMode = 'k' // flag arg
166
+	Moderated       ChannelMode = 'm' // flag
167
+	NoOutside       ChannelMode = 'n' // flag
168
+	OpOnlyTopic     ChannelMode = 't' // flag
169
+	Secret          ChannelMode = 's' // flag
170
+	UserLimit       ChannelMode = 'l' // flag arg
169 171
 )
170 172
 
171 173
 var (
@@ -177,7 +179,7 @@ var (
177 179
 
178 180
 	SupportedChannelModes = ChannelModes{
179 181
 		BanMask, ExceptMask, InviteMask, InviteOnly, Key, NoOutside,
180
-		OpOnlyTopic, Secret, UserLimit,
182
+		OpOnlyTopic, Secret, UserLimit, ChanRoleplaying,
181 183
 	}
182 184
 	// supportedChannelModesString acts as a cache for when we introduce users
183 185
 	supportedChannelModesString = SupportedChannelModes.String()
@@ -297,7 +299,7 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
297 299
 
298 300
 		for _, change := range changes {
299 301
 			switch change.mode {
300
-			case Invisible, ServerNotice, WallOps:
302
+			case Invisible, ServerNotice, WallOps, UserRoleplaying:
301 303
 				switch change.op {
302 304
 				case Add:
303 305
 					if target.flags[change.mode] {
@@ -471,7 +473,7 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
471 473
 				}
472 474
 				applied = append(applied, change)
473 475
 
474
-			case InviteOnly, Moderated, NoOutside, OpOnlyTopic, Secret:
476
+			case InviteOnly, Moderated, NoOutside, OpOnlyTopic, Secret, ChanRoleplaying:
475 477
 				switch change.op {
476 478
 				case Add:
477 479
 					if channel.flags[change.mode] {

+ 5
- 5
irc/nickname.go View File

@@ -25,7 +25,7 @@ func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
25 25
 		return false
26 26
 	}
27 27
 
28
-	if err != nil || len(nicknameRaw) > server.limits.NickLen {
28
+	if err != nil || len(nicknameRaw) > server.limits.NickLen || nickname == "=scene=" {
29 29
 		client.Send(nil, server.name, ERR_ERRONEUSNICKNAME, client.nick, nicknameRaw, "Erroneous nickname")
30 30
 		return false
31 31
 	}
@@ -59,14 +59,14 @@ func sanickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
59 59
 	}
60 60
 
61 61
 	oldnick, oerr := CasefoldName(msg.Params[0])
62
-	casefoldedNickname, err := CasefoldName(msg.Params[1])
62
+	nickname, err := CasefoldName(msg.Params[1])
63 63
 
64
-	if len(casefoldedNickname) < 1 {
64
+	if len(nickname) < 1 {
65 65
 		client.Send(nil, server.name, ERR_NONICKNAMEGIVEN, client.nick, "No nickname given")
66 66
 		return false
67 67
 	}
68 68
 
69
-	if oerr != nil || err != nil || len(strings.TrimSpace(msg.Params[1])) > server.limits.NickLen {
69
+	if oerr != nil || err != nil || len(strings.TrimSpace(msg.Params[1])) > server.limits.NickLen || nickname == "=scene=" {
70 70
 		client.Send(nil, server.name, ERR_ERRONEUSNICKNAME, client.nick, msg.Params[0], "Erroneous nickname")
71 71
 		return false
72 72
 	}
@@ -82,7 +82,7 @@ func sanickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
82 82
 	}
83 83
 
84 84
 	//TODO(dan): There's probably some races here, we should be changing this in the primary server thread
85
-	if server.clients.Get(casefoldedNickname) != nil || server.clients.Get(casefoldedNickname) != target {
85
+	if server.clients.Get(nickname) != nil || server.clients.Get(nickname) != target {
86 86
 		client.Send(nil, server.name, ERR_NICKNAMEINUSE, client.nick, msg.Params[0], "Nickname is already in use")
87 87
 		return false
88 88
 	}

+ 94
- 0
irc/roleplay.go View File

@@ -0,0 +1,94 @@
1
+// Copyright (c) 2016- Daniel Oaks <daniel@danieloaks.net>
2
+// released under the MIT license
3
+
4
+package irc
5
+
6
+import (
7
+	"fmt"
8
+
9
+	"github.com/DanielOaks/girc-go/ircmsg"
10
+)
11
+
12
+const (
13
+	npcNickMask   = "%s!%s@npc.fakeuser.invalid"
14
+	sceneNickMask = "=Scene=!%s@npc.fakeuser.invalid"
15
+)
16
+
17
+// SCENE <target> <text to be sent>
18
+func sceneHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
19
+	target := msg.Params[0]
20
+	message := msg.Params[1]
21
+	sourceString := fmt.Sprintf(sceneNickMask, client.nick)
22
+
23
+	sendRoleplayMessage(server, client, sourceString, target, false, message)
24
+
25
+	return false
26
+}
27
+
28
+// NPC <target> <text to be sent>
29
+func npcHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
30
+	target := msg.Params[0]
31
+	fakeSource := msg.Params[1]
32
+	message := msg.Params[2]
33
+	sourceString := fmt.Sprintf(npcNickMask, fakeSource, client.nick)
34
+
35
+	sendRoleplayMessage(server, client, sourceString, target, false, message)
36
+
37
+	return false
38
+}
39
+
40
+// NPCA <target> <text to be sent>
41
+func npcaHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
42
+	target := msg.Params[0]
43
+	fakeSource := msg.Params[1]
44
+	message := msg.Params[2]
45
+	sourceString := fmt.Sprintf(npcNickMask, fakeSource, client.nick)
46
+
47
+	sendRoleplayMessage(server, client, sourceString, target, true, message)
48
+
49
+	return false
50
+}
51
+
52
+func sendRoleplayMessage(server *Server, client *Client, source string, targetString string, isAction bool, message string) {
53
+	if isAction {
54
+		message = fmt.Sprintf("\x01ACTION %s (%s)\x01", message, client.nick)
55
+	} else {
56
+		message = fmt.Sprintf("%s (%s)", message, client.nick)
57
+	}
58
+
59
+	target, cerr := CasefoldChannel(targetString)
60
+	if cerr == nil {
61
+		channel := server.channels.Get(target)
62
+		if channel == nil {
63
+			client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, targetString, "No such channel")
64
+			return
65
+		}
66
+
67
+		if !channel.CanSpeak(client) {
68
+			client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, "Cannot send to channel")
69
+			return
70
+		}
71
+
72
+		for member := range channel.members {
73
+			if member == client && !client.capabilities[EchoMessage] {
74
+				continue
75
+			}
76
+			member.Send(nil, source, "PRIVMSG", channel.name, message)
77
+		}
78
+	} else {
79
+		target, err := CasefoldName(targetString)
80
+		user := server.clients.Get(target)
81
+		if err != nil || user == nil {
82
+			client.Send(nil, server.name, ERR_NOSUCHNICK, target, "No such nick")
83
+			return
84
+		}
85
+		user.Send(nil, source, "PRIVMSG", user.nick, message)
86
+		if client.capabilities[EchoMessage] {
87
+			client.Send(nil, source, "PRIVMSG", user.nick, message)
88
+		}
89
+		if user.flags[Away] {
90
+			//TODO(dan): possibly implement cooldown of away notifications to users
91
+			client.Send(nil, server.name, RPL_AWAY, user.nick, user.awayMessage)
92
+		}
93
+	}
94
+}

+ 4
- 2
irc/server.go View File

@@ -264,7 +264,7 @@ func (server *Server) setISupport() {
264 264
 	server.isupport = NewISupportList()
265 265
 	server.isupport.Add("AWAYLEN", strconv.Itoa(server.limits.AwayLen))
266 266
 	server.isupport.Add("CASEMAPPING", "rfc7700")
267
-	server.isupport.Add("CHANMODES", strings.Join([]string{ChannelModes{BanMask, ExceptMask, InviteMask}.String(), "", ChannelModes{UserLimit, Key}.String(), ChannelModes{InviteOnly, Moderated, NoOutside, OpOnlyTopic, Secret}.String()}, ","))
267
+	server.isupport.Add("CHANMODES", strings.Join([]string{ChannelModes{BanMask, ExceptMask, InviteMask}.String(), "", ChannelModes{UserLimit, Key}.String(), ChannelModes{InviteOnly, Moderated, NoOutside, OpOnlyTopic, ChanRoleplaying, Secret}.String()}, ","))
268 268
 	server.isupport.Add("CHANNELLEN", strconv.Itoa(server.limits.ChannelLen))
269 269
 	server.isupport.Add("CHANTYPES", "#")
270 270
 	server.isupport.Add("EXCEPTS", "")
@@ -277,6 +277,8 @@ func (server *Server) setISupport() {
277 277
 	server.isupport.Add("NETWORK", server.networkName)
278 278
 	server.isupport.Add("NICKLEN", strconv.Itoa(server.limits.NickLen))
279 279
 	server.isupport.Add("PREFIX", "(qaohv)~&@%+")
280
+	server.isupport.Add("RPCHAN", "E")
281
+	server.isupport.Add("RPUSER", "E")
280 282
 	server.isupport.Add("STATUSMSG", "~&@%+")
281 283
 	server.isupport.Add("TARGMAX", fmt.Sprintf("NAMES:1,LIST:1,KICK:1,WHOIS:1,PRIVMSG:%s,NOTICE:%s,MONITOR:", maxTargetsString, maxTargetsString))
282 284
 	server.isupport.Add("TOPICLEN", strconv.Itoa(server.limits.TopicLen))
@@ -1436,7 +1438,7 @@ func versionHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool
1436 1438
 		target = msg.Params[0]
1437 1439
 	}
1438 1440
 	casefoldedTarget, err := Casefold(target)
1439
-	if (target != "") && err != nil || (casefoldedTarget != server.nameCasefolded) {
1441
+	if target != "" && (err != nil || casefoldedTarget != server.nameCasefolded) {
1440 1442
 		client.Send(nil, server.name, ERR_NOSUCHSERVER, client.nick, target, "No such server")
1441 1443
 		return false
1442 1444
 	}

Loading…
Cancel
Save