Merge pull request #1173 from Abextm/rs-input-widget

Add ChatboxInputManager to provide a RuneScape styled input box
This commit is contained in:
Adam
2018-04-01 16:32:13 -04:00
committed by GitHub
14 changed files with 396 additions and 19 deletions

View File

@@ -24,7 +24,7 @@
*/
grammar rs2asm;
prog: (header NEWLINE+)* (line NEWLINE+)+ ;
prog: NEWLINE* (header NEWLINE+)* (line NEWLINE+)+ ;
header: id | int_stack_count | string_stack_count | int_var_count | string_var_count ;

View File

@@ -352,4 +352,6 @@ public interface Client extends GameEngine
World createWorld();
SpritePixels drawInstanceMap(int z);
void runScript(int id, Object... args);
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) 2018 Abex
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.api;
public final class ScriptID
{
public static final int RUNELITE_CHATBOX_INPUT_INIT = 10001;
}

View File

@@ -28,7 +28,7 @@ import lombok.Data;
import net.runelite.api.Script;
@Data
public class ScriptEvent
public class ScriptCallbackEvent
{
private Script script;
private String eventName;

View File

@@ -0,0 +1,112 @@
/*
* Copyright (c) 2018 Abex
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.game;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.function.Consumer;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.ScriptID;
import net.runelite.api.events.ScriptCallbackEvent;
import net.runelite.client.callback.ClientThread;
@Singleton
@Slf4j
public class ChatboxInputManager
{
private final Client client;
private final ClientThread clientThread;
private Consumer<String> done;
@Inject
public ChatboxInputManager(Client client, ClientThread clientThread, EventBus eventBus)
{
this.client = client;
this.clientThread = clientThread;
eventBus.register(this);
}
/**
* Opens a RuneScape-style chatbox input
*
* @param text Text to show at the top of the window
* @param defaul Default text in the editable field
* @param done Callback when the text box has been exited, called with "" on esc
*/
public void openInputWindow(String text, String defaul, Consumer<String> done)
{
this.done = done;
clientThread.invokeLater(() -> client.runScript(
ScriptID.RUNELITE_CHATBOX_INPUT_INIT,
text,
defaul
));
}
@Subscribe
public void scriptCallback(ScriptCallbackEvent ev)
{
// This replaces script 74 and most of 112
if ("chatboxInputHandler".equals(ev.getEventName()))
{
int intStackSize = client.getIntStackSize();
int stringStackSize = client.getStringStackSize();
int typedKey = client.getIntStack()[--intStackSize];
String str = client.getStringStack()[--stringStackSize];
int retval = 0;
switch (typedKey)
{
case 27: // Escape
str = "";
// fallthrough
case '\n':
done.accept(str);
retval = 1;
break;
case '\b':
if (str.length() > 0)
{
str = str.substring(0, str.length() - 1);
}
default:
// If we wanted to do numbers only, we could add a limit here
if (typedKey >= 32)
{
str += Character.toString((char) typedKey);
}
}
client.getStringStack()[stringStackSize++] = str;
client.getIntStack()[intStackSize++] = retval;
client.setIntStackSize(intStackSize);
client.setStringStackSize(stringStackSize);
}
}
}

View File

@@ -38,7 +38,7 @@ import net.runelite.api.Item;
import net.runelite.api.ItemContainer;
import net.runelite.api.MenuAction;
import net.runelite.api.events.MenuOptionClicked;
import net.runelite.api.events.ScriptEvent;
import net.runelite.api.events.ScriptCallbackEvent;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetConfig;
import net.runelite.api.widgets.WidgetInfo;
@@ -109,7 +109,7 @@ public class BankTagsPlugin extends Plugin
}
@Subscribe
public void onScriptEvent(ScriptEvent event)
public void onScriptEvent(ScriptCallbackEvent event)
{
String eventName = event.getEventName();

View File

@@ -31,7 +31,7 @@ import com.google.inject.Provides;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.events.ConfigChanged;
import net.runelite.api.events.ScriptEvent;
import net.runelite.api.events.ScriptCallbackEvent;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
@@ -55,7 +55,7 @@ public class ZoomPlugin extends Plugin
}
@Subscribe
public void onScriptEvent(ScriptEvent event)
public void onScriptEvent(ScriptCallbackEvent event)
{
int[] intStack = client.getIntStack();
int intStackSize = client.getIntStackSize();

View File

@@ -64,17 +64,38 @@ public abstract class RSIndexDataBaseMixin implements RSIndexDataBase
return rsData;
}
HashCode rsDataHash = Hashing.sha256().hashBytes(rsData);
String rsHash = BaseEncoding.base16().encode(rsDataHash.asBytes());
InputStream in2 = getClass().getResourceAsStream("/runelite/" + indexData.getIndex() + "/" + archiveId + ".hash");
if (rsData == null)
{
if (in2 != null)
{
log.warn("Hash file for non existing archive {}/{}", indexData.getIndex(), archiveId);
return null;
}
log.debug("Adding archive {}/{}", indexData.getIndex(), archiveId);
try
{
return ByteStreams.toByteArray(in);
}
catch (IOException ex)
{
log.warn("error loading archive replacement", ex);
}
return null;
}
if (in2 == null)
{
log.warn("Missing hash file for {}/{}", indexData.getIndex(), archiveId);
return rsData;
}
HashCode rsDataHash = Hashing.sha256().hashBytes(rsData);
String rsHash = BaseEncoding.base16().encode(rsDataHash.asBytes());
try
{
String replaceHash = CharStreams.toString(new InputStreamReader(in2));

View File

@@ -24,8 +24,10 @@
*/
package net.runelite.mixins;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.runelite.api.Client;
import net.runelite.api.events.ScriptEvent;
import net.runelite.api.events.ScriptCallbackEvent;
import net.runelite.api.mixins.Copy;
import net.runelite.api.mixins.Inject;
import net.runelite.api.mixins.Mixin;
@@ -64,7 +66,39 @@ public abstract class ScriptVMMixin implements RSClient
String stringOp = client.getStringStack()[--stringStackSize];
client.setStringStackSize(stringStackSize);
ScriptEvent event = new ScriptEvent();
if ("debug".equals(stringOp))
{
int intStackSize = client.getIntStackSize();
String fmt = client.getStringStack()[--stringStackSize];
StringBuffer out = new StringBuffer();
Matcher m = Pattern.compile("%(.)").matcher(fmt);
for (; m.find(); )
{
m.appendReplacement(out, "");
switch (m.group(1).charAt(0))
{
case 'i':
case 'd':
out.append(client.getIntStack()[--intStackSize]);
break;
case 's':
out.append(client.getStringStack()[--stringStackSize]);
break;
default:
out.append(m.group(0)).append("=unknown");
}
}
m.appendTail(out);
Hooks.log.debug(out.toString());
client.setStringStackSize(stringStackSize);
client.setIntStackSize(intStackSize);
return true;
}
ScriptCallbackEvent event = new ScriptCallbackEvent();
event.setScript(currentScript);
event.setEventName(stringOp);
Hooks.eventBus.post(event);
@@ -91,4 +125,17 @@ public abstract class ScriptVMMixin implements RSClient
currentScript = null;
}
}
@Inject
@Override
public void runScript(int id, Object... args)
{
assert isClientThread();
Object[] cargs = new Object[args.length + 1];
cargs[0] = id;
System.arraycopy(args, 0, cargs, 1, args.length);
RSScriptEvent se = createScriptEvent();
se.setArguments(cargs);
runScript(se, 200000);
}
}

View File

@@ -73,12 +73,6 @@ public class AssembleMojo extends AbstractMojo
try (FileInputStream fin = new FileInputStream(scriptFile))
{
File hashFile = new File(scriptDirectory, Files.getNameWithoutExtension(scriptFile.getName()) + ".hash");
if (!hashFile.exists())
{
throw new MojoExecutionException("Unable to find hash file for " + scriptFile);
}
ScriptDefinition script = assembler.assemble(fin);
byte[] packedScript = saver.save(script);
@@ -86,7 +80,16 @@ public class AssembleMojo extends AbstractMojo
Files.write(packedScript, targetFile);
// Copy hash file
Files.copy(hashFile, new File(scriptOut, Integer.toString(script.getId()) + ".hash"));
File hashFile = new File(scriptDirectory, Files.getNameWithoutExtension(scriptFile.getName()) + ".hash");
if (hashFile.exists())
{
Files.copy(hashFile, new File(scriptOut, Integer.toString(script.getId()) + ".hash"));
}
else if (script.getId() < 10000) // Scripts >=10000 are RuneLite scripts, so they shouldn't have a .hash
{
throw new MojoExecutionException("Unable to find hash file for " + scriptFile);
}
++count;
}

View File

@@ -0,0 +1,67 @@
; Copyright (c) 2018 Abex
; All rights reserved.
;
; Redistribution and use in source and binary forms, with or without
; modification, are permitted provided that the following conditions are met:
;
; 1. Redistributions of source code must retain the above copyright notice, this
; list of conditions and the following disclaimer.
; 2. Redistributions in binary form must reproduce the above copyright notice,
; this list of conditions and the following disclaimer in the documentation
; and/or other materials provided with the distribution.
;
; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
; ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
; (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
; ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
;;
; Keylistener for ChatboxInputInit
;
; Script 112 Normal keylistener
;
; @param int pressedKey
; @param int typedKey
;;
.id 10002
.int_stack_count 1
.string_stack_count 1
.int_var_count 2
.string_var_count 1
; Discard zero presses
iload 0
load_int 0
if_icmpeq LABEL1
; Call runelite
iload 0
get_varc_string 22
load_string "chatboxInputHandler"
runelite_callback
istore 0
put_varc_string 22
; Check return value
iload 0
load_int 1
if_icmpne LABEL0
; Close the dialog
load_int 1
load_int 1
invoke 299
; Update UI
LABEL0:
load_string ""
invoke 222
LABEL1:
return

View File

@@ -0,0 +1,82 @@
; Copyright (c) 2018 Abex
; All rights reserved.
;
; Redistribution and use in source and binary forms, with or without
; modification, are permitted provided that the following conditions are met:
;
; 1. Redistributions of source code must retain the above copyright notice, this
; list of conditions and the following disclaimer.
; 2. Redistributions in binary form must reproduce the above copyright notice,
; this list of conditions and the following disclaimer in the documentation
; and/or other materials provided with the distribution.
;
; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
; ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
; (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
; ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
;;
; Creates a chatbox text input
;
; @param String Prompt text
; @param String Default value
;
; Script 752 GE input panel
; Script 103-111 various input panels
; Script 74 validates input
; script 112 key callback
;;
.id 10001
.int_stack_count 0
.string_stack_count 2
.int_var_count 0
.string_var_count 2
; Hide the chat pane
invoke 677
; Set current value
sload 1
put_varc_string 22
; Set text
sload 0
load_int 10616867 ; 162:35
widget_put_text_widget
; Init the widgets
load_string ""
invoke 222
; Register the key listener
load_int 10002
load_int -2147483639 ; typedKey
load_string "i"
load_int 10616868
widget_put_key_listener_widget
; Restore the chatbox on exit
load_int 299
load_int 1
load_int 1
load_string "ii"
load_int 10616868
widget_put_dialog_abort_listener_widget
; 70% sure this opens the keyboard on mobile
invoke 1972
load_int 1
if_icmpeq LABEL25
jump LABEL26
LABEL25:
invoke 1983
LABEL26:
return

View File

@@ -550,4 +550,10 @@ public interface RSClient extends RSGameEngine, Client
@Import("drawObject")
void drawObject(int z, int x, int y, int randomColor1, int randomColor2);
@Construct
RSScriptEvent createScriptEvent();
@Import("runScript")
void runScript(RSScriptEvent ev, int ex);
}

View File

@@ -24,6 +24,13 @@
*/
package net.runelite.rs.api;
import net.runelite.mapping.Import;
public interface RSScriptEvent
{
@Import("objs")
Object[] getArguments();
@Import("objs")
void setArguments(Object[] args);
}