ID:2424564
 
(See the best response by Multiverse7.)
Code:
proc/output(msg,control)
if(msg=="Change this message")
msg="Case 01"
else if(msg=="Change this message\n")
msg="Case 02"
else
msg="Case: [msg]")
..()
mob/proc/output(msg,control)
if(msg=="Change this message")
msg="Case 11"
else if(msg=="Change this message\n")
msg="Case 12"
else
msg="Case 10: [msg]")
..()


Problem description:

Neither of these work. I want to create the ability for hosts to override server messages with their own custom ones. But this isn't working the way it should. In fact it isn't working at all. Is changing every single output into a custom output procedure the only way to do it? As someone who makes text-games, this is as time consuming as it is un-necessary.
world/proc/operator<<(B,target)
if(B=="Change this message")
B="Case 11"
else if(B=="Change this message\n")
B="Case 12"
else
B="Case 10: [B]")
..()


this attempt at using operator overloading didn't work, the BYOND guide suggests the correct override for this is:

A << B (output) A.operator<<(B,A)
world.operator<<(B,target) Ignores return value
..() falls back on world proc, then default behavior
This was my testcase that i wrote:

mob/verb/test_override()
world << "Change this message"

world/proc/operator<<(out,target)
if(out!="FIXED")
if(out=="Change this message")
out="Case 11"
..(out,target)
else if(out=="Change this message\n")
out="Case 12"
..(out,target)
else
out="Case 10: [out]")
..(out,target)
world << "FIXED"


The FIXED message never appears showing that this operator function was never checked.

This is BYOND's tutorial for this override:
// Send an effect to a player or list of players
proc/DoEffect(target, effect/E)
if(istype(target, /list))
for(var/i in target) DoEffect(i, E)
return
if(target == world || target == world.contents)
for(var/client/C) DoEffect(C, E)
if(istype(target, /client))
DoEffect(target:mob)
if(istype(target, /mob))
if(target:client)
... // do something here to show the effect

world/proc/operator<<(out, target)
if(istype(target,/savefile)) return ..() // always save normally
if(istype(out, /effect)) DoEffect(target, out)
else ..()


if anybody finds a way to make this override work please let me know
In response to Mista-mage123
Mista-mage123 wrote:
This was my testcase that i wrote:

> mob/verb/test_override()
> world << "Change this message"
>
> world/proc/operator<<(out,target)
> if(out!="FIXED")
> if(out=="Change this message")
> out="Case 11"
> ..(out,target)
> else if(out=="Change this message\n")
> out="Case 12"
> ..(out,target)
> else
> out="Case 10: [out]")
> ..(out,target)
> world << "FIXED"
>


In that code, write ..("FIXED", world) instead of world << "FIXED".

I'm not sure what to expect if you use the operator inside of its own definition.
In response to Multiverse7
Multiverse7 wrote:
In that code, write ..("FIXED", world) instead of world << "FIXED".

I'm not sure what to expect if you use the operator inside of its own definition.

Thanks for the reply! I'll add context.

My game is littered with stuff like
world << "Hello"


and
world << output("Hello",output-window)


that is what i'm trying to override. The BYOND helpbox suggests that can be overriden, so if it cannot that's a glitch that I should post in bug reports.

The suggestion is here:


this suggests that the output() proc and associated << out procs can be overriden with this feature, and i'm trying to figure out how to do exactly that since it appears to be undocumented?
Silly me, my testcode proc wasn't checked off and included in the build. I was wondering why nothing was working.

Thanks! I can check this off as solved.

edit: Nevermind, I can't. It only effects world << text, it doesn't change mob << text or mob << output(text)

mob/verb/test_case()
world << "Change this message"
usr << "Change this message"
usr << output("Change this message")


only shows one final output instead of 3.


mob/verb/test_case()
world << "Change this message"
usr << "Change this message"
usr << output("Change this message")

mob/proc/operator<<(out,target)
if(out!="FIXED")
if(out=="Change this message")
out="Case 11"
..(out,target)
else if(out=="Change this message\n")
out="Case 12"
..(out,target)
else
out="Case 10: [out]"
..(out,target)
world << "FIXED"
if we're testcasing this we should probably try to test
world << output("Change this message")


as well. all four cases should nip this in the bud i think, but so far i think we're going to need an operator for world/operator<< as well as for mob/operator<< included in the solution

P.S. the ..() has special functionality for operator overrides, it cannot call itself. it will call the intended behavior, which is why it's included in my current code.
Almost at a solution, just trying to interpret this data

Output is:
Case 14: (Change this message: world <<)
Case 14: (Case 11: (Change this message: usr <<))
Case 14: (Case 11: (Change this message: usr << output()))
Case 14: (Change this message: world << output())


from:
mob/verb/test_case()
world << "Change this message: world <<"
usr << "Change this message: usr <<"
usr << output("Change this message: usr << output()")
world << output("Change this message: world << output()")

mob/proc/operator<<(out,target)
if(out!="FIXED")
if(findtextEx(out,"Change this message"))
out="Case 11: ([out])"
..(out,target)
else if(out=="Change this message\n")
out="Case 12: ([out])"
..(out,target)
else
out="Case 10: ([out])"
..(out,target)
world << "FIXED"
world/proc/operator<<(out,target)
if(out!="FIXED")
if(findtextEx(out,"Change this message"))
out="Case 14: ([out])"
..(out,target)
else if(out=="Change this message\n")
out="Case 15: ([out])"
..(out,target)
else
out="Case 13: ([out])"
..(out,target)
world << "FIXED"


as i feared, we need to both overide the world/operator<< and mob/operator<< to catch all 4 cases. just trying to interpret what this means now. for some reason, usr << and usr << output() is triggering two cases, and i need to reduce that to one case.
Solved:

For whatever reason, changing it back to world/operator<< override works now whereas it wasn't before. Odd.

Solution code:
mob/verb/test_case()
world << "Change this message: world <<"
usr << "Change this message: usr <<"
usr << output("Change this message: usr << output()")
world << output("Change this message: world << output()")

world/proc/operator<<(out,target)
if(out!="FIXED")
if(findtextEx(out,"Change this message"))
out="Case 14: ([out])"
..(out,target)
else if(out=="Change this message\n")
out="Case 15: ([out])"
..(out,target)
else
out="Case 13: ([out])"
..(out,target)
world << "FIXED"


thanks.

also, this test also confirms that a mob's output() is fed into the world's output() so that's something to keep mindful of in the future. if i could find a way to block that behavior it would be nice, but at least we're good for now.
Best response
This is my test case, to keep things simple:
world/proc/operator<<(out, target)
..("oldmessage = \"[out]\"", target)

mob/verb/test_case()
world << "Change this message" // outputs: oldmessage = "Change this message"
usr << "Change this message" // outputs: oldmessage = "Change this message"
usr << output("Change this message") // outputs: oldmessage = "Change this message"
world << output("Change this message") // outputs: oldmessage = "Change this message"
world << output("Change this message", "outputwindow.output") // outputs: Change this message


As you can see, specifying a control to output to doesn't work. It doesn't seem to be getting passed through the overload. I tried some hacks involving #defines, but I can't think of a way to make it work.
In response to Multiverse7
Multiverse7 wrote:
This is my test case, to keep things simple:
> world/proc/operator<<(out, target)
> ..("oldmessage = \"[out]\"", target)
>
> mob/verb/test_case()
> world << "Change this message" // outputs: oldmessage = "Change this message"
> usr << "Change this message" // outputs: oldmessage = "Change this message"
> usr << output("Change this message") // outputs: oldmessage = "Change this message"
> world << output("Change this message") // outputs: oldmessage = "Change this message"
> world << output("Change this message", "outputwindow.output") // outputs: Change this message
>

As you can see, specifying a control to output to doesn't work. It doesn't seem to be getting passed through the overload. I tried some hacks involving #defines, but I can't think of a way to make it work.

oh, you caught a case i didn't. back to unresolved it is. crap
thanks for that, i would've felt dumb if i thought this was solved and found that use case didn't work
I finally found a way around the problem by using a variadic macro and the .output command: Update: This one is better.
#define output(args...) _output(args)
// Redirect output() calls to a custom version.


proc/_output(msg, control)
. = list(msg = msg, control = control)
// Return an associative list containing the arguments that were passed.


world/proc/operator<<(out, target)
if(istype(out, /list))
// If output() was used, out will be a list.
if(out["control"])
// If a control was specified.
if(istype(target, /list) || (target == world))
// If the target represents a list of mobs and/or clients.
var/list/clients = list()
// Populate a list of user clients.
for(var/mob/t in target)
if(t.client)
clients += t.client

for(var/client/c in target)
if(!(c in clients))
clients += c

for(var/client/u in clients)
winset(u, null, list("command" = ".output [out["control"]] oldmessage = \\\"[out["msg"]]\\\""))
// Use the .output command to send the modified message to each client.
else
// If the target is meant to be a single user.
winset(target, null, list("command" = ".output [out["control"]] oldmessage = \\\"[out["msg"]]\\\""))
// Use the .output command to send them the modified message.
else
// If the control was not specified, send the modified message in the default way.
..("oldmessage = \"[out["msg"]]\"", target)
else
// This is the normal case where out is expected to be text.
..("oldmessage = \"[out]\"", target)


mob/verb/test_case()
world << "Change this message" // outputs: oldmessage = "Change this message"
usr << "Change this message" // outputs: oldmessage = "Change this message"
usr << output("Change this message") // outputs: oldmessage = "Change this message"
world << output("Change this message") // outputs: oldmessage = "Change this message"
world << output("Change this message", "outputwindow.output") // outputs: oldmessage = "Change this message"
usr << output("Change this message", "outputwindow.output") // outputs: oldmessage = "Change this message"
(raw pastebin link to copy from)

Be sure you have the right number of backslashes to escape certain characters like quotes, when running commands from the winset() proc. I'm not even sure if I put enough backslashes in there. It's really hard to tell sometimes.

Also, since output() is now a #define macro, if you place the control arg on a new line, you will have to escape the previous line with a backslash, so you would place a backslash right after the comma.

For example:
output({"my
really
long
message"}
,\
"mycontrol")


That backslash hides the new line from the preprocessor, so the macro thinks it's all on one line. Without it, the code will not compile. It would be nice if cases like this were recognized so we wouldn't have to do this.


Other procs that make use of the << output operator, such as browse(), can probably be fixed by using a similar technique. That may require abusing some ancient undocumented features found in The Red Book.
In response to Multiverse7
Multiverse7 wrote:
I finally found a way around the problem by using a variadic macro and the .output command:
> #define output(args...) _output(args)
> // Redirect output() calls to a custom version.
>
>
> proc/_output(msg, control)
> . = list(msg = msg, control = control)
> // Return an associative list containing the arguments that were passed.
>
>
> world/proc/operator<<(out, target)
> if(istype(out, /list))
> // If output() was used, out will be a list.
> if(out["control"])
> // If a control was specified.
> if(istype(target, /list) || (target == world))
> // If the target represents a list of mobs and/or clients.
> var/list/clients = list()
> // Populate a list of user clients.
> for(var/mob/t in target)
> if(t.client)
> clients += t.client
>
> for(var/client/c in target)
> if(!(c in clients))
> clients += c
>
> for(var/client/u in clients)
> winset(u, null, list("command" = ".output [out["control"]] oldmessage = \\\"[out["msg"]]\\\""))
> // Use the .output command to send the modified message to each client.
> else
> // If the target is meant to be a single user.
> winset(target, null, list("command" = ".output [out["control"]] oldmessage = \\\"[out["msg"]]\\\""))
> // Use the .output command to send them the modified message.
> else
> // If the control was not specified, send the modified message in the default way.
> ..("oldmessage = \"[out["msg"]]\"", target)
> else
> // This is the normal case where out is expected to be text.
> ..("oldmessage = \"[out]\"", target)
>
>
> mob/verb/test_case()
> world << "Change this message" // outputs: oldmessage = "Change this message"
> usr << "Change this message" // outputs: oldmessage = "Change this message"
> usr << output("Change this message") // outputs: oldmessage = "Change this message"
> world << output("Change this message") // outputs: oldmessage = "Change this message"
> world << output("Change this message", "outputwindow.output") // outputs: oldmessage = "Change this message"
> usr << output("Change this message", "outputwindow.output") // outputs: oldmessage = "Change this message"
>
(raw pastebin link to copy from)
Be sure you have the right number of backslashes to escape certain characters like quotes, when running commands from the winset() proc. I'm not even sure if I put enough backslashes in there. It's really hard to tell sometimes.

Also, since output() is now a #define macro, if you place the control arg on a new line, you will have to escape the previous line with a backslash, so you would place a backslash right after the comma.

For example:
> output({"my
> really
> long
> message"}
,\
> "mycontrol")
>

That backslash hides the new line from the preprocessor, so the macro thinks it's all on one line. Without it, the code will not compile. It would be nice if cases like this were recognized so we wouldn't have to do this.


Other procs that make use of the << output operator, such as browse(), can probably be fixed by using a similar technique. That may require abusing some ancient undocumented features found in The Red Book.

Thanks a lot for your help Multiverse, i wouldn't have been able to do this without you. I'll put Workaround in the post title for now, i sent Lummox a url to this thread and hopefully he takes a look at the issue.

+1! Thanks!

Although, if we're doing a redirected _output(), doesn't that make the override itself redundant? couldn't we just do the override stuff within the _output() function itself?
In response to Mista-mage123
Mista-mage123 wrote:
Thanks a lot for your help Multiverse, i wouldn't have been able to do this without you. I'll put Workaround in the post title for now, i sent Lummox a url to this thread and hopefully he takes a look at the issue.

+1! Thanks!

You're welcome. Thanks for bringing attention to this issue. I'm learning a lot from this.

Although, if we're doing a redirected _output(), doesn't that make the override itself redundant? couldn't we just do the override stuff within the _output() function itself?

No, you couldn't. The _output() in that code knows nothing about where to send the output. Only the world.operator<<() has access to that information, and _output() isn't even called when just outputting a raw string.

However, that is no longer relevant, because I wrote a new version that's better in every way:
Edit: Cleared up several redundancies and bugs:
#define output(msg, control...) (out.update(msg, control))
/*
!!!!!!!!!!!!!
!!IMPORTANT!!
!!!!!!!!!!!!!
Due to this #define macro, "output" should be considered a reserved word.
This word should not be used for anything other than referencing this macro.
Doing so WILL result in errors!
*/



_out
// This is the ancestor of the global output handler's type, where the defaults are defined.
// None of this should be modified.
var/tmp
msg
target
control
script

proc/update(msg, control)
// The output() macro resolves into this, which gets passed directly into the world's output operator overload proc: world.operator<<().
src.msg = msg
src.control = control
. = src

proc/Output(msg, target, control, script)
// This is inherited by the global output handler as the default definition for out.Output().
src.msg = msg
src.target = target
src.control = control
src.script = script
. = src


out
// The global output handler's type.
parent_type = /_out
// This sets the ancestor that allows this to work.


var/tmp/out/out
// This is the global output handler. It must be initialized right away.
world/New()
out = new


#undef output
// This definition requires access to the original output(), so the macro must be undefined then redefined at the end of the definition.
world/proc/operator<<(out/out, target)
// This is called whenever << is used.
if(istype(target, /savefile))
// Savefiles are an exception that must be handled separately.
return ..()

var
canout
scriptpos
if(out == global.out)
// If output() was used, out will be the global output handler.
if(out.control)
// If a control was specified.
scriptpos = findtext(out.control, ":")
// Search for a script delimiter.
if(scriptpos)
// If a script delimiter was found.
canout = out.Output(out.msg, target, copytext(out.control, 1, scriptpos), copytext(out.control, scriptpos + 1))
// Pass all of the arguments back to the global output handler, with the control and script separated.
else
// If no script was found.
canout = out.Output(out.msg, target, out.control)
// Pass all of the arguments back to the global output handler.
else
// This is the normal case where out is expected to be text.
// The global keyword must be used here, since the "out" argument is not the global output handler.
canout = global.out.Output(out, target)
out = global.out

if(canout)
// If the output has been allowed.
if(out.control)
// If a control was specified.
if(out.script)
// If a script was specified.
out.target << output(out.msg, "[out.control]:[out.script]")
// Send the returned message to the specified [browser] control, while executing the specified script.
else
// If a script was not specified.
out.target << output(out.msg, out.control)
// Send the returned message to the specified control.
else
// If a control was not specified.
..(out.msg, out.target)
// Send the returned message in the default way.
#define output(msg, control...) (out.update(msg, control))
// This is the end of the definition, so the macro can be redefined.


out/Output(msg, target, control, script)
/*
This is where all text output using << can be overridden.
Simply write . = ..() or . = (msg, target, control, script) if you do not wish to override.
THE FULL . = ..() OR return ..() SYNTAX IS REQUIRED.
All text output passes through here, so this cannot be left blank, unless you want to disable all output.
*/

// Example:
if(control && isnull(msg))
// This allows the case of clearing a control by setting it to null.
. = ..()
else
. = ..("oldmessage = \"[msg]\"", target, control, script)


mob/verb/test_case()
world.log << "Change this message" // outputs: oldmessage = "Change this message" to world.log
world << "Change this message" // outputs: oldmessage = "Change this message"
usr << "Change this message" // outputs: oldmessage = "Change this message"
usr << output("Change this message") // outputs: oldmessage = "Change this message"
world << output("Change this message") // outputs: oldmessage = "Change this message"
world << output("Change this message", "outputwindow.output") // outputs: oldmessage = "Change this message"
usr << output("Change this message", "outputwindow.output") // outputs: oldmessage = "Change this message"
sleep(50) // waits for 5 seconds
usr << output(null, "outputwindow.output") // clears the control

(raw pastebin link to copy from)

I cleaned things up and went for a more object-oriented approach. I also got rid of the need for winset() by using #undef to create a kind of void region in the code where the output macro isn't defined.

Using << inside of its own definition doesn't seem to be causing an infinite loop in this case but it still has me worried. That sort of thing is hard to prevent because output can't always be expected to behave in synchronous and predictable ways, so race conditions might occur. I'm not entirely sure why this is working!

This new code is able to support the case of clearing a control by setting it to null, which wasn't possible when using the .output command. The main improvement is that it's now far easier to override the output.

Be sure to name this file with a bunch of ########## in front so that the compiler will see the macro before any other code.