diff --git a/.gitignore b/.gitignore index 8a645851d8..e1f1bc5cc7 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ Thumbs.db # Others ################################################################################ .gradle +.gradletasknamecache build/ /bin/ target/ diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md index cea202e57e..c084f6d76c 100644 --- a/CHANGE_LOG.md +++ b/CHANGE_LOG.md @@ -1,3 +1,90 @@ +Maptool 1.5.7 +===== + +**Highlights** +- New accessibility features allows the user to apply [Perlin noise](https://en.wikipedia.org/wiki/Perlin_noise) to tiled textures to reduce obvious patterning. +- New user preference (Edit -> Preferences -> Application -> Performance) for Max Frame Rate allows users to set a desired Max Frame Rate. +- More updated macro functions for improved performance and reliability. +- New GM macro panel for GM only macros. These macros are saved as part of the campaign. +- Bug fixes! + +Enhancements +----- +- [#878][i878] ISO 8601 time date field added to result of `getInfo("client")`. +- [#872][i872] Metadata added to JSON returned from `getMacroProps` function to add in external managment of macros. +- [#850][i850] New macro function `log.trace` added for log messages at `TRACE` level. +- [#848][i848] File Sync Directory preference setting adding to `getInfo("client")` result. +- [#829][i829] New macro function `capitalize` will change the first character at each word break to upper case. e.g. "jolly green giant" -> "Jolly Green Giant". +- [#810][i810] New macro function `playClip` more suited for short sound FX clips. New convenience function `defineAudioSource` to allow you to assign a nickname to audio sources and refer to the nickname in the audio functions. Two existing audio functions were renamed as part of the other changes: `stopStream` -> `stopSound`, `getStreamProperties` -> `getSoundProperties`. +- [#804][i804] Chat notification flash now picks flash color based on task bar color. +- [#801][i801] Macro function `getTokenStates` now accepts Token ID and Map Name parameters. +- [#790][i790] New `execFunction` macro that works like `execLink` but is used for built-in macro functions and UDFs. +- [#784][i784] Macro function `json.toVars` now accepts JSON arrays as well as objects. +- [#782][i782] Macro function `broadcast` no accepts "not-gm", "not-self" and "not-gm-self" as targets. +- [#766][i766] New perlin noise overlay on tiled textures to reduce visible repetition. +- [#761][i761] Bulk macro function changes. + - These functions no longer force whole token updates when used: addToInitiative, setInitiative, setInitiativeHold, setBar, setBarVisible, setName, setGMName, setHalo, setTokenOpacity, setTokenImage, setTokenPortrait, getTokenHandout, setLabel, resetProperty, setTerrainModifier, setVisible, setOwnerOnlyVisible, setAlwaysVisible, setTokenVBL + - These functions now accept additional Token ID and Map Name parameters: setBar, getBar, isBarVisible, setBarVisible, addToInitiative, setInitiative, setInitiativeHold +- [#745][i745] Macro functions `getTokens` and `getTokenNames` now take optional Light condition for getting tokens with lights. +- [#642][i642] New GM macro panel can be opened from the Window menu. Macros on the GM panel are not visible to players. + +Bug Fixes +----- +- [#883][i883] Command key shortcuts on MacOS not working. Fixed. +- [#874][i874] `REST.delete` did not support a header and payload. Fixed. +- [#846][i846] `getInfo("server")` was returning true/false instead of 1/0 for "hosting server". Fixed. +- [#831][i831] Macro function `json.path.read` was returning numbers as strings. Fixed. +- [#822][i822] `playStream` was ignoring stream parameters set with `editStream`. Fixed. +- [#820][i820] Functions `execLink` and `execFunction` were not running immediately on local client. Fixed. +- [#814][i814] Some `update` keywords for `copyToken` were producing exceptions. Fixed. Alternate keywords *tokenPortrait* and *tokenHandout* added +- [#803][i803] `getStreamProps` was returning malformed JSON. Fixed. +- [#800][i800] Incorrect tooltip on Chat Notification Background preference. Fixed. +- [#788][i788] Player clients were showing the last campaign file they had loaded in the title bar when connected to servers. Fixed. +- [#786][i786] Bugs with the various bar functions returning incorrect error messages or no error when passed a bad bar name. Fixed. +- [#775][i775] `json.path.read` was returning invalid JSON for JSON arrays of objects. Fixed. +- [#769][i769] Tokens created with `copyToken` could not be modified in same macro without jumping through hoops. Fixed. +- [#767][i767] A recent change to improve program responsiveness had capped frame rate at 30 fps making for jerky map panning. Default is now 60 fps and can be adjusted in preferences under Application -> Performance -> Max Frame Rate. Note either reloading the current campaign or restarting MapTool is required after making a change. +- [#740][i740] Selecting New Map in the Library image pane with no image underneath would thrown an exception. Fixed. +- [#687][i687] The table functions `addTableEntry`, `createTable`, `setTableImage` and `setTableEntry` if passed an empty `AssetID` string would incorrectly put an empty "Asset://" into the entries asset id field. Fixed. `getTableImage` would thrown an exeption if no table image was set. Fixed. +- [#640][i640] Workaround for errors which occured when three monitors are in use. Related exceptions caught and information is logged. +- [#627][i627] Version check on MapTool startup should no longer prompt for updates when using release candidates. +- [#529][i529] Smileys are now working again. + +[i883]: https://github.com/RPTools/maptool/issues/883 +[i878]: https://github.com/RPTools/maptool/issues/878 +[i874]: https://github.com/RPTools/maptool/issues/874 +[i872]: https://github.com/RPTools/maptool/issues/872 +[i850]: https://github.com/RPTools/maptool/issues/850 +[i848]: https://github.com/RPTools/maptool/issues/848 +[i846]: https://github.com/RPTools/maptool/issues/846 +[i831]: https://github.com/RPTools/maptool/issues/831 +[i829]: https://github.com/RPTools/maptool/issues/829 +[i822]: https://github.com/RPTools/maptool/issues/822 +[i820]: https://github.com/RPTools/maptool/issues/820 +[i814]: https://github.com/RPTools/maptool/issues/814 +[i810]: https://github.com/RPTools/maptool/issues/810 +[i804]: https://github.com/RPTools/maptool/issues/804 +[i803]: https://github.com/RPTools/maptool/issues/803 +[i801]: https://github.com/RPTools/maptool/issues/801 +[i800]: https://github.com/RPTools/maptool/issues/800 +[i790]: https://github.com/RPTools/maptool/issues/790 +[i788]: https://github.com/RPTools/maptool/issues/788 +[i786]: https://github.com/RPTools/maptool/issues/786 +[i784]: https://github.com/RPTools/maptool/issues/784 +[i782]: https://github.com/RPTools/maptool/issues/782 +[i775]: https://github.com/RPTools/maptool/issues/775 +[i769]: https://github.com/RPTools/maptool/issues/769 +[i767]: https://github.com/RPTools/maptool/issues/767 +[i766]: https://github.com/RPTools/maptool/issues/766 +[i761]: https://github.com/RPTools/maptool/issues/761 +[i745]: https://github.com/RPTools/maptool/issues/745 +[i740]: https://github.com/RPTools/maptool/issues/740 +[i687]: https://github.com/RPTools/maptool/issues/687 +[i642]: https://github.com/RPTools/maptool/issues/642 +[i640]: https://github.com/RPTools/maptool/issues/640 +[i627]: https://github.com/RPTools/maptool/issues/627 +[i529]: https://github.com/RPTools/maptool/issues/529 + Maptool 1.5.6 ===== Emergency fix for MacOS. Otherwise the same as 1.5.5. @@ -318,7 +405,7 @@ Enhancements * [getRolled][igrd]() * [getNewRolls][ignr]() * [clearRolls][icrl]() -* [#406][i406] - New [dice expression](http://www.lmwcs.com/rptools/wiki/Dice_Expressions) **XdYdhZ** (drop highest) and 7 others. +* [#406][i406] - New [dice expression](http://www.lmwcs.com/rptools/wiki/Dice_Expressions) **XdYdhZ** (drop highest) and 7 others. * [#355][i355] - Macro Editor details tab reorganized to give some fields more room. Macro button tooltip entry field made into a larger text area with HTML highlighting. Checkbox to enable/disable hotkey display on button. UDFs now show in auto-complete of macro editor with their tooltip as help text. * [#426][i426] - New Line & Radius templates that start at cells. New icons for all template types. * [#424][i424] - Auto-completion in macro editor now works even if complete function name has already been entered. @@ -330,7 +417,7 @@ Enhancements * [#106][i106] - Reset Size added to right-click menu for tokens/stamps. * [#299][i299] - Mouse pointer now visible when dragging tokens. * [#389][i389] - File -> Export -> Campaign File As... now supports converting back to non-decimal map units-per-cell values. -* [#332][i332] - Added support for multiple personal lights and setting color for personal lights. +* [#332][i332] - Added support for multiple personal lights and setting color for personal lights. [igrd]: http://www.lmwcs.com/rptools/wiki/getRolled [ignr]: http://www.lmwcs.com/rptools/wiki/getNewRolls @@ -389,8 +476,8 @@ Enhancements -[i50]: https://github.com/RPTools/maptool/issues/50 -[i107]: https://github.com/RPTools/maptool/issues/107 +[i50]: https://github.com/RPTools/maptool/issues/50 +[i107]: https://github.com/RPTools/maptool/issues/107 [i189]: https://github.com/RPTools/maptool/issues/189 [i255]: https://github.com/RPTools/maptool/issues/255 [i278]: https://github.com/RPTools/maptool/issues/278 @@ -461,7 +548,7 @@ A new shift+ctrl+spacebar command along with a new pointer image is now availabl * New RESTful functions getRequest & postRequest to send GET & POST requests to a URI. *Note: You must first turn access on in Preferences for these macro functions to work. * New function exportData exportData(FilePath file, String data, boolean appendToFile) which saves string data to external file. * New function getEnvironmentVariable(String name), Returns the value stored in the Environment Variable. -* New menu option added to the "Connections" window. Right clicking a player will offer a "Whisper" command that prepopulates the chat window with a whisper macro. +* New menu option added to the "Connections" window. Right clicking a player will offer a "Whisper" command that prepopulates the chat window with a whisper macro. * [#237][i237] - Added support to use shift-enter to insert newlines into the command entry box (also known as the chat entry box) * [#239][i239] - MapToolScriptTokenMaker now handles function names with . notation and dynamically pulls in all functions names. TokenMakerMaker no longer needs to be ran upon changes to MTScript. * [#240][i240] - Macro Editor now has Auto-Completion for macro functions! A brief description and summary can be displayed (these will be added as time permits) diff --git a/README.md b/README.md index 32e0c65c19..4fe72ea520 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -| Branch | Travis | AppVeyor | -| :--- | :--: | :--: | +| Branch | Travis | AppVeyor | Localization | +| :--- | :--: | :--: | :--: | | master | [![Build Status](https://travis-ci.org/RPTools/maptool.svg?branch=master)](https://travis-ci.org/RPTools/maptool) | [![Build status](https://ci.appveyor.com/api/projects/status/1fccyq1tqp8py6c5/branch/master?svg=true)](https://ci.appveyor.com/project/rptools-automation/maptool/branch/master) -| develop | [![Build Status](https://travis-ci.org/RPTools/maptool.svg?branch=develop)](https://travis-ci.org/RPTools/maptool) | [![Build status](https://ci.appveyor.com/api/projects/status/1fccyq1tqp8py6c5/branch/develop?svg=true)](https://ci.appveyor.com/project/rptools-automation/maptool/branch/develop) +| develop | [![Build Status](https://travis-ci.org/RPTools/maptool.svg?branch=develop)](https://travis-ci.org/RPTools/maptool) | [![Build status](https://ci.appveyor.com/api/projects/status/1fccyq1tqp8py6c5/branch/develop?svg=true)](https://ci.appveyor.com/project/rptools-automation/maptool/branch/develop) | [![Crowdin](https://badges.crowdin.net/maptool/localized.svg)](https://crowdin.com/project/maptool)| diff --git a/build.gradle b/build.gradle index e11fe3358e..57f371af9d 100644 --- a/build.gradle +++ b/build.gradle @@ -235,7 +235,7 @@ dependencies { // For syntax highlighting in macro editor implementation group: 'com.fifesoft', name: 'rsyntaxtextarea', version: '3.0.2' // https://mvnrepository.com/artifact/com.fifesoft/rsyntaxtextarea implementation group: 'com.fifesoft', name: 'rstaui', version: '3.0.0' // https://mvnrepository.com/artifact/com.fifesoft/rstaui - implementation group: 'com.fifesoft', name: 'autocomplete', version: '3.0.0' // https://mvnrepository.com/artifact/com.fifesoft/autocomplete + implementation group: 'com.fifesoft', name: 'autocomplete', version: '3.0.2' // https://mvnrepository.com/artifact/com.fifesoft/autocomplete // For simple xml work in Hero Lab integration implementation group: 'com.jcabi', name: 'jcabi-xml', version: '0.18.1' // https://mvnrepository.com/artifact/com.jcabi/jcabi-xml @@ -265,6 +265,11 @@ dependencies { // using jitpack.io for now // compile ('net.rptools.dicelib:dicelib:1.5.2') implementation 'com.github.RPTools:dicelib:1.5.5' + + + // Noise Generator + implementation 'com.github.cwisniew:NoiseLib:1.0.0-rc3' + } diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 0000000000..04bbf659bf --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,3 @@ +files: + - source: /src/main/resources/net/rptools/maptool/language/i18n.properties + translation: /%original_path%/%file_name%_%two_letters_code%.%file_extension% diff --git a/doc/Code_Style_and_Guidelines.md b/doc/Code_Style_and_Guidelines.md index eaa64b8f01..4bd383384a 100644 --- a/doc/Code_Style_and_Guidelines.md +++ b/doc/Code_Style_and_Guidelines.md @@ -1,20 +1,26 @@ # Code Style and Guidelines -Please follow the following rules when working on MapTool. Failure to follow these will typically result in your patch being rejected. +Please observe the following rules when working on MapTool. Failure to do so will typically result in your pull request being rejected. -For now patches should be posted to the Testers forum. Access to that forum is by request only; send a PM to [jfrazierjr](http://forums.rptools.net/memberlist.php?mode=viewprofile&u=773) here on the [forums](http://forums.rptools.net/) and he'll add you to the group. You may then visit that hidden forum and review information on how to post patches for submission. - -1. Thou shalt always highlight the content of any Java source code thee hath modified and select **Source -> Format**. Note: this requires that the user reset the line length in the Eclipse properties: Trevor wants the line length set to 200 characters. *(But see the attached preferences import file as the formatting settings are implemented there.)* -2. Thou shalt always select **Source -> Reorganize Imports.** -3. Thou shalt always submit code that includes Javadoc comments for public classes and methods. (T'would be nice to require full Javadoc for everything, but alas, that is unlikely.) -4. Thou shalt always use the `/* */` style of comments in front of classes and methods and may use single-line comments in front of member variables and small snippets of code, but **See Rule #3**! -5. Thou shalt always use parameter names different from member field names so that disambiguation using **this** is not necessary. *(Exception: code generated by Eclipse's Source menu items is exempt from this requirement as a productivity enhancement.)* -6. Thou shalt always clearly delineate private constructors with comments so that those who come after thee may retain thy sanity. -7. Thou shalt never use hard-coded strings in code when a property from an external file can be used. (In MapTool's case, this means calling `I18N.getText(propertyKey)` and adding a definition for the `propertyKey` to `i18n.properties`. Also, all of the `show*()` methods in MapTool, such as `showError()` and `showWarning()`, take `propertyKeys` as well as strings -- only use `propertyKeys`!) -8. Thou shalt always use `static final String` when hard-coded strings _are_ appropriate. Examples include resources that are embedded inside the MapTool JAR, such as **unknown.png** -- the question mark image. Other image names that may be considered part of the "theme", such as toolbar images, button images, and so forth, should be retrieved from an images property file; I propose `images.properties` since we already have `sounds.properties`. A string in the code should reference a pathname in the property file. -9. Thou shalt report all exceptions that are true errors. `InterruptedException` while waiting for a timer can be ignored, for example. But all other errors should be handled by calling `MapTool.showError(propertyKey)` or similar and passing both a `propertyKey` and the `Throwable` object representing the exception. Note that the various "show" methods already provide logging to the `.maptool/log.txt` file, but separate logging should be performed in the class if possible, since the XML configuration is an all-or-nothing for `net.rptools.maptool.client.MapTool`. -10. Thou shalt always use defined properties instead of hard-coded strings when possible. Such as `File.separator` instead of **"/"** and now `AppActions.menuShortcut` instead of **"ctrl"**. +1. The [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html) is incorporated by reference. Submitted code shall follow those guidelines unless modified by one or more of the following rules. +2. Use parameter names different from member field names so that disambiguation using **this** is not necessary. This rule is relaxed for simple short methods (e.g. setters). +3. Avoid hard-coded strings in code when a property from an external file can be used. In MapTool's case, this means calling `I18N.getText(propertyKey)` and adding a definition for the `propertyKey` to `i18n.properties`. Also, all of the `show*()` methods in MapTool, such as `showError()` and `showWarning()`, take `propertyKeys` as well as strings -- only use `propertyKeys`!) +4. Use `static final String` when hard-coded strings _are_ appropriate. Examples include resources that are embedded inside the MapTool JAR, such as **unknown.png** -- the question mark image. +5. Report to the user all exceptions that are true errors. `InterruptedException` while waiting for a timer can be ignored, for example. But all other errors should be handled by calling `MapTool.showError(propertyKey)` or similar and passing both a `propertyKey` and the `Throwable` object representing the exception. Note that the various "show" methods already provide logging to the `.maptool/log.txt` file. +6. Use the language-defined static variables instead of hard-coded strings when possible. Examples include `File.separator` instead of **"/"** and `AppActions.menuShortcut` instead of **"ctrl"**. There are surely others that you (the contributors) may want added and that we (the dev team) determine to be acceptable. Please speak up. :) -An exported set of Eclipse Preferences as a ZIP file is available [here](http://forums.rptools.net/download/file.php?id=4151). Unpack the ZIP and use **File > Import...** to read them. These preferences only include Java appearance and style-related settings, plus Task tag definitions. (Keyboard shortcuts and other settings are left untouched.) \ No newline at end of file +## Formatting Source Files + +### Formatting with your IDE +Most IDEs include some Source Formatting functionality and using that functionality can make following the guidelines easier. You will need to ensure that using any such functionality does follow the guidelines. + +Example for Eclipse: +* Source -> Format +* Source -> Reorganize Imports + +An exported set of Eclipse Preferences can be found in the GitHub repo under `build-resources/eclipse`. Other IDEs/editors may be able to import those preference files. If you create one for your preferred environment, you can always create a Pull Request to submit it to the MapTool repo. + +### Formatting with Spotless +The gradle build file for MapTool includes the Spotless targets: spotlessCheck and spotlessApply. Make use of them by doing a `gradlew spotlessCheck` and/or `spotlessApply` prior to committing or pushing your changes. Spotless will enforce the majority of the rules but not all. diff --git a/src/main/java/net/rptools/lib/TaskBarFlasher.java b/src/main/java/net/rptools/lib/TaskBarFlasher.java index 29940141f1..2ae9d363ca 100644 --- a/src/main/java/net/rptools/lib/TaskBarFlasher.java +++ b/src/main/java/net/rptools/lib/TaskBarFlasher.java @@ -32,13 +32,23 @@ public class TaskBarFlasher { public TaskBarFlasher(Frame frame) { this.frame = frame; - + Color bgColor = frame.getBackground(); + Color flashColor; + // W3C formula for calculating perceived brightness - basically RGB -> YIQ but just Y. + // https://www.w3.org/TR/AERT/#color-contrast + int brightness = + (bgColor.getRed() * 299 + bgColor.getGreen() * 587 + bgColor.getBlue() * 114) / 1000; + if (brightness < 128) { + flashColor = bgColor.brighter(); + } else { + flashColor = bgColor.darker(); + } originalImage = frame.getIconImage(); flashImage = new BufferedImage( originalImage.getWidth(null), originalImage.getHeight(null), BufferedImage.OPAQUE); Graphics g = flashImage.getGraphics(); - g.setColor(Color.blue); + g.setColor(flashColor); g.fillRect(0, 0, flashImage.getWidth(), flashImage.getHeight()); g.drawImage(originalImage, 0, 0, null); g.dispose(); diff --git a/src/main/java/net/rptools/lib/sound/SoundManager.java b/src/main/java/net/rptools/lib/sound/SoundManager.java index 242b81f62b..4c405261c9 100644 --- a/src/main/java/net/rptools/lib/sound/SoundManager.java +++ b/src/main/java/net/rptools/lib/sound/SoundManager.java @@ -17,21 +17,72 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; +import java.util.*; import javafx.application.Platform; import javafx.scene.media.AudioClip; import net.rptools.maptool.client.functions.MediaPlayerAdapter; +import net.rptools.maptool.client.functions.SoundFunctions; +import net.rptools.maptool.language.I18N; +import net.rptools.parser.ParserException; +import net.sf.json.JSONArray; +import net.sf.json.JSONObject; /** * This class stores AudioClip for system sounds and sound events. Event sounds are played through * the playSoundEvent method. */ public class SoundManager { - private final Map registeredSoundMap = new HashMap(); - private final Map soundEventMap = new HashMap(); + private static final Map registeredSoundMap = new HashMap<>(); + private static final Map soundEventMap = new HashMap<>(); + private static final Map userSounds = new HashMap<>(); + + private final String strUri; + private final AudioClip clip; + private int cycleCount; // store to remember last cycleCount used. Never 0. + private double volume; // store because player volume also depends on global volume. + + private SoundManager(String strUri, Integer cycleCount, Double volume) { + this.strUri = strUri; + this.clip = new AudioClip(strUri); + + editClip(cycleCount, volume, true); + } + + /** + * Edit the SoundManager values, and update the AudioClip. Should be accessed from JavaFX app + * thread. If a parameter is null and useDefault is false, no change * to that value. If null and + * useDefault is true, change to the defaults. + * + * @param cycleCount how many times should the clip play (-1: infinite, default: 1) + * @param volume the volume level of the clip (0-1, default: 1) + */ + private void editClip(Integer cycleCount, Double volume, boolean useDefault) { + if (cycleCount != null && cycleCount != 0) { + this.cycleCount = cycleCount; + } else if (useDefault) { + this.cycleCount = 1; + } + if (volume != null) { + this.volume = volume; + } else if (useDefault) { + this.volume = 1.0; + } + updateClip(cycleCount != null && cycleCount == 0); + } + + /** + * Update the clip with the values in the adapter + * + * @param stopPlay should the clip be stopped + */ + private void updateClip(boolean stopPlay) { + this.clip.setCycleCount(this.cycleCount); + this.clip.setVolume(this.volume * MediaPlayerAdapter.getGlobalVolume()); + + if (stopPlay) { + this.clip.stop(); + } + } /** * Loads the sound list and register the system sounds. @@ -39,7 +90,7 @@ public class SoundManager { * @param configPath The path for the sound resources * @throws IOException when configPath can't be read */ - public void configure(String configPath) throws IOException { + public static void configure(String configPath) throws IOException { Properties props = new Properties(); InputStream clipList = SoundManager.class.getClassLoader().getResourceAsStream(configPath); if (clipList == null) throw new IOException(); @@ -53,7 +104,7 @@ public void configure(String configPath) throws IOException { * @param properties the property file */ @SuppressWarnings("unchecked") - public void configure(Properties properties) { + public static void configure(Properties properties) { for (Enumeration e = (Enumeration) properties.propertyNames(); e.hasMoreElements(); ) { String key = e.nextElement(); @@ -62,19 +113,24 @@ public void configure(Properties properties) { } /** - * Register a system sound from a path. If path incorrect or null, remove sound. + * Register a system sound from a path. If path incorrect or null, remove sound. Also add define + * the sound to be used in SoundFunctions. * * @param name the name of the sound * @param path the path to the sound */ - public void registerSound(String name, String path) { + public static void registerSound(String name, String path) { if (path != null && path.trim().length() == 0) path = null; - URL url = path != null ? getClass().getClassLoader().getResource(path) : null; + URL url = path != null ? SoundManager.class.getClassLoader().getResource(path) : null; AudioClip clip = url != null ? new AudioClip(url.toExternalForm()) : null; - if (clip != null) registeredSoundMap.put(name, clip); - else registeredSoundMap.remove(name); + if (clip != null) { + registeredSoundMap.put(name, clip); + SoundFunctions.defineSound(name, url.toExternalForm()); // add sound with defineAudioSource + } else { + registeredSoundMap.remove(name); + } } /** @@ -83,7 +139,7 @@ public void registerSound(String name, String path) { * @param name the name of the sound * @return the audioclip of the sound */ - public AudioClip getRegisteredSound(String name) { + public static AudioClip getRegisteredSound(String name) { return registeredSoundMap.get(name); } @@ -93,7 +149,7 @@ public AudioClip getRegisteredSound(String name) { * @param eventId a string for the eventId * @param clip the audio clip for the sound */ - public void registerSoundEvent(String eventId, AudioClip clip) { + public static void registerSoundEvent(String eventId, AudioClip clip) { soundEventMap.put(eventId, clip); } @@ -102,7 +158,7 @@ public void registerSoundEvent(String eventId, AudioClip clip) { * * @param eventId a string for the eventId */ - public void registerSoundEvent(String eventId) { + public static void registerSoundEvent(String eventId) { registerSoundEvent(eventId, null); } @@ -111,7 +167,7 @@ public void registerSoundEvent(String eventId) { * * @param eventId a string for the eventId */ - public void playSoundEvent(String eventId) { + public static void playSoundEvent(String eventId) { AudioClip clip = soundEventMap.get(eventId); double volume = MediaPlayerAdapter.getGlobalVolume(); @@ -121,10 +177,138 @@ public void playSoundEvent(String eventId) { new Runnable() { @Override public void run() { - clip.setVolume(volume); - clip.play(); + clip.play(volume); } }); } } + + /** + * Start a given clip from its url string. + * + * @param strUri the String url of the clip + * @param cycleCount how many times should the clip play. -1: infinite + * @param volume the volume level of the clip (0-1), null keeps previous value + * @param preloadOnly should the stream be only preloaded and not played + * @return false if the file doesn't exist, true otherwise + * @throws ParserException if issue with file + */ + public static boolean playClip( + String strUri, Integer cycleCount, Double volume, boolean preloadOnly) + throws ParserException { + if (!userSounds.containsKey(strUri)) { + try { + if (!SoundFunctions.uriExists(strUri)) + return false; // leave without error message if uri ok but no file + } catch (Exception e) { + throw new ParserException( + I18N.getText( + "macro.function.sound.illegalargument", + "playClip", + strUri, + e.getLocalizedMessage())); + } + } + + // run this on the JavaFX thread + Platform.runLater( + new Runnable() { + @Override + public void run() { + SoundManager manager = userSounds.get(strUri); + if (manager != null) { + manager.editClip(cycleCount, volume, false); + } else { + manager = new SoundManager(strUri, cycleCount, volume); + userSounds.put(strUri, manager); + } + AudioClip clip = manager.clip; + + double playVolume = manager.volume * MediaPlayerAdapter.getGlobalVolume(); + boolean play = + (cycleCount == null || cycleCount != 0) + && playVolume > 0 + && !MediaPlayerAdapter.getGlobalMute() + && !preloadOnly; + if (play) { + clip.play(playVolume); + } + } + }); + return true; + } + + /** + * Stop a given clip from its url string. + * + * @param strUri the String url of the clip + * @param remove should the clip be disposed + */ + public static void stopClip(String strUri, boolean remove) { + Platform.runLater( + new Runnable() { + @Override + public void run() { + if (strUri.equals("*")) { + for (HashMap.Entry mapElement : userSounds.entrySet()) { + ((SoundManager) mapElement.getValue()).clip.stop(); + } + if (remove) userSounds.clear(); + } else { + SoundManager manager = userSounds.get(strUri); + if (manager != null) manager.clip.stop(); + if (remove) userSounds.remove(strUri); + } + } + }); + } + + /** + * Return the properties of a clip from its uri + * + * @param strUri the String uri of the clip + * @return JSONObject for one clip, JSONArray of JSONObjects if all clips + */ + public static Object getClipProperties(String strUri) { + JSONObject info; + if (strUri.equals("*")) { + JSONArray infoArray = new JSONArray(); + for (HashMap.Entry mapElement : userSounds.entrySet()) { + info = ((SoundManager) mapElement.getValue()).getInfo(); + if (info != null) infoArray.add(info); + } + return infoArray; + } else { + SoundManager manager = userSounds.get(strUri); + if (manager == null) return ""; + else info = manager.getInfo(); + if (info == null) return ""; + else return info; + } + } + + /** + * Return the properties of a clip + * + * @return JSONObject of the properties + */ + private JSONObject getInfo() { + try { + JSONObject info = new JSONObject(); + + List listNicks = SoundFunctions.getNicks(this.strUri); + if (listNicks.size() > 0) { + info.put("nicknames", String.join(",", listNicks)); + } + info.put("uri", this.strUri); + info.put("cycleCount", this.cycleCount); + info.put("volume", this.volume); + String status = this.clip.isPlaying() ? "PLAYING" : "STOPPED"; + info.put("status", status); + info.put("type", "clip"); + return info; + } catch (Exception e) { + return null; + } + } } diff --git a/src/main/java/net/rptools/lib/swing/ImagePanel.java b/src/main/java/net/rptools/lib/swing/ImagePanel.java index 344e098a09..7cfd3867ff 100644 --- a/src/main/java/net/rptools/lib/swing/ImagePanel.java +++ b/src/main/java/net/rptools/lib/swing/ImagePanel.java @@ -320,6 +320,14 @@ protected void paintComponent(Graphics gfx) { } } + /** + * Go through the image bounds map to see if any of the entries encompass the passed in X,Y values + * and return the index. + * + * @param x + * @param y + * @return the index or -1 if not found + */ protected int getIndex(int x, int y) { for (Entry entry : imageBoundsMap.entrySet()) { if (entry.getKey().contains(x, y)) { @@ -329,8 +337,19 @@ protected int getIndex(int x, int y) { return -1; } + /** + * Get the ID for the image currently displayed at X,Y + * + * @param x + * @param y + * @return Asset ID or null if no selection + */ protected Object getImageIDAt(int x, int y) { - return model != null ? model.getID(getIndex(x, y)) : null; + int index = getIndex(x, y); + if (index == -1 || model == null) { + return null; + } + return model.getID(index); } protected void fireSelectionEvent() { @@ -406,29 +425,35 @@ public String getToolTipText(MouseEvent event) { } // SCROLLABLE + @Override public Dimension getPreferredScrollableViewportSize() { return getPreferredSize(); } + @Override public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { ensureFontHeight(null); return ((gridSize + gridPadding.height * 2) + (showCaptions ? fontHeight + captionPadding : 0)); } + @Override public boolean getScrollableTracksViewportHeight() { Dimension parentSize = SwingUtilities.getAncestorOfClass(JScrollPane.class, this).getSize(); return getPreferredSize().height < parentSize.height; } + @Override public boolean getScrollableTracksViewportWidth() { return true; } + @Override public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { return gridSize / 4; } // DRAG GESTURE LISTENER + @Override public void dragGestureRecognized(DragGestureEvent dge) { if (model == null || !isDraggingEnabled) { return; @@ -452,28 +477,38 @@ protected Cursor getDragCursor() { } // DRAG SOURCE LISTENER + @Override public void dragDropEnd(DragSourceDropEvent dsde) { DragSource.getDefaultDragSource().removeDragSourceMotionListener(this); } + @Override public void dragEnter(DragSourceDragEvent dsde) {} + @Override public void dragExit(DragSourceEvent dse) {} + @Override public void dragOver(DragSourceDragEvent dsde) {} + @Override public void dropActionChanged(DragSourceDragEvent dsde) {} // DRAG SOURCE MOTION LISTENER + @Override public void dragMouseMoved(DragSourceDragEvent dsde) {} // MOUSE LISTENER + @Override public void mouseClicked(MouseEvent e) {} + @Override public void mouseEntered(MouseEvent e) {} + @Override public void mouseExited(MouseEvent e) {} + @Override public void mousePressed(MouseEvent e) { if (selectionMode == SelectionMode.NONE) { return; @@ -491,5 +526,6 @@ public void mousePressed(MouseEvent e) { fireSelectionEvent(); } + @Override public void mouseReleased(MouseEvent e) {} } diff --git a/src/main/java/net/rptools/maptool/client/AppActions.java b/src/main/java/net/rptools/maptool/client/AppActions.java index 348627803d..c548998264 100644 --- a/src/main/java/net/rptools/maptool/client/AppActions.java +++ b/src/main/java/net/rptools/maptool/client/AppActions.java @@ -26,6 +26,7 @@ import java.text.SimpleDateFormat; import java.util.*; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.zip.GZIPOutputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -114,7 +115,7 @@ private static int getMenuShortcutKeyMask() { } @Override - public void execute(ActionEvent ae) { + protected void executeAction(ActionEvent ae) { Token chosenOne = null; ZoneRenderer renderer = MapTool.getFrame().getCurrentZoneRenderer(); List myPlayers = new ArrayList(); @@ -163,7 +164,7 @@ public boolean isAvailable() { } @Override - public void execute(ActionEvent ae) { + protected void executeAction(ActionEvent ae) { // Do nothing } }; @@ -175,7 +176,7 @@ public void execute(ActionEvent ae) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { try { ExportDialog d = MapTool.getCampaign().getExportDialog(); d.setVisible(true); @@ -193,7 +194,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { ExportDialog d = MapTool.getCampaign().getExportDialog(); if (d == null || d.getExportLocation() == null || d.getExportSettings() == null) { // Can't do a save.. so try "save as" @@ -215,7 +216,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { try { doCampaignExport(); } catch (Exception ex) { @@ -232,7 +233,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { JFileChooser chooser = MapTool.getFrame().getSaveFileChooser(); @@ -364,7 +365,7 @@ public void execute(ActionEvent e) { * good, but the library itself is 2.7MB. */ @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { /* * 1. Ask the user to select repositories which should be considered. 2. Ask the user for FTP upload information. */ @@ -463,7 +464,7 @@ private String getFormattedDate(Date d) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { ZoneRenderer renderer = MapTool.getFrame().getCurrentZoneRenderer(); if (renderer == null) { return; @@ -481,7 +482,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { try { AppSetup.installDefaultTokens(); @@ -505,7 +506,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { try { // Load the defaults InputStream in = @@ -540,7 +541,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { Zone zone = MapTool.getFrame().getCurrentZoneRenderer().getZone(); String oldName = zone.getName(); if (oldName == null) oldName = ""; @@ -562,7 +563,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { if (MapTool.getFrame().isFullScreen()) { MapTool.getFrame().showWindowed(); @@ -584,7 +585,7 @@ public boolean isAvailable() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { if (MapTool.getServer() == null) { return; @@ -602,7 +603,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { // Probably don't have to create a new one each time PreferencesDialog dialog = new PreferencesDialog(); @@ -617,7 +618,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { JFileChooser chooser = MapTool.getFrame().getSaveFileChooser(); chooser.setDialogTitle(I18N.getText("msg.title.saveMessageHistory")); chooser.setFileSelectionMode(JFileChooser.FILES_ONLY); @@ -650,7 +651,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { Zone z = MapTool.getFrame().getCurrentZoneRenderer().getZone(); z.undoDrawable(); isAvailable(); @@ -683,7 +684,7 @@ public boolean isAvailable() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { Zone z = MapTool.getFrame().getCurrentZoneRenderer().getZone(); z.redoDrawable(); isAvailable(); @@ -728,7 +729,7 @@ public boolean isAvailable() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { ZoneRenderer renderer = MapTool.getFrame().getCurrentZoneRenderer(); if (renderer == null) { return; @@ -760,7 +761,7 @@ public boolean isAvailable() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { ZoneRenderer renderer = MapTool.getFrame().getCurrentZoneRenderer(); Set selectedSet = renderer.getSelectedTokenSet(); cutTokens(renderer.getZone(), selectedSet); @@ -816,7 +817,7 @@ public boolean isAvailable() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { ZoneRenderer renderer = MapTool.getFrame().getCurrentZoneRenderer(); copyTokens(renderer.getSelectedTokenSet()); } @@ -962,7 +963,7 @@ public boolean isAvailable() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { ZoneRenderer renderer = MapTool.getFrame().getCurrentZoneRenderer(); if (renderer == null) { return; @@ -1107,7 +1108,7 @@ private static void pasteTokens(ZonePoint destination, Layer layer) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { AssetPanel assetPanel = MapTool.getFrame().getAssetPanel(); Directory dir = assetPanel.getSelectedAssetRoot(); @@ -1132,7 +1133,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { AssetPanel assetPanel = MapTool.getFrame().getAssetPanel(); Directory dir = assetPanel.getSelectedAssetRoot(); @@ -1152,7 +1153,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { ClientConnectionPanel panel = MapTool.getFrame().getConnectionPanel(); Player selectedPlayer = (Player) panel.getSelectedValue(); @@ -1188,7 +1189,7 @@ public boolean isAvailable() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { ClientConnectionPanel panel = MapTool.getFrame().getConnectionPanel(); Player selectedPlayer = (Player) panel.getSelectedValue(); @@ -1228,7 +1229,7 @@ public boolean isSelected() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { AppState.setNotificationEnforced(!AppState.isNotificationEnforced()); MapTool.serverCommand().enforceNotification(AppState.isNotificationEnforced()); } @@ -1247,7 +1248,7 @@ public boolean isSelected() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { AppState.setPlayerViewLinked(!AppState.isPlayerViewLinked()); MapTool.getFrame().getCurrentZoneRenderer().maybeForcePlayersView(); @@ -1266,7 +1267,7 @@ public boolean isSelected() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { AppState.setShowAsPlayer(!AppState.isShowAsPlayer()); MapTool.getFrame().refresh(); @@ -1285,7 +1286,7 @@ public boolean isSelected() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { AppState.setShowLightSources(!AppState.isShowLightSources()); MapTool.getFrame().refresh(); @@ -1304,7 +1305,7 @@ public boolean isSelected() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { AppState.setCollectProfilingData(!AppState.isCollectProfilingData()); MapTool.getProfilingNoteFrame().setVisible(AppState.isCollectProfilingData()); } @@ -1322,7 +1323,7 @@ public boolean isSelected() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { AppState.setLoggingToConsole(!AppState.isLoggingToConsole()); MapTool.getLogConsoleNoteFrame().setVisible(AppState.isLoggingToConsole()); } @@ -1340,7 +1341,7 @@ public boolean isSelected() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { AppState.setShowMovementMeasurements(!AppState.getShowMovementMeasurements()); if (MapTool.getFrame().getCurrentZoneRenderer() != null) { @@ -1356,7 +1357,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { Zone zone = MapTool.getFrame().getCurrentZoneRenderer().getZone(); // XXX Perhaps ask the user if the copied map should have its GEA and/or TEA cleared? An // imported map would ask... @@ -1377,7 +1378,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { if (!MapTool.confirm("msg.confirm.removeZone")) { return; } @@ -1393,7 +1394,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { MapTool.getFrame().showAboutDialog(); } }; @@ -1406,7 +1407,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { ZoneRenderer renderer = MapTool.getFrame().getCurrentZoneRenderer(); if (renderer == null) { return; @@ -1425,7 +1426,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { if (!MapTool.getFrame().isCommandPanelVisible()) { MapTool.getFrame().showCommandPanel(); MapTool.getFrame().getCommandPanel().startChat(); @@ -1449,7 +1450,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { MapTool.getFrame().getCommandPanel().startMacro(); } }; @@ -1464,7 +1465,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { MapTool.getFrame().getCommandPanel().commitCommand(); } }; @@ -1479,7 +1480,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { MapTool.getFrame().getCommandPanel().cancelCommand(); } }; @@ -1494,7 +1495,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { MapTool.getFrame().getCommandPanel().insertNewline(); } }; @@ -1506,7 +1507,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { MapTool.getFrame().getToolbox().setSelectedTool(GridTool.class); } @@ -1519,7 +1520,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { if (MapTool.getFrame().getCurrentZoneRenderer().getZone().getMapAssetId() != null) { MapTool.getFrame().getToolbox().setSelectedTool(BoardTool.class); @@ -1537,7 +1538,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { if (transferProgressDialog == null) { transferProgressDialog = new TransferProgressDialog(); @@ -1571,7 +1572,7 @@ public boolean isSelected() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { AppState.setShowGrid(!AppState.isShowGrid()); if (MapTool.getFrame().getCurrentZoneRenderer() != null) { MapTool.getFrame().getCurrentZoneRenderer().repaint(); @@ -1599,7 +1600,7 @@ public boolean isSelected() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { AppState.setShowCoordinates(!AppState.isShowCoordinates()); @@ -1626,7 +1627,7 @@ public boolean isSelected() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { AppState.setZoomLocked(!AppState.isZoomLocked()); MapTool.getFrame().getZoomStatusBar().update(); // So the textfield becomes grayed out } @@ -1649,7 +1650,7 @@ public boolean isSelected() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { ZoneRenderer renderer = MapTool.getFrame().getCurrentZoneRenderer(); if (renderer == null) { @@ -1688,7 +1689,7 @@ public boolean isSelected() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { MapTool.getFrame() .getCurrentZoneRenderer() .getZone() @@ -1703,7 +1704,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { if (!MapTool.confirm("msg.confirm.restoreFoW")) { return; } @@ -1731,7 +1732,7 @@ public boolean isSelected() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { ZoneRenderer renderer = MapTool.getFrame().getCurrentZoneRenderer(); if (renderer == null) { @@ -1768,7 +1769,7 @@ public void execute(ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { AppState.setShowTokenNames(!AppState.isShowTokenNames()); if (MapTool.getFrame().getCurrentZoneRenderer() != null) { @@ -1794,7 +1795,7 @@ public boolean isSelected() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { ZoneRenderer renderer = MapTool.getFrame().getCurrentZoneRenderer(); if (renderer == null) { @@ -1846,7 +1847,7 @@ private boolean confirmNewCampaign() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { if (!confirmNewCampaign()) return; @@ -1881,7 +1882,7 @@ public boolean isAvailable() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { ZoneRenderer renderer = MapTool.getFrame().getCurrentZoneRenderer(); if (renderer != null) { Dimension size = renderer.getSize(); @@ -1903,7 +1904,7 @@ public boolean isAvailable() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { ZoneRenderer renderer = MapTool.getFrame().getCurrentZoneRenderer(); if (renderer != null) { Dimension size = renderer.getSize(); @@ -1927,7 +1928,7 @@ public boolean isAvailable() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { ZoneRenderer renderer = MapTool.getFrame().getCurrentZoneRenderer(); if (renderer != null) { // Revert to last zoom if we have one, but don't if the user has manually @@ -1960,7 +1961,7 @@ public boolean isSelected() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { JComponent panel = MapTool.getFrame().getZoneMiniMapPanel(); panel.setVisible(!panel.isVisible()); } @@ -1978,7 +1979,7 @@ public boolean isSelected() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { ServerPolicy policy = MapTool.getServerPolicy(); policy.setIsMovementLocked(!policy.isMovementLocked()); @@ -2000,7 +2001,7 @@ public boolean isAvailable() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { runBackground( new Runnable() { public void run() { @@ -2144,7 +2145,7 @@ public boolean isAvailable() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { if (MapTool.isCampaignDirty() && !MapTool.confirm("msg.confirm.loseChanges")) return; final ConnectToServerDialog dialog = new ConnectToServerDialog(); @@ -2219,7 +2220,7 @@ public boolean isAvailable() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { if (MapTool.isHostingServer() && !MapTool.confirm("msg.confirm.hostingDisconnect")) return; disconnectFromServer(); @@ -2252,7 +2253,7 @@ public boolean isAvailable() { } @Override - public void execute(ActionEvent ae) { + protected void executeAction(ActionEvent ae) { if (MapTool.isCampaignDirty() && !MapTool.confirm("msg.confirm.loseChanges")) return; JFileChooser chooser = new CampaignPreviewFileChooser(); chooser.setDialogTitle(I18N.getText("msg.title.loadCampaign")); @@ -2324,6 +2325,7 @@ public void run() { AppState.setCampaignFile(campaignFile); AppPreferences.setLoadDir(campaignFile.getParentFile()); AppMenuBar.getMruManager().addMRUCampaign(campaignFile); + campaign.campaign.setName(AppState.getCampaignName()); // Update campaign name /* * Bypass the serialization when we are hosting the server. @@ -2378,7 +2380,7 @@ public void run() { } @Override - public void execute(ActionEvent ae) { + protected void executeAction(ActionEvent ae) { LoadSaveImpl impl = new LoadSaveImpl(); impl.saveApplication(); // All the work is done here } @@ -2396,7 +2398,7 @@ public boolean isAvailable() { } @Override - public void execute(final ActionEvent ae) { + protected void executeAction(final ActionEvent ae) { Observer callback = null; if (ae.getSource() instanceof Observer) callback = (Observer) ae.getSource(); if (AppState.getCampaignFile() == null) { @@ -2419,7 +2421,7 @@ public boolean isAvailable() { } @Override - public void execute(final ActionEvent ae) { + protected void executeAction(final ActionEvent ae) { doSaveCampaignAs(null); } }; @@ -2509,7 +2511,9 @@ public static void doSaveCampaignAs(final Observer callback) { AppState.setCampaignFile(campaignFile); AppPreferences.setSaveDir(campaignFile.getParentFile()); AppMenuBar.getMruManager().addMRUCampaign(AppState.getCampaignFile()); - MapTool.getFrame().setTitleViaRenderer(MapTool.getFrame().getCurrentZoneRenderer()); + if (MapTool.isHostingServer() || MapTool.isPersonalServer()) { + MapTool.serverCommand().setCampaignName(AppState.getCampaignName()); + } } } @@ -2531,7 +2535,9 @@ public static void doCampaignExport() { AppState.setCampaignFile(campaignFile); AppPreferences.setSaveDir(campaignFile.getParentFile()); AppMenuBar.getMruManager().addMRUCampaign(AppState.getCampaignFile()); - MapTool.getFrame().setTitleViaRenderer(MapTool.getFrame().getCurrentZoneRenderer()); + if (MapTool.isHostingServer() || MapTool.isPersonalServer()) { + MapTool.serverCommand().setCampaignName(AppState.getCampaignName()); + } } } @@ -2548,7 +2554,7 @@ public boolean isAvailable() { } @Override - public void execute(ActionEvent ae) { + protected void executeAction(ActionEvent ae) { ZoneRenderer zr = MapTool.getFrame().getCurrentZoneRenderer(); JFileChooser chooser = MapTool.getFrame().getSaveFileChooser(); chooser.setFileFilter(MapTool.getFrame().getMapFileFilter()); @@ -2609,7 +2615,7 @@ public boolean isAvailable() { } @Override - public void execute(ActionEvent ae) { + protected void executeAction(ActionEvent ae) { boolean isConnected = !MapTool.isHostingServer() && !MapTool.isPersonalServer(); JFileChooser chooser = new MapPreviewFileChooser(); chooser.setDialogTitle(I18N.getText("msg.title.loadMap")); @@ -2705,7 +2711,7 @@ public boolean isAvailable() { } @Override - public void execute(ActionEvent ae) { + protected void executeAction(ActionEvent ae) { Campaign campaign = MapTool.getCampaign(); // TODO: There should probably be only one of these @@ -2735,7 +2741,7 @@ public boolean isSelected() { } @Override - public void execute(ActionEvent arg0) { + protected void executeAction(ActionEvent arg0) { AppState.setGridSize(size); MapTool.getFrame().refresh(); } @@ -2749,7 +2755,7 @@ public DownloadRemoteLibraryAction(URL url) { } @Override - public void execute(ActionEvent arg0) { + protected void executeAction(ActionEvent arg0) { if (!MapTool.confirm("confirm.downloadRemoteLibrary", url)) { return; } @@ -2812,7 +2818,7 @@ public QuickMapAction(String name, File imagePath) { } @Override - public void execute(java.awt.event.ActionEvent e) { + protected void executeAction(java.awt.event.ActionEvent e) { runBackground( new Runnable() { public void run() { @@ -2835,7 +2841,7 @@ public void run() { } @Override - public void execute(java.awt.event.ActionEvent e) { + protected void executeAction(java.awt.event.ActionEvent e) { runBackground( new Runnable() { public void run() { @@ -2860,7 +2866,7 @@ public void run() { } @Override - public void execute(java.awt.event.ActionEvent e) { + protected void executeAction(java.awt.event.ActionEvent e) { runBackground( new Runnable() { public void run() { @@ -2889,7 +2895,7 @@ public void run() { } @Override - public void execute(java.awt.event.ActionEvent e) { + protected void executeAction(java.awt.event.ActionEvent e) { SysInfo.createAndShowGUI((String) getValue(Action.NAME)); } }; @@ -2901,7 +2907,7 @@ public void execute(java.awt.event.ActionEvent e) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { runBackground( new Runnable() { public void run() { @@ -2919,7 +2925,7 @@ public void run() { } @Override - public void execute(ActionEvent ae) { + protected void executeAction(ActionEvent ae) { if (!MapTool.getFrame().confirmClose()) { return; } else { @@ -2941,7 +2947,7 @@ public boolean isSelected() { } @Override - public void execute(ActionEvent ae) { + protected void executeAction(ActionEvent ae) { MapTool.getFrame() .setPaintDrawingMeasurement(!MapTool.getFrame().isPaintDrawingMeasurement()); } @@ -2960,7 +2966,7 @@ public boolean isSelected() { } @Override - public void execute(ActionEvent ae) { + protected void executeAction(ActionEvent ae) { AppState.setUseDoubleWideLine(!AppState.useDoubleWideLine()); if (MapTool.getFrame() != null && MapTool.getFrame().getCurrentZoneRenderer() != null) MapTool.getFrame().getCurrentZoneRenderer().repaint(); @@ -2986,7 +2992,7 @@ public boolean isAvailable() { } @Override - public void execute(ActionEvent event) { + protected void executeAction(ActionEvent event) { DockableFrame frame = MapTool.getFrame().getFrame(mtFrame); if (frame.isVisible()) { MapTool.getFrame().getDockingManager().hideFrame(mtFrame.name()); @@ -3013,6 +3019,39 @@ public static void updateActions() { } public abstract static class ClientAction extends AbstractAction { + /** Does the code need to guard against bug https://bugs.openjdk.java.net/browse/JDK-8208712. */ + private static final boolean NEEDS_GUARD; + + static { + String prop = System.getProperty("os.name"); + if ("Mac OS X".equals(prop)) { + NEEDS_GUARD = + true; // MapTool doesnt run on version 8 or less of JDK so no need to check that + } else { + NEEDS_GUARD = false; + } + } + + /** + * The last time this action was called via accelerator key. This will only ever be set if + * {@link #NEEDS_GUARD} is true and the menu item is a JMenuCheckBoxItem + */ + private long lastAccelInvoke; + + public final void execute(ActionEvent e) { + if (NEEDS_GUARD && e != null && e.getSource() instanceof JCheckBoxMenuItem) { + if (e.getModifiers() == 0) { + if (TimeUnit.MILLISECONDS.toSeconds(e.getWhen() - lastAccelInvoke) < 1) { + return; // Nothing to do as its due to the JDK bug + // https://bugs.openjdk.java.net/browse/JDK-8208712 + } + } else if ((e.getModifiers() & menuShortcut) != 0) { + lastAccelInvoke = e.getWhen(); + } + } + executeAction(e); + } + public void init(String key) { init(key, true); } @@ -3046,7 +3085,7 @@ public final void actionPerformed(ActionEvent e) { updateActions(); } - public abstract void execute(ActionEvent e); + protected abstract void executeAction(ActionEvent e); public void runBackground(final Runnable r) { new Thread() { @@ -3112,7 +3151,7 @@ public OpenUrlAction(String key) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { if (getValue(Action.SHORT_DESCRIPTION) != null) MapTool.showDocument((String) getValue(Action.SHORT_DESCRIPTION)); } diff --git a/src/main/java/net/rptools/maptool/client/AppPreferences.java b/src/main/java/net/rptools/maptool/client/AppPreferences.java index 659cb9b5f5..1d933b5dd2 100644 --- a/src/main/java/net/rptools/maptool/client/AppPreferences.java +++ b/src/main/java/net/rptools/maptool/client/AppPreferences.java @@ -426,6 +426,9 @@ public static int getHaloLineWidth() { private static final String KEY_TYPING_NOTIFICATION_DURATION = "typingNotificationDuration"; private static final int DEFAULT_TYPING_NOTIFICATION_DURATION = 5000; + private static final String KEY_FRAME_RATE_CAP = "frameRateCap"; + private static final int DEFAULT_FRAME_RATE_CAP = 60; + private static final String KEY_UPNP_DISCOVERY_TIMEOUT = "upnpDiscoveryTimeout"; private static final int DEFAULT_UPNP_DISCOVERY_TIMEOUT = 5000; @@ -855,6 +858,14 @@ public static void setMovementMetric(WalkerMetric metric) { prefs.put(KEY_MOVEMENT_METRIC, metric.toString()); } + public static void setFrameRateCap(int cap) { + prefs.putInt(KEY_FRAME_RATE_CAP, cap); + } + + public static int getFrameRateCap() { + return prefs.getInt(KEY_FRAME_RATE_CAP, DEFAULT_FRAME_RATE_CAP); + } + public static void setUpnpDiscoveryTimeout(int timeout) { prefs.putInt(KEY_UPNP_DISCOVERY_TIMEOUT, timeout); } diff --git a/src/main/java/net/rptools/maptool/client/AppState.java b/src/main/java/net/rptools/maptool/client/AppState.java index 375dc524ca..07aa649a2a 100644 --- a/src/main/java/net/rptools/maptool/client/AppState.java +++ b/src/main/java/net/rptools/maptool/client/AppState.java @@ -137,6 +137,22 @@ public static void setCampaignFile(File campaignFile) { AppState.campaignFile = campaignFile; } + /** + * Returns the campaign name (without extension) from the campaign file. If no campaign file is + * defined, instead returns "Default". + * + * @return The string containing the campaign name + */ + public static String getCampaignName() { + if (AppState.campaignFile == null) { + return "Default"; + } else { + String s = AppState.campaignFile.getName(); + // remove the file extension of the campaign file name + return s.substring(0, s.length() - AppConstants.CAMPAIGN_FILE_EXTENSION.length()); + } + } + public static void setShowMovementMeasurements(boolean show) { showMovementMeasurements = show; } diff --git a/src/main/java/net/rptools/maptool/client/AppUpdate.java b/src/main/java/net/rptools/maptool/client/AppUpdate.java index 02eaa7a574..af38367b55 100644 --- a/src/main/java/net/rptools/maptool/client/AppUpdate.java +++ b/src/main/java/net/rptools/maptool/client/AppUpdate.java @@ -14,8 +14,10 @@ */ package net.rptools.maptool.client; +import com.jayway.jsonpath.JsonPath; import java.io.*; import java.net.*; +import java.util.List; import java.util.Properties; import java.util.jar.*; import javax.swing.*; @@ -31,18 +33,27 @@ public class AppUpdate { private static final Logger log = LogManager.getLogger(AppUpdate.class); - static final String GIT_HUB_API_URL = "github.api.url"; - static final String GIT_HUB_OAUTH_TOKEN = + private static final String GIT_HUB_RELEASES = "github.api.releases"; + private static final String GIT_HUB_OAUTH_TOKEN = "github.api.oauth.token"; // Grants read-only access to public information + /** + * Look for a newer version of MapTool. If a newer release is found and the AppPreferences tell us + * the update should not be ignored, give a prompt to update. If current version is a release, + * update to the most recent release. If the current version is a pre-release, update to the most + * recent version (pre-release or release). + * + * @return has an update been made + */ public static boolean gitHubReleases() { // AppPreferences.setSkipAutoUpdate(false); // For testing only if (AppPreferences.getSkipAutoUpdate()) return false; + String strURL = getProperty(GIT_HUB_RELEASES); + String strRequest = strURL + getProperty(GIT_HUB_OAUTH_TOKEN); - String responseBody = null; - String jarCommit = null; - String latestGitHubReleaseCommit = ""; - String latestGitHubReleaseTagName = ""; + String jarCommit; + String latestGitHubReleaseCommit; + String latestGitHubReleaseTagName; // Default for Linux? String DOWNLOAD_EXTENSION = ".deb"; @@ -55,36 +66,32 @@ public static boolean gitHubReleases() { // If we don't have a commit attribute from JAR, we're done! if (jarCommit == null) { - log.info( - "No commit SHA (running in DEVELOPMENT mode?): " - + getProperty(GIT_HUB_API_URL) - + getProperty(GIT_HUB_OAUTH_TOKEN)); + log.info("No commit SHA (running in DEVELOPMENT mode?): " + strRequest); return false; } - try { - Request request = - new Request.Builder() - .url(getProperty(GIT_HUB_API_URL) + getProperty(GIT_HUB_OAUTH_TOKEN)) - .build(); - - Response response = new OkHttpClient().newCall(request).execute(); - if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); - - responseBody = response.body().string(); + String strReleases = getReleases(); + // If can't access the list of releases, we're done + if (strReleases == null) return false; - log.debug("GitHub API Response: " + responseBody); - } catch (IOException e) { - log.error("Unable to reach " + getProperty(GIT_HUB_API_URL), e.getLocalizedMessage()); - return false; - } - - JSONObject releases = new JSONObject(); + JSONObject release; try { - releases = JSONObject.fromObject(responseBody); - latestGitHubReleaseCommit = releases.get("target_commitish").toString(); + // Get pre-release information regarding MapTool version from github list + String path = "$.[?(@.target_commitish == '" + jarCommit + "')].prerelease"; + List listMatches = JsonPath.parse(strReleases).read(path); + boolean prerelease = listMatches.isEmpty() || listMatches.get(0); + + if (prerelease) { + JSONArray releasesList = JSONArray.fromObject(strReleases); + release = JSONObject.fromObject(releasesList.get(0)); // the latest is at top of list + } else { + path = "$.[?(@.prerelease == false)]"; + listMatches = JsonPath.parse(strReleases).read(path); // get sublist of releases + release = JSONObject.fromObject(listMatches.get(0)); // the latest is at top of list + } + latestGitHubReleaseCommit = release.get("target_commitish").toString(); log.info("target_commitish from GitHub: " + latestGitHubReleaseCommit); - latestGitHubReleaseTagName = releases.get("tag_name").toString(); + latestGitHubReleaseTagName = release.get("tag_name").toString(); log.info("tag_name from GitHub: " + latestGitHubReleaseTagName); } catch (Exception e) { log.error("Unable to parse JSON payload from GitHub...", e); @@ -95,7 +102,7 @@ public static boolean gitHubReleases() { if (jarCommit.equals(latestGitHubReleaseCommit) || AppPreferences.getSkipAutoUpdateCommit().equals(latestGitHubReleaseCommit)) return false; - JSONArray releaseAssets = releases.getJSONArray("assets"); + JSONArray releaseAssets = release.getJSONArray("assets"); String assetDownloadURL = null; JSONObject asset; @@ -132,6 +139,33 @@ public static boolean gitHubReleases() { return false; } + /** + * Get the String containing the list of the releases and pre-releases from github. + * + * @return the String with the list of releases, or null if IOException + */ + private static String getReleases() { + String strURL = getProperty(GIT_HUB_RELEASES); + String strRequest = strURL + getProperty(GIT_HUB_OAUTH_TOKEN); + try { + Request request = new Request.Builder().url(strRequest).build(); + Response response = new OkHttpClient().newCall(request).execute(); + if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); + + String responseBody = response.body().string(); + log.debug("GitHub API Response: " + responseBody); + return responseBody; + } catch (IOException e) { + log.error("Unable to reach " + strURL, e.getLocalizedMessage()); + return null; + } + } + + /** + * Get the latest commit SHA from MANIFEST.MF + * + * @return the String of the commit SHA, or null if IOException + */ public static String getCommitSHA() { String jarCommit = ""; diff --git a/src/main/java/net/rptools/maptool/client/ClientCommand.java b/src/main/java/net/rptools/maptool/client/ClientCommand.java index 40f4f5327f..f11ec91499 100644 --- a/src/main/java/net/rptools/maptool/client/ClientCommand.java +++ b/src/main/java/net/rptools/maptool/client/ClientCommand.java @@ -38,6 +38,7 @@ public static enum COMMAND { playerConnected, playerDisconnected, message, + execFunction, execLink, undoDraw, showPointer, @@ -64,6 +65,7 @@ public static enum COMMAND { updateTokenInitiative, setUseVision, updateCampaignMacros, + updateGmMacros, setTokenLocation, // NOTE: This is to support third party token placement and shouldn't be // depended on for general purpose token movement setLiveTypingLabel, // Experimental chat notification @@ -72,6 +74,7 @@ public static enum COMMAND { setBoard, updateExposedAreaMeta, clearExposedArea, + setCampaignName, restoreZoneView // Jamz: New command to restore player's view and let GM temporarily center and // scale a player's view // @formatter:on diff --git a/src/main/java/net/rptools/maptool/client/ClientMethodHandler.java b/src/main/java/net/rptools/maptool/client/ClientMethodHandler.java index de95763282..bc096f639f 100644 --- a/src/main/java/net/rptools/maptool/client/ClientMethodHandler.java +++ b/src/main/java/net/rptools/maptool/client/ClientMethodHandler.java @@ -24,6 +24,7 @@ import java.util.Set; import net.rptools.clientserver.hessian.AbstractMethodHandler; import net.rptools.lib.MD5Key; +import net.rptools.maptool.client.functions.ExecFunction; import net.rptools.maptool.client.functions.MacroLinkFunction; import net.rptools.maptool.client.ui.MapToolFrame; import net.rptools.maptool.client.ui.tokenpanel.InitiativePanel; @@ -204,6 +205,11 @@ public void run() { MapTool.getFrame().hideGlassPane(); return; + case setCampaignName: + MapTool.getCampaign().setName((String) parameters[0]); + MapTool.getFrame().setTitle(); + return; + case putZone: zone = (Zone) parameters[0]; MapTool.getCampaign().putZone(zone); @@ -378,8 +384,14 @@ public void run() { MapTool.addServerMessage(message); return; + case execFunction: + ExecFunction.receiveExecFunction( + (String) parameters[0], (String) parameters[1], (String) parameters[2]); + return; + case execLink: - MacroLinkFunction.receiveExecLink((String) parameters[0], (String) parameters[1]); + MacroLinkFunction.receiveExecLink( + (String) parameters[0], (String) parameters[1], (String) parameters[2]); return; case showPointer: @@ -592,8 +604,14 @@ public void run() { (ArrayList) parameters[0])); MapTool.getFrame().getCampaignPanel().reset(); return; - // moved this down into the event queue section so that the threading works as - // expected + + case updateGmMacros: + MapTool.getCampaign() + .setGmMacroButtonPropertiesArray( + new ArrayList( + (ArrayList) parameters[0])); + MapTool.getFrame().getGmPanel().reset(); + return; case setLiveTypingLabel: if ((Boolean) parameters[1]) { diff --git a/src/main/java/net/rptools/maptool/client/MapTool.java b/src/main/java/net/rptools/maptool/client/MapTool.java index a6a2d4c7f3..8e07bfa158 100644 --- a/src/main/java/net/rptools/maptool/client/MapTool.java +++ b/src/main/java/net/rptools/maptool/client/MapTool.java @@ -185,7 +185,6 @@ public static enum PreferencesEvent { private static AssetTransferManager assetTransferManager; private static ServiceAnnouncer announcer; private static AutoSaveManager autoSaveManager; - private static SoundManager soundManager; private static TaskBarFlasher taskbarFlasher; private static EventDispatcher eventDispatcher; private static MapToolLineParser parser = new MapToolLineParser(); @@ -531,10 +530,6 @@ public static void showDocument(String url) { } } - public static SoundManager getSoundManager() { - return soundManager; - } - /** * Play the sound registered to an eventId. * @@ -545,7 +540,7 @@ public static void playSound(String eventId) { if (AppPreferences.getPlaySystemSoundsOnlyWhenNotFocused() && isInFocus()) { return; } - soundManager.playSoundEvent(eventId); + SoundManager.playSoundEvent(eventId); } } @@ -689,11 +684,10 @@ private static void initialize() { eventDispatcher = new EventDispatcher(); registerEvents(); - soundManager = new SoundManager(); try { - soundManager.configure(SOUND_PROPERTIES); - soundManager.registerSoundEvent( - SND_INVALID_OPERATION, soundManager.getRegisteredSound("Dink")); + SoundManager.configure(SOUND_PROPERTIES); + SoundManager.registerSoundEvent( + SND_INVALID_OPERATION, SoundManager.getRegisteredSound("Dink")); } catch (IOException ioe) { MapTool.showError("While initializing (configuring sound)", ioe); } @@ -828,12 +822,25 @@ public static ObservableList getMessageList() { return messageList; } - /** These are the messages that originate from the server */ + /** + * These are the messages that originate from the server + * + * @param message the message to display + */ public static void addServerMessage(TextMessage message) { // Filter if (message.isGM() && !getPlayer().isGM()) { return; } + if (message.isGmMe() && !getPlayer().isGM() && !message.isFromSelf()) { + return; + } + if ((message.isNotGm() || message.isNotGmMe()) && getPlayer().isGM()) { + return; + } + if ((message.isNotMe() || message.isNotGmMe()) && message.isFromSelf()) { + return; + } if (message.isWhisper() && !getPlayer().getName().equalsIgnoreCase(message.getTarget())) { return; } @@ -863,7 +870,7 @@ public static void addMessage(TextMessage message) { /** * Add a message only this client can see. This is a shortcut for addMessage(ME, ...) * - * @param message + * @param message message to be sent */ public static void addLocalMessage(String message) { addMessage(TextMessage.me(null, message)); @@ -872,7 +879,7 @@ public static void addLocalMessage(String message) { /** * Add a message all clients can see. This is a shortcut for addMessage(SAY, ...) * - * @param message + * @param message message to be sent */ public static void addGlobalMessage(String message) { addMessage(TextMessage.say(null, message)); @@ -904,15 +911,22 @@ public static void addGlobalMessage(String message, List targets) { for (String target : targets) { switch (target.toLowerCase()) { case "gm-self": - if (!MapTool.getPlayer().isGM()) { - // don't duplicate message if self is GM - addMessage(TextMessage.whisper(null, MapTool.getPlayer().getName(), message)); - } // FALLTHRU + addMessage(TextMessage.gmMe(null, message)); + break; case "gm": addMessage(TextMessage.gm(null, message)); break; case "self": - addMessage(TextMessage.whisper(null, MapTool.getPlayer().getName(), message)); + addLocalMessage(message); + break; + case "not-gm": + addMessage(TextMessage.notGm(null, message)); + break; + case "not-self": + addMessage(TextMessage.notMe(null, message)); + break; + case "not-gm-self": + addMessage(TextMessage.notGmMe(null, message)); break; case "all": addGlobalMessage(message); @@ -970,6 +984,7 @@ public static void setCampaign(Campaign campaign, GUID defaultRendererId) { AssetManager.updateRepositoryList(); MapTool.getFrame().getCampaignPanel().reset(); + MapTool.getFrame().getGmPanel().reset(); UserDefinedMacroFunctions.getInstance().loadCampaignLibFunctions(); } diff --git a/src/main/java/net/rptools/maptool/client/MapToolLineParser.java b/src/main/java/net/rptools/maptool/client/MapToolLineParser.java index 7b3f59af57..2e5fb1e4e8 100644 --- a/src/main/java/net/rptools/maptool/client/MapToolLineParser.java +++ b/src/main/java/net/rptools/maptool/client/MapToolLineParser.java @@ -67,6 +67,7 @@ public class MapToolLineParser { CurrentInitiativeFunction.getInstance(), DefineMacroFunction.getInstance(), EvalMacroFunctions.getInstance(), + ExecFunction.getInstance(), FindTokenFunctions.getInstance(), HasImpersonated.getInstance(), InitiativeRoundFunction.getInstance(), @@ -1625,6 +1626,19 @@ public String runMacro( } macroBody = mbp.getCommand(); macroContext = new MapToolMacroContext(macroName, "campaign", !mbp.getAllowPlayerEdits()); + } else if (macroLocation.equalsIgnoreCase("Gm")) { + MacroButtonProperties mbp = null; + for (MacroButtonProperties m : MapTool.getCampaign().getGmMacroButtonPropertiesArray()) { + if (m.getLabel().equals(macroName)) { + mbp = m; + break; + } + } + if (mbp == null) { + throw new ParserException(I18N.getText("lineParser.unknownCampaignMacro", macroName)); + } + macroBody = mbp.getCommand(); + macroContext = new MapToolMacroContext(macroName, "Gm", MapTool.getPlayer().isGM()); } else if (macroLocation.equalsIgnoreCase("GLOBAL")) { macroContext = new MapToolMacroContext(macroName, "global", MapTool.getPlayer().isGM()); MacroButtonProperties mbp = null; @@ -1744,6 +1758,28 @@ private boolean isSecure(String macroName, Token token) { return true; } + /** + * Run a block of text as a macro. + * + * @param tokenInContext the token in context. + * @param macroBody the macro text to run. + * @param contextName the name of the macro context to use. + * @param contextSource the source of the macro block. + * @param trusted is the context trusted or not. + * @return the macro output. + */ + public String runMacroBlock( + Token tokenInContext, + String macroBody, + String contextName, + String contextSource, + boolean trusted) + throws ParserException { + MapToolVariableResolver resolver = new MapToolVariableResolver(tokenInContext); + MapToolMacroContext context = new MapToolMacroContext(contextName, contextSource, trusted); + return runMacroBlock(resolver, tokenInContext, macroBody, context); + } + /** Executes a string as a block of macro code. */ String runMacroBlock(MapToolVariableResolver resolver, Token tokenInContext, String macroBody) throws ParserException { diff --git a/src/main/java/net/rptools/maptool/client/MapToolVariableResolver.java b/src/main/java/net/rptools/maptool/client/MapToolVariableResolver.java index 62bef8dbe4..2cf6931ad7 100644 --- a/src/main/java/net/rptools/maptool/client/MapToolVariableResolver.java +++ b/src/main/java/net/rptools/maptool/client/MapToolVariableResolver.java @@ -18,22 +18,12 @@ import java.util.ArrayList; import java.util.List; import javax.swing.JOptionPane; -import net.rptools.maptool.client.functions.CurrentInitiativeFunction; -import net.rptools.maptool.client.functions.InitiativeRoundFunction; -import net.rptools.maptool.client.functions.JSONMacroFunctions; -import net.rptools.maptool.client.functions.TokenBarFunction; -import net.rptools.maptool.client.functions.TokenGMNameFunction; -import net.rptools.maptool.client.functions.TokenHaloFunction; -import net.rptools.maptool.client.functions.TokenInitFunction; -import net.rptools.maptool.client.functions.TokenInitHoldFunction; -import net.rptools.maptool.client.functions.TokenLabelFunction; -import net.rptools.maptool.client.functions.TokenNameFunction; -import net.rptools.maptool.client.functions.TokenStateFunction; -import net.rptools.maptool.client.functions.TokenVisibleFunction; +import net.rptools.maptool.client.functions.*; import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.GUID; import net.rptools.maptool.model.Token; import net.rptools.maptool.model.TokenProperty; +import net.rptools.maptool.util.FunctionUtil; import net.rptools.parser.MapVariableResolver; import net.rptools.parser.ParserException; import net.rptools.parser.VariableModifiers; @@ -163,31 +153,31 @@ public Object getVariable(String name, VariableModifiers mods) throws ParserExce if (name.startsWith(STATE_PREFIX)) { String stateName = name.substring(STATE_PREFIX.length()); - return TokenStateFunction.getInstance().getState(tokenInContext, stateName); + return TokenStateFunction.getState(tokenInContext, stateName); } else if (name.startsWith(BAR_PREFIX)) { String barName = name.substring(BAR_PREFIX.length()); - return TokenBarFunction.getInstance().getValue(getTokenInContext(), barName); + return TokenBarFunction.getValue(getTokenInContext(), barName); } else if (name.equals(TOKEN_HALO)) { // We don't want this evaluated as the # format is more useful to us then the // evaluated // format. - return TokenHaloFunction.getInstance().getHalo(tokenInContext).toString(); + return TokenHaloFunction.getHalo(tokenInContext).toString(); } else if (name.equals(TOKEN_NAME)) { // Don't evaluate return value. - return TokenNameFunction.getInstance().getName(tokenInContext); + return TokenNameFunction.getName(tokenInContext); } else if (name.equals(TOKEN_GMNAME)) { // Don't evaluate return value. - return TokenGMNameFunction.getInstance().getGMName(tokenInContext); + return TokenGMNameFunction.getGMName(tokenInContext); } else if (name.equals(TOKEN_LABEL)) { // Don't evaluate return value. - return TokenLabelFunction.getInstance().getLabel(tokenInContext); + return TokenLabelFunction.getLabel(tokenInContext); } else if (name.equals(TOKEN_VISIBLE)) { // Don't evaluate return value. - return TokenVisibleFunction.getInstance().getVisible(tokenInContext); + return TokenVisibleFunction.getVisible(tokenInContext); } else if (name.equals(TOKEN_INITIATIVE)) { - return TokenInitFunction.getInstance().getTokenValue(tokenInContext); + return TokenInitFunction.getInitiative(tokenInContext); } else if (name.equals(TOKEN_INITIATIVE_HOLD)) { - return TokenInitHoldFunction.getInstance().getTokenValue(tokenInContext); + return TokenInitHoldFunction.getInitiativeHold(tokenInContext); } // endif if (this.validTokenProperty(name, tokenInContext)) { @@ -213,9 +203,9 @@ public Object getVariable(String name, VariableModifiers mods) throws ParserExce if (name.equals(INITIATIVE_CURRENT)) { if (!MapTool.getFrame().getInitiativePanel().hasGMPermission()) throw new ParserException(I18N.getText("lineParser.onlyGMCanGet", INITIATIVE_CURRENT)); - return CurrentInitiativeFunction.getInstance().getCurrentInitiative(); + return CurrentInitiativeFunction.getCurrentInitiative(); } else if (name.equals(INITIATIVE_ROUND)) { - return InitiativeRoundFunction.getInstance().getInitiativeRound(); + return InitiativeRoundFunction.getInitiativeRound(); } // endif } @@ -295,59 +285,54 @@ public void setVariable(String varname, VariableModifiers modifiers, Object valu throws ParserException { if (tokenInContext != null) { if (validTokenProperty(varname, tokenInContext)) { - tokenInContext.setProperty(varname, value.toString()); - addDelayedAction( - new PutTokenAction( - MapTool.getFrame().getCurrentZoneRenderer().getZone().getId(), tokenInContext)); - return; + MapTool.serverCommand() + .updateTokenProperty(tokenInContext, "setProperty", varname, value.toString()); } } // Check to see if it is a token state. if (varname.startsWith(STATE_PREFIX)) { String stateName = varname.substring(STATE_PREFIX.length()); - TokenStateFunction.getInstance().setState(tokenInContext, stateName, value); - addDelayedAction( - new PutTokenAction( - MapTool.getFrame().getCurrentZoneRenderer().getZone().getId(), tokenInContext)); + TokenStateFunction.setState(tokenInContext, stateName, value); return; } else if (varname.startsWith(BAR_PREFIX)) { String barName = varname.substring(BAR_PREFIX.length()); - TokenBarFunction.getInstance().setValue(tokenInContext, barName, value); + TokenBarFunction.setValue(tokenInContext, barName, value); return; } else if (varname.equals(TOKEN_HALO)) { - TokenHaloFunction.getInstance().setHalo(tokenInContext, value); + TokenHaloFunction.setHalo(tokenInContext, value); return; } else if (varname.equals(TOKEN_NAME)) { if (value.toString().equals("")) { throw new ParserException(I18N.getText("lineParser.emptyTokenName")); } - TokenNameFunction.getInstance().setName(tokenInContext, value.toString()); + TokenNameFunction.setName(tokenInContext, value.toString()); return; } else if (varname.equals(TOKEN_GMNAME)) { - TokenGMNameFunction.getInstance().setGMName(tokenInContext, value.toString()); + TokenGMNameFunction.setGMName(tokenInContext, value.toString()); return; } else if (varname.equals(TOKEN_LABEL)) { - TokenLabelFunction.getInstance().setLabel(tokenInContext, value.toString()); + TokenLabelFunction.setLabel(tokenInContext, value.toString()); return; } else if (varname.endsWith(TOKEN_VISIBLE)) { - TokenVisibleFunction.getInstance().setVisible(tokenInContext, value.toString()); + TokenVisibleFunction.setVisible(tokenInContext, value.toString()); return; } else if (varname.equals(TOKEN_INITIATIVE)) { - TokenInitFunction.getInstance().setTokenValue(tokenInContext, value); + TokenInitFunction.setInitiative(tokenInContext, value.toString()); return; } else if (varname.equals(TOKEN_INITIATIVE_HOLD)) { - TokenInitHoldFunction.getInstance().setTokenValue(tokenInContext, value); + boolean set = FunctionUtil.getBooleanValue(value); + TokenInitHoldFunction.setInitiativeHold(tokenInContext, set); return; } else if (varname.equals(INITIATIVE_CURRENT)) { if (!MapTool.getFrame().getInitiativePanel().hasGMPermission()) throw new ParserException(I18N.getText("lineParser.onlyGMCanSet", INITIATIVE_CURRENT)); - CurrentInitiativeFunction.getInstance().setCurrentInitiative(value); + CurrentInitiativeFunction.setCurrentInitiative(value); return; } else if (varname.equals(INITIATIVE_ROUND)) { if (!MapTool.getFrame().getInitiativePanel().hasGMPermission()) throw new ParserException(I18N.getText("lineParser.onlyGMCanSet", INITIATIVE_ROUND)); - InitiativeRoundFunction.getInstance().setInitiativeRound(value); + InitiativeRoundFunction.setInitiativeRound(value); return; } super.setVariable(varname, modifiers, value); @@ -394,8 +379,8 @@ private void setBooleanTokenState(Token token, String stateName, Object val) { } catch (NumberFormatException e) { set = Boolean.parseBoolean(val.toString()); } - token.setState(stateName, set); } + token.setState(stateName, set); } /** diff --git a/src/main/java/net/rptools/maptool/client/ServerCommandClientImpl.java b/src/main/java/net/rptools/maptool/client/ServerCommandClientImpl.java index 9ec130ebe2..19c1cac37e 100644 --- a/src/main/java/net/rptools/maptool/client/ServerCommandClientImpl.java +++ b/src/main/java/net/rptools/maptool/client/ServerCommandClientImpl.java @@ -19,6 +19,8 @@ import java.util.Set; import java.util.concurrent.LinkedBlockingQueue; import net.rptools.lib.MD5Key; +import net.rptools.maptool.client.functions.ExecFunction; +import net.rptools.maptool.client.functions.MacroLinkFunction; import net.rptools.maptool.model.Asset; import net.rptools.maptool.model.AssetManager; import net.rptools.maptool.model.Campaign; @@ -77,6 +79,12 @@ public void setCampaign(Campaign campaign) { } } + public void setCampaignName(String name) { + MapTool.getCampaign().setName(name); + MapTool.getFrame().setTitle(); + makeServerCall(COMMAND.setCampaignName, name); + } + public void setVisionType(GUID zoneGUID, VisionType visionType) { makeServerCall(COMMAND.setVisionType, zoneGUID, visionType); } @@ -200,8 +208,21 @@ public void message(TextMessage message) { } @Override - public void execLink(String link, String target) { - makeServerCall(COMMAND.execLink, link, target); + public void execFunction(String functionText, String target, String source) { + ExecFunction.receiveExecFunction(functionText, target, source); // receive locally right away + + if (ExecFunction.isMessageGlobal(target, source)) { + makeServerCall(COMMAND.execFunction, functionText, target, source); + } + } + + @Override + public void execLink(String link, String target, String source) { + MacroLinkFunction.receiveExecLink(link, target, source); // receive locally right away + + if (ExecFunction.isMessageGlobal(target, source)) { + makeServerCall(COMMAND.execLink, link, target, source); + } } public void showPointer(String player, Pointer pointer) { @@ -296,6 +317,10 @@ public void updateCampaignMacros(List properties) { makeServerCall(COMMAND.updateCampaignMacros, properties); } + public void updateGmMacros(List properties) { + makeServerCall(COMMAND.updateGmMacros, properties); + } + public void clearExposedArea(GUID zoneGUID) { // System.out.println("in ServerCommandClientImpl"); makeServerCall(COMMAND.clearExposedArea, zoneGUID); diff --git a/src/main/java/net/rptools/maptool/client/functions/AbstractTokenAccessorFunction.java b/src/main/java/net/rptools/maptool/client/functions/AbstractTokenAccessorFunction.java deleted file mode 100644 index ccb123144f..0000000000 --- a/src/main/java/net/rptools/maptool/client/functions/AbstractTokenAccessorFunction.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.functions; - -import java.math.BigDecimal; -import java.util.List; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.MapToolVariableResolver; -import net.rptools.maptool.language.I18N; -import net.rptools.maptool.model.GUID; -import net.rptools.maptool.model.Token; -import net.rptools.parser.Parser; -import net.rptools.parser.ParserException; -import net.rptools.parser.function.AbstractFunction; - -/** - * Support for accessing getters and setters on tokens using a function - * - * @author Jay - */ -public abstract class AbstractTokenAccessorFunction extends AbstractFunction { - - /** - * @param minParameters Maximum number of parameters allowed on a function call - * @param maxParameters Minimum number of parameters allowed on a function call - * @param aliases All function names handled by this instance. - */ - public AbstractTokenAccessorFunction(int minParameters, int maxParameters, String... aliases) { - super(minParameters, maxParameters, aliases); - } - - /** - * External call to get the token value. - * - * @param token Get the value from this token - * @return Get the value from the token. - * @throws ParserException Error setting value - */ - public Object getTokenValue(Token token) throws ParserException { - return getValue(token); - } - - /** - * External call to set the token value - * - * @param token Set this token - * @param value To this value. - * @return The new value of the token variable. - * @throws ParserException Error setting value - */ - public Object setTokenValue(Token token, Object value) throws ParserException { - Object ret = setValue(token, value); - MapTool.serverCommand() - .putToken(MapTool.getFrame().getCurrentZoneRenderer().getZone().getId(), token); - return ret; - } - - /** - * Get the token that is being modified. - * - * @param parser Parser being evaluated. - * @param args parameters to the function. - * @param count Number of parameters expected if the token GUID was passed. - * @return The token being modified. - * @throws ParserException Unable to get a token. - */ - public static Token getTarget(Parser parser, List args, int count) - throws ParserException { - Token token = null; - if ((args.size() == count || !args.isEmpty() && count < 0) && args.get(0) instanceof GUID) { - GUID guid = (GUID) args.get(0); - args.remove(0); - token = MapTool.getFrame().getCurrentZoneRenderer().getZone().getToken(guid); - if (token == null) - throw new ParserException( - I18N.getText( - "macro.function.initiative.unknownToken", - guid, - MapTool.getFrame().getCurrentZoneRenderer().getZone().getName())); - } else { - token = ((MapToolVariableResolver) parser.getVariableResolver()).getTokenInContext(); - if (token == null) - throw new ParserException(I18N.getText("macro.function.initiative.noImpersonated")); - } // endif - return token; - } - - /** - * @see net.rptools.parser.function.AbstractFunction#childEvaluate(net.rptools.parser.Parser, - * java.lang.String, java.util.List) - */ - @Override - public Object childEvaluate(Parser parser, String functionName, List parameters) - throws ParserException { - Token token = getTarget(parser, parameters, functionName.startsWith("set") ? 2 : 1); - if (functionName.startsWith("set")) { - return setTokenValue(token, parameters.get(0)); - } // endif - return getTokenValue(token); - } - - /** - * @param token Get the value from this token - * @return Get the value from the token. - * @throws ParserException Error getting value - */ - protected abstract Object getValue(Token token) throws ParserException; - - /** - * @param token Set this token - * @param value To this value. - * @return The value that was set - * @throws ParserException Error setting value - */ - protected abstract Object setValue(Token token, Object value) throws ParserException; - - /** - * Convert an object into a boolean value. - * - * @param value Convert this object. Must be {@link Boolean}, {@link BigDecimal}, or a can have - * its string value be converted to one of those types. - * @return The boo - */ - public static boolean getBooleanValue(Object value) { - boolean set = false; - if (value instanceof Boolean) { - set = ((Boolean) value).booleanValue(); - } else if (value instanceof Number) { - set = ((Number) value).doubleValue() != 0; - } else if (value == null) { - set = false; - } else { - try { - set = !new BigDecimal(value.toString()).equals(BigDecimal.ZERO); - } catch (NumberFormatException e) { - set = Boolean.parseBoolean(value.toString()); - } // endif - } // endif - return set; - } -} diff --git a/src/main/java/net/rptools/maptool/client/functions/AddAllToInitiativeFunction.java b/src/main/java/net/rptools/maptool/client/functions/AddAllToInitiativeFunction.java index 4429a532d4..55fd717b7e 100644 --- a/src/main/java/net/rptools/maptool/client/functions/AddAllToInitiativeFunction.java +++ b/src/main/java/net/rptools/maptool/client/functions/AddAllToInitiativeFunction.java @@ -22,6 +22,7 @@ import net.rptools.maptool.model.InitiativeList; import net.rptools.maptool.model.Token; import net.rptools.maptool.model.Token.Type; +import net.rptools.maptool.util.FunctionUtil; import net.rptools.parser.Parser; import net.rptools.parser.ParserException; import net.rptools.parser.function.AbstractFunction; @@ -61,7 +62,7 @@ public Object childEvaluate(Parser parser, String functionName, List arg InitiativeList list = MapTool.getFrame().getCurrentZoneRenderer().getZone().getInitiativeList(); boolean allowDuplicates = false; if (!args.isEmpty()) { - allowDuplicates = TokenInitFunction.getBooleanValue(args.get(0)); + allowDuplicates = FunctionUtil.getBooleanValue(args.get(0)); args.remove(0); } // endif diff --git a/src/main/java/net/rptools/maptool/client/functions/CurrentInitiativeFunction.java b/src/main/java/net/rptools/maptool/client/functions/CurrentInitiativeFunction.java index 2b7161f3a0..024a4c8f6b 100644 --- a/src/main/java/net/rptools/maptool/client/functions/CurrentInitiativeFunction.java +++ b/src/main/java/net/rptools/maptool/client/functions/CurrentInitiativeFunction.java @@ -72,7 +72,7 @@ public Object childEvaluate(Parser parser, String functionName, List arg * * @return The current initiative */ - public Object getInitiativeToken() { + public static Object getInitiativeToken() { InitiativeList list = MapTool.getFrame().getCurrentZoneRenderer().getZone().getInitiativeList(); int index = list.getCurrent(); return index != -1 ? list.getToken(index).getId().toString() : ""; @@ -83,7 +83,7 @@ public Object getInitiativeToken() { * * @return The current initiative */ - public Object getCurrentInitiative() { + public static Object getCurrentInitiative() { InitiativeList list = MapTool.getFrame().getCurrentZoneRenderer().getZone().getInitiativeList(); return new BigDecimal(list.getCurrent()); } @@ -93,7 +93,7 @@ public Object getCurrentInitiative() { * * @param value New value for the round. */ - public void setCurrentInitiative(Object value) { + public static void setCurrentInitiative(Object value) { InitiativeList list = MapTool.getFrame().getCurrentZoneRenderer().getZone().getInitiativeList(); list.setCurrent(InitiativeRoundFunction.getInt(value)); } diff --git a/src/main/java/net/rptools/maptool/client/functions/DrawingFunctions.java b/src/main/java/net/rptools/maptool/client/functions/DrawingFunctions.java index fcb1c07dce..91e13a424c 100644 --- a/src/main/java/net/rptools/maptool/client/functions/DrawingFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/DrawingFunctions.java @@ -41,6 +41,7 @@ import net.rptools.maptool.model.drawing.LineSegment; import net.rptools.maptool.model.drawing.Pen; import net.rptools.maptool.model.drawing.ShapeDrawable; +import net.rptools.maptool.util.FunctionUtil; import net.rptools.parser.Parser; import net.rptools.parser.ParserException; import net.rptools.parser.function.AbstractFunction; @@ -379,7 +380,7 @@ private List pathToJSON(AbstractDrawing d) { protected boolean parseBoolean(String functionName, List args, int param) throws ParserException { try { - return AbstractTokenAccessorFunction.getBooleanValue(args.get(param)); + return FunctionUtil.getBooleanValue(args.get(param)); } catch (NumberFormatException ne) { throw new ParserException( I18N.getText( diff --git a/src/main/java/net/rptools/maptool/client/functions/ExecFunction.java b/src/main/java/net/rptools/maptool/client/functions/ExecFunction.java new file mode 100644 index 0000000000..387662ecc8 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/functions/ExecFunction.java @@ -0,0 +1,219 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.functions; + +import java.awt.*; +import java.util.*; +import java.util.List; +import net.rptools.maptool.client.MapTool; +import net.rptools.maptool.language.I18N; +import net.rptools.maptool.util.FunctionUtil; +import net.rptools.parser.Parser; +import net.rptools.parser.ParserException; +import net.rptools.parser.function.AbstractFunction; +import net.rptools.parser.function.Function; +import net.rptools.parser.function.ParameterException; +import net.sf.json.JSONArray; + +public class ExecFunction extends AbstractFunction { + + /** Singleton instance of the ExecFunction class. */ + private static final ExecFunction instance = new ExecFunction(); + + /** + * Gets and instance of the ExecFunction class. + * + * @return an instance of ExecFunction. + */ + public static ExecFunction getInstance() { + return instance; + } + + private ExecFunction() { + super(0, UNLIMITED_PARAMETERS, "execFunction"); + } + + @Override + public Object childEvaluate(Parser parser, String functionName, List args) + throws ParserException { + FunctionUtil.checkNumberParam(functionName, args, 2, 5); + if (!MapTool.getParser().isMacroTrusted()) { + throw new ParserException(I18N.getText("macro.function.general.noPerm", functionName)); + } + + String execName = args.get(0).toString(); + Function function = parser.getFunction(execName); + if (function == null) { + throw new ParameterException( + I18N.getText("macro.function.execFunction.incorrectName", functionName, execName)); + } + + JSONArray jsonArgs = FunctionUtil.paramAsJsonArray(functionName, args, 1); + @SuppressWarnings("unchecked") + ArrayList execArgs = new ArrayList(jsonArgs); + + boolean defer = + args.size() > 2 ? FunctionUtil.paramAsBoolean(functionName, args, 2, true) : false; + + String strTargets = args.size() > 3 ? args.get(3).toString() : "self"; + String delim = args.size() > 4 ? args.get(4).toString() : ","; + + JSONArray jsonTargets; + if ("json".equals(delim) || strTargets.charAt(0) == '[') { + jsonTargets = JSONArray.fromObject(strTargets); + } else { + jsonTargets = new JSONArray(); + for (String t : strTargets.split(delim)) jsonTargets.add(t.trim()); + } + if (jsonTargets.isEmpty()) { + return ""; // dont send to empty lists + } + + @SuppressWarnings("unchecked") + // Returns an ArrayList + Collection targets = JSONArray.toCollection(jsonTargets, List.class); + + sendExecFunction(execName, execArgs, defer, targets); + return ""; + } + + /** + * Send the execFunction to targets, either immediately or with a delay + * + * @param execName the name of the function to execute + * @param defer should the execFunction be delayed + * @param targets the list of targets + */ + private static void sendExecFunction( + final String execName, List execArgs, boolean defer, Collection targets) { + if (defer) { + EventQueue.invokeLater( + new Runnable() { + public void run() { + sendExecFunction(execName, execArgs, targets); + } + }); + } else { + sendExecFunction(execName, execArgs, targets); + } + } + + /** + * Send the execFunction. If target is local, run locally instead. + * + * @param execName the name of the function. + * @param execArgs the list of arguments to the function. + * @param targets the list of targets. + */ + private static void sendExecFunction( + final String execName, List execArgs, Collection targets) { + String functionText = getExecFunctionText(execName, execArgs); + String source = MapTool.getPlayer().getName(); + + for (String target : targets) { + MapTool.serverCommand().execFunction(functionText, target, source); + } + } + + /** + * Receive the execFunction, and execute it if need be. + * + * @param functionText the text of the function call. + * @param target the target player. + * @param source the name of the player who sent the link. + */ + public static void receiveExecFunction(final String functionText, String target, String source) { + if (isMessageForMe(target, source)) { + runExecFunction(functionText); + } + } + + /** + * Determines if the message / execLink / execFunction should be ran on the client. + * + * @param target the target player. + * @param source the name of the player who sent the link. + * @return is the message for the player or not + */ + public static boolean isMessageForMe(String target, String source) { + boolean isGM = MapTool.getPlayer().isGM(); + boolean fromSelf = source.equals(MapTool.getPlayer().getName()); + boolean targetSelf = target.equals(MapTool.getPlayer().getName()); + + switch (target.toLowerCase()) { + case "gm": + return isGM; + case "self": + return fromSelf; + case "gm-self": + return isGM || fromSelf; + case "not-self": + return !fromSelf; + case "not-gm": + return !isGM; + case "not-gm-self": + return !isGM && !fromSelf; + case "none": + return false; + case "all": + return true; + default: + return targetSelf; + } + } + + public static boolean isMessageGlobal(String target, String source) { + if (target.equals(source)) return false; + if (target.equalsIgnoreCase("none")) return false; + if (target.equalsIgnoreCase("self")) return false; + return true; + } + + /** + * Get a String corresponding to the function call from the function name and list of arguments. + * The text is then intended to be run through runMacroBlock. This is a workaround as it is not + * currently possible to run a macro directly as doing so would require a reference to the parser, + * which is not available to us. + * + * @param execName the name of the function. + * @param execArgs a list of arguments to the function. + * @return the string of the function call. + */ + private static String getExecFunctionText(final String execName, List execArgs) { + StringBuilder functionText = new StringBuilder("[h:" + execName + "("); + + for (int i = 0; i < execArgs.size(); i++) { + if (execArgs.get(i) instanceof String) { + functionText.append('"').append(execArgs.get(i)).append('"'); + } else { + functionText.append(execArgs.get(i).toString()); + } + + if (i < (execArgs.size() - 1)) { + functionText.append(","); + } else { + functionText.append(")]"); + } + } + return functionText.toString(); + } + + private static void runExecFunction(final String functionText) { + try { + MapTool.getParser().runMacroBlock(null, functionText, "execFunction", "remote", true); + } catch (ParserException ignored) { + } + } +} diff --git a/src/main/java/net/rptools/maptool/client/functions/FindTokenFunctions.java b/src/main/java/net/rptools/maptool/client/functions/FindTokenFunctions.java index 4bc0a0a13a..e7ea79b20e 100644 --- a/src/main/java/net/rptools/maptool/client/functions/FindTokenFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/FindTokenFunctions.java @@ -14,6 +14,8 @@ */ package net.rptools.maptool.client.functions; +import static net.rptools.maptool.client.functions.JSONMacroFunctions.convertToJSON; + import java.awt.*; import java.math.BigDecimal; import java.util.*; @@ -22,10 +24,8 @@ import net.rptools.maptool.client.MapToolVariableResolver; import net.rptools.maptool.client.ui.zone.ZoneRenderer; import net.rptools.maptool.language.I18N; -import net.rptools.maptool.model.CellPoint; -import net.rptools.maptool.model.GUID; -import net.rptools.maptool.model.Token; -import net.rptools.maptool.model.Zone; +import net.rptools.maptool.model.*; +import net.rptools.maptool.util.FunctionUtil; import net.rptools.parser.Parser; import net.rptools.parser.ParserException; import net.rptools.parser.function.AbstractFunction; @@ -95,6 +95,30 @@ public boolean matchToken(Token t) { } } + /** Filter for Light */ + private class LightFilter implements Zone.Filter { + private final String type; + private final String name; + private final boolean match; + + public LightFilter(String type, String name, boolean match) { + this.type = type; + this.name = name; + this.match = match; + } + + @Override + public boolean matchToken(Token t) { + try { + return match == TokenLightFunctions.hasLightSource(t, type, name); + } catch (ParserException e) { + // Should not happen: a test was done already + MapTool.showError(e.getLocalizedMessage()); + return false; + } + } + } + /** Filter for player exposed tokens. */ private class ExposedFilter implements Zone.Filter { private final Zone zone; @@ -233,11 +257,7 @@ public Object childEvaluate(Parser parser, String functionName, List par } } if (functionName.equals("findToken")) { - if (parameters.size() < 1) { - throw new ParserException( - I18N.getText( - "macro.function.general.notEnoughParam", functionName, 1, parameters.size())); - } + FunctionUtil.checkNumberParam(functionName, parameters, 1, 2); String mapName = parameters.size() > 1 ? parameters.get(1).toString() : null; return findTokenId(parameters.get(0).toString(), mapName); } @@ -245,43 +265,43 @@ public Object childEvaluate(Parser parser, String functionName, List par FindType findType; String findArgs = null; if (functionName.equals("currentToken")) { + FunctionUtil.checkNumberParam(functionName, parameters, 0, 0); findType = FindType.CURRENT; } else if (functionName.startsWith("getSelected")) { + FunctionUtil.checkNumberParam(functionName, parameters, 0, 1); findType = FindType.SELECTED; delim = !parameters.isEmpty() ? parameters.get(0).toString() : delim; } else if (functionName.startsWith("getImpersonated")) { + FunctionUtil.checkNumberParam(functionName, parameters, 0, 0); findType = FindType.IMPERSONATED; } else if (functionName.startsWith("getPC")) { + FunctionUtil.checkNumberParam(functionName, parameters, 0, 1); findType = FindType.PC; delim = !parameters.isEmpty() ? parameters.get(0).toString() : delim; } else if (functionName.startsWith("getNPC")) { + FunctionUtil.checkNumberParam(functionName, parameters, 0, 1); findType = FindType.NPC; delim = !parameters.isEmpty() ? parameters.get(0).toString() : delim; } else if (functionName.startsWith("getToken")) { + FunctionUtil.checkNumberParam(functionName, parameters, 0, 2); findType = FindType.ALL; delim = !parameters.isEmpty() ? parameters.get(0).toString() : delim; } else if (functionName.startsWith("getExposedToken")) { + FunctionUtil.checkNumberParam(functionName, parameters, 0, 1); findType = FindType.EXPOSED; delim = !parameters.isEmpty() ? parameters.get(0).toString() : delim; } else if (functionName.startsWith("getWithState")) { - if (parameters.size() < 1) { - throw new ParserException( - I18N.getText( - "macro.function.general.notEnoughParam", functionName, 1, parameters.size())); - } + FunctionUtil.checkNumberParam(functionName, parameters, 1, 2); findType = FindType.STATE; findArgs = parameters.get(0).toString(); delim = parameters.size() > 1 ? parameters.get(1).toString() : delim; } else if (functionName.startsWith("getOwned")) { - if (parameters.size() < 1) { - throw new ParserException( - I18N.getText( - "macro.function.general.notEnoughParam", functionName, 1, parameters.size())); - } + FunctionUtil.checkNumberParam(functionName, parameters, 1, 2); findType = FindType.OWNED; findArgs = parameters.get(0).toString(); delim = parameters.size() > 1 ? parameters.get(1).toString() : delim; } else if (functionName.startsWith("getVisibleToken")) { + FunctionUtil.checkNumberParam(functionName, parameters, 0, 1); findType = FindType.VISIBLE; delim = parameters.size() > 0 ? parameters.get(0).toString() : delim; } else { @@ -309,7 +329,7 @@ public Object childEvaluate(Parser parser, String functionName, List par * @param delim either json or a string delimiter between output entries * @param jsonString incoming JSON data structure to filter results * @return list of filtered tokens - * @throws ParserException + * @throws ParserException if a condition is incorrect */ private Object getTokenList(Parser parser, boolean nameOnly, String delim, String jsonString) throws ParserException { @@ -331,7 +351,23 @@ private Object getTokenList(Parser parser, boolean nameOnly, String delim, Strin layers.add(o.toString()); } } - Zone zone = MapTool.getFrame().getCurrentZoneRenderer().getZone(); + ZoneRenderer zoneRenderer; + String mapName; + if (!jobj.containsKey("mapName")) { + mapName = null; // set to null so findToken searches the current map + zoneRenderer = MapTool.getFrame().getCurrentZoneRenderer(); + } else { + mapName = jobj.get("mapName").toString(); + zoneRenderer = MapTool.getFrame().getZoneRenderer(mapName); + if (zoneRenderer == null) { + throw new ParserException( + I18N.getText( + "macro.function.moveTokenMap.unknownMap", + nameOnly ? "getTokenNames" : "getTokens", + mapName)); + } + } + Zone zone = zoneRenderer.getZone(); allTokens = zone.getTokensFiltered(new LayerFilter(layers)); List tokenList = new ArrayList(allTokens.size()); tokenList.addAll(allTokens); @@ -353,7 +389,8 @@ private Object getTokenList(Parser parser, boolean nameOnly, String delim, Strin match = "setStates".equalsIgnoreCase(searchType); // Looking for tokens that either match or don't match the states for (Object item : states) { - tokenList = getTokenList(parser, FindType.STATE, item.toString(), match, tokenList); + tokenList = + getTokenList(parser, FindType.STATE, item.toString(), match, tokenList, zoneRenderer); } } else if ("range".equalsIgnoreCase(searchType)) { // We will do this as one of the last steps as it's one of the most expensive so we want to @@ -374,23 +411,60 @@ private Object getTokenList(Parser parser, boolean nameOnly, String delim, Strin types.add(o.toString()); } tokenList = getTokensFiltered(new PropertyTypeFilter(types), tokenList); + } else if ("light".equalsIgnoreCase(searchType)) { + String value = jobj.get(searchType).toString(); + String type, name; + if ("true".equalsIgnoreCase(value) || "1".equals(value)) { + match = true; + type = name = "*"; + } else if ("false".equalsIgnoreCase(value) || "0".equals(value)) { + match = false; + type = name = "*"; + } else { + Object jsonLight = convertToJSON(value); + if (jsonLight instanceof JSONObject) { + JSONObject jobjLight = (JSONObject) jsonLight; + match = !jobjLight.has("value") || FunctionUtil.getBooleanValue(jobjLight.get("value")); + type = jobjLight.has("category") ? jobjLight.get("category").toString() : "*"; + name = jobjLight.has("name") ? jobjLight.get("name").toString() : "*"; + + Map> lightSourcesMap = + MapTool.getCampaign().getLightSourcesMap(); + + if (!"*".equals(type) && !lightSourcesMap.containsKey(type)) + throw new ParserException( + I18N.getText("macro.function.tokenLight.unknownLightType", "light", type)); + + } else { + throw new ParserException( + I18N.getText("macro.function.json.onlyObject", value.toString(), "light")); + } + } + tokenList = getTokensFiltered(new LightFilter(type, name, match), tokenList); } else { match = booleanCheck(jobj, searchType); if ("npc".equalsIgnoreCase(searchType)) { - tokenList = getTokenList(parser, FindType.NPC, "", match, tokenList); + tokenList = getTokenList(parser, FindType.NPC, "", match, tokenList, zoneRenderer); } else if ("pc".equalsIgnoreCase(searchType)) { - tokenList = getTokenList(parser, FindType.PC, "", match, tokenList); + tokenList = getTokenList(parser, FindType.PC, "", match, tokenList, zoneRenderer); } else if ("selected".equalsIgnoreCase(searchType)) { - tokenList = getTokenList(parser, FindType.SELECTED, "", match, tokenList); + tokenList = getTokenList(parser, FindType.SELECTED, "", match, tokenList, zoneRenderer); } else if ("visible".equalsIgnoreCase(searchType)) { - tokenList = getTokenList(parser, FindType.VISIBLE, "", match, tokenList); + tokenList = getTokenList(parser, FindType.VISIBLE, "", match, tokenList, zoneRenderer); } else if ("owned".equalsIgnoreCase(searchType)) { tokenList = - getTokenList(parser, FindType.OWNED, MapTool.getPlayer().getName(), match, tokenList); + getTokenList( + parser, + FindType.OWNED, + MapTool.getPlayer().getName(), + match, + tokenList, + zoneRenderer); } else if ("current".equalsIgnoreCase(searchType)) { - tokenList = getTokenList(parser, FindType.CURRENT, "", match, tokenList); + tokenList = getTokenList(parser, FindType.CURRENT, "", match, tokenList, zoneRenderer); } else if ("impersonated".equalsIgnoreCase(searchType)) { - tokenList = getTokenList(parser, FindType.IMPERSONATED, "", match, tokenList); + tokenList = + getTokenList(parser, FindType.IMPERSONATED, "", match, tokenList, zoneRenderer); } } } @@ -400,7 +474,7 @@ private Object getTokenList(Parser parser, boolean nameOnly, String delim, Strin TokenLocationFunctions instance = TokenLocationFunctions.getInstance(); Token token; if (range.containsKey("token")) { - token = findToken(range.getString("token"), null); + token = findToken(range.getString("token"), mapName); if (token == null) { throw new ParserException( I18N.getText( @@ -408,9 +482,8 @@ private Object getTokenList(Parser parser, boolean nameOnly, String delim, Strin } } else { GUID guid = MapTool.getFrame().getCommandPanel().getIdentityGUID(); - if (guid != null) - token = MapTool.getFrame().getCurrentZoneRenderer().getZone().getToken(guid); - else token = findToken(MapTool.getFrame().getCommandPanel().getIdentity(), null); + if (guid != null) token = zone.getToken(guid); + else token = findToken(MapTool.getFrame().getCommandPanel().getIdentity(), mapName); if (token == null) { throw new ParserException( I18N.getText("macro.function.general.noImpersonated", "getTokens")); @@ -449,7 +522,7 @@ private Object getTokenList(Parser parser, boolean nameOnly, String delim, Strin TokenLocationFunctions instance = TokenLocationFunctions.getInstance(); Token token; if (area.containsKey("token")) { - token = findToken(area.getString("token"), null); + token = findToken(area.getString("token"), mapName); if (token == null) { throw new ParserException( I18N.getText( @@ -457,9 +530,8 @@ private Object getTokenList(Parser parser, boolean nameOnly, String delim, Strin } } else { GUID guid = MapTool.getFrame().getCommandPanel().getIdentityGUID(); - if (guid != null) - token = MapTool.getFrame().getCurrentZoneRenderer().getZone().getToken(guid); - else token = findToken(MapTool.getFrame().getCommandPanel().getIdentity(), null); + if (guid != null) token = zone.getToken(guid); + else token = findToken(MapTool.getFrame().getCommandPanel().getIdentity(), mapName); if (token == null) { throw new ParserException( I18N.getText("macro.function.general.noImpersonated", "getTokens")); @@ -543,15 +615,20 @@ private boolean booleanCheck(JSONObject jobj, String searchType) { * @param findArgs additional argument for the search * @param match should the property match? true: only include matches, false: exclude matches * @param originalList the list of tokens to search from + * @param zoneRenderer the zone render of the map where the tokens are * @return tokenList satisfying the requirement */ private List getTokenList( - Parser parser, FindType findType, String findArgs, boolean match, List originalList) + Parser parser, + FindType findType, + String findArgs, + boolean match, + List originalList, + ZoneRenderer zoneRenderer) throws ParserException { List tokenList = new LinkedList(); if (originalList.size() == 0) return tokenList; - ZoneRenderer zoneRenderer = MapTool.getFrame().getCurrentZoneRenderer(); Zone zone = zoneRenderer.getZone(); switch (findType) { case ALL: @@ -575,7 +652,7 @@ private List getTokenList( case IMPERSONATED: Token t; GUID guid = MapTool.getFrame().getCommandPanel().getIdentityGUID(); - if (guid != null) t = MapTool.getFrame().getCurrentZoneRenderer().getZone().getToken(guid); + if (guid != null) t = zone.getToken(guid); else t = zone.resolveToken(MapTool.getFrame().getCommandPanel().getIdentity()); if (t != null) { tokenList = getTokensFiltered(Collections.singletonList(t), originalList, match); @@ -626,7 +703,7 @@ private static List getTokensFiltered( } /** - * Gets the names or ids of the tokens on the current map. + * Gets the names or ids of the tokens on a map. * * @param parser The parser that called the function. * @param findType The type of tokens to find. @@ -640,8 +717,10 @@ private String getTokens( Parser parser, FindType findType, boolean nameOnly, String delim, String findArgs) throws ParserException { ArrayList values = new ArrayList(); - Zone zone = MapTool.getFrame().getCurrentZoneRenderer().getZone(); - List tokens = getTokenList(parser, findType, findArgs, true, zone.getAllTokens()); + ZoneRenderer zoneRenderer = MapTool.getFrame().getCurrentZoneRenderer(); + Zone zone = zoneRenderer.getZone(); + List tokens = + getTokenList(parser, findType, findArgs, true, zone.getAllTokens(), zoneRenderer); if (tokens != null && !tokens.isEmpty()) { for (Token token : tokens) { @@ -660,10 +739,11 @@ private String getTokens( } /** - * Finds the specified token. + * Finds the specified token id. * * @param identifier the name of the token. - * @return the token. + * @param zoneName the name of the zone. + * @return the token Id, or a blank string if none found. */ private String findTokenId(String identifier, String zoneName) { Token token = findToken(identifier, zoneName); @@ -674,7 +754,8 @@ private String findTokenId(String identifier, String zoneName) { * Finds the specified token. * * @param identifier the name of the token. - * @return the token. + * @param zoneName the name of the zone. + * @return the token, or null if none found. */ public static Token findToken(String identifier, String zoneName) { if (zoneName == null || zoneName.length() == 0) { diff --git a/src/main/java/net/rptools/maptool/client/functions/InitiativeRoundFunction.java b/src/main/java/net/rptools/maptool/client/functions/InitiativeRoundFunction.java index 689dd41e19..4f24a79fcc 100644 --- a/src/main/java/net/rptools/maptool/client/functions/InitiativeRoundFunction.java +++ b/src/main/java/net/rptools/maptool/client/functions/InitiativeRoundFunction.java @@ -43,10 +43,6 @@ public static InitiativeRoundFunction getInstance() { return instance; } - /** - * @see net.rptools.parser.function.AbstractFunction#childEvaluate(net.rptools.parser.Parser, - * java.lang.String, java.util.List) - */ @Override public Object childEvaluate(Parser parser, String functionName, List args) throws ParserException { @@ -70,7 +66,7 @@ public Object childEvaluate(Parser parser, String functionName, List arg * * @return The initiative round */ - public Object getInitiativeRound() { + public static Object getInitiativeRound() { InitiativeList list = MapTool.getFrame().getCurrentZoneRenderer().getZone().getInitiativeList(); return new BigDecimal(list.getRound()); } @@ -80,7 +76,7 @@ public Object getInitiativeRound() { * * @param value New value for the round. */ - public void setInitiativeRound(Object value) { + public static void setInitiativeRound(Object value) { InitiativeList list = MapTool.getFrame().getCurrentZoneRenderer().getZone().getInitiativeList(); list.setRound(getInt(value)); } diff --git a/src/main/java/net/rptools/maptool/client/functions/JSONMacroFunctions.java b/src/main/java/net/rptools/maptool/client/functions/JSONMacroFunctions.java index eabd2837fc..49460f83bf 100644 --- a/src/main/java/net/rptools/maptool/client/functions/JSONMacroFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/JSONMacroFunctions.java @@ -14,7 +14,9 @@ */ package net.rptools.maptool.client.functions; +import com.google.gson.JsonElement; import com.jayway.jsonpath.*; +import com.jayway.jsonpath.spi.json.GsonJsonProvider; import java.math.BigDecimal; import java.util.*; import net.rptools.common.expression.ExpressionParser; @@ -38,6 +40,9 @@ public enum JSONObjectType { private static final JSONMacroFunctions instance = new JSONMacroFunctions(); + private static final Configuration jaywayConfig = + Configuration.builder().jsonProvider(new GsonJsonProvider()).build(); + private JSONMacroFunctions() { super( 1, @@ -103,7 +108,22 @@ public Object childEvaluate(Parser parser, String functionName, List par String path = parameters.get(1).toString(); try { - return JsonPath.parse(jsonStr).read(path); + JsonElement obj = JsonPath.using(jaywayConfig).parse(jsonStr).read(path); + if (obj.isJsonPrimitive()) { + try { + // Maybe it's a number. + BigDecimal bd = obj.getAsBigDecimal(); + return bd; + } catch (NumberFormatException e) { + // So, not a number. + // Doing a toString() was wrapping the returned string in quotes. + return obj.getAsString(); + } + } else { + // Curiously using getAsString() on JsonObjects threw an exception but using + // toString() works fine for objects and arrays. + return obj.toString(); + } } catch (Exception e) { throw new ParserException( I18N.getText("macro.function.json.path", functionName, e.getLocalizedMessage())); @@ -182,23 +202,41 @@ public Object childEvaluate(Parser parser, String functionName, List par if (functionName.equalsIgnoreCase("json.toVars")) { FunctionUtil.checkNumberParam(functionName, parameters, 1, 3); - JSONObject jsonObject = FunctionUtil.paramAsJsonObject(functionName, parameters, 0); - String prefix = parameters.size() > 1 ? parameters.get(1).toString() : ""; - String suffix = parameters.size() > 2 ? parameters.get(2).toString() : ""; - + Object json = FunctionUtil.paramAsJson(functionName, parameters, 0); JSONArray jsonNames = new JSONArray(); - for (Object keyStr : jsonObject.keySet()) { - // add prefix and suffix - String varName = prefix + keyStr.toString().trim() + suffix; + if (json instanceof JSONObject) { + JSONObject jsonObject = (JSONObject) json; + String prefix = parameters.size() > 1 ? parameters.get(1).toString() : ""; + String suffix = parameters.size() > 2 ? parameters.get(2).toString() : ""; + + for (Object keyStr : jsonObject.keySet()) { + // add prefix and suffix + String varName = prefix + keyStr.toString().trim() + suffix; + // replace spaces by underscores + varName = varName.replaceAll("\\s", "_"); + // delete special characters other than "." & "_" in var name + varName = varName.replaceAll("[^a-zA-Z0-9._]", ""); + + if (!varName.equals("")) { + parser.setVariable(varName, jsonObject.get(keyStr)); + jsonNames.add(varName); + } + } + } else { + FunctionUtil.checkNumberParam(functionName, parameters, 2, 2); + JSONArray jsonArray = (JSONArray) json; + + String varName = parameters.get(1).toString(); // replace spaces by underscores varName = varName.replaceAll("\\s", "_"); // delete special characters other than "." & "_" in var name varName = varName.replaceAll("[^a-zA-Z0-9._]", ""); if (!varName.equals("")) { - Object value = jsonObject.get(keyStr); - parser.setVariable(varName, value); - jsonNames.add(varName); + for (int i = 0; i < jsonArray.size(); i++) { + parser.setVariable(varName + i, jsonArray.get(i)); + jsonNames.add(varName + i); + } } } return jsonNames; diff --git a/src/main/java/net/rptools/maptool/client/functions/LogFunctions.java b/src/main/java/net/rptools/maptool/client/functions/LogFunctions.java index 865dbff119..d635bd1707 100644 --- a/src/main/java/net/rptools/maptool/client/functions/LogFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/LogFunctions.java @@ -32,6 +32,7 @@ /** Log functions to dynamically set log levels, log configurations, and log messages from macros */ public class LogFunctions extends AbstractFunction { + private static final Logger log = LogManager.getLogger(LogFunctions.class); private static final Logger logger = LogManager.getLogger("macro-logger"); @@ -48,7 +49,8 @@ private LogFunctions() { "log.error", "log.warn", "log.info", - "log.debug"); + "log.debug", + "log.trace"); } public static LogFunctions getInstance() { @@ -59,20 +61,22 @@ public static LogFunctions getInstance() { public Object childEvaluate(Parser parser, String functionName, List parameters) throws ParserException { - if (!MapTool.getParser().isMacroPathTrusted()) + if (!MapTool.getParser().isMacroPathTrusted()) { throw new ParserException(I18N.getText("macro.function.general.noPerm", functionName)); + } - if (functionName.equalsIgnoreCase("log.getLoggers")) + if (functionName.equalsIgnoreCase("log.getLoggers")) { return getLoggers(functionName, parameters); - else if (functionName.equalsIgnoreCase("log.setLevel")) + } else if (functionName.equalsIgnoreCase("log.setLevel")) { return setLogLevel(functionName, parameters); - else if (functionName.equalsIgnoreCase("log.setPattern")) + } else if (functionName.equalsIgnoreCase("log.setPattern")) { return setPattern(functionName, parameters); - else if (functionName.toLowerCase().startsWith("log.")) + } else if (functionName.toLowerCase().startsWith("log.")) { return logMessage(functionName, parameters); - else + } else { throw new ParserException( I18N.getText("macro.function.general.unknownFunction", functionName)); + } } /** @@ -89,12 +93,13 @@ private Object getLoggers(String functionName, List parameters) throws P LoggerContext logContext = (LoggerContext) LogManager.getContext(false); List loggerLevels = new ArrayList<>(); - for (Logger logger : logContext.getLoggers()) + for (Logger logger : logContext.getLoggers()) { try { loggerLevels.add(new LoggerResponse(logger.getName(), logger.getLevel().name())); } catch (IOException ioe) { log.error("Unable to get loggers from LogManager!", ioe); } + } Gson gson = new Gson(); String jsonResponse = gson.toJson(loggerLevels); @@ -117,7 +122,9 @@ private Object setLogLevel(String functionName, List parameters) throws // Convert the parameter string to a logger Level and return false if no match is found Level newLevel = Level.getLevel(parameters.get(1).toString().toUpperCase()); - if (newLevel == null) return BigDecimal.ZERO; + if (newLevel == null) { + return BigDecimal.ZERO; + } Configurator.setLevel(loggerName, newLevel); @@ -129,14 +136,22 @@ private Object logMessage(String functionName, List parameters) throws P String logMessage = parameters.get(0).toString(); - if (functionName.equalsIgnoreCase("log.fatal")) logger.fatal(logMessage); - else if (functionName.equalsIgnoreCase("log.error")) logger.error(logMessage); - else if (functionName.equalsIgnoreCase("log.warn")) logger.warn(logMessage); - else if (functionName.equalsIgnoreCase("log.info")) logger.info(logMessage); - else if (functionName.equalsIgnoreCase("log.debug")) logger.debug(logMessage); - else + if (functionName.equalsIgnoreCase("log.fatal")) { + logger.fatal(logMessage); + } else if (functionName.equalsIgnoreCase("log.error")) { + logger.error(logMessage); + } else if (functionName.equalsIgnoreCase("log.warn")) { + logger.warn(logMessage); + } else if (functionName.equalsIgnoreCase("log.info")) { + logger.info(logMessage); + } else if (functionName.equalsIgnoreCase("log.debug")) { + logger.debug(logMessage); + } else if (functionName.equalsIgnoreCase("log.trace")) { + logger.trace(logMessage); + } else { throw new ParserException( I18N.getText("macro.function.general.unknownFunction", functionName)); + } return ""; } @@ -201,21 +216,24 @@ private void checkParameters(String functionName, List parameters, int m throws ParserException { if (min == max) { - if (parameters.size() != max) + if (parameters.size() != max) { throw new ParserException( I18N.getText( "macro.function.general.wrongNumParam", functionName, max, parameters.size())); + } } else { - if (parameters.size() < min) + if (parameters.size() < min) { throw new ParserException( I18N.getText( "macro.function.general.notEnoughParam", functionName, min, parameters.size())); + } - if (parameters.size() > max) + if (parameters.size() > max) { throw new ParserException( I18N.getText( "macro.function.general.tooManyParam", functionName, max, parameters.size())); + } } } @@ -223,6 +241,7 @@ private void checkParameters(String functionName, List parameters, int m * A POJO to hold an Logger Config to marshal as a nice JSON object */ private final class LoggerResponse { + private final String name; private final String level; diff --git a/src/main/java/net/rptools/maptool/client/functions/LookupTableFunction.java b/src/main/java/net/rptools/maptool/client/functions/LookupTableFunction.java index 4dbf2a774e..df5e36aa29 100644 --- a/src/main/java/net/rptools/maptool/client/functions/LookupTableFunction.java +++ b/src/main/java/net/rptools/maptool/client/functions/LookupTableFunction.java @@ -102,7 +102,7 @@ public Object childEvaluate(Parser parser, String function, List params) String name = params.get(0).toString(); String visible = params.get(1).toString(); LookupTable lookupTable = getMaptoolTable(name, function); - lookupTable.setVisible(AbstractTokenAccessorFunction.getBooleanValue(visible)); + lookupTable.setVisible(FunctionUtil.getBooleanValue(visible)); MapTool.serverCommand().updateCampaign(MapTool.getCampaign().getCampaignProperties()); return lookupTable.getVisible() ? "1" : "0"; @@ -121,7 +121,7 @@ public Object childEvaluate(Parser parser, String function, List params) String name = params.get(0).toString(); String access = params.get(1).toString(); LookupTable lookupTable = getMaptoolTable(name, function); - lookupTable.setAllowLookup(AbstractTokenAccessorFunction.getBooleanValue(access)); + lookupTable.setAllowLookup(FunctionUtil.getBooleanValue(access)); MapTool.serverCommand().updateCampaign(MapTool.getCampaign().getCampaignProperties()); return lookupTable.getAllowLookup() ? "1" : "0"; @@ -201,8 +201,8 @@ public Object childEvaluate(Parser parser, String function, List params) } LookupTable lookupTable = new LookupTable(); lookupTable.setName(name); - lookupTable.setVisible(AbstractTokenAccessorFunction.getBooleanValue(visible)); - lookupTable.setAllowLookup(AbstractTokenAccessorFunction.getBooleanValue(lookups)); + lookupTable.setVisible(FunctionUtil.getBooleanValue(visible)); + lookupTable.setAllowLookup(FunctionUtil.getBooleanValue(lookups)); if (asset != null) lookupTable.setTableImage(asset); MapTool.getCampaign().getLookupTableMap().put(name, lookupTable); MapTool.serverCommand().updateCampaign(MapTool.getCampaign().getCampaignProperties()); @@ -226,7 +226,13 @@ public Object childEvaluate(Parser parser, String function, List params) FunctionUtil.checkNumberParam("getTableImage", params, 1, 1); String name = params.get(0).toString(); LookupTable lookupTable = getMaptoolTable(name, function); - return lookupTable.getTableImage(); + MD5Key img = lookupTable.getTableImage(); + if (img == null) { + // Returning null causes an NPE when output is dumped to chat. + return ""; + } else { + return img; + } } else if ("setTableImage".equalsIgnoreCase(function)) { @@ -437,9 +443,13 @@ private LookupTable getMaptoolTable(String tableName, String functionName) * "asset://" urls. * * @param assetString String containing either an asset ID or asset URL. - * @return MD5Key asset id. + * @return MD5Key asset id or null */ private MD5Key getAssetFromString(String assetString) { + if (assetString.isEmpty()) { + return null; + } + if (assetString.toLowerCase().startsWith("asset://")) { String id = assetString.substring(8); return new MD5Key(id); diff --git a/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java b/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java index f855f3c56a..8abfffad67 100644 --- a/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/MacroFunctions.java @@ -16,10 +16,11 @@ import java.math.BigDecimal; import java.util.ArrayList; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import net.rptools.lib.MD5Key; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.MapToolVariableResolver; import net.rptools.maptool.model.MacroButtonProperties; @@ -30,8 +31,12 @@ import net.sf.json.JSONArray; import net.sf.json.JSONException; import net.sf.json.JSONObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class MacroFunctions extends AbstractFunction { + private static final Logger log = LogManager.getLogger(MacroFunctions.class); + private static final MacroFunctions instance = new MacroFunctions(); private MacroFunctions() { @@ -109,7 +114,7 @@ public Object getMacroButtonProps(Token token, int index, String delim) throws P } if ("json".equals(delim)) { - Map props = new HashMap(); + Map props = new LinkedHashMap(); props.put("autoExecute", mbp.getAutoExecute()); props.put("color", mbp.getColorKey()); props.put("fontColor", mbp.getFontColorKey()); @@ -153,6 +158,13 @@ public Object getMacroButtonProps(Token token, int index, String delim) throws P props.put("compare", compare); + Map propsMetadata = new LinkedHashMap(); + propsMetadata.put("uuid", mbp.getMacroUUID()); + propsMetadata.put("commandChecksum", new MD5Key(mbp.getCommand().getBytes()).toString()); + propsMetadata.put("propsChecksum", new MD5Key(props.toString().getBytes()).toString()); + + props.put("metadata", propsMetadata); + return JSONObject.fromObject(props); } else { StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/net/rptools/maptool/client/functions/MacroLinkFunction.java b/src/main/java/net/rptools/maptool/client/functions/MacroLinkFunction.java index d88a92b212..90a06870ca 100644 --- a/src/main/java/net/rptools/maptool/client/functions/MacroLinkFunction.java +++ b/src/main/java/net/rptools/maptool/client/functions/MacroLinkFunction.java @@ -207,21 +207,10 @@ public void run() { * @param targets the list of targets */ private static void sendExecLink(final String link, Collection targets) { - boolean isGM = MapTool.getPlayer().isGM(); + String source = MapTool.getPlayer().getName(); for (String target : targets) { - switch (target.toLowerCase()) { - case "gm-self": - MapTool.serverCommand().execLink(link, "gm"); - if (isGM) break; // // FALLTHRU if not a GM - case "self": - runMacroLink(link); - break; - case "none": - break; - default: - MapTool.serverCommand().execLink(link, target); - } + MapTool.serverCommand().execLink(link, target, source); } } @@ -229,21 +218,12 @@ private static void sendExecLink(final String link, Collection targets) * Receive an execLink, and run it if the player is a target. * * @param link the macroLinkText - * @param target the target. Can also be "gm" or "all". + * @param target the target. + * @param source the name of the source. */ - public static void receiveExecLink(final String link, String target) { - String playerName = MapTool.getPlayer().getName(); - boolean isGM = MapTool.getPlayer().isGM(); - switch (target.toLowerCase()) { - case "gm": - if (isGM) runMacroLink(link); - break; - case "all": - runMacroLink(link); - break; - default: - if (target.equals(playerName)) runMacroLink(link); - break; + public static void receiveExecLink(final String link, String target, String source) { + if (ExecFunction.isMessageForMe(target, source)) { + runMacroLink(link); } } diff --git a/src/main/java/net/rptools/maptool/client/functions/MapFunctions.java b/src/main/java/net/rptools/maptool/client/functions/MapFunctions.java index ce066604be..2257f15247 100644 --- a/src/main/java/net/rptools/maptool/client/functions/MapFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/MapFunctions.java @@ -73,7 +73,7 @@ public Object childEvaluate(Parser parser, String functionName, List par } else if ("setMapVisible".equalsIgnoreCase(functionName)) { checkTrusted(functionName); FunctionUtil.checkNumberParam(functionName, parameters, 1, 2); - boolean visible = AbstractTokenAccessorFunction.getBooleanValue(parameters.get(0).toString()); + boolean visible = FunctionUtil.getBooleanValue(parameters.get(0).toString()); Zone zone = MapTool.getFrame().getCurrentZoneRenderer().getZone(); if (parameters.size() > 1) { String mapName = parameters.get(1).toString(); diff --git a/src/main/java/net/rptools/maptool/client/functions/MediaPlayerAdapter.java b/src/main/java/net/rptools/maptool/client/functions/MediaPlayerAdapter.java index f0a7a23a2a..c7ed552994 100644 --- a/src/main/java/net/rptools/maptool/client/functions/MediaPlayerAdapter.java +++ b/src/main/java/net/rptools/maptool/client/functions/MediaPlayerAdapter.java @@ -14,10 +14,9 @@ */ package net.rptools.maptool.client.functions; -import java.io.File; -import java.io.IOException; import java.net.*; import java.util.HashMap; +import java.util.List; import java.util.concurrent.ConcurrentHashMap; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; @@ -26,6 +25,7 @@ import javafx.scene.media.Media; import javafx.scene.media.MediaPlayer; import javafx.util.Duration; +import net.rptools.lib.sound.SoundManager; import net.rptools.maptool.language.I18N; import net.rptools.parser.ParserException; import net.sf.json.JSONArray; @@ -47,12 +47,31 @@ public class MediaPlayerAdapter { private final String strUri; private final Media media; private final MediaPlayer player; - private Double volume; // stored because player volume also depends on global volume - private MediaPlayerAdapter(String strUri, Media media) { + private int cycleCount; // store to remember last cycleCount used. Never 0. + private double volume; // store because player volume also depends on global volume + private Duration start; + private Duration stop; // can be null to not set stop time for player, needed to avoid bug + + /** + * Set the values of the adapter, create the MediaPlayer, and set the properties of the + * MediaPlayer + * + * @param strUri the String url of the stream + * @param media the media to play + * @param cycleCount the number of cycles (null: 1) + * @param volume the volume level of the stream (0-1, null: 1) + * @param start the start time in seconds (null: 0) + * @param stop the stop time in seconds (null/negative: max length) + */ + private MediaPlayerAdapter( + String strUri, Media media, Integer cycleCount, Double volume, Double start, Double stop) { + this.player = new MediaPlayer(media); + this.strUri = strUri; this.media = media; - this.player = new MediaPlayer(media); + editStream(cycleCount, volume, start, stop, true); + this.player.setOnEndOfMedia( new Runnable() { @Override @@ -63,7 +82,64 @@ public void run() { player.stop(); // otherwise, status stuck on "PLAYING" at end } }); - }; + } + + /** + * Edit the adapter values, and update the player. Should be accessed from JavaFX app thread. If a + * parameter is null and useDefault is false, no change to that value. If null and useDefault is + * true, change to the defaults. + * + * @param cycleCount how many times should the stream play (-1: infinite, default: 1) + * @param volume the volume level of the stream (0-1, default: 1) + * @param start the start time in seconds (default: 0) + * @param stop the stop time in seconds (-1: file duration, default: -1) + * @param useDefault if true, use default when receiving a null argument + */ + private void editStream( + Integer cycleCount, Double volume, Double start, Double stop, boolean useDefault) { + // don't change adapter cycleCount if 0, but stop play. + if (cycleCount != null && cycleCount != 0) { + this.cycleCount = cycleCount; + } else if (useDefault) { + this.cycleCount = 1; + } + if (volume != null) { + this.volume = volume; + } else if (useDefault) { + this.volume = 1.0; + } + if (start != null) { + this.start = Duration.seconds(start); + } else if (useDefault) { + this.start = Duration.seconds(0.0); + } + if (stop != null) { + this.stop = stop >= 0 ? Duration.seconds(stop) : player.getMedia().getDuration(); + } else if (useDefault) { + this.stop = null; + } + updatePlayer(cycleCount != null && cycleCount == 0); + } + + /** + * Update the MediaPlayer with the values in the adapter + * + * @param stopPlay should the player be stopped + */ + private void updatePlayer(boolean stopPlay) { + int newCycle = + this.cycleCount >= 0 ? this.cycleCount + player.getCurrentCount() : this.cycleCount; + + this.player.setCycleCount(newCycle); + this.player.setVolume(this.volume * globalVolume); + this.player.setStartTime(this.start); + if (this.stop != null) this.player.setStopTime(this.stop); + + this.player.setMute(globalMute); + if (stopPlay) { + this.player.stop(); + } + } /** * Start a given stream from its url string. If already streaming the file, dispose of the * previous stream. @@ -73,16 +149,27 @@ public void run() { * @param volume the volume level of the stream (0-1) * @param start the start time in ms * @param stop the stop time in ms, -1: file duration + * @param preloadOnly should the stream be only preloaded and not played * @return false if the file doesn't exist, true otherwise * @throws ParserException if issue with file */ public static boolean playStream( - String strUri, int cycleCount, double volume, double start, double stop) + String strUri, + Integer cycleCount, + Double volume, + Double start, + Double stop, + boolean preloadOnly) throws ParserException { final Media media; try { - if (!uriExists(strUri)) return false; // leave without error message if uri ok but no file - media = new Media(strUri); + if (mapStreams.containsKey(strUri)) { + media = mapStreams.get(strUri).media; + } else { + if (!SoundFunctions.uriExists(strUri)) + return false; // leave without error message if uri ok but no file + media = new Media(strUri); + } } catch (Exception e) { throw new ParserException( I18N.getText( @@ -99,25 +186,18 @@ public static boolean playStream( public void run() { MediaPlayerAdapter adapter = mapStreams.get(strUri); boolean old = adapter != null; - if (!old) { - adapter = new MediaPlayerAdapter(strUri, media); + if (old) { + adapter.editStream(cycleCount, volume, start, stop, false); + } else { + adapter = new MediaPlayerAdapter(strUri, media, cycleCount, volume, start, stop); mapStreams.put(strUri, adapter); } - adapter.volume = volume; MediaPlayer player = adapter.player; - int newCycle = cycleCount >= 0 ? cycleCount + player.getCurrentCount() : cycleCount; - Duration durStart = new Duration(start); - Duration durStop = stop >= 0 ? new Duration(stop) : player.getMedia().getDuration(); - - player.setCycleCount(newCycle); - player.setVolume(volume * globalVolume); - player.setStartTime(durStart); - player.setStopTime(durStop); - - player.setMute(globalMute); - player.seek(durStart); // start playing from the start - if (cycleCount != 0) { + // cycleCount of zero doesn't change adapter, but instead doesn't activate play + boolean play = (cycleCount == null || cycleCount != 0) && !preloadOnly; + if (play) { + player.seek(player.getStartTime()); // start playing from the start if (old) player.play(); else player.setAutoPlay(true); } else player.stop(); @@ -131,7 +211,7 @@ public void run() { * * @param strUri the String url of the stream * @param remove should the stream be disposed - * @param fadeout time in ms to fadeout (0: no fadeout) + * @param fadeout time in seconds to fadeout (0: no fadeout) */ public static void stopStream(String strUri, boolean remove, double fadeout) { Platform.runLater( @@ -165,14 +245,14 @@ private void stopStream(boolean remove) { * Stop the stream. Should be ran from JavaFX app thread. * * @param remove should the stream be disposed and map updated - * @param fadeout time in ms to fadeout (0: no fadeout) + * @param fadeout time in seconds to fadeout (0: no fadeout) */ private void stopStream(boolean remove, double fadeout) { if (fadeout <= 0) stopStream(remove); else { Timeline timeline = new Timeline( - new KeyFrame(Duration.millis(fadeout), new KeyValue(player.volumeProperty(), 0))); + new KeyFrame(Duration.seconds(fadeout), new KeyValue(player.volumeProperty(), 0))); timeline.setOnFinished( (event -> { stopStream(remove); // stop the stream at the end @@ -199,91 +279,15 @@ public void run() { if (strUri.equals("*")) { for (HashMap.Entry mapElement : mapStreams.entrySet()) ((MediaPlayerAdapter) mapElement.getValue()) - .editStream(cycleCount, volume, start, stop); + .editStream(cycleCount, volume, start, stop, false); } else { MediaPlayerAdapter adapter = mapStreams.get(strUri); - if (adapter != null) adapter.editStream(cycleCount, volume, start, stop); + if (adapter != null) adapter.editStream(cycleCount, volume, start, stop, false); } } }); } - /** - * Edit the stream. Should be accessed from JavaFX app thread. If a parameter is null, no change - * to that value. - * - * @param cycleCount how many times should the stream play. -1: infinite - * @param volume the volume level of the stream (0-1) - * @param start the start time in ms - * @param stop the stop time in ms, -1: file duration - */ - private void editStream(Integer cycleCount, Double volume, Double start, Double stop) { - if (cycleCount != null) { - if (cycleCount == 0) player.stop(); - else { - int newCycle = cycleCount >= 0 ? cycleCount + player.getCurrentCount() : cycleCount; - player.setCycleCount(newCycle); - } - } - if (volume != null) { - this.volume = volume; - player.setVolume(volume * globalVolume); - } - if (start != null) player.setStartTime(new Duration(start)); - if (stop != null) { - Duration durStop = stop >= 0 ? new Duration(stop) : Duration.INDEFINITE; - player.setStopTime(durStop); - } - } - - /** - * Return the existence status of resource from String uri - * - * @param strUri the String uri of the resource - * @return true if resource exists, false otherwise - * @throws IOException if uri is url, but url is incorrect - * @throws URISyntaxException if uri is for local file, but uri is incorrect - */ - public static boolean uriExists(String strUri) throws IOException, URISyntaxException { - return isWeb(strUri) ? urlExist(strUri) : fileExist(strUri); - } - - /** - * Returns true if the uri is for a web resource, false otherwise - * - * @param strUri the String uri of the resource - * @return true if String uri is URL, false otherwise - */ - private static boolean isWeb(String strUri) { - String s = strUri.trim().toLowerCase(); - return s.startsWith("http://") || s.startsWith("https://"); - } - - /** - * Return the existence status of web resource from String uri - * - * @param strUri the String uri of the resource - * @return true if resource exists, false otherwise - * @throws IOException if uri is incorrect - */ - private static boolean urlExist(String strUri) throws IOException { - HttpURLConnection.setFollowRedirects(false); - HttpURLConnection con = (HttpURLConnection) new URL(strUri).openConnection(); - con.setRequestMethod("HEAD"); - return (con.getResponseCode() == HttpURLConnection.HTTP_OK); - } - - /** - * Return the existence status of local resource from String uri - * - * @param strUri the String uri of the resource - * @return true if resource exists, false otherwise - * @throws URISyntaxException if uri is incorrect - */ - private static boolean fileExist(String strUri) throws URISyntaxException { - return new File(new URI(strUri).getPath()).exists(); - } - /** * Return the properties of a stream from its uri * @@ -329,16 +333,23 @@ private JSONObject getInfo() { else objStop = durStop.toSeconds(); JSONObject info = new JSONObject(); + + List listNicks = SoundFunctions.getNicks(strUri); + if (listNicks.size() > 0) { + info.put("nicknames", String.join(",", listNicks)); + } info.put("uri", strUri); - info.put("cycleCount", player.getCycleCount()); info.put("volume", volume); - info.put("startTime", " " + player.getStartTime().toSeconds()); - info.put("stopTime", " " + objStop); - info.put("currentTime", " " + player.getCurrentTime().toSeconds()); - info.put("totalTime", " " + objTotal); - info.put("bufferTime", " " + player.getBufferProgressTime().toSeconds()); - info.put("currentCount", " " + player.getCurrentCount()); + info.put("cycleCount", cycleCount); + info.put("startTime", start.toSeconds()); + info.put("stopTime", objStop); + info.put("currentTime", player.getCurrentTime().toSeconds()); + info.put("totalTime", objTotal); + info.put("bufferTime", player.getBufferProgressTime().toSeconds()); + info.put("currentCount", player.getCurrentCount()); + info.put("endCount", player.getCycleCount()); info.put("status", player.getStatus().toString()); + info.put("type", "stream"); return info; } catch (Exception e) { return null; @@ -388,8 +399,14 @@ public static void setGlobalMute(boolean mute) { new Runnable() { @Override public void run() { - for (HashMap.Entry mapElement : mapStreams.entrySet()) + // mute / unmute all streams + for (HashMap.Entry mapElement : mapStreams.entrySet()) { ((MediaPlayerAdapter) mapElement.getValue()).updateMute(); + } + // if muting, stop all audio clips + if (mute) { + SoundManager.stopClip("*", false); + } } }); } @@ -406,36 +423,4 @@ private void updateMute() { public static boolean getGlobalMute() { return globalMute; } - - /** - * Convert a string into a uri string. Spaces are replaced by %20, among other things. The string - * "*" is returned as-is - * - * @param string the string to convert - * @return the converted string - */ - public static String convertToURI(Object string) { - String strUri = string.toString().trim(); - if (strUri.equals("*")) return strUri; - if (!isWeb(strUri) && !strUri.toUpperCase().startsWith("FILE")) { - strUri = "FILE:/" + strUri; - } - - try { - String decodedURL = URLDecoder.decode(strUri, "UTF-8"); - URL url = new URL(decodedURL); - URI uri = - new URI( - url.getProtocol(), - url.getUserInfo(), - url.getHost(), - url.getPort(), - url.getPath(), - url.getQuery(), - url.getRef()); - return uri.toString(); - } catch (Exception ex) { - return strUri; - } - } } diff --git a/src/main/java/net/rptools/maptool/client/functions/RESTfulFunctions.java b/src/main/java/net/rptools/maptool/client/functions/RESTfulFunctions.java index 741d6b7f05..55aee28ee8 100644 --- a/src/main/java/net/rptools/maptool/client/functions/RESTfulFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/RESTfulFunctions.java @@ -28,6 +28,7 @@ import net.rptools.maptool.client.AppPreferences; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.language.I18N; +import net.rptools.maptool.util.FunctionUtil; import net.rptools.parser.Parser; import net.rptools.parser.ParserException; import net.rptools.parser.function.AbstractFunction; @@ -55,6 +56,7 @@ * body of the response is returned in what ever form the call returns. */ public class RESTfulFunctions extends AbstractFunction { + private static final RESTfulFunctions instance = new RESTfulFunctions(); private RESTfulFunctions() { @@ -72,29 +74,34 @@ public static RESTfulFunctions getInstance() { public Object childEvaluate(Parser parser, String functionName, List parameters) throws ParserException { - if (!MapTool.getParser().isMacroPathTrusted()) + if (!MapTool.getParser().isMacroPathTrusted()) { throw new ParserException(I18N.getText("macro.function.general.noPerm", functionName)); + } - if (!AppPreferences.getAllowExternalMacroAccess()) + if (!AppPreferences.getAllowExternalMacroAccess()) { throw new ParserException(I18N.getText("macro.function.general.accessDenied", functionName)); + } // Check do we need this? checkParameters(functionName, parameters, 1, 5); - if (functionName.equalsIgnoreCase("REST.get") || functionName.equalsIgnoreCase("REST.delete")) - return buildGetOrDeleteRequest(functionName, parameters); + if (functionName.equalsIgnoreCase("REST.get")) { + return buildGetRequest(functionName, parameters); + } if (functionName.equalsIgnoreCase("REST.post") || functionName.equalsIgnoreCase("REST.put") - || functionName.equalsIgnoreCase("REST.patch")) + || functionName.equalsIgnoreCase("REST.patch") + || functionName.equalsIgnoreCase("REST.delete")) { return buildRequest(functionName, parameters); - else + } else { throw new ParserException( I18N.getText("macro.function.general.unknownFunction", functionName)); + } } /** - * Performs a RESTful GET or DELETE request using OkHttp + * Performs a RESTful GET request using OkHttp * * @param functionName * @param parameters include URL, headers (optional) and if full response is requested (optional) @@ -102,7 +109,7 @@ public Object childEvaluate(Parser parser, String functionName, List par * XML, HTML, or other formats. * @throws ParserException */ - private Object buildGetOrDeleteRequest(String functionName, List parameters) + private Object buildGetRequest(String functionName, List parameters) throws ParserException { checkParameters(functionName, parameters, 1, 3); @@ -117,30 +124,29 @@ private Object buildGetOrDeleteRequest(String functionName, List paramet return launchSyrinscape(baseURL); } - if (functionName.equalsIgnoreCase("REST.get")) + if (functionName.equalsIgnoreCase("REST.get")) { request = new Request.Builder().url(baseURL).build(); - else if (functionName.equalsIgnoreCase("REST.delete")) - request = new Request.Builder().url(baseURL).delete().build(); - else + } else { throw new ParserException( I18N.getText("macro.function.general.unknownFunction", functionName)); + } // If we need to add headers then rebuild a new request with new headers - if (!headerMap.isEmpty()) - if (functionName.equalsIgnoreCase("REST.get")) + if (!headerMap.isEmpty()) { + if (functionName.equalsIgnoreCase("REST.get")) { request = new Request.Builder().url(baseURL).headers(headers).build(); - else if (functionName.equalsIgnoreCase("REST.delete")) - request = new Request.Builder().url(baseURL).headers(headers).delete().build(); - else + } else { throw new ParserException( I18N.getText("macro.function.general.unknownFunction", functionName)); + } + } return executeClientCall(functionName, request, isFullResponseRequested(parameters)); } /** - * Performs a RESTful POST, PATCH, or PUT request using OkHttp Minimum requirements are URL, - * Payload, & MediaType Optional parameters are Headers & Full Response + * Performs a RESTful POST, PATCH, PUT or DELETE request using OkHttp Minimum requirements are + * URL, Payload, & MediaType Optional parameters are Headers & Full Response * * @param functionName * @param parameters include URL, payload, media type of payload, headers (optional) and if full @@ -166,23 +172,32 @@ private Object buildRequest(String functionName, List parameters) throws if (headerMap.isEmpty()) { // Build without any headers... - if (functionName.equals("REST.post")) + if (functionName.equals("REST.post")) { request = new Request.Builder().url(baseURL).post(requestBody).build(); - else if (functionName.equals("REST.put")) + } else if (functionName.equals("REST.put")) { request = new Request.Builder().url(baseURL).put(requestBody).build(); - else if (functionName.equals("REST.patch")) + } else if (functionName.equals("REST.patch")) { request = new Request.Builder().url(baseURL).patch(requestBody).build(); - else + } else if (functionName.equals("REST.delete")) { + request = new Request.Builder().url(baseURL).delete(requestBody).build(); + } else { throw new ParserException( I18N.getText("macro.function.general.unknownFunction", functionName)); + } } else { // Now build request with headers - if (functionName.equals("REST.post")) + if (functionName.equals("REST.post")) { request = new Request.Builder().url(baseURL).headers(headers).post(requestBody).build(); - else if (functionName.equals("REST.put")) + } else if (functionName.equals("REST.put")) { request = new Request.Builder().url(baseURL).headers(headers).put(requestBody).build(); - else if (functionName.equals("REST.patch")) + } else if (functionName.equals("REST.patch")) { request = new Request.Builder().url(baseURL).headers(headers).patch(requestBody).build(); + } else if (functionName.equals("REST.delete")) { + request = new Request.Builder().url(baseURL).headers(headers).delete(requestBody).build(); + } else { + throw new ParserException( + I18N.getText("macro.function.general.unknownFunction", functionName)); + } } return executeClientCall(functionName, request, isFullResponseRequested(parameters)); @@ -202,12 +217,16 @@ private Object executeClientCall(String functionName, Request request, boolean f // Execute the call and check the response... try (Response response = client.newCall(request).execute()) { - if (!response.isSuccessful() && !fullResponse) + if (!response.isSuccessful() && !fullResponse) { throw new ParserException( I18N.getText("macro.function.rest.error.response", functionName, response.code())); + } - if (fullResponse) return gson.toJson(new RestResponseObj(response)); - else return response.body().string(); + if (fullResponse) { + return gson.toJson(new RestResponseObj(response)); + } else { + return response.body().string(); + } } catch (IllegalArgumentException | IOException e) { throw new ParserException(I18N.getText("macro.function.rest.error.unknown", functionName, e)); @@ -220,7 +239,9 @@ private Headers buildHeaders(Map> headerMap) { for (Map.Entry> entry : headerMap.entrySet()) { String name = entry.getKey(); List values = entry.getValue(); - for (String value : values) headerBuilder.add(name, value); + for (String value : values) { + headerBuilder.add(name, value); + } } return headerBuilder.build(); @@ -231,9 +252,11 @@ private boolean isLastParamBoolean(List parameters) { } private boolean isFullResponseRequested(List parameters) { - if (isLastParamBoolean(parameters)) - return AbstractTokenAccessorFunction.getBooleanValue(parameters.get(parameters.size() - 1)); - else return false; + if (isLastParamBoolean(parameters)) { + return FunctionUtil.getBooleanValue(parameters.get(parameters.size() - 1)); + } else { + return false; + } } private Map> getHeaderMap(List parameters, int headerIndex) { @@ -241,12 +264,13 @@ private Map> getHeaderMap(List parameters, int head // and the last parameter is a boolean then headers were not passed in. // of if parameter size is not large enough to hold either value... if (headerIndex >= parameters.size() - || (headerIndex == parameters.size() - 1 && isLastParamBoolean(parameters))) + || (headerIndex == parameters.size() - 1 && isLastParamBoolean(parameters))) { return new HashMap>(); - else + } else { return gson.fromJson( (String) parameters.get(headerIndex), new TypeToken>>() {}.getType()); + } } /* @@ -255,7 +279,9 @@ private Map> getHeaderMap(List parameters, int head * @throws ParserException */ private BigDecimal launchSyrinscape(String baseURL) throws ParserException { - if (!AppPreferences.getSyrinscapeActive()) return BigDecimal.ZERO; + if (!AppPreferences.getSyrinscapeActive()) { + return BigDecimal.ZERO; + } URI uri; @@ -282,21 +308,24 @@ private void checkParameters(String functionName, List parameters, int m throws ParserException { if (min == max) { - if (parameters.size() != max) + if (parameters.size() != max) { throw new ParserException( I18N.getText( "macro.function.general.wrongNumParam", functionName, max, parameters.size())); + } } else { - if (parameters.size() < min) + if (parameters.size() < min) { throw new ParserException( I18N.getText( "macro.function.general.notEnoughParam", functionName, min, parameters.size())); + } - if (parameters.size() > max) + if (parameters.size() > max) { throw new ParserException( I18N.getText( "macro.function.general.tooManyParam", functionName, max, parameters.size())); + } } } @@ -304,6 +333,7 @@ private void checkParameters(String functionName, List parameters, int m * A POJO to hold an HTTP Response object to marshal as a JSON object */ private final class RestResponseObj { + private final int status; private final String message; private final Map> headers; @@ -313,12 +343,18 @@ public RestResponseObj(Response response) throws IOException { this.status = response.code(); this.headers = response.headers().toMultimap(); - if (!response.message().isEmpty()) this.message = response.message(); - else this.message = null; + if (!response.message().isEmpty()) { + this.message = response.message(); + } else { + this.message = null; + } String responseBody = response.body().string(); - if (isValidJSON(responseBody)) body = gson.fromJson(responseBody, JsonElement.class); - else this.body = new Gson().toJsonTree(responseBody); + if (isValidJSON(responseBody)) { + body = gson.fromJson(responseBody, JsonElement.class); + } else { + this.body = new Gson().toJsonTree(responseBody); + } } private boolean isValidJSON(String jsonInString) { diff --git a/src/main/java/net/rptools/maptool/client/functions/SoundFunctions.java b/src/main/java/net/rptools/maptool/client/functions/SoundFunctions.java index f8afefc40d..58b677ca5c 100644 --- a/src/main/java/net/rptools/maptool/client/functions/SoundFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/SoundFunctions.java @@ -14,23 +14,43 @@ */ package net.rptools.maptool.client.functions; +import java.io.File; +import java.io.IOException; import java.math.BigDecimal; +import java.net.*; +import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import net.rptools.lib.sound.SoundManager; import net.rptools.maptool.client.AppPreferences; import net.rptools.maptool.util.FunctionUtil; import net.rptools.parser.Parser; import net.rptools.parser.ParserException; import net.rptools.parser.function.AbstractFunction; +import net.sf.json.JSONArray; +/** This class handles functions to play, stop, and edit audio clips and streams. */ public class SoundFunctions extends AbstractFunction { /** The singleton instance. */ private static final SoundFunctions instance = new SoundFunctions(); private SoundFunctions() { - super(0, 5, "playStream", "stopStream", "editStream", "getStreamProperties"); + super( + 0, + 7, + "playStream", + "playClip", + "stopSound", + "getSoundProperties", + "editStream", + "defineAudioSource"); } + private static final ConcurrentHashMap mapSounds = new ConcurrentHashMap<>(); + /** * Gets the SoundFunctions instance. * @@ -47,47 +67,266 @@ public Object childEvaluate(Parser parser, String functionName, List arg if (functionName.equalsIgnoreCase("playStream")) { FunctionUtil.checkNumberParam(functionName, args, 1, 5); if (!AppPreferences.getPlayStreams()) return -1; // do nothing if disabled in preferences - String strUri = MediaPlayerAdapter.convertToURI(args.get(0)); - int cycleCount = psize > 1 ? FunctionUtil.paramAsInteger(functionName, args, 1, true) : 1; - double volume = psize > 2 ? FunctionUtil.paramAsDouble(functionName, args, 2, true) : 1; - double start = psize > 3 ? FunctionUtil.paramAsDouble(functionName, args, 3, true) * 1000 : 0; - double stop = psize > 4 ? FunctionUtil.paramAsDouble(functionName, args, 4, true) * 1000 : -1; + String strUri = convertToURI(args.get(0), true); + + Integer cycleCount = getCycleCount(functionName, args, 1); + Double volume = getDouble(functionName, args, 2); + Double start = getDouble(functionName, args, 3); + Double stop = getDouble(functionName, args, 4); + + return MediaPlayerAdapter.playStream(strUri, cycleCount, volume, start, stop, false) + ? BigDecimal.ONE + : BigDecimal.ZERO; + } else if (functionName.equalsIgnoreCase("playClip")) { + FunctionUtil.checkNumberParam(functionName, args, 1, 3); + if (!AppPreferences.getPlayStreams()) return -1; // do nothing if disabled in preferences + String strUri = convertToURI(args.get(0), true); - return MediaPlayerAdapter.playStream(strUri, cycleCount, volume, start, stop) + Integer cycleCount = getCycleCount(functionName, args, 1); + Double volume = getDouble(functionName, args, 2); + return SoundManager.playClip(strUri, cycleCount, volume, false) ? BigDecimal.ONE : BigDecimal.ZERO; + } else if (functionName.equalsIgnoreCase("editStream")) { FunctionUtil.checkNumberParam(functionName, args, 2, 5); - String strUri = MediaPlayerAdapter.convertToURI(args.get(0)); - - Integer cycleCount = null; - Double volume = null; - Double start = null; - Double stop = null; - - if (psize > 1 && !args.get(1).equals("")) - cycleCount = FunctionUtil.paramAsInteger(functionName, args, 1, true); - if (psize > 2 && !args.get(2).equals("")) - volume = FunctionUtil.paramAsDouble(functionName, args, 2, true); - if (psize > 3 && !args.get(3).equals("")) - start = FunctionUtil.paramAsDouble(functionName, args, 3, true) * 1000; - if (psize > 4 && !args.get(4).equals("")) - stop = FunctionUtil.paramAsDouble(functionName, args, 4, true) * 1000; + String strUri = convertToURI(args.get(0), true); + + Integer cycleCount = getCycleCount(functionName, args, 1); + Double volume = getDouble(functionName, args, 2); + Double start = getDouble(functionName, args, 3); + Double stop = getDouble(functionName, args, 4); MediaPlayerAdapter.editStream(strUri, cycleCount, volume, start, stop); return ""; - } else if (functionName.equalsIgnoreCase("stopStream")) { + } else if (functionName.equalsIgnoreCase("stopSound")) { FunctionUtil.checkNumberParam(functionName, args, 0, 3); - String strUri = psize > 0 ? MediaPlayerAdapter.convertToURI(args.get(0)) : "*"; + String strUri = psize > 0 ? convertToURI(args.get(0), true) : "*"; + boolean del = psize > 1 ? FunctionUtil.paramAsBoolean(functionName, args, 1, true) : true; - double fade = psize > 2 ? FunctionUtil.paramAsDouble(functionName, args, 2, true) * 1000 : 0; - MediaPlayerAdapter.stopStream(strUri, del, fade); + double fade = psize > 2 ? FunctionUtil.paramAsDouble(functionName, args, 2, true) : 0; + stopSound(strUri, del, fade); // stop clip and/or stream return ""; - } else if (functionName.equalsIgnoreCase("getStreamProperties")) { + } else if (functionName.equalsIgnoreCase("getSoundProperties")) { FunctionUtil.checkNumberParam(functionName, args, 0, 1); - String strUri = psize > 0 ? MediaPlayerAdapter.convertToURI(args.get(0)) : "*"; - return MediaPlayerAdapter.getStreamProperties(strUri); + String strUri = psize > 0 ? convertToURI(args.get(0), true) : "*"; + return getSoundProperties(strUri); + } else if (functionName.equalsIgnoreCase("defineAudioSource")) { + FunctionUtil.checkNumberParam(functionName, args, 2, 7); + String nickName = args.get(0).toString(); + String strUri = convertToURI(args.get(1), false); + defineSound(nickName, strUri); + + String preload = args.size() > 2 ? args.get(2).toString() : "none"; + if (!preload.equalsIgnoreCase("none")) { + Integer cycleCount = getCycleCount(functionName, args, 3); + Double volume = getDouble(functionName, args, 4); + Double start = getDouble(functionName, args, 5); + Double stop = getDouble(functionName, args, 6); + + if (preload.equalsIgnoreCase("clip")) { + SoundManager.playClip(strUri, cycleCount, volume, true); + } else if (preload.equalsIgnoreCase("stream")) { + MediaPlayerAdapter.playStream(strUri, cycleCount, volume, start, stop, true); + } + } + return ""; } return null; } + + /** + * Return the cycle count, or a null + * + * @param functionName the name of the function + * @param args the arguments to the function + * @param index the index of the cycleCount + * @return an integer for the cycleCount or a null + * @throws ParserException if the parameter is of incorrect type + */ + private static Integer getCycleCount(String functionName, List args, int index) + throws ParserException { + if (args.size() > index && !args.get(index).equals("")) { + return FunctionUtil.paramAsInteger(functionName, args, index, true); + } else { + return null; + } + } + + /** + * Return the double of the parameter, or a null + * + * @param functionName the name of the function + * @param args the arguments to the function + * @param index the index of the parameter + * @return a double, or a null if parameter is "" or not set + * @throws ParserException if the parameter is of incorrect type + */ + private static Double getDouble(String functionName, List args, int index) + throws ParserException { + if (args.size() > index && !args.get(index).equals("")) { + return FunctionUtil.paramAsDouble(functionName, args, index, true); + } else { + return null; + } + } + + /** + * Give a sound URI a nickname for easier access. + * + * @param nickName the nickname to use for the audio + * @param strUri the String uri of the stream or clip + */ + public static void defineSound(String nickName, String strUri) { + mapSounds.put(nickName, strUri); + } + + /** + * Get a JSON containing the properties of the sound(s) + * + * @param strUri the String uri of the stream or clip. Can also be "*", "streams", or "clips". + */ + private static Object getSoundProperties(String strUri) { + switch (strUri.toLowerCase()) { + case "streams": + return MediaPlayerAdapter.getStreamProperties("*"); + case "clips": + return SoundManager.getClipProperties("*"); + case "*": + JSONArray all = (JSONArray) MediaPlayerAdapter.getStreamProperties("*"); + all.addAll((JSONArray) SoundManager.getClipProperties("*")); + return all; + default: + Object streams = MediaPlayerAdapter.getStreamProperties(strUri); + if (!"".equals(streams)) { + return streams; + } else { + return SoundManager.getClipProperties(strUri); + } + } + } + + /** + * Stop a sound, either a stream, clip, or both + * + * @param strUri the String uri of the stream or clip. Can also be "*", "streams", or "clips". + */ + private static void stopSound(String strUri, boolean remove, double fadeout) { + switch (strUri.toLowerCase()) { + case "streams": + MediaPlayerAdapter.stopStream("*", remove, fadeout); + break; + case "clips": + SoundManager.stopClip("*", remove); + break; + default: + MediaPlayerAdapter.stopStream(strUri, remove, fadeout); + SoundManager.stopClip(strUri, remove); + break; + } + } + + /** + * Convert a string into a uri string. Spaces are replaced by %20, among other things. The string + * "*" is returned as-is + * + * @param string the string to convert + * @param checkMapSounds should we check mapSounds for a defined sound? + * @return the converted string + */ + private static String convertToURI(Object string, boolean checkMapSounds) { + String strUri = string.toString().trim(); + if (strUri.equals("*") + || strUri.equalsIgnoreCase("clips") + || strUri.equalsIgnoreCase("streams")) { + return strUri; + } + if (checkMapSounds && mapSounds.containsKey(strUri)) { + return mapSounds.get(strUri); + } + + if (!isWeb(strUri) && !strUri.toUpperCase().startsWith("FILE")) { + strUri = "FILE:/" + strUri; + } + + try { + String decodedURL = URLDecoder.decode(strUri, StandardCharsets.UTF_8); + URL url = new URL(decodedURL); + URI uri = + new URI( + url.getProtocol(), + url.getUserInfo(), + url.getHost(), + url.getPort(), + url.getPath(), + url.getQuery(), + url.getRef()); + return uri.toString(); + } catch (Exception ex) { + return strUri; + } + } + + /** + * Return the existence status of resource from String uri + * + * @param strUri the String uri of the resource + * @return true if resource exists, false otherwise + * @throws IOException if uri is url, but url is incorrect + * @throws URISyntaxException if uri is for local file, but uri is incorrect + */ + public static boolean uriExists(String strUri) throws IOException, URISyntaxException { + return isWeb(strUri) ? urlExist(strUri) : fileExist(strUri); + } + + /** + * Returns true if the uri is for a web resource, false otherwise + * + * @param strUri the String uri of the resource + * @return true if String uri is URL, false otherwise + */ + private static boolean isWeb(String strUri) { + String s = strUri.trim().toLowerCase(); + return s.startsWith("http://") || s.startsWith("https://"); + } + + /** + * Return the existence status of web resource from String uri + * + * @param strUri the String uri of the resource + * @return true if resource exists, false otherwise + * @throws IOException if uri is incorrect + */ + private static boolean urlExist(String strUri) throws IOException { + HttpURLConnection.setFollowRedirects(false); + HttpURLConnection con = (HttpURLConnection) new URL(strUri).openConnection(); + con.setRequestMethod("HEAD"); + return (con.getResponseCode() == HttpURLConnection.HTTP_OK); + } + + /** + * Return the existence status of local resource from String uri + * + * @param strUri the String uri of the resource + * @return true if resource exists, false otherwise + * @throws URISyntaxException if uri is incorrect + */ + private static boolean fileExist(String strUri) throws URISyntaxException { + return new File(new URI(strUri).getPath()).exists(); + } + + /** + * Return the nicknames associated with a string uri + * + * @param strUri the String uri of the resource + * @return a list with the nicknames corresponding to the resource + */ + public static List getNicks(String strUri) { + return mapSounds + .entrySet() + .stream() + .filter(entry -> strUri.equals(entry.getValue())) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/net/rptools/maptool/client/functions/StringFunctions.java b/src/main/java/net/rptools/maptool/client/functions/StringFunctions.java index b072290466..9451e41d56 100644 --- a/src/main/java/net/rptools/maptool/client/functions/StringFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/StringFunctions.java @@ -62,7 +62,8 @@ private StringFunctions() { "encode", "decode", "startsWith", - "endsWith"); + "endsWith", + "capitalize"); } public static StringFunctions getInstance() { @@ -387,10 +388,40 @@ public Object childEvaluate(Parser parser, String functionName, List par ? BigDecimal.ONE : BigDecimal.ZERO; } + if (functionName.equals("capitalize")) { + if (parameters.size() < 1) { + throw new ParserException( + I18N.getText( + "macro.function.general.notEnoughParam", functionName, 1, parameters.size())); + } + return capitalize(parameters.get(0).toString()); + } // should never happen throw new ParserException(functionName + "(): Unknown function."); } + /** + * This method returns a version of the passed in string where all the first letters of words are + * title case. + * + * @param str The string converted to title case. + * @return The string converted to title case. + */ + private String capitalize(String str) { + Pattern pattern = Pattern.compile("(\\p{IsAlphabetic}+)"); + Matcher matcher = pattern.matcher(str); + + StringBuffer result = new StringBuffer(); + while (matcher.find()) { + String word = matcher.group(); + matcher.appendReplacement(result, Character.toTitleCase(word.charAt(0)) + word.substring(1)); + } + + matcher.appendTail(result); + + return result.toString(); + } + /** * Formats a string using the String.format() rules, as well as replacing any values in %{} with * the contents of the variable. diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenAddToInitiativeFunction.java b/src/main/java/net/rptools/maptool/client/functions/TokenAddToInitiativeFunction.java index 866f0f1798..5b514f03be 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenAddToInitiativeFunction.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenAddToInitiativeFunction.java @@ -17,10 +17,12 @@ import java.math.BigDecimal; import java.util.List; import net.rptools.maptool.client.MapTool; +import net.rptools.maptool.client.MapToolVariableResolver; import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.InitiativeList; import net.rptools.maptool.model.InitiativeList.TokenInitiative; import net.rptools.maptool.model.Token; +import net.rptools.maptool.util.FunctionUtil; import net.rptools.parser.Parser; import net.rptools.parser.ParserException; import net.rptools.parser.function.AbstractFunction; @@ -34,7 +36,7 @@ public class TokenAddToInitiativeFunction extends AbstractFunction { /** Handle adding one, all, all PCs or all NPC tokens. */ private TokenAddToInitiativeFunction() { - super(0, 3, "addToInitiative"); + super(0, 4, "addToInitiative"); } /** singleton instance of this function */ @@ -45,15 +47,18 @@ public static TokenAddToInitiativeFunction getInstance() { return instance; } - /** - * @see net.rptools.parser.function.AbstractFunction#childEvaluate(net.rptools.parser.Parser, - * java.lang.String, java.util.List) - */ @Override public Object childEvaluate(Parser parser, String functionName, List args) throws ParserException { - InitiativeList list = MapTool.getFrame().getCurrentZoneRenderer().getZone().getInitiativeList(); - Token token = AbstractTokenAccessorFunction.getTarget(parser, args, -1); + MapToolVariableResolver res = (MapToolVariableResolver) parser.getVariableResolver(); + + boolean allowDuplicates = + args.size() > 0 ? FunctionUtil.paramAsBoolean(functionName, args, 0, true) : false; + String state = args.size() > 1 && !"".equals(args.get(1)) ? args.get(1).toString() : null; + Token token = FunctionUtil.getTokenFromParam(res, functionName, args, 2, 3); + + InitiativeList list = token.getZoneRenderer().getZone().getInitiativeList(); + if (!MapTool.getParser().isMacroTrusted()) { if (!MapTool.getFrame().getInitiativePanel().hasOwnerPermission(token)) { String message = I18N.getText("macro.function.initiative.gmOnly", functionName); @@ -62,24 +67,13 @@ public Object childEvaluate(Parser parser, String functionName, List arg throw new ParserException(message); } // endif } - boolean allowDuplicates = false; - if (!args.isEmpty()) { - allowDuplicates = TokenInitFunction.getBooleanValue(args.get(0)); - args.remove(0); - } // endif - String state = null; - if (!args.isEmpty()) { - state = args.get(0).toString(); - args.remove(0); - } // endif - // insert the token if needed TokenInitiative ti = null; if (allowDuplicates || list.indexOf(token).isEmpty()) { ti = list.insertToken(-1, token); if (state != null) ti.setState(state); } else { - TokenInitFunction.getInstance().setTokenValue(token, state); + MapTool.serverCommand().updateTokenProperty(token, "setInitiative", state); } // endif return ti != null ? BigDecimal.ONE : BigDecimal.ZERO; } diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenBarFunction.java b/src/main/java/net/rptools/maptool/client/functions/TokenBarFunction.java index 9dc0d1d924..5b953312b1 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenBarFunction.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenBarFunction.java @@ -17,7 +17,10 @@ import java.math.BigDecimal; import java.util.List; import net.rptools.maptool.client.MapTool; +import net.rptools.maptool.client.MapToolVariableResolver; +import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.Token; +import net.rptools.maptool.util.FunctionUtil; import net.rptools.parser.Parser; import net.rptools.parser.ParserException; import net.rptools.parser.function.AbstractFunction; @@ -27,7 +30,7 @@ public class TokenBarFunction extends AbstractFunction { /** Support get and set bar on tokens */ private TokenBarFunction() { - super(1, 3, "getBar", "setBar", "isBarVisible", "setBarVisible"); + super(1, 4, "getBar", "setBar", "isBarVisible", "setBarVisible"); } /** singleton instance of this function */ @@ -45,17 +48,27 @@ public static TokenBarFunction getInstance() { @Override public Object childEvaluate(Parser parser, String functionName, List parameters) throws ParserException { - Token token = AbstractTokenAccessorFunction.getTarget(parser, parameters, -1); + MapToolVariableResolver res = (MapToolVariableResolver) parser.getVariableResolver(); String bar = (String) parameters.get(0); + verifyBar(functionName, bar); + if (functionName.equals("getBar")) { + FunctionUtil.checkNumberParam(functionName, parameters, 1, 3); + Token token = FunctionUtil.getTokenFromParam(res, functionName, parameters, 1, 2); return getValue(token, bar); } else if (functionName.equals("setBar")) { + FunctionUtil.checkNumberParam(functionName, parameters, 2, 4); + Token token = FunctionUtil.getTokenFromParam(res, functionName, parameters, 2, 3); return setValue(token, bar, parameters.get(1)); } else if (functionName.equals("isBarVisible")) { + FunctionUtil.checkNumberParam(functionName, parameters, 1, 3); + Token token = FunctionUtil.getTokenFromParam(res, functionName, parameters, 1, 2); return isVisible(token, bar); - } else { - return setVisible( - token, bar, AbstractTokenAccessorFunction.getBooleanValue(parameters.get(1))); + } else { // setBarVisible + FunctionUtil.checkNumberParam(functionName, parameters, 2, 4); + boolean visible = FunctionUtil.paramAsBoolean(functionName, parameters, 1, true); + Token token = FunctionUtil.getTokenFromParam(res, functionName, parameters, 2, 3); + return setVisible(token, bar, visible); } } @@ -64,11 +77,11 @@ public Object childEvaluate(Parser parser, String functionName, List par * * @param token Get the value from this token * @param bar For this bar - * @return A {@link BigDecimal} value. - * @throws ParserException + * @return A {@link BigDecimal} value, or an empty string "" if bar is not visible */ - public Object getValue(Token token, String bar) throws ParserException { - return token.getState(bar); + public static Object getValue(Token token, String bar) { + Object value = token.getState(bar); + return value != null ? value : ""; } /** @@ -76,24 +89,31 @@ public Object getValue(Token token, String bar) throws ParserException { * @param bar For this bar * @param value New value for the bar. Will be converted into a {@link BigDecimal} before setting * @return The {@link BigDecimal} value that was actually set. - * @throws ParserException */ - public Object setValue(Token token, String bar, Object value) throws ParserException { + public static Object setValue(Token token, String bar, Object value) { BigDecimal val = getBigDecimalValue(value); - token.setState(bar, val); - MapTool.serverCommand() - .putToken(MapTool.getFrame().getCurrentZoneRenderer().getZone().getId(), token); + MapTool.serverCommand().updateTokenProperty(token, "setState", bar, value); return val; } - public BigDecimal isVisible(Token token, String bar) { + /** + * @param token Get the value of this token + * @param bar For this bar + * @return If the bar visible or not + */ + public static BigDecimal isVisible(Token token, String bar) { return token.getState(bar) == null ? BigDecimal.ZERO : BigDecimal.ONE; } - public BigDecimal setVisible(Token token, String bar, boolean show) { - token.setState(bar, show ? BigDecimal.ONE : null); - MapTool.serverCommand() - .putToken(MapTool.getFrame().getCurrentZoneRenderer().getZone().getId(), token); + /** + * @param token Set the value of this token + * @param bar For this bar + * @param show Should this bar be visible + * @return If the bar visible or not + */ + public static BigDecimal setVisible(Token token, String bar, boolean show) { + BigDecimal value = show ? BigDecimal.ONE : null; + MapTool.serverCommand().updateTokenProperty(token, "setState", bar, value); return show ? BigDecimal.ONE : BigDecimal.ZERO; } @@ -119,4 +139,16 @@ public static BigDecimal getBigDecimalValue(Object value) { } // endif return val; } + + /** + * @param functionName the name of the function + * @param bar the name of the bar + * @throws ParserException if the bar doesn't exist + */ + private static void verifyBar(String functionName, String bar) throws ParserException { + if (!MapTool.getCampaign().getTokenBarsMap().containsKey(bar)) { + throw new ParserException( + I18N.getText("macro.function.tokenBarFunction.unknownBar", functionName, bar)); + } + } } diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenCopyDeleteFunctions.java b/src/main/java/net/rptools/maptool/client/functions/TokenCopyDeleteFunctions.java index 7fc4969135..696876f94c 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenCopyDeleteFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenCopyDeleteFunctions.java @@ -17,6 +17,7 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; +import net.rptools.lib.MD5Key; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.MapToolUtil; import net.rptools.maptool.client.MapToolVariableResolver; @@ -80,9 +81,13 @@ private String deleteToken(MapToolVariableResolver res, List parameters) return "Deleted token " + token.getId() + " (" + token.getName() + ")"; } - /* - * Token copyToken(String tokenId, Number numCopies: 1, String fromMap: (""|currentMap()), JSONObject updates: null) JSONArray copyToken(String tokenId, Number numCopies, String fromMap: + /** + * Token copyToken(String tokenId, Number numCopies: 1, String fromMap: (""|currentMap()), + * JSONObject updates: null) JSONArray copyToken(String tokenId, Number numCopies, String fromMap: * (""|currentMap()), JSONObject updates: null) + * + * @param res the MapToolVariableResolver + * @param param the list of parameters */ private Object copyTokens(MapToolVariableResolver res, List param) throws ParserException { @@ -155,6 +160,15 @@ private Object copyTokens(MapToolVariableResolver res, List param) } } + /** + * This change various properties of a token. It is intended to be run before the token is sent to + * the server via putToken and as such, should only make local changes. + * + * @param token the token to change + * @param vals a JSONObject containing the new values + * @param zone the zone where the token is + * @param res the MapToolVariableResolver + */ private void setTokenValues(Token token, JSONObject vals, Zone zone, MapToolVariableResolver res) throws ParserException { JSONObject newVals = JSONObject.fromObject(vals); @@ -195,7 +209,12 @@ private void setTokenValues(Token token, JSONObject vals, Zone zone, MapToolVari BigDecimal val = new BigDecimal(value); forceShape = !BigDecimal.ZERO.equals(val); } - TokenPropertyFunctions.getInstance().setLayer(token, newVals.getString("layer"), forceShape); + Zone.Layer layer = TokenPropertyFunctions.getLayer(newVals.getString("layer")); + Token.TokenShape tokenShape = TokenPropertyFunctions.getTokenShape(token, layer, forceShape); + token.setLayer(layer); + if (tokenShape != null) { + token.setShape(tokenShape); + } } int x = token.getX(); @@ -240,7 +259,9 @@ private void setTokenValues(Token token, JSONObject vals, Zone zone, MapToolVari if (tokenMoved) { // System.err.println(newVals + " @ (" + x + ", " + y + ")"); - TokenLocationFunctions.getInstance().moveToken(token, x, y, useDistance); + ZonePoint zp = TokenLocationFunctions.getZonePoint(x, y, useDistance); + token.setX(zp.x); + token.setY(zp.y); } // Facing @@ -266,20 +287,30 @@ private void setTokenValues(Token token, JSONObject vals, Zone zone, MapToolVari } } + // legacy use, from pre 1.5.7. + if (!newVals.containsKey("tokenHandout") && newVals.containsKey("handoutImage")) { + // handoutImage -> tokenHandout + newVals.put("tokenHandout", newVals.get("handoutImage")); + } + if (!newVals.containsKey("tokenPortrait") && newVals.containsKey("portraitImage")) { + // portraitImage -> tokenPortrait + newVals.put("tokenPortrait", newVals.get("portraitImage")); + } + // tokenImage if (newVals.containsKey("tokenImage")) { - String assetName = newVals.getString("tokenImage"); - TokenImage.setImage(token, assetName); + MD5Key md5key = TokenImage.getMD5Key(newVals.getString("tokenImage"), COPY_FUNC); + token.setImageAsset(null, md5key); } // handoutImage - if (newVals.containsKey("handoutImage")) { - String assetName = newVals.getString("handoutImage"); - TokenImage.setHandout(token, assetName); + if (newVals.containsKey("tokenHandout")) { + MD5Key md5key = TokenImage.getMD5Key(newVals.getString("tokenHandout"), COPY_FUNC); + token.setCharsheetImage(md5key); } // portraitImage - if (newVals.containsKey("portraitImage")) { - String assetName = newVals.getString("portraitImage"); - TokenImage.setPortrait(token, assetName); + if (newVals.containsKey("tokenPortrait")) { + MD5Key md5key = TokenImage.getMD5Key(newVals.getString("tokenPortrait"), COPY_FUNC); + token.setPortraitImage(md5key); } } } diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenGMNameFunction.java b/src/main/java/net/rptools/maptool/client/functions/TokenGMNameFunction.java index d20db0e0ff..0435654585 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenGMNameFunction.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenGMNameFunction.java @@ -19,7 +19,7 @@ import net.rptools.maptool.client.MapToolVariableResolver; import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.Token; -import net.rptools.maptool.model.Zone; +import net.rptools.maptool.util.FunctionUtil; import net.rptools.parser.Parser; import net.rptools.parser.ParserException; import net.rptools.parser.function.AbstractFunction; @@ -49,7 +49,7 @@ public static final TokenGMNameFunction getInstance() { * @return the GMName. * @throws ParserException if the user does not have the permission. */ - public String getGMName(Token token) throws ParserException { + public static String getGMName(Token token) throws ParserException { if (!MapTool.getParser().isMacroTrusted()) { throw new ParserException(I18N.getText("macro.function.general.noPerm", "getGMName")); } @@ -63,7 +63,7 @@ public String getGMName(Token token) throws ParserException { * @param name The name to set the GMName to. * @throws ParserException if the user does not have the permission. */ - public void setGMName(Token token, String name) throws ParserException { + public static void setGMName(Token token, String name) throws ParserException { if (!MapTool.getParser().isMacroTrusted()) { throw new ParserException(I18N.getText("macro.function.general.noPerm", "setGMName")); } @@ -122,33 +122,13 @@ private Object getGMName(Parser parser, List args) throws ParserExceptio * @throws ParserException when an error occurs. */ private Object setGMName(Parser parser, List args) throws ParserException { - Token token; - if (args.size() == 2) { - token = FindTokenFunctions.findToken(args.get(1).toString(), null); - if (token == null) { - throw new ParserException( - I18N.getText( - "macro.function.general.unknownToken", "setGMName", args.get(1).toString())); - } - } else if (args.size() == 1) { - MapToolVariableResolver res = (MapToolVariableResolver) parser.getVariableResolver(); - token = res.getTokenInContext(); - if (token == null) { - throw new ParserException( - I18N.getText("macro.function.general.noImpersonated", "setGMName")); - } - } else if (args.size() == 0) { - throw new ParserException( - I18N.getText("macro.function.general.notEnoughParam", "setGMName", 1, args.size())); - } else { - throw new ParserException( - I18N.getText("macro.function.general.tooManyParam", "setGMName", 2, args.size())); - } - token.setGMName(args.get(0).toString()); - Zone zone = MapTool.getFrame().getCurrentZoneRenderer().getZone(); - MapTool.serverCommand().putToken(zone.getId(), token); - zone.putToken(token); + MapToolVariableResolver resolver = (MapToolVariableResolver) parser.getVariableResolver(); + FunctionUtil.checkNumberParam("setGMName", args, 1, 3); + String gmName = args.get(0).toString(); + Token token = FunctionUtil.getTokenFromParam(resolver, "setGMName", args, 1, 2); + + MapTool.serverCommand().updateTokenProperty(token, "setGMName", gmName); - return args.get(0); + return gmName; } } diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenHaloFunction.java b/src/main/java/net/rptools/maptool/client/functions/TokenHaloFunction.java index e557e0cca3..bb51e5b186 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenHaloFunction.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenHaloFunction.java @@ -22,7 +22,6 @@ import net.rptools.maptool.client.MapToolVariableResolver; import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.Token; -import net.rptools.maptool.model.Zone; import net.rptools.maptool.util.StringUtil; import net.rptools.parser.Parser; import net.rptools.parser.ParserException; @@ -62,7 +61,7 @@ public Object childEvaluate(Parser parser, String functionName, List arg * @param token the token to get the halo for. * @return the halo. */ - public Object getHalo(Token token) { + public static Object getHalo(Token token) { if (token.getHaloColor() != null) { return "#" + Integer.toHexString(token.getHaloColor().getRGB()).substring(2); } else { @@ -75,29 +74,26 @@ public Object getHalo(Token token) { * * @param token the token to set halo of. * @param value the value to set. - * @throws ParserException if there is an error determining color. */ - public void setHalo(Token token, Object value) throws ParserException { + public static void setHalo(Token token, Object value) { + Color haloColor; if (value instanceof Color) { - token.setHaloColor((Color) value); + haloColor = (Color) value; } else if (value instanceof BigDecimal) { - token.setHaloColor(new Color(((BigDecimal) value).intValue())); + haloColor = new Color(((BigDecimal) value).intValue()); } else { String col = value.toString(); if (StringUtil.isEmpty(col) || col.equalsIgnoreCase("none") || col.equalsIgnoreCase("default")) { - token.setHaloColor(null); + haloColor = null; } else { String hex = col; Color color = MapToolUtil.getColor(hex); - token.setHaloColor(color); + haloColor = color; } } - // TODO: This works for now but could result in a lot of resending of data - Zone zone = MapTool.getFrame().getCurrentZoneRenderer().getZone(); - zone.putToken(token); - MapTool.serverCommand().putToken(zone.getId(), token); + MapTool.serverCommand().updateTokenProperty(token, "setHaloColor", haloColor); } /** diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenImage.java b/src/main/java/net/rptools/maptool/client/functions/TokenImage.java index f0a515c1ca..99b9e98d78 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenImage.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenImage.java @@ -85,25 +85,23 @@ public static TokenImage getInstance() { public Object childEvaluate(Parser parser, String functionName, List args) throws ParserException { Token token; - Zone zone; MapToolVariableResolver resolver = (MapToolVariableResolver) parser.getVariableResolver(); if (functionName.equals("setTokenOpacity")) { - if (!MapTool.getParser().isMacroPathTrusted()) + if (!MapTool.getParser().isMacroTrusted()) throw new ParserException(I18N.getText("macro.function.general.noPerm", functionName)); FunctionUtil.checkNumberParam(functionName, args, 1, 3); - String opacityValue = args.get(0).toString(); + String strOpacity = args.get(0).toString(); + FunctionUtil.paramAsFloat(functionName, args, 0, true); token = FunctionUtil.getTokenFromParam(resolver, functionName, args, 1, 2); - zone = token.getZoneRenderer().getZone(); - float newOpacity = token.setTokenOpacity(Float.parseFloat(opacityValue)); - MapTool.serverCommand().putToken(zone.getId(), token); - return newOpacity; + MapTool.serverCommand().updateTokenProperty(token, "setTokenOpacity", strOpacity); + return token.getTokenOpacity(); } if (functionName.equals("getTokenOpacity")) { - if (!MapTool.getParser().isMacroPathTrusted()) + if (!MapTool.getParser().isMacroTrusted()) throw new ParserException(I18N.getText("macro.function.general.noPerm", functionName)); FunctionUtil.checkNumberParam(functionName, args, 0, 2); @@ -117,7 +115,6 @@ public Object childEvaluate(Parser parser, String functionName, List arg String assetName = args.get(0).toString(); token = FunctionUtil.getTokenFromParam(resolver, functionName, args, 1, 2); - zone = token.getZoneRenderer().getZone(); setImage(token, assetName); return ""; @@ -128,7 +125,6 @@ public Object childEvaluate(Parser parser, String functionName, List arg String assetName = args.get(0).toString(); token = FunctionUtil.getTokenFromParam(resolver, functionName, args, 1, 2); - zone = token.getZoneRenderer().getZone(); setPortrait(token, assetName); return ""; @@ -139,7 +135,6 @@ public Object childEvaluate(Parser parser, String functionName, List arg String assetName = args.get(0).toString(); token = FunctionUtil.getTokenFromParam(resolver, functionName, args, 1, 2); - zone = token.getZoneRenderer().getZone(); setHandout(token, assetName); return ""; @@ -218,48 +213,50 @@ private String typeOf(Object ob) { return null; } - private static void assignImage(Token token, String assetName, imageType type, String func) - throws ParserException { + /** + * Get the MD5Key corresponding to an asset. + * + * @param assetName either an assetId or the name of an image token. + * @param functionName the name of the function, to display the exception message. + * @return the MD5Key associated with the asset. + * @throws ParserException if assetName not found or assetName doesn't + */ + public static MD5Key getMD5Key(String assetName, String functionName) throws ParserException { Matcher m = assetRE.matcher(assetName); String assetId; if (m.matches()) { assetId = m.group(1); } else if (assetName.toLowerCase().startsWith("image:")) { - assetId = findImageToken(assetName, func).getImageAssetId().toString(); + Token imageToken = findImageToken(assetName, functionName); + if (imageToken == null) { + throw new ParserException( + I18N.getText("macro.function.general.unknownToken", functionName, assetName)); + } + assetId = imageToken.getImageAssetId().toString(); } else { throw new ParserException( - I18N.getText("macro.function.general.argumentTypeInvalid", func, 1, assetName)); - } - switch (type) { - case TOKEN_IMAGE: - token.setImageAsset(null, new MD5Key(assetId)); - break; - case TOKEN_PORTRAIT: - token.setPortraitImage(new MD5Key(assetId)); - break; - case TOKEN_HANDOUT: - token.setCharsheetImage(new MD5Key(assetId)); - break; - default: - throw new IllegalArgumentException("unknown image type " + type); + I18N.getText("macro.function.general.argumentTypeInvalid", functionName, 1, assetName)); } - MapTool.serverCommand().putToken(token.getZoneRenderer().getZone().getId(), token); + return new MD5Key(assetId); } - public static void setImage(Token token, String assetName) throws ParserException { - assignImage(token, assetName, imageType.TOKEN_IMAGE, SET_IMAGE); + private static void setImage(Token token, String assetName) throws ParserException { + MD5Key md5key = getMD5Key(assetName, SET_IMAGE); + MapTool.serverCommand().updateTokenProperty(token, "setImageAsset", null, md5key); } - public static void setPortrait(Token token, String assetName) throws ParserException { - assignImage(token, assetName, imageType.TOKEN_PORTRAIT, SET_PORTRAIT); + private static void setPortrait(Token token, String assetName) throws ParserException { + MD5Key md5key = getMD5Key(assetName, SET_PORTRAIT); + MapTool.serverCommand().updateTokenProperty(token, "setPortraitImage", md5key); } - public static void setHandout(Token token, String assetName) throws ParserException { - assignImage(token, assetName, imageType.TOKEN_HANDOUT, SET_HANDOUT); + private static void setHandout(Token token, String assetName) throws ParserException { + MD5Key md5key = getMD5Key(assetName, SET_HANDOUT); + MapTool.serverCommand().updateTokenProperty(token, "setCharsheetImage", md5key); } - public static Token findImageToken(final String name, String functionName) + private static Token findImageToken(final String name, String functionName) throws ParserException { Token imageToken = null; if (name != null && name.length() > 0) { diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenInitFunction.java b/src/main/java/net/rptools/maptool/client/functions/TokenInitFunction.java index 7372d5a491..2f96e866b2 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenInitFunction.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenInitFunction.java @@ -14,26 +14,27 @@ */ package net.rptools.maptool.client.functions; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import net.rptools.maptool.client.MapTool; +import net.rptools.maptool.client.MapToolVariableResolver; import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.InitiativeList.TokenInitiative; import net.rptools.maptool.model.Token; -import net.rptools.maptool.model.Zone; +import net.rptools.maptool.util.FunctionUtil; +import net.rptools.parser.Parser; import net.rptools.parser.ParserException; +import net.rptools.parser.function.AbstractFunction; /** * Set the token initiative * * @author Jay */ -public class TokenInitFunction extends AbstractTokenAccessorFunction { +public class TokenInitFunction extends AbstractFunction { - /** Getter has 0 or 1, setter has 1 or 2 */ + /** Getter has 0 to 2, setter has 1 to 3 */ private TokenInitFunction() { - super(0, 2, "setInitiative", "getInitiative"); + super(0, 3, "setInitiative", "getInitiative"); } /** singleton instance of this function */ @@ -44,14 +45,26 @@ public static TokenInitFunction getInstance() { return singletonInstance; }; - /** - * @see - * net.rptools.maptool.client.functions.AbstractTokenAccessorFunction#getValue(net.rptools.maptool.model.Token) - */ @Override - protected Object getValue(Token token) throws ParserException { + public Object childEvaluate(Parser parser, String functionName, List args) + throws ParserException { + MapToolVariableResolver res = (MapToolVariableResolver) parser.getVariableResolver(); + + if (functionName.equalsIgnoreCase("getInitiative")) { + FunctionUtil.checkNumberParam(functionName, args, 0, 2); + Token token = FunctionUtil.getTokenFromParam(res, functionName, args, 0, 1); + return getInitiative(token); + } else { + FunctionUtil.checkNumberParam(functionName, args, 1, 3); + String value = args.get(0).toString(); + Token token = FunctionUtil.getTokenFromParam(res, functionName, args, 1, 2); + return setInitiative(token, value); + } + } + + public static String getInitiative(Token token) { String ret = ""; - List tis = getTokenInitiatives(token); + List tis = token.getInitiatives(); if (tis.isEmpty()) return I18N.getText("macro.function.TokenInit.notOnList"); for (TokenInitiative ti : tis) { if (ret.length() > 0) ret += ", "; @@ -60,50 +73,10 @@ protected Object getValue(Token token) throws ParserException { return ret; } - /** - * @see - * net.rptools.maptool.client.functions.AbstractTokenAccessorFunction#setValue(net.rptools.maptool.model.Token, - * java.lang.Object) - */ - @Override - protected Object setValue(Token token, Object value) throws ParserException { - String sValue = null; - if (value != null) sValue = value.toString(); - List tis = getTokenInitiatives(token); - if (tis.isEmpty()) return I18N.getText("macro.function.TokenInit.notOnListSet"); - for (TokenInitiative ti : tis) ti.setState(sValue); + public static Object setInitiative(Token token, String value) { + if (token.getInitiatives().isEmpty()) + return I18N.getText("macro.function.TokenInit.notOnListSet"); + MapTool.serverCommand().updateTokenProperty(token, "setInitiative", value); return value; } - - /** - * Get the first token initiative - * - * @param token Get it for this token - * @return The first token initiative value for the passed token - * @throws ParserException Token isn't in initiative. - */ - public static TokenInitiative getTokenInitiative(Token token) throws ParserException { - Zone zone = MapTool.getFrame().getCurrentZoneRenderer().getZone(); - List list = zone.getInitiativeList().indexOf(token); - if (list.isEmpty()) return null; - return zone.getInitiativeList().getTokenInitiative(list.get(0).intValue()); - } - - /** - * Get the first token initiative - * - * @param token Get it for this token - * @return The first token initiative value for the passed token - * @throws ParserException Token isn't in initiative. - */ - @SuppressWarnings("unchecked") - public static List getTokenInitiatives(Token token) throws ParserException { - Zone zone = MapTool.getFrame().getCurrentZoneRenderer().getZone(); - List list = zone.getInitiativeList().indexOf(token); - if (list.isEmpty()) return Collections.EMPTY_LIST; - List ret = new ArrayList(list.size()); - for (Integer index : list) - ret.add(zone.getInitiativeList().getTokenInitiative(index.intValue())); - return ret; - } } diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenInitHoldFunction.java b/src/main/java/net/rptools/maptool/client/functions/TokenInitHoldFunction.java index 2571378e66..639bcd994b 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenInitHoldFunction.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenInitHoldFunction.java @@ -16,21 +16,26 @@ import java.math.BigDecimal; import java.util.List; +import net.rptools.maptool.client.MapTool; +import net.rptools.maptool.client.MapToolVariableResolver; import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.InitiativeList.TokenInitiative; import net.rptools.maptool.model.Token; +import net.rptools.maptool.util.FunctionUtil; +import net.rptools.parser.Parser; import net.rptools.parser.ParserException; +import net.rptools.parser.function.AbstractFunction; /** * Set the token initiative hold value * * @author Jay */ -public class TokenInitHoldFunction extends AbstractTokenAccessorFunction { +public class TokenInitHoldFunction extends AbstractFunction { - /** Getter has 0 or 1, setter has 1 or 2 */ + /** Getter has 0 to 2, setter has 1 to 3 */ private TokenInitHoldFunction() { - super(0, 2, "setInitiativeHold", "getInitiativeHold"); + super(0, 3, "setInitiativeHold", "getInitiativeHold"); } /** singleton instance of this function */ @@ -41,28 +46,33 @@ public static TokenInitHoldFunction getInstance() { return singletonInstance; }; - /** - * @see - * net.rptools.maptool.client.functions.AbstractTokenAccessorFunction#getValue(net.rptools.maptool.model.Token) - */ @Override - protected Object getValue(Token token) throws ParserException { - TokenInitiative ti = TokenInitFunction.getTokenInitiative(token); + public Object childEvaluate(Parser parser, String functionName, List args) + throws ParserException { + MapToolVariableResolver res = (MapToolVariableResolver) parser.getVariableResolver(); + + if (functionName.equalsIgnoreCase("getInitiativeHold")) { + FunctionUtil.checkNumberParam(functionName, args, 0, 2); + Token token = FunctionUtil.getTokenFromParam(res, functionName, args, 0, 1); + return getInitiativeHold(token); + } else { + FunctionUtil.checkNumberParam(functionName, args, 1, 3); + boolean set = FunctionUtil.paramAsBoolean(functionName, args, 0, true); + Token token = FunctionUtil.getTokenFromParam(res, functionName, args, 1, 2); + return setInitiativeHold(token, set); + } + } + + public static Object getInitiativeHold(Token token) { + TokenInitiative ti = token.getInitiative(); if (ti == null) return I18N.getText("macro.function.TokenInit.notOnList"); return ti.isHolding() ? BigDecimal.ONE : BigDecimal.ZERO; } - /** - * @see - * net.rptools.maptool.client.functions.AbstractTokenAccessorFunction#setValue(net.rptools.maptool.model.Token, - * java.lang.Object) - */ - @Override - protected Object setValue(Token token, Object value) throws ParserException { - boolean set = getBooleanValue(value); - List tis = TokenInitFunction.getTokenInitiatives(token); - if (tis.isEmpty()) return I18N.getText("macro.function.TokenInit.notOnListSet"); - for (TokenInitiative ti : tis) ti.setHolding(set); + public static Object setInitiativeHold(Token token, boolean set) { + if (token.getInitiatives().isEmpty()) + return I18N.getText("macro.function.TokenInit.notOnListSet"); + MapTool.serverCommand().updateTokenProperty(token, "setInitiativeHold", set); return set ? BigDecimal.ONE : BigDecimal.ZERO; } } diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenLabelFunction.java b/src/main/java/net/rptools/maptool/client/functions/TokenLabelFunction.java index 534f7e8694..0ce1d1dd1d 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenLabelFunction.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenLabelFunction.java @@ -19,7 +19,7 @@ import net.rptools.maptool.client.MapToolVariableResolver; import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.Token; -import net.rptools.maptool.model.Zone; +import net.rptools.maptool.util.FunctionUtil; import net.rptools.parser.Parser; import net.rptools.parser.ParserException; import net.rptools.parser.function.AbstractFunction; @@ -42,7 +42,7 @@ private TokenLabelFunction() { * @param token The token to get the label for. * @return the label. */ - public String getLabel(Token token) { + public static String getLabel(Token token) { return token.getLabel() != null ? token.getLabel() : ""; } @@ -52,10 +52,8 @@ public String getLabel(Token token) { * @param token The token to set the label for. * @param label the label to set. */ - public void setLabel(Token token, String label) { - token.setLabel(label); - MapTool.serverCommand() - .putToken(MapTool.getFrame().getCurrentZoneRenderer().getZone().getId(), token); + public static void setLabel(Token token, String label) { + MapTool.serverCommand().updateTokenProperty(token, "setLabel", label); } @Override @@ -76,7 +74,7 @@ public Object childEvaluate(Parser parser, String functionName, List arg * @return the name of the token. * @throws ParserException when an error occurs. */ - private Object getLabel(Parser parser, List args) throws ParserException { + private static Object getLabel(Parser parser, List args) throws ParserException { Token token; if (args.size() == 1) { @@ -112,37 +110,13 @@ private Object getLabel(Parser parser, List args) throws ParserException * @return the new name of the token. * @throws ParserException when an error occurs. */ - private Object setLabel(Parser parser, List args) throws ParserException { - Token token; + private static Object setLabel(Parser parser, List args) throws ParserException { + FunctionUtil.checkNumberParam("setLabel", args, 1, 3); + MapToolVariableResolver res = (MapToolVariableResolver) parser.getVariableResolver(); - if (args.size() == 2) { - if (!MapTool.getParser().isMacroTrusted()) { - throw new ParserException(I18N.getText("macro.function.general.noPermOther", "setLabel")); - } - token = FindTokenFunctions.findToken(args.get(1).toString(), null); - if (token == null) { - throw new ParserException( - I18N.getText( - "macro.function.general.unknownToken", "setLabel", args.get(1).toString())); - } - } else if (args.size() == 1) { - MapToolVariableResolver res = (MapToolVariableResolver) parser.getVariableResolver(); - token = res.getTokenInContext(); - if (token == null) { - throw new ParserException( - I18N.getText("macro.function.general.noImpersonated", "setLabel")); - } - } else if (args.isEmpty()) { - throw new ParserException( - I18N.getText("macro.function.general.notEnoughParam", "setLabel", 1, args.size())); - } else { - throw new ParserException( - I18N.getText("macro.function.general.tooManyParam", "setLabel", 2, args.size())); - } - setLabel(token, args.get(0).toString()); - Zone zone = MapTool.getFrame().getCurrentZoneRenderer().getZone(); - MapTool.serverCommand().putToken(zone.getId(), token); - zone.putToken(token); - return args.get(0); + String label = args.get(0).toString(); + Token token = FunctionUtil.getTokenFromParam(res, "setLabel", args, 1, 2); + setLabel(token, label); + return label; } } diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenLightFunctions.java b/src/main/java/net/rptools/maptool/client/functions/TokenLightFunctions.java index f5618979ff..497731187f 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenLightFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenLightFunctions.java @@ -93,7 +93,8 @@ public Object childEvaluate(Parser parser, String functionName, List par * @return a string list containing the lights that are on. * @throws ParserException if the light type can't be found. */ - private String getLights(Token token, String category, String delim) throws ParserException { + private static String getLights(Token token, String category, String delim) + throws ParserException { ArrayList lightList = new ArrayList(); Map> lightSourcesMap = MapTool.getCampaign().getLightSourcesMap(); @@ -135,7 +136,7 @@ private String getLights(Token token, String category, String delim) throws Pars * @return 0 if the light was not found, otherwise 1; * @throws ParserException if the light type can't be found. */ - private BigDecimal setLight(Token token, String category, String name, BigDecimal val) + private static BigDecimal setLight(Token token, String category, String name, BigDecimal val) throws ParserException { boolean found = false; Map> lightSourcesMap = @@ -175,7 +176,8 @@ private BigDecimal setLight(Token token, String category, String name, BigDecima * @return true if the token has the light source. * @throws ParserException if the light type can't be found. */ - private boolean hasLightSource(Token token, String category, String name) throws ParserException { + public static boolean hasLightSource(Token token, String category, String name) + throws ParserException { if (category.equals("*") && name.equals("*")) { return token.hasLightSources(); } diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenLocationFunctions.java b/src/main/java/net/rptools/maptool/client/functions/TokenLocationFunctions.java index 6b2741ef87..e7bdb4849b 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenLocationFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenLocationFunctions.java @@ -554,33 +554,27 @@ private BigDecimal getDistanceToXY(MapToolVariableResolver res, List arg } /** - * Moves a token to the specified x,y location. If units is true, the incoming (x,y) - * is treated as a ZonePoint. If units is false, the incoming (x,y) is - * treated as a CellPoint and is converted to a ZonePoint by calling {@link + * Get a ZonePoint of the specified x,y location. If units is true, the incoming + * (x,y) is treated as a ZonePoint. If units is false, the incoming + * (x,y) is treated as a CellPoint and is converted to a ZonePoint by calling {@link * Grid#convert(CellPoint)}. * - * @param token The token to move. * @param x the x co-ordinate of the destination. * @param y the y co-ordinate of the destination. * @param units whether the (x,y) coordinate is a ZonePoint (true) or CellPoint * (false) + * @return the ZonePoint of the coordinates */ - public void moveToken(Token token, int x, int y, boolean units) { - Grid grid = MapTool.getFrame().getCurrentZoneRenderer().getZone().getGrid(); - int newX; - int newY; - + public static ZonePoint getZonePoint(int x, int y, boolean units) { + ZonePoint zp; if (units) { - ZonePoint zp = new ZonePoint(x, y); - newX = zp.x; - newY = zp.y; + zp = new ZonePoint(x, y); } else { + Grid grid = MapTool.getFrame().getCurrentZoneRenderer().getZone().getGrid(); CellPoint cp = new CellPoint(x, y); - ZonePoint zp = grid.convert(cp); - newX = zp.x; - newY = zp.y; + zp = grid.convert(cp); } - MapTool.serverCommand().updateTokenProperty(token, "setXY", newX, newY); + return zp; } /** @@ -589,44 +583,19 @@ public void moveToken(Token token, int x, int y, boolean units) { * @param res the variable resolver. * @param args the arguments to the function. */ - private String moveToken(MapToolVariableResolver res, List args) throws ParserException { - Token token = FunctionUtil.getTokenFromParam(res, "moveToken", args, 3, -1); - boolean useDistance = true; - - if (args.size() < 2) { - throw new ParserException( - I18N.getText("macro.function.general.notEnoughParam", "moveToken", 2, args.size())); - } - - int x, y; - - if (!(args.get(0) instanceof BigDecimal)) { - throw new ParserException( - I18N.getText( - "macro.function.general.argumentTypeN", "moveToken", 1, args.get(0).toString())); - } - if (!(args.get(1) instanceof BigDecimal)) { - throw new ParserException( - I18N.getText( - "macro.function.general.argumentTypeN", "moveToken", 2, args.get(1).toString())); - } + private static String moveToken(MapToolVariableResolver res, List args) + throws ParserException { + FunctionUtil.checkNumberParam("moveToken", args, 2, 5); - x = ((BigDecimal) args.get(0)).intValue(); - y = ((BigDecimal) args.get(1)).intValue(); + int x = FunctionUtil.paramAsInteger("moveToken", args, 0, false); + int y = FunctionUtil.paramAsInteger("moveToken", args, 1, false); + boolean useDistance = + args.size() > 2 ? FunctionUtil.paramAsBoolean("moveToken", args, 2, false) : true; + Token token = FunctionUtil.getTokenFromParam(res, "moveToken", args, 3, 4); - if (args.size() > 2) { - if (!(args.get(2) instanceof BigDecimal)) { - throw new ParserException( - I18N.getText( - "macro.function.general.argumentTypeN", "moveToken", 3, args.get(2).toString())); - } - BigDecimal val = (BigDecimal) args.get(2); - useDistance = val.equals(BigDecimal.ZERO) ? false : true; - } - moveToken(token, x, y, useDistance); - ZoneRenderer renderer = MapTool.getFrame().getCurrentZoneRenderer(); - Zone zone = renderer.getZone(); - renderer.flushLight(); + ZonePoint zp = getZonePoint(x, y, useDistance); + MapTool.serverCommand().updateTokenProperty(token, "setXY", zp.x, zp.y); + token.getZoneRenderer().flushLight(); return ""; } diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenNameFunction.java b/src/main/java/net/rptools/maptool/client/functions/TokenNameFunction.java index 8767fa0562..f2bd3161fa 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenNameFunction.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenNameFunction.java @@ -17,7 +17,6 @@ import java.util.List; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.MapToolVariableResolver; -import net.rptools.maptool.client.ui.zone.ZoneRenderer; import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.Token; import net.rptools.maptool.util.FunctionUtil; @@ -53,17 +52,14 @@ public Object childEvaluate(Parser parser, String functionName, List arg token = FunctionUtil.getTokenFromParam(resolver, functionName, args, 0, 1); } else { FunctionUtil.checkNumberParam(functionName, args, 1, 3); + String name = args.get(0).toString(); token = FunctionUtil.getTokenFromParam(resolver, functionName, args, 1, 2); if (args.get(0).toString().equals("")) { throw new ParserException( I18N.getText("macro.function.tokenName.emptyTokenNameForbidden", "setName")); } - - token.setName(args.get(0).toString()); - - ZoneRenderer renderer = token.getZoneRenderer(); - MapTool.serverCommand().putToken(renderer.getZone().getId(), token); + MapTool.serverCommand().updateTokenProperty(token, "setName", name); } return token.getName(); } @@ -74,7 +70,7 @@ public Object childEvaluate(Parser parser, String functionName, List arg * @param token the token to get the name of. * @return the name of the token. */ - public String getName(Token token) { + public static String getName(Token token) { return token.getName(); } @@ -84,7 +80,7 @@ public String getName(Token token) { * @param token The token to set the name of. * @param name the name of the token. */ - public void setName(Token token, String name) { + public static void setName(Token token, String name) { token.setName(name); } } diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenPropertyFunctions.java b/src/main/java/net/rptools/maptool/client/functions/TokenPropertyFunctions.java index 9f2f990317..3a553d5197 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenPropertyFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenPropertyFunctions.java @@ -304,14 +304,12 @@ public Object childEvaluate(Parser parser, String functionName, List par /* * String empty = resetProperty(String propName, String tokenId: currentToken(), string mapName: current map) */ - if (functionName.equals("resetProperty")) { + if (functionName.equalsIgnoreCase("resetProperty")) { FunctionUtil.checkNumberParam(functionName, parameters, 1, 3); + String property = parameters.get(0).toString(); Token token = FunctionUtil.getTokenFromParam(resolver, functionName, parameters, 1, 2); - ZoneRenderer zoneR = token.getZoneRenderer(); - Zone zone = zoneR.getZone(); - token.resetProperty(parameters.get(0).toString()); - MapTool.serverCommand().putToken(zone.getId(), token); + MapTool.serverCommand().updateTokenProperty(token, "resetProperty", property); return ""; } @@ -803,7 +801,7 @@ public Object childEvaluate(Parser parser, String functionName, List par if (functionName.equals("setTokenSnapToGrid")) { FunctionUtil.checkNumberParam(functionName, parameters, 1, 3); Token token = FunctionUtil.getTokenFromParam(resolver, functionName, parameters, 1, 2); - Boolean toGrid = AbstractTokenAccessorFunction.getBooleanValue((Object) parameters.get(0)); + Boolean toGrid = FunctionUtil.getBooleanValue((Object) parameters.get(0)); MapTool.serverCommand().updateTokenProperty(token, "setSnapToGrid", toGrid); return token.isSnapToGrid() ? BigDecimal.ONE : BigDecimal.ZERO; } @@ -873,18 +871,14 @@ private void resetSize(Token token) { } /** - * Sets the layer of the token. + * Get the Zone.Layer element corresponding to a layerName * - * @param token The token to move to a different layer. - * @param layerName the name of the layer to move the token to. - * @param forceShape normally true, but can be optionally set to false - * by MTscript - * @return the name of the layer the token was moved to. + * @param layerName the String of the name of the layer * @throws ParserException if the layer name is invalid. + * @return the Zone.Layer corresponding to the layerName */ - public String setLayer(Token token, String layerName, boolean forceShape) throws ParserException { + public static Zone.Layer getLayer(String layerName) throws ParserException { Zone.Layer layer; - if (layerName.equalsIgnoreCase(Zone.Layer.TOKEN.toString())) { layer = Zone.Layer.TOKEN; } else if (layerName.equalsIgnoreCase(Zone.Layer.BACKGROUND.toString())) { @@ -898,9 +892,21 @@ public String setLayer(Token token, String layerName, boolean forceShape) throws throw new ParserException( I18N.getText("macro.function.tokenProperty.unknownLayer", "setLayer", layerName)); } - MapTool.serverCommand().updateTokenProperty(token, "setLayer", layer); + return layer; + } + + /** + * Get the token shape corresponding to the token & layer. Returns null if can't find match, or if + * forceShape is set to false. + * + * @param token the token to get the new shape of. + * @param layer the layer of the token. + * @param forceShape should we even get a new shape? + * @return the new TokenShape of the token + */ + public static Token.TokenShape getTokenShape(Token token, Zone.Layer layer, boolean forceShape) { + Token.TokenShape tokenShape = null; if (forceShape) { - Token.TokenShape tokenShape = null; switch (layer) { case BACKGROUND: case OBJECT: @@ -910,16 +916,34 @@ public String setLayer(Token token, String layerName, boolean forceShape) throws case TOKEN: Image image = ImageManager.getImage(token.getImageAssetId()); if (image == null || image == ImageManager.TRANSFERING_IMAGE) { - tokenShape = Token.TokenShape.TOP_DOWN; } else { tokenShape = TokenUtil.guessTokenType(image); } break; } - if (tokenShape != null) { - MapTool.serverCommand().updateTokenProperty(token, "setShape", tokenShape); - } + } + return tokenShape; + } + /** + * Sets the layer of the token. + * + * @param token The token to move to a different layer. + * @param layerName the name of the layer to move the token to. + * @param forceShape normally true, but can be optionally set to false + * by MTscript + * @return the name of the layer the token was moved to. + * @throws ParserException if the layer name is invalid. + */ + private static String setLayer(Token token, String layerName, boolean forceShape) + throws ParserException { + Zone.Layer layer = getLayer(layerName); + Token.TokenShape tokenShape = getTokenShape(token, layer, forceShape); + + if (tokenShape != null) { + MapTool.serverCommand().updateTokenProperty(token, "setLayerShape", layer, tokenShape); + } else { + MapTool.serverCommand().updateTokenProperty(token, "setLayer", layer); } return layerName; } diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenRemoveFromInitiativeFunction.java b/src/main/java/net/rptools/maptool/client/functions/TokenRemoveFromInitiativeFunction.java index f329f1d7f7..318a0f49c5 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenRemoveFromInitiativeFunction.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenRemoveFromInitiativeFunction.java @@ -17,9 +17,11 @@ import java.math.BigDecimal; import java.util.List; import net.rptools.maptool.client.MapTool; +import net.rptools.maptool.client.MapToolVariableResolver; import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.InitiativeList; import net.rptools.maptool.model.Token; +import net.rptools.maptool.util.FunctionUtil; import net.rptools.parser.Parser; import net.rptools.parser.ParserException; import net.rptools.parser.function.AbstractFunction; @@ -33,7 +35,7 @@ public class TokenRemoveFromInitiativeFunction extends AbstractFunction { /** Handle adding one, all, all PCs or all NPC tokens. */ private TokenRemoveFromInitiativeFunction() { - super(0, 1, "removeFromInitiative"); + super(0, 2, "removeFromInitiative"); } /** singleton instance of this function */ @@ -52,8 +54,11 @@ public static TokenRemoveFromInitiativeFunction getInstance() { @Override public Object childEvaluate(Parser parser, String functionName, List args) throws ParserException { - InitiativeList list = MapTool.getFrame().getCurrentZoneRenderer().getZone().getInitiativeList(); - Token token = AbstractTokenAccessorFunction.getTarget(parser, args, 1); + MapToolVariableResolver res = (MapToolVariableResolver) parser.getVariableResolver(); + Token token = FunctionUtil.getTokenFromParam(res, functionName, args, 0, 1); + + InitiativeList list = token.getZoneRenderer().getZone().getInitiativeList(); + if (!MapTool.getParser().isMacroTrusted()) { if (!MapTool.getFrame().getInitiativePanel().hasOwnerPermission(token)) { String message = I18N.getText("macro.function.initiative.gmOnly", functionName); diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenSelectionFunctions.java b/src/main/java/net/rptools/maptool/client/functions/TokenSelectionFunctions.java index 690dbb6904..d3aea064ae 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenSelectionFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenSelectionFunctions.java @@ -24,6 +24,7 @@ import net.rptools.maptool.model.GUID; import net.rptools.maptool.model.Token; import net.rptools.maptool.model.Zone; +import net.rptools.maptool.util.FunctionUtil; import net.rptools.parser.Parser; import net.rptools.parser.ParserException; import net.rptools.parser.function.AbstractFunction; @@ -156,7 +157,7 @@ private void selectTokens(List parameters) throws ParserException { } else if (parameters.size() == 2) { String paramStr = parameters.get(0).toString(); String addOrReplace = parameters.get(1).toString(); - boolean add = AbstractTokenAccessorFunction.getBooleanValue(addOrReplace); + boolean add = FunctionUtil.getBooleanValue(addOrReplace); if (add) { allGUIDs = zr.getSelectedTokenSet(); @@ -176,7 +177,7 @@ private void selectTokens(List parameters) throws ParserException { String paramStr = parameters.get(0).toString(); String addOrReplace = parameters.get(1).toString(); String delim = parameters.get(2).toString(); - boolean add = AbstractTokenAccessorFunction.getBooleanValue(addOrReplace); + boolean add = FunctionUtil.getBooleanValue(addOrReplace); if (add) { allGUIDs = zr.getSelectedTokenSet(); diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenStateFunction.java b/src/main/java/net/rptools/maptool/client/functions/TokenStateFunction.java index 20f6e4813a..d0f140c6cf 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenStateFunction.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenStateFunction.java @@ -74,8 +74,14 @@ public Object childEvaluate(Parser parser, String functionName, List arg Token token = FunctionUtil.getTokenFromParam(resolver, functionName, args, 2, 3); return setState(token, stateName, value); } else if (functionName.equals("getTokenStates")) { - FunctionUtil.checkNumberParam(functionName, args, 0, 2); - return getTokenStates(parser, args); + FunctionUtil.checkNumberParam(functionName, args, 0, 4); + String delim = args.size() > 0 ? args.get(0).toString() : ","; + String group = args.size() > 1 ? args.get(1).toString() : "*"; + Token token = + args.size() > 2 + ? FunctionUtil.getTokenFromParam(resolver, functionName, args, 2, 3) + : null; + return getTokenStates(delim, group, token); } else { throw new ParserException( I18N.getText("macro.function.general.unknownFunction", functionName)); @@ -90,7 +96,7 @@ public Object childEvaluate(Parser parser, String functionName, List arg * @return the value of the state. * @throws ParserException if the state is unknown. */ - public Object getState(Token token, String stateName) throws ParserException { + public static Object getState(Token token, String stateName) throws ParserException { return getBooleanTokenState(token, stateName) ? BigDecimal.valueOf(1) : BigDecimal.valueOf(0); } @@ -102,7 +108,7 @@ public Object getState(Token token, String stateName) throws ParserException { * @return the value of the state. * @throws ParserException if an error occurs. */ - public boolean getBooleanTokenState(Token token, String stateName) throws ParserException { + public static boolean getBooleanTokenState(Token token, String stateName) throws ParserException { if (!MapTool.getCampaign().getTokenStatesMap().containsKey(stateName)) { throw new ParserException( I18N.getText("macro.function.tokenStateFunctions.unknownState", stateName)); @@ -120,7 +126,8 @@ public boolean getBooleanTokenState(Token token, String stateName) throws Parser * @return the value of the state. * @throws ParserException if the state is unknown. */ - public BigDecimal setState(Token token, String stateName, Object val) throws ParserException { + public static BigDecimal setState(Token token, String stateName, Object val) + throws ParserException { boolean set = getBooleanFromValue(val); if (stateName.equals(ALL_STATES)) { MapTool.serverCommand().updateTokenProperty(token, "setAllStates", set); @@ -139,7 +146,7 @@ public BigDecimal setState(Token token, String stateName, Object val) throws Par * * @param value the object to get the value from */ - private boolean getBooleanFromValue(Object value) { + private static boolean getBooleanFromValue(Object value) { if (value == null) { // If state does not exist then it can't be set ;) return false; } @@ -157,27 +164,32 @@ private boolean getBooleanFromValue(Object value) { } /** - * Gets a list of the valid token states. + * Gets a list of the token states, either from the Campaign or from a token. * - * @param parser The parser. - * @param args The arguments. + * @param delim The delimiter to use to return the list. + * @param group The group to get the states of. If "*" returns states of all groups. + * @param token The token to get the states of. If null, get the Campaign states instead. * @return A string with the states. */ - private String getTokenStates(Parser parser, List args) { - String delim = args.size() > 0 ? args.get(0).toString() : ","; + private String getTokenStates(String delim, String group, Token token) { Set stateNames; - if (args.size() > 1) { - String group = (String) args.get(1); + if ("*".equals(group)) { // get all Campaign states + stateNames = MapTool.getCampaign().getTokenStatesMap().keySet(); + } else { Map states = MapTool.getCampaign().getTokenStatesMap(); stateNames = new HashSet(); for (BooleanTokenOverlay bto : states.values()) { - if (group.equals(bto.getGroup())) { - stateNames.add(bto.getName()); - } + // return states of the group that matches + if (group.equals(bto.getGroup())) stateNames.add(bto.getName()); } - } else { - stateNames = MapTool.getCampaign().getTokenStatesMap().keySet(); + } + + if (token != null) { + // only keep states set to true on token + Set tokenStates = token.getStatePropertyNames(true); + tokenStates.retainAll(stateNames); + stateNames = tokenStates; } StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenTerrainModifierFunctions.java b/src/main/java/net/rptools/maptool/client/functions/TokenTerrainModifierFunctions.java index 98146881de..ce38426001 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenTerrainModifierFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenTerrainModifierFunctions.java @@ -20,6 +20,7 @@ import net.rptools.maptool.client.MapToolVariableResolver; import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.Token; +import net.rptools.maptool.util.FunctionUtil; import net.rptools.parser.Parser; import net.rptools.parser.ParserException; import net.rptools.parser.function.AbstractFunction; @@ -81,14 +82,14 @@ public double getTerrainModifier(Token token) throws ParserException { * * @param token the token to set. * @param val the value to set the terrain modifier to. - * @throws ParserException + * @throws ParserException if no permission */ public void setTerrainModifier(Token token, BigDecimal val) throws ParserException { if (!MapTool.getParser().isMacroTrusted()) { throw new ParserException( I18N.getText("macro.function.general.noPerm", "setTerrainModifier")); } - token.setTerrainModifier(val.doubleValue()); + MapTool.serverCommand().updateTokenProperty(token, "setTerrainModifier", val.doubleValue()); } /** @@ -100,55 +101,13 @@ public void setTerrainModifier(Token token, BigDecimal val) throws ParserExcepti * @throws ParserException if an error occurs. */ private Object setTerrainModifier(Parser parser, List args) throws ParserException { - BigDecimal val; - Token token; + MapToolVariableResolver res = (MapToolVariableResolver) parser.getVariableResolver(); + FunctionUtil.checkNumberParam("setTerrainModifier", args, 1, 3); - switch (args.size()) { - case 2: - token = FindTokenFunctions.findToken(args.get(1).toString(), null); - if (token == null) { - throw new ParserException( - I18N.getText( - "macro.function.general.unknownToken", - "setTerrainModifier", - args.get(1).toString())); - } - break; - case 1: - MapToolVariableResolver res = (MapToolVariableResolver) parser.getVariableResolver(); - token = res.getTokenInContext(); - if (token == null) { - throw new ParserException( - I18N.getText("macro.function.general.noImpersonated", "setTerrainModifier")); - } - break; - case 0: - throw new ParserException( - I18N.getText( - "macro.function.general.notEnoughParam", "setTerrainModifier", 1, args.size())); - default: - throw new ParserException( - I18N.getText( - "macro.function.general.tooManyParam", "setTerrainModifier", 2, args.size())); - } - - if (args.get(0) instanceof BigDecimal) { - val = (BigDecimal) args.get(0); - } else { - throw new ParserException( - I18N.getText( - "macro.function.general.argumentTypeN", - "setTerrainModifier", - 1, - args.get(0).toString())); - } + BigDecimal val = FunctionUtil.paramAsBigDecimal("setTerrainModifier", args, 0, false); + Token token = FunctionUtil.getTokenFromParam(res, "setTerrainModifier", args, 1, 2); setTerrainModifier(token, val); - - MapTool.getFrame().getCurrentZoneRenderer().getZone().putToken(token); - MapTool.serverCommand() - .putToken(MapTool.getFrame().getCurrentZoneRenderer().getZone().getId(), token); - return val; } @@ -190,11 +149,6 @@ private Object getTerrainModifier(Parser parser, List args) throws Parse } val = getTerrainModifier(token); - - MapTool.getFrame().getCurrentZoneRenderer().getZone().putToken(token); - MapTool.serverCommand() - .putToken(MapTool.getFrame().getCurrentZoneRenderer().getZone().getId(), token); - return val; } } diff --git a/src/main/java/net/rptools/maptool/client/functions/TokenVisibleFunction.java b/src/main/java/net/rptools/maptool/client/functions/TokenVisibleFunction.java index 40a2b85192..82050aef15 100644 --- a/src/main/java/net/rptools/maptool/client/functions/TokenVisibleFunction.java +++ b/src/main/java/net/rptools/maptool/client/functions/TokenVisibleFunction.java @@ -21,6 +21,7 @@ import net.rptools.maptool.client.MapToolVariableResolver; import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.Token; +import net.rptools.maptool.util.FunctionUtil; import net.rptools.parser.Parser; import net.rptools.parser.ParserException; import net.rptools.parser.function.AbstractFunction; @@ -51,7 +52,7 @@ public static TokenVisibleFunction getInstance() { return instance; } - public boolean getBooleanVisible(Token token) throws ParserException { + public static boolean getBooleanVisible(Token token) throws ParserException { if (!MapTool.getParser().isMacroTrusted()) { throw new ParserException(I18N.getText("macro.function.general.noPerm", "getVisible")); } @@ -69,7 +70,7 @@ public boolean getBooleanVisible(Token token) throws ParserException { * @return if the token is visible. * @throws ParserException if the player does not have permissions to check. */ - public Object getVisible(Token token) throws ParserException { + public static Object getVisible(Token token) throws ParserException { return getBooleanVisible(token) ? BigDecimal.ONE : BigDecimal.ZERO; } @@ -78,9 +79,9 @@ public Object getVisible(Token token) throws ParserException { * * @param token the token to set. * @param val the value to set the visible flag to. - * @throws ParserException + * @throws ParserException if no permission */ - public void setVisible(Token token, Object val) throws ParserException { + public static void setVisible(Token token, Object val) throws ParserException { if (!MapTool.getParser().isMacroTrusted()) { throw new ParserException(I18N.getText("macro.function.general.noPerm", "setVisible")); } @@ -96,12 +97,10 @@ public void setVisible(Token token, Object val) throws ParserException { set = Boolean.parseBoolean(val.toString()); } } - token.setVisible(set); - MapTool.serverCommand() - .putToken(MapTool.getFrame().getCurrentZoneRenderer().getZone().getId(), token); + MapTool.serverCommand().updateTokenProperty(token, "setVisible", set); } - public void setOwnerOnlyVisible(Token token, Object val) throws ParserException { + public static void setOwnerOnlyVisible(Token token, Object val) throws ParserException { if (!MapTool.getParser().isMacroTrusted()) { throw new ParserException( I18N.getText("macro.function.general.noPerm", "setOwnerOnlyVisible")); @@ -118,9 +117,7 @@ public void setOwnerOnlyVisible(Token token, Object val) throws ParserException set = Boolean.parseBoolean(val.toString()); } } - token.setVisibleOnlyToOwner(set); - MapTool.serverCommand() - .putToken(MapTool.getFrame().getCurrentZoneRenderer().getZone().getId(), token); + MapTool.serverCommand().updateTokenProperty(token, "setVisibleOnlyToOwner", set); } @Override @@ -151,7 +148,7 @@ public Object childEvaluate(Parser parser, String functionName, List par * @return if the token is visible or not. * @throws ParserException if an error occurs. */ - private Object getVisible(Parser parser, List args) throws ParserException { + private static Object getVisible(Parser parser, List args) throws ParserException { Token token; if (args.size() == 1) { @@ -183,7 +180,7 @@ private Object getVisible(Parser parser, List args) throws ParserExcepti * @return the value visible is set to. * @throws ParserException if an error occurs. */ - private Object setVisible(Parser parser, List args) throws ParserException { + private static Object setVisible(Parser parser, List args) throws ParserException { Object val; Token token; @@ -209,14 +206,12 @@ private Object setVisible(Parser parser, List args) throws ParserExcepti I18N.getText("macro.function.general.tooManyParam", "setVisible", 2, args.size())); } val = args.get(0); - setVisible(token, val); // Already calls serverCommand().putToken() - // MapTool.serverCommand().putToken(MapTool.getFrame().getCurrentZoneRenderer().getZone().getId(), token); - MapTool.getFrame().getCurrentZoneRenderer().getZone().putToken(token); - + setVisible(token, val); return val; } - private Object setOwnerOnlyVisible(Parser parser, List args) throws ParserException { + private static Object setOwnerOnlyVisible(Parser parser, List args) + throws ParserException { Object val; Token token; @@ -246,12 +241,12 @@ private Object setOwnerOnlyVisible(Parser parser, List args) throws Pars "macro.function.general.tooManyParam", "setOwnerOnlyVisible", 2, args.size())); } val = args.get(0); - setOwnerOnlyVisible(token, val); // Already calls serverCommand().putToken() - MapTool.getFrame().getCurrentZoneRenderer().getZone().putToken(token); + setOwnerOnlyVisible(token, val); return val; } - private Object getOwnerOnlyVisible(Parser parser, List args) throws ParserException { + private static Object getOwnerOnlyVisible(Parser parser, List args) + throws ParserException { Token token; if (args.size() == 1) { @@ -278,7 +273,7 @@ private Object getOwnerOnlyVisible(Parser parser, List args) throws Pars return getOwnerOnlyVisible(token); } - public Object getOwnerOnlyVisible(Token token) throws ParserException { + public static Object getOwnerOnlyVisible(Token token) throws ParserException { if (!MapTool.getParser().isMacroTrusted()) { throw new ParserException( I18N.getText("macro.function.general.noPerm", "getOwnerOnlyVisible")); @@ -293,7 +288,7 @@ public Object getOwnerOnlyVisible(Token token) throws ParserException { * @param val the value to set the visible flag to. * @throws ParserException */ - public void setAlwaysVisible(Token token, Object val) throws ParserException { + public static void setAlwaysVisible(Token token, Object val) throws ParserException { if (!MapTool.getParser().isMacroTrusted()) { throw new ParserException(I18N.getText("macro.function.general.noPerm", "setAlwaysVisible")); } @@ -309,9 +304,7 @@ public void setAlwaysVisible(Token token, Object val) throws ParserException { set = Boolean.parseBoolean(val.toString()); } } - token.setIsAlwaysVisible(set); - MapTool.serverCommand() - .putToken(MapTool.getFrame().getCurrentZoneRenderer().getZone().getId(), token); + MapTool.serverCommand().updateTokenProperty(token, "setIsAlwaysVisible", set); } /** @@ -322,41 +315,18 @@ public void setAlwaysVisible(Token token, Object val) throws ParserException { * @return the value visible is set to. * @throws ParserException if an error occurs. */ - private Object setAlwaysVisible(Parser parser, List args) throws ParserException { - Object val; - Token token; + private static Object setAlwaysVisible(Parser parser, List args) throws ParserException { + MapToolVariableResolver res = (MapToolVariableResolver) parser.getVariableResolver(); + FunctionUtil.checkNumberParam("setAlwaysVisible", args, 1, 3); - if (args.size() == 2) { - token = FindTokenFunctions.findToken(args.get(1).toString(), null); - if (token == null) { - throw new ParserException( - I18N.getText( - "macro.function.general.unknownToken", "setAlwaysVisible", args.get(1).toString())); - } - } else if (args.size() == 1) { - MapToolVariableResolver res = (MapToolVariableResolver) parser.getVariableResolver(); - token = res.getTokenInContext(); - if (token == null) { - throw new ParserException( - I18N.getText("macro.function.general.noImpersonated", "setAlwaysVisible")); - } - } else if (args.size() == 0) { - throw new ParserException( - I18N.getText( - "macro.function.general.notEnoughParam", "setAlwaysVisible", 1, args.size())); - } else { - throw new ParserException( - I18N.getText("macro.function.general.tooManyParam", "setAlwaysVisible", 2, args.size())); - } - val = args.get(0); - setAlwaysVisible(token, val); // Already calls serverCommand().putToken() - // MapTool.serverCommand().putToken(MapTool.getFrame().getCurrentZoneRenderer().getZone().getId(), token); - MapTool.getFrame().getCurrentZoneRenderer().getZone().putToken(token); + Object val = args.get(0); + Token token = FunctionUtil.getTokenFromParam(res, "setAlwaysVisible", args, 1, 2); + setAlwaysVisible(token, val); return val; } - public boolean getBooleanAlwaysVisible(Token token) throws ParserException { + public static boolean getBooleanAlwaysVisible(Token token) throws ParserException { if (!MapTool.getParser().isMacroTrusted()) { throw new ParserException(I18N.getText("macro.function.general.noPerm", "getAlwaysVisible")); } @@ -374,7 +344,7 @@ public boolean getBooleanAlwaysVisible(Token token) throws ParserException { * @return if the token is visible. * @throws ParserException if the player does not have permissions to check. */ - public Object getAlwaysVisible(Token token) throws ParserException { + public static Object getAlwaysVisible(Token token) throws ParserException { return getBooleanAlwaysVisible(token) ? BigDecimal.ONE : BigDecimal.ZERO; } @@ -386,7 +356,7 @@ public Object getAlwaysVisible(Token token) throws ParserException { * @return if the token is visible or not. * @throws ParserException if an error occurs. */ - private Object getAlwaysVisible(Parser parser, List args) throws ParserException { + private static Object getAlwaysVisible(Parser parser, List args) throws ParserException { Token token; if (args.size() == 1) { diff --git a/src/main/java/net/rptools/maptool/client/functions/VBL_Functions.java b/src/main/java/net/rptools/maptool/client/functions/VBL_Functions.java index 12c5a6e2ed..27efd075b0 100644 --- a/src/main/java/net/rptools/maptool/client/functions/VBL_Functions.java +++ b/src/main/java/net/rptools/maptool/client/functions/VBL_Functions.java @@ -210,7 +210,7 @@ public Object childEvaluate(Parser parser, String functionName, List par I18N.getText( "macro.function.general.notenoughparms", functionName, 1, parameters.size())); - if (!MapTool.getParser().isMacroPathTrusted()) + if (!MapTool.getParser().isMacroTrusted()) throw new ParserException(I18N.getText("macro.function.general.noPerm", functionName)); Object jsonArea = JSONMacroFunctions.asJSON(parameters.get(0).toString().toLowerCase()); @@ -277,15 +277,12 @@ public Object childEvaluate(Parser parser, String functionName, List par } // Replace with new VBL - token.setVBL(tokenVBL); + MapTool.serverCommand().updateTokenProperty(token, "setVBL", tokenVBL); // Force a TOPOLOGY_CHANGED event. This made the client performing the // VBL change actually show the change but not the other clients. renderer.getZone().tokenTopologyChanged(); renderer.repaint(); - - // Make sure the other clients get the updated token. - MapTool.serverCommand().putToken(renderer.getZone().getId(), token); } if (functionName.equals("transferVBL")) { diff --git a/src/main/java/net/rptools/maptool/client/functions/getInfoFunction.java b/src/main/java/net/rptools/maptool/client/functions/getInfoFunction.java index f1ae618671..fcf7fa2f30 100644 --- a/src/main/java/net/rptools/maptool/client/functions/getInfoFunction.java +++ b/src/main/java/net/rptools/maptool/client/functions/getInfoFunction.java @@ -16,6 +16,8 @@ import java.math.BigDecimal; import java.text.SimpleDateFormat; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.Calendar; import java.util.HashMap; import java.util.HashSet; @@ -160,10 +162,12 @@ private JSONObject getClientInfo() { cinfo.put("portrait size", AppPreferences.getPortraitSize()); cinfo.put("show portrait", AppPreferences.getShowPortrait()); cinfo.put("show stat sheet", AppPreferences.getShowStatSheet()); + cinfo.put("file sync directory", AppPreferences.getFileSyncPath()); cinfo.put("version", MapTool.getVersion()); cinfo.put("isFullScreen", MapTool.getFrame().isFullScreen() ? BigDecimal.ONE : BigDecimal.ZERO); cinfo.put("timeInMs", System.currentTimeMillis()); cinfo.put("timeDate", getTimeDate()); + cinfo.put("isoTimeDate", getIsoTimeDate()); if (MapTool.getParser().isMacroTrusted()) { Map libInfo = new HashMap(); for (ZoneRenderer zr : MapTool.getFrame().getZoneRenderers()) { @@ -195,6 +199,10 @@ private String getTimeDate() { return sdf.format(cal.getTime()); } + private String getIsoTimeDate() { + return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(ZonedDateTime.now()); + } + /** * Retrieves the server side preferences as a json object. * diff --git a/src/main/java/net/rptools/maptool/client/macro/MacroManager.java b/src/main/java/net/rptools/maptool/client/macro/MacroManager.java index c8d1771c1d..2265c7222b 100644 --- a/src/main/java/net/rptools/maptool/client/macro/MacroManager.java +++ b/src/main/java/net/rptools/maptool/client/macro/MacroManager.java @@ -77,6 +77,7 @@ public class MacroManager { registerMacro(new WhisperReplyMacro()); registerMacro(new EmotePluralMacro()); registerMacro(new ExperimentsMacro()); + registerMacro(new TextureNoise()); registerMacro(UNDEFINED_MACRO); } diff --git a/src/main/java/net/rptools/maptool/client/macro/impl/SetTokenStateMacro.java b/src/main/java/net/rptools/maptool/client/macro/impl/SetTokenStateMacro.java index a6365fbba1..d96260909c 100644 --- a/src/main/java/net/rptools/maptool/client/macro/impl/SetTokenStateMacro.java +++ b/src/main/java/net/rptools/maptool/client/macro/impl/SetTokenStateMacro.java @@ -151,11 +151,9 @@ private void handleBooleanValue(Token token, String state, String value) { } else { newValue = Boolean.valueOf(value); } - token.setState(state, newValue); - MapTool.serverCommand() - .putToken(MapTool.getFrame().getCurrentZoneRenderer().getZone().getId(), token); + MapTool.serverCommand().updateTokenProperty(token, "setState", state, newValue); - if (newValue.booleanValue()) { + if (newValue) { MapTool.addLocalMessage(I18N.getText("settokenstate.marked", token.getName(), state)); } else { MapTool.addLocalMessage(I18N.getText("settokenstate.unmarked", token.getName(), state)); diff --git a/src/main/java/net/rptools/maptool/client/macro/impl/TextureNoise.java b/src/main/java/net/rptools/maptool/client/macro/impl/TextureNoise.java new file mode 100644 index 0000000000..bdcfc9530a --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/macro/impl/TextureNoise.java @@ -0,0 +1,83 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.macro.impl; + +import net.rptools.maptool.client.MapTool; +import net.rptools.maptool.client.MapToolMacroContext; +import net.rptools.maptool.client.macro.Macro; +import net.rptools.maptool.client.macro.MacroContext; +import net.rptools.maptool.client.macro.MacroDefinition; +import net.rptools.maptool.client.ui.zone.ZoneRenderer; +import net.rptools.maptool.language.I18N; + +@MacroDefinition( + name = "texturenoise", + aliases = {"tn"}, + description = "texturenoise.description" +) +/** + * This class implements the slash command for adjusting the values for noise to be applied to + * repeating background textures. + */ +public class TextureNoise implements Macro { + + @Override + public void execute(MacroContext context, String macro, MapToolMacroContext executionContext) { + ZoneRenderer zr = MapTool.getFrame().getCurrentZoneRenderer(); + if (macro.length() == 0) { + if (zr.isBgTextureNoiseFilterOn()) { + MapTool.addLocalMessage( + I18N.getText("texturenoise.currentValsOn", zr.getNoiseAlpha(), zr.getNoiseSeed())); + } else { + I18N.getText("texturenoise.currentValsOff"); + } + MapTool.addLocalMessage(I18N.getText("texturenoise.usage")); + } else { + String args[] = macro.split("\\s+"); + + if ("off".equalsIgnoreCase(args[0])) { + zr.setBgTextureNoiseFilterOn(false); + return; + } + + if ("on".equalsIgnoreCase(args[0])) { + zr.setBgTextureNoiseFilterOn(true); + return; + } + + float alpha; + try { + alpha = Float.parseFloat(args[0]); + } catch (NumberFormatException e) { + MapTool.addLocalMessage(I18N.getText("texturenoise.usage")); + return; + } + + long seed; + if (args.length == 1) { + seed = zr.getNoiseSeed(); + } else { + try { + seed = Long.parseLong(args[1]); + } catch (NumberFormatException e) { + MapTool.addLocalMessage(I18N.getText("texturenoise.usage")); + return; + } + } + + zr.setNoiseValues(seed, alpha); + } + } +} diff --git a/src/main/java/net/rptools/maptool/client/ui/MacroButtonDialog.java b/src/main/java/net/rptools/maptool/client/ui/MacroButtonDialog.java index e7bd1066cd..4647cd786d 100644 --- a/src/main/java/net/rptools/maptool/client/ui/MacroButtonDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/MacroButtonDialog.java @@ -596,7 +596,7 @@ public ShowFindDialogAction(MacroButtonDialog macroButtonDialog) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { if (replaceDialog.isVisible()) { replaceDialog.setVisible(false); } @@ -615,7 +615,7 @@ public ShowReplaceDialogAction(MacroButtonDialog macroButtonDialog) { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { if (findDialog.isVisible()) { findDialog.setVisible(false); } @@ -631,7 +631,7 @@ public GoToLineAction() { } @Override - public void execute(ActionEvent e) { + protected void executeAction(ActionEvent e) { if (findDialog.isVisible()) { findDialog.setVisible(false); } @@ -785,6 +785,12 @@ private void save(boolean closeDialog) { MapTool.getFrame().getCampaignPanel().reset(); } + if (button.getPanelClass().equals("GmPanel")) { + MapTool.serverCommand() + .updateGmMacros(MapTool.getCampaign().getGmMacroButtonPropertiesArray()); + MapTool.getFrame().getGmPanel().reset(); + } + if (closeDialog) { // setVisible(false); updateOpenMacroList(false); diff --git a/src/main/java/net/rptools/maptool/client/ui/MapToolFrame.java b/src/main/java/net/rptools/maptool/client/ui/MapToolFrame.java index d36db24a75..4a55f78934 100644 --- a/src/main/java/net/rptools/maptool/client/ui/MapToolFrame.java +++ b/src/main/java/net/rptools/maptool/client/ui/MapToolFrame.java @@ -98,7 +98,6 @@ import net.rptools.maptool.client.AppActions.ClientAction; import net.rptools.maptool.client.AppConstants; import net.rptools.maptool.client.AppPreferences; -import net.rptools.maptool.client.AppState; import net.rptools.maptool.client.AppStyle; import net.rptools.maptool.client.AppUtil; import net.rptools.maptool.client.MapTool; @@ -124,10 +123,7 @@ import net.rptools.maptool.client.ui.drawpanel.DrawablesPanel; import net.rptools.maptool.client.ui.lookuptable.LookupTablePanel; import net.rptools.maptool.client.ui.macrobuttons.buttons.MacroButton; -import net.rptools.maptool.client.ui.macrobuttons.panels.CampaignPanel; -import net.rptools.maptool.client.ui.macrobuttons.panels.GlobalPanel; -import net.rptools.maptool.client.ui.macrobuttons.panels.ImpersonatePanel; -import net.rptools.maptool.client.ui.macrobuttons.panels.SelectionPanel; +import net.rptools.maptool.client.ui.macrobuttons.panels.*; import net.rptools.maptool.client.ui.token.EditTokenDialog; import net.rptools.maptool.client.ui.tokenpanel.InitiativePanel; import net.rptools.maptool.client.ui.tokenpanel.TokenPanelTreeCellRenderer; @@ -245,6 +241,7 @@ public class MapToolFrame extends DefaultDockableHolder private EditTokenDialog tokenPropertiesDialog; private final CampaignPanel campaignPanel = new CampaignPanel(); + private final GmPanel gmPanel = new GmPanel(); private final GlobalPanel globalPanel = new GlobalPanel(); private final SelectionPanel selectionPanel = new SelectionPanel(); private final ImpersonatePanel impersonatePanel = new ImpersonatePanel(); @@ -628,6 +625,7 @@ public enum MTFrame { LOOKUP_TABLES("Tables"), GLOBAL("Global"), CAMPAIGN("Campaign"), + GM("Gm"), SELECTION("Selected"), IMPERSONATED("Impersonate"); // @formatter:on @@ -670,6 +668,7 @@ private void configureDocking() { getDockingManager().addFrame(getFrame(MTFrame.LOOKUP_TABLES)); getDockingManager().addFrame(getFrame(MTFrame.GLOBAL)); getDockingManager().addFrame(getFrame(MTFrame.CAMPAIGN)); + getDockingManager().addFrame(getFrame(MTFrame.GM)); getDockingManager().addFrame(getFrame(MTFrame.SELECTION)); getDockingManager().addFrame(getFrame(MTFrame.IMPERSONATED)); @@ -678,10 +677,17 @@ private void configureDocking() { .loadInitialLayout( MapToolFrame.class.getClassLoader().getResourceAsStream(INITIAL_LAYOUT_XML)); } catch (ParserConfigurationException | SAXException | IOException e) { + MapTool.showError("msg.error.layoutInitial", e); + } + try { + getDockingManager() + .loadLayoutDataFromFile(AppUtil.getAppHome("config").getAbsolutePath() + "/layout.dat"); + } catch (IllegalArgumentException e) { + // This error sometimes comes up when using three monitors due to a bug in the java jdk + // incorrectly + // reporting screen size as zero. MapTool.showError("msg.error.layoutParse", e); } - getDockingManager() - .loadLayoutDataFromFile(AppUtil.getAppHome("config").getAbsolutePath() + "/layout.dat"); } public DockableFrame getFrame(MTFrame frame) { @@ -726,6 +732,7 @@ private void initializeFrames() { MTFrame.INITIATIVE, initiativePanel, new ImageIcon(AppStyle.initiativePanelImage))); JScrollPane campaign = scrollPaneFactory(campaignPanel); + JScrollPane gm = scrollPaneFactory(gmPanel); JScrollPane global = scrollPaneFactory(globalPanel); JScrollPane selection = scrollPaneFactory(selectionPanel); JScrollPane impersonate = scrollPaneFactory(impersonatePanel); @@ -735,6 +742,8 @@ private void initializeFrames() { frameMap.put( MTFrame.CAMPAIGN, createDockingFrame(MTFrame.CAMPAIGN, campaign, new ImageIcon(AppStyle.campaignPanelImage))); + frameMap.put( + MTFrame.GM, createDockingFrame(MTFrame.GM, gm, new ImageIcon(AppStyle.campaignPanelImage))); frameMap.put( MTFrame.SELECTION, createDockingFrame( @@ -1558,14 +1567,14 @@ public void setCurrentZoneRenderer(ZoneRenderer renderer) { getZoomStatusBar().update(); } + /** + * Set the MapTool title bar. The title includes the name of the app, the player name, the + * campaign name and the name of the specified zone. + * + * @param renderer the ZoneRenderer of the zone. + */ public void setTitleViaRenderer(ZoneRenderer renderer) { - String campaignName = " - [Default]"; - if (AppState.getCampaignFile() != null) { - String s = AppState.getCampaignFile().getName(); - // remove the file extension of the campaign file name - s = s.substring(0, s.length() - AppConstants.CAMPAIGN_FILE_EXTENSION.length()); - campaignName = " - [" + s + "]"; - } + String campaignName = " - [" + MapTool.getCampaign().getName() + "]"; setTitle( AppConstants.APP_NAME + " - " @@ -1574,6 +1583,14 @@ public void setTitleViaRenderer(ZoneRenderer renderer) { + (renderer != null ? " - " + renderer.getZone().getName() : "")); } + /** + * Set the MapTool title bar. The title includes the name of the app, the player name, the + * campaign name and the current zone name. + */ + public void setTitle() { + setTitleViaRenderer(MapTool.getFrame().getCurrentZoneRenderer()); + } + public Toolbox getToolbox() { return toolbox; } @@ -1887,6 +1904,10 @@ public CampaignPanel getCampaignPanel() { return campaignPanel; } + public GmPanel getGmPanel() { + return gmPanel; + } + public GlobalPanel getGlobalPanel() { return globalPanel; } @@ -1908,6 +1929,7 @@ public void resetTokenPanels() { public void resetPanels() { MacroButtonHotKeyManager.clearKeyStrokes(); campaignPanel.reset(); + gmPanel.reset(); globalPanel.reset(); impersonatePanel.reset(); selectionPanel.reset(); diff --git a/src/main/java/net/rptools/maptool/client/ui/PreferencesDialog.java b/src/main/java/net/rptools/maptool/client/ui/PreferencesDialog.java index 8130353420..05a6e1bf27 100644 --- a/src/main/java/net/rptools/maptool/client/ui/PreferencesDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/PreferencesDialog.java @@ -190,6 +190,7 @@ public void stateChanged(ChangeEvent ce) { // Application private final JCheckBox fitGMView; private final JCheckBox fillSelectionCheckBox; + private final JTextField frameRateCapTextField; // private final JCheckBox initEnableServerSyncCheckBox; private final JCheckBox hideNPCs; private final JCheckBox ownerPermissions; @@ -247,6 +248,7 @@ public void actionPerformed(java.awt.event.ActionEvent e) { showNumberingCombo = panel.getComboBox("showNumberingCombo"); saveReminderCheckBox = panel.getCheckBox("saveReminderCheckBox"); fillSelectionCheckBox = panel.getCheckBox("fillSelectionCheckBox"); + frameRateCapTextField = panel.getTextField("frameRateCapTextField"); // initEnableServerSyncCheckBox = panel.getCheckBox("initEnableServerSyncCheckBox"); autoSaveSpinner = panel.getSpinner("autoSaveSpinner"); duplicateTokenCombo = panel.getComboBox("duplicateTokenCombo"); @@ -483,6 +485,20 @@ public void actionPerformed(ActionEvent e) { AppPreferences.setFillSelectionBox(fillSelectionCheckBox.isSelected()); } }); + frameRateCapTextField + .getDocument() + .addDocumentListener( + new DocumentListenerProxy(frameRateCapTextField) { + @Override + protected void storeNumericValue(Integer value) { + AppPreferences.setFrameRateCap(value); + } + + @Override + protected Integer convertString(String value) throws ParseException { + return StringUtil.parseInteger(value); + } + }); // initEnableServerSyncCheckBox.addActionListener(new ActionListener() { // public void actionPerformed(ActionEvent e) { // AppPreferences.setInitEnableServerSync(initEnableServerSyncCheckBox.isSelected()); @@ -1078,6 +1094,7 @@ private void setInitialState() { showDialogOnNewToken.setSelected(AppPreferences.getShowDialogOnNewToken()); saveReminderCheckBox.setSelected(AppPreferences.getSaveReminder()); fillSelectionCheckBox.setSelected(AppPreferences.getFillSelectionBox()); + frameRateCapTextField.setText(Integer.toString(AppPreferences.getFrameRateCap())); // initEnableServerSyncCheckBox.setSelected(AppPreferences.getInitEnableServerSync()); autoSaveSpinner.setValue(AppPreferences.getAutoSaveIncrement()); newMapsHaveFOWCheckBox.setSelected(AppPreferences.getNewMapsHaveFOW()); diff --git a/src/main/java/net/rptools/maptool/client/ui/TokenPopupMenu.java b/src/main/java/net/rptools/maptool/client/ui/TokenPopupMenu.java index a467084a17..93e1a75a6b 100644 --- a/src/main/java/net/rptools/maptool/client/ui/TokenPopupMenu.java +++ b/src/main/java/net/rptools/maptool/client/ui/TokenPopupMenu.java @@ -49,7 +49,6 @@ import net.rptools.maptool.client.AppUtil; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.MapToolUtil; -import net.rptools.maptool.client.functions.AbstractTokenAccessorFunction; import net.rptools.maptool.client.functions.TokenBarFunction; import net.rptools.maptool.client.ui.token.BarTokenOverlay; import net.rptools.maptool.client.ui.token.BooleanTokenOverlay; @@ -67,6 +66,7 @@ import net.rptools.maptool.model.Token; import net.rptools.maptool.model.Zone; import net.rptools.maptool.model.ZonePoint; +import net.rptools.maptool.util.FunctionUtil; public class TokenPopupMenu extends AbstractTokenPopupMenu { private static final long serialVersionUID = -622385975780832588L; @@ -598,7 +598,7 @@ public void actionPerformed(ActionEvent e) { private JCheckBoxMenuItem createStateItem(String state, JMenu menu, Token token) { JCheckBoxMenuItem item = new JCheckBoxMenuItem(new ChangeStateAction(state)); Object value = token.getState(state); - if (AbstractTokenAccessorFunction.getBooleanValue(value)) item.setSelected(true); + if (FunctionUtil.getBooleanValue(value)) item.setSelected(true); menu.add(item); return item; } diff --git a/src/main/java/net/rptools/maptool/client/ui/assetpanel/ImageFileImagePanelModel.java b/src/main/java/net/rptools/maptool/client/ui/assetpanel/ImageFileImagePanelModel.java index bfc67a7984..1bac55a9b7 100644 --- a/src/main/java/net/rptools/maptool/client/ui/assetpanel/ImageFileImagePanelModel.java +++ b/src/main/java/net/rptools/maptool/client/ui/assetpanel/ImageFileImagePanelModel.java @@ -591,7 +591,15 @@ private void refresh() { } Collections.sort(fileList, filenameComparator); - MapTool.getFrame().getAssetPanel().updateGlobalSearchLabel(fileList.size()); + try { + MapTool.getFrame().getAssetPanel().updateGlobalSearchLabel(fileList.size()); + } catch (NullPointerException e) { + // This currently throws a NPE if the frame was not finished initializing when runs. For now, + // lets log a message and continue. + log.warn( + "NullPointerException encountered while trying to update ImageFileImagePanelModel global search label", + e); + } } private static class ListFilesSwingWorker extends SwingWorker { diff --git a/src/main/java/net/rptools/maptool/client/ui/chat/SmileyChatTranslationRuleGroup.java b/src/main/java/net/rptools/maptool/client/ui/chat/SmileyChatTranslationRuleGroup.java index c61d887a65..ecbb78418f 100644 --- a/src/main/java/net/rptools/maptool/client/ui/chat/SmileyChatTranslationRuleGroup.java +++ b/src/main/java/net/rptools/maptool/client/ui/chat/SmileyChatTranslationRuleGroup.java @@ -89,8 +89,9 @@ private void initSmilies() { String value = smileyProps.getProperty(key); /* * Make sure we're not in roll output. Wouldn't let me do this usinglookbehind :-/ + * Prevent all characters surrounding smiley except space, tab, and newline */ - key = "^((?:[^\036]|\036[^\036]*\036)*)" + key; + key = "^((?:[^\036]|\036[^\036]*\036)*)(? and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.ui.macrobuttons.panels; + +import java.util.ArrayList; +import java.util.List; +import net.rptools.maptool.client.MapTool; +import net.rptools.maptool.client.ui.macrobuttons.buttongroups.AbstractButtonGroup; +import net.rptools.maptool.model.MacroButtonProperties; + +public class GmPanel extends AbstractMacroPanel { + + public GmPanel() { + setPanelClass("GmPanel"); + addMouseListener(this); + init(); + } + + private static GmPanel getGmPanel() { + return MapTool.getFrame().getGmPanel(); + } + + private static List getGmMacroButtonArray() { + return MapTool.getCampaign().getGmMacroButtonPropertiesArray(); + } + + private void init() { + if (MapTool.getPlayer() == null || MapTool.getPlayer().isGM()) + addArea(getGmMacroButtonArray(), ""); + } + + public void reset() { + clear(); + init(); + } + + public static void deleteButtonGroup(String macroGroup) { + AbstractButtonGroup.clearHotkeys(getGmPanel(), macroGroup); + List campProps = getGmMacroButtonArray(); + List startingProps = + new ArrayList(getGmMacroButtonArray()); + campProps.clear(); + for (MacroButtonProperties nextProp : startingProps) { + if (!macroGroup.equals(nextProp.getGroup())) { + MapTool.getCampaign().saveGmMacroButtonProperty(nextProp); + } + } + getGmPanel().reset(); + } + + public static void clearPanel() { + AbstractMacroPanel.clearHotkeys(getGmPanel()); + getGmMacroButtonArray().clear(); + getGmPanel().reset(); + } +} diff --git a/src/main/java/net/rptools/maptool/client/ui/macrobuttons/panels/Tab.java b/src/main/java/net/rptools/maptool/client/ui/macrobuttons/panels/Tab.java index eed0bbc956..570af67c51 100644 --- a/src/main/java/net/rptools/maptool/client/ui/macrobuttons/panels/Tab.java +++ b/src/main/java/net/rptools/maptool/client/ui/macrobuttons/panels/Tab.java @@ -17,8 +17,9 @@ public enum Tab { GLOBAL(0, "Global"), CAMPAIGN(1, "Campaign"), - SELECTED(2, "Selection"), - IMPERSONATED(3, "Impersonated"); + GM(2, "Gm"), + SELECTED(3, "Selection"), + IMPERSONATED(4, "Impersonated"); public final int index; public final String title; diff --git a/src/main/java/net/rptools/maptool/client/ui/token/BooleanTokenOverlay.java b/src/main/java/net/rptools/maptool/client/ui/token/BooleanTokenOverlay.java index 5543e7122e..2317e279c5 100644 --- a/src/main/java/net/rptools/maptool/client/ui/token/BooleanTokenOverlay.java +++ b/src/main/java/net/rptools/maptool/client/ui/token/BooleanTokenOverlay.java @@ -17,8 +17,8 @@ import java.awt.AlphaComposite; import java.awt.Graphics2D; import java.awt.Rectangle; -import net.rptools.maptool.client.functions.AbstractTokenAccessorFunction; import net.rptools.maptool.model.Token; +import net.rptools.maptool.util.FunctionUtil; /** * An overlay that may be applied to a token to show state. @@ -52,7 +52,7 @@ protected BooleanTokenOverlay(String aName) { */ @Override public void paintOverlay(Graphics2D g, Token token, Rectangle bounds, Object value) { - if (AbstractTokenAccessorFunction.getBooleanValue(value)) { + if (FunctionUtil.getBooleanValue(value)) { // Apply Alpha Transparency float opacity = token.getTokenOpacity(); if (opacity < 1.0f) diff --git a/src/main/java/net/rptools/maptool/client/ui/token/EditTokenDialog.java b/src/main/java/net/rptools/maptool/client/ui/token/EditTokenDialog.java index e7553dab5f..1e793f8702 100644 --- a/src/main/java/net/rptools/maptool/client/ui/token/EditTokenDialog.java +++ b/src/main/java/net/rptools/maptool/client/ui/token/EditTokenDialog.java @@ -95,7 +95,6 @@ import net.rptools.lib.image.ImageUtil; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.MapToolUtil; -import net.rptools.maptool.client.functions.AbstractTokenAccessorFunction; import net.rptools.maptool.client.functions.TokenBarFunction; import net.rptools.maptool.client.swing.AbeillePanel; import net.rptools.maptool.client.swing.GenericDialog; @@ -113,6 +112,7 @@ import net.rptools.maptool.model.TokenProperty; import net.rptools.maptool.model.Zone.Layer; import net.rptools.maptool.util.ExtractHeroLab; +import net.rptools.maptool.util.FunctionUtil; import net.rptools.maptool.util.ImageManager; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; @@ -213,8 +213,7 @@ public void bind(final Token token) { Component[] states = ((Container) statePanels[j]).getComponents(); for (int i = 0; i < states.length; i++) { JCheckBox state = (JCheckBox) states[i]; - state.setSelected( - AbstractTokenAccessorFunction.getBooleanValue(token.getState(state.getText()))); + state.setSelected(FunctionUtil.getBooleanValue(token.getState(state.getText()))); } } diff --git a/src/main/java/net/rptools/maptool/client/ui/tokenpanel/TokenPanelTreeModel.java b/src/main/java/net/rptools/maptool/client/ui/tokenpanel/TokenPanelTreeModel.java index 194e866637..559eaac07b 100644 --- a/src/main/java/net/rptools/maptool/client/ui/tokenpanel/TokenPanelTreeModel.java +++ b/src/main/java/net/rptools/maptool/client/ui/tokenpanel/TokenPanelTreeModel.java @@ -37,8 +37,13 @@ import net.rptools.maptool.model.Token; import net.rptools.maptool.model.Zone; import net.rptools.maptool.server.ServerPolicy; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class TokenPanelTreeModel implements TreeModel, ModelChangeListener { + + private static final Logger log = LogManager.getLogger(TokenPanelTreeModel.class); + private static final String _TOKENS = "panel.MapExplorer.View.TOKENS"; private static final String _PLAYERS = "panel.MapExplorer.View.PLAYERS"; private static final String _GROUPS = "panel.MapExplorer.View.GROUPS"; @@ -209,7 +214,14 @@ private void updateInternal() { // Plan to show all of the views in order to keep the order for (TokenFilter filter : filterList) { - if (filter.view.isAdmin && !MapTool.getPlayer().isGM()) { + try { + if (filter.view.isAdmin && !MapTool.getPlayer().isGM()) { + continue; + } + } catch (NullPointerException e) { + // This seems to happen when there was a problem creating the initial window. Lets just + // ignore this filter for now. + log.warn("NullPointerException encountered while trying to update TokenPanelTreeModel", e); continue; } currentViewList.add(filter.view); diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/ZoneRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/ZoneRenderer.java index f8abb5801b..6d69ea671b 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/ZoneRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/ZoneRenderer.java @@ -30,6 +30,7 @@ import java.awt.RenderingHints; import java.awt.Shape; import java.awt.Stroke; +import java.awt.TexturePaint; import java.awt.Toolkit; import java.awt.Transparency; import java.awt.dnd.DropTargetDragEvent; @@ -129,6 +130,7 @@ import net.rptools.maptool.model.Zone; import net.rptools.maptool.model.ZonePoint; import net.rptools.maptool.model.drawing.Drawable; +import net.rptools.maptool.model.drawing.DrawableNoise; import net.rptools.maptool.model.drawing.DrawableTexturePaint; import net.rptools.maptool.model.drawing.DrawnElement; import net.rptools.maptool.model.drawing.Pen; @@ -149,12 +151,14 @@ public class ZoneRenderer extends JComponent private static final Color TRANSLUCENT_YELLOW = new Color(Color.yellow.getRed(), Color.yellow.getGreen(), Color.yellow.getBlue(), 50); - /** The interval, in milliseconds, during which calls to repaint() will be debounced. */ - private static final int REPAINT_DEBOUNCE_INTERVAL = 33; - /** DebounceExecutor for throttling repaint() requests. */ - private final DebounceExecutor repaintDebouncer = - new DebounceExecutor(REPAINT_DEBOUNCE_INTERVAL, this::repaint); + private final DebounceExecutor repaintDebouncer; + + /** Noise for mask on repeating tiles. */ + private final DrawableNoise noise = new DrawableNoise(); + + /** Is the noise filter on for disrupting pattens in background tiled textures. */ + private boolean bgTextureNoiseFilterOn = false; public static final int MIN_GRID_SIZE = 10; private static LightSourceIconOverlay lightSourceIconOverlay = new LightSourceIconOverlay(); @@ -233,6 +237,10 @@ public ZoneRenderer(Zone zone) { this.zone = zone; zone.addModelChangeListener(new ZoneModelChangeListener()); + // The interval, in milliseconds, during which calls to repaint() will be debounced. + int repaintDebounceInterval = 1000 / AppPreferences.getFrameRateCap(); + repaintDebouncer = new DebounceExecutor(repaintDebounceInterval, this::repaint); + setFocusable(true); setZoneScale(new Scale()); zoneView = new ZoneView(zone); @@ -271,6 +279,7 @@ public void mouseMoved(MouseEvent e) { } }); // fps.start(); + } public void setAutoResizeStamp(boolean value) { @@ -1997,6 +2006,12 @@ protected void renderBoard(Graphics2D g, PlayerView view) { bbg.setPaint(paint); bbg.fillRect(0, 0, size.width, size.height); + // Only apply the noise if the feature is on and the background a textured paint + if (bgTextureNoiseFilterOn && paint instanceof TexturePaint) { + bbg.setPaint(noise.getPaint(getViewOffsetX(), getViewOffsetY(), getScale())); + bbg.fillRect(0, 0, size.width, size.height); + } + // Map if (zone.getMapAssetId() != null) { BufferedImage mapImage = ImageManager.getImage(zone.getMapAssetId(), this); @@ -4708,4 +4723,57 @@ public Cursor createCustomCursor(String resource, String tokenName) { } return c; } + + /** + * Returns the alpha level used to apply the noise to back ground repeating textures. + * + * @return the alpha level used to apply the noise. + */ + public float getNoiseAlpha() { + return noise.getNoiseAlpha(); + } + + /** + * Returns the seed value used to generate the noise that is applied to tback ground repeating + * images. + * + * @return the seed value used to generate the noise. + */ + public long getNoiseSeed() { + return noise.getNoiseSeed(); + } + + /** + * Sets the seed value and alpha level used for the noise applied to repeating background + * textures. + * + * @param seed The seed value used to generate the noise to be applied. + * @param alpha The alpha level to apply the noise. + */ + public void setNoiseValues(long seed, float alpha) { + noise.setNoiseValues(seed, alpha); + drawBackground = true; + } + + /** + * Returns if the setting for applying background noise to textures is on or off. + * + * @return true if noise will be applied to repeating background textures, otherwise + * false + */ + public boolean isBgTextureNoiseFilterOn() { + return bgTextureNoiseFilterOn; + } + + /** + * Turn on / off application of noise to repeated background textures. + * + * @param on true to turn on, false to turn off. + */ + public void setBgTextureNoiseFilterOn(boolean on) { + if (on != bgTextureNoiseFilterOn) { + bgTextureNoiseFilterOn = on; + drawBackground = true; + } + } } diff --git a/src/main/java/net/rptools/maptool/model/Campaign.java b/src/main/java/net/rptools/maptool/model/Campaign.java index ab79ee4774..7698a0b686 100644 --- a/src/main/java/net/rptools/maptool/model/Campaign.java +++ b/src/main/java/net/rptools/maptool/model/Campaign.java @@ -51,6 +51,7 @@ public class Campaign { private GUID id = new GUID(); private Map zones = Collections.synchronizedMap(new LinkedHashMap()); + private String name; // the name of the campaign, to be displayed in the MapToolFrame title bar @SuppressWarnings("unused") private static transient ExportDialog exportInfo = @@ -72,11 +73,15 @@ public class Campaign { // campaign macro button properties. these are saved along with the campaign. // as of 1.3b32 - private List macroButtonProperties = - new ArrayList(); + private List macroButtonProperties; // need to have a counter for additions to macroButtonProperties array // otherwise deletions/insertions from/to that array will go out of sync private int macroButtonLastIndex = 0; + private int gmMacroButtonLastIndex = 0; + + // campaign GM macro button properties. these are saved along with the campaign. + // as of 1.5.6 + private List gmMacroButtonProperties; // DEPRECATED: As of 1.3b20 these are now in campaignProperties, but are here for backward // compatibility @@ -104,8 +109,11 @@ public class Campaign { private Boolean hasUsedFogToolbar = null; public Campaign() { + name = "Default"; macroButtonLastIndex = 0; + gmMacroButtonLastIndex = 0; macroButtonProperties = new ArrayList(); + gmMacroButtonProperties = new ArrayList(); } private void checkCampaignPropertyConversion() { @@ -141,6 +149,7 @@ public List getRemoteRepositoryList() { * @param campaign The campaign to copy from. */ public Campaign(Campaign campaign) { + name = campaign.getName(); zones = Collections.synchronizedMap(new LinkedHashMap()); /* @@ -154,12 +163,22 @@ public Campaign(Campaign campaign) { campaignProperties = new CampaignProperties(campaign.campaignProperties); macroButtonProperties = new ArrayList(campaign.getMacroButtonPropertiesArray()); + gmMacroButtonProperties = + new ArrayList(campaign.getGmMacroButtonPropertiesArray()); } public GUID getId() { return id; } + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + /** * This is a workaround to avoid the renderer and the serializer interating on the drawables at * the same time @@ -399,11 +418,18 @@ public void replaceCampaignProperties(CampaignProperties properties) { /** * Get a copy of the properties. This is for persistence. Modification of the properties do not * affect this campaign + * + * @return a copy of the properties */ public CampaignProperties getCampaignProperties() { return new CampaignProperties(campaignProperties); } + /** + * Getter for the array of Campaign macros + * + * @return the Campaign macros + */ public List getMacroButtonPropertiesArray() { if (macroButtonProperties == null) { // macroButtonProperties is null if you are loading an old campaign file < 1.3b32 @@ -412,10 +438,37 @@ public List getMacroButtonPropertiesArray() { return macroButtonProperties; } + /** + * Setter for the list of Campaign macros + * + * @param properties the List of Campaign Macros + */ public void setMacroButtonPropertiesArray(List properties) { macroButtonProperties = properties; } + /** + * Getter for the array of GM macros + * + * @return the GM macros + */ + public List getGmMacroButtonPropertiesArray() { + if (gmMacroButtonProperties == null) { + // gmMacroButtonProperties is null if you are loading an old campaign file < 1.5.6 + gmMacroButtonProperties = new ArrayList(); + } + return gmMacroButtonProperties; + } + + /** + * Setter for the list of GM macros + * + * @param properties the List of GM Macros + */ + public void setGmMacroButtonPropertiesArray(List properties) { + gmMacroButtonProperties = properties; + } + public void saveMacroButtonProperty(MacroButtonProperties properties) { // find the matching property in the array // TODO: hashmap? or equals()? or what? @@ -444,6 +497,34 @@ public void saveMacroButtonProperty(MacroButtonProperties properties) { MapTool.getFrame().getCampaignPanel().reset(); } + public void saveGmMacroButtonProperty(MacroButtonProperties properties) { + // find the matching property in the array + // TODO: hashmap? or equals()? or what? + for (MacroButtonProperties prop : gmMacroButtonProperties) { + if (prop.getIndex() == properties.getIndex()) { + prop.setColorKey(properties.getColorKey()); + prop.setAutoExecute(properties.getAutoExecute()); + prop.setCommand(properties.getCommand()); + prop.setHotKey(properties.getHotKey()); + prop.setIncludeLabel(properties.getIncludeLabel()); + prop.setApplyToTokens(properties.getApplyToTokens()); + prop.setLabel(properties.getLabel()); + prop.setGroup(properties.getGroup()); + prop.setSortby(properties.getSortby()); + prop.setFontColorKey(properties.getFontColorKey()); + prop.setFontSize(properties.getFontSize()); + prop.setMinWidth(properties.getMinWidth()); + prop.setMaxWidth(properties.getMaxWidth()); + prop.setToolTip(properties.getToolTip()); + prop.setAllowPlayerEdits(properties.getAllowPlayerEdits()); + MapTool.getFrame().getGmPanel().reset(); + return; + } + } + gmMacroButtonProperties.add(properties); + MapTool.getFrame().getGmPanel().reset(); + } + public int getMacroButtonNextIndex() { for (MacroButtonProperties prop : macroButtonProperties) { if (prop.getIndex() > macroButtonLastIndex) { @@ -453,11 +534,25 @@ public int getMacroButtonNextIndex() { return ++macroButtonLastIndex; } + public int getGmMacroButtonNextIndex() { + for (MacroButtonProperties prop : gmMacroButtonProperties) { + if (prop.getIndex() > gmMacroButtonLastIndex) { + gmMacroButtonLastIndex = prop.getIndex(); + } + } + return ++gmMacroButtonLastIndex; + } + public void deleteMacroButton(MacroButtonProperties properties) { macroButtonProperties.remove(properties); MapTool.getFrame().getCampaignPanel().reset(); } + public void deleteGmMacroButton(MacroButtonProperties properties) { + gmMacroButtonProperties.remove(properties); + MapTool.getFrame().getGmPanel().reset(); + } + /** * This method iterates through all Zones, TokenStates, TokenBars, and LookupTables and writes the * keys into a new, empty set. That set is the return value. diff --git a/src/main/java/net/rptools/maptool/model/MacroButtonProperties.java b/src/main/java/net/rptools/maptool/model/MacroButtonProperties.java index 8c3d2e7b84..1e9098fc66 100644 --- a/src/main/java/net/rptools/maptool/model/MacroButtonProperties.java +++ b/src/main/java/net/rptools/maptool/model/MacroButtonProperties.java @@ -336,6 +336,8 @@ public void save() { MacroButtonPrefs.savePreferences(this); } else if (saveLocation.equals("CampaignPanel")) { MapTool.getCampaign().saveMacroButtonProperty(this); + } else if (saveLocation.equals("GmPanel")) { + MapTool.getCampaign().saveGmMacroButtonProperty(this); } } @@ -437,6 +439,9 @@ private void executeCommand(GUID tokenId) { trusted = MapTool.getPlayer().isGM(); } else if (saveLocation.equals("CampaignPanel")) { loc = "campaign"; + } else if (saveLocation.equals("GmPanel")) { + loc = "gm"; + trusted = MapTool.getPlayer().isGM(); } else if (contextToken != null) { // Should this IF stmt really be: // contextToken.matches("^[^:\\s]+:") @@ -606,7 +611,7 @@ public static String[] getFontColors() { * if it's not then the color is converted to CSS format #FF00FF format and that string is * returned. * - * @return + * @return string of the color */ public String getFontColorAsHtml() { Color c = null; @@ -726,6 +731,8 @@ public boolean isDuplicateMacro(String source, Token token) { List existingMacroList = null; if (source.equalsIgnoreCase("CampaignPanel")) { existingMacroList = MapTool.getCampaign().getMacroButtonPropertiesArray(); + } else if (source.equalsIgnoreCase("GmPanel")) { + existingMacroList = MapTool.getCampaign().getGmMacroButtonPropertiesArray(); } else if (source.equalsIgnoreCase("GlobalPanel")) { existingMacroList = MacroButtonPrefs.getButtonProperties(); } else if (token != null) { diff --git a/src/main/java/net/rptools/maptool/model/TextMessage.java b/src/main/java/net/rptools/maptool/model/TextMessage.java index f0ebb2abaa..5c797a5647 100644 --- a/src/main/java/net/rptools/maptool/model/TextMessage.java +++ b/src/main/java/net/rptools/maptool/model/TextMessage.java @@ -27,6 +27,10 @@ public interface Channel { public static final int ME = 3; // Targeted to the current maptool client public static final int GROUP = 4; // All in the group public static final int WHISPER = 5; // To a specific player/character + public static final int GMME = 6; // Self and gms + public static final int NOTME = 7; // To all but self + public static final int NOTGM = 8; // To non-gms + public static final int NOTGMME = 9; // To non-gms that aren't self } private int channel; @@ -61,6 +65,26 @@ public static TextMessage me(List transformHistory, String message) { Channel.ME, null, MapTool.getPlayer().getName(), message, transformHistory); } + public static TextMessage gmMe(List transformHistory, String message) { + return new TextMessage( + Channel.GMME, null, MapTool.getPlayer().getName(), message, transformHistory); + } + + public static TextMessage notMe(List transformHistory, String message) { + return new TextMessage( + Channel.NOTME, null, MapTool.getPlayer().getName(), message, transformHistory); + } + + public static TextMessage notGm(List transformHistory, String message) { + return new TextMessage( + Channel.NOTGM, null, MapTool.getPlayer().getName(), message, transformHistory); + } + + public static TextMessage notGmMe(List transformHistory, String message) { + return new TextMessage( + Channel.NOTGMME, null, MapTool.getPlayer().getName(), message, transformHistory); + } + public static TextMessage group(List transformHistory, String target, String message) { return new TextMessage( Channel.GROUP, target, MapTool.getPlayer().getName(), message, transformHistory); @@ -71,6 +95,10 @@ public static TextMessage whisper(List transformHistory, String target, Channel.WHISPER, target, MapTool.getPlayer().getName(), message, transformHistory); } + public boolean isFromSelf() { + return MapTool.getPlayer().getName().equalsIgnoreCase(getSource()); + } + @Override public String toString() { return message; @@ -138,6 +166,22 @@ public boolean isMe() { return channel == Channel.ME; } + public boolean isGmMe() { + return channel == Channel.GMME; + } + + public boolean isNotMe() { + return channel == Channel.NOTME; + } + + public boolean isNotGm() { + return channel == Channel.NOTGM; + } + + public boolean isNotGmMe() { + return channel == Channel.NOTGMME; + } + public boolean isGroup() { return channel == Channel.GROUP; } diff --git a/src/main/java/net/rptools/maptool/model/Token.java b/src/main/java/net/rptools/maptool/model/Token.java index 8067ebedd0..f5c81b594e 100644 --- a/src/main/java/net/rptools/maptool/model/Token.java +++ b/src/main/java/net/rptools/maptool/model/Token.java @@ -525,6 +525,21 @@ public float getTokenOpacity() { return tokenOpacity; } + /** + * Set the token opacity from a string trimmed to [0.05f, 1.0f] + * + * @param alpha the String of the opacity value. + * @return the float of the opacity + */ + public float setTokenOpacity(String alpha) { + return setTokenOpacity(Float.parseFloat(alpha)); + } + /** + * Set the token opacity from a float trimmed to [0.05f, 1.0f] + * + * @param alpha the float of the opacity. + * @return the float of the opacity trimmed. + */ public float setTokenOpacity(float alpha) { if (alpha > 1.0f) alpha = 1.0f; if (alpha <= 0.0f) alpha = 0.05f; @@ -1609,6 +1624,19 @@ public Set getStatePropertyNames() { return state.keySet(); } + /** + * Get a set containing the names of all the states that match the passed value. + * + * @return The set of state property names that match the passed value. + */ + public Set getStatePropertyNames(Object value) { + Map matches = new HashMap(state); + for (Map.Entry entry : state.entrySet()) { + if (!value.equals(entry.getValue())) matches.remove(entry.getKey()); + } + return matches.keySet(); + } + /** @return Getter for notes */ public String getNotes() { return notes; @@ -1836,6 +1864,55 @@ private static boolean getBoolean( return bool.booleanValue(); } + /** + * Set the initiatives of the token + * + * @param value the new value of the initiatives + */ + public void setInitiative(String value) { + List tis = getInitiatives(); + for (InitiativeList.TokenInitiative ti : tis) ti.setState(value); + } + + /** + * Set the hold on token initiative + * + * @param set the boolean value for the hold + */ + public void setInitiativeHold(boolean set) { + List tis = getInitiatives(); + for (InitiativeList.TokenInitiative ti : tis) ti.setHolding(set); + } + + /** + * Get the list of initiatives for the token + * + * @return The List of initiative + */ + @SuppressWarnings("unchecked") + public List getInitiatives() { + Zone zone = getZoneRenderer().getZone(); + List list = zone.getInitiativeList().indexOf(this); + if (list.isEmpty()) return Collections.EMPTY_LIST; + List ret = + new ArrayList(list.size()); + for (Integer index : list) + ret.add(zone.getInitiativeList().getTokenInitiative(index.intValue())); + return ret; + } + + /** + * Get the first initiative of the token + * + * @return The first token initiative value for the token + */ + public InitiativeList.TokenInitiative getInitiative() { + Zone zone = getZoneRenderer().getZone(); + List list = zone.getInitiativeList().indexOf(this); + if (list.isEmpty()) return null; + return zone.getInitiativeList().getTokenInitiative(list.get(0).intValue()); + } + public static boolean isTokenFile(String filename) { return filename != null && filename.toLowerCase().endsWith(FILE_EXTENSION); } @@ -1994,6 +2071,11 @@ public void updateProperty(Zone zone, String methodName, Object[] parameters) { case "setLayer": setLayer((Zone.Layer) parameters[0]); break; + case "setLayerShape": + setLayer((Zone.Layer) parameters[0]); + setShape((TokenShape) parameters[1]); + if (hasVBL()) zone.tokenTopologyChanged(); // update VBL if token has any + break; case "setShape": setShape((TokenShape) parameters[0]); if (hasVBL()) zone.tokenTopologyChanged(); // update VBL if token has any @@ -2013,6 +2095,9 @@ public void updateProperty(Zone zone, String methodName, Object[] parameters) { case "setProperty": setProperty(parameters[0].toString(), parameters[1].toString()); break; + case "resetProperty": + resetProperty(parameters[0].toString()); + break; case "setZOrder": setZOrder((int) parameters[0]); zone.sortZOrder(); // update new ZOrder @@ -2049,6 +2134,12 @@ public void updateProperty(Zone zone, String methodName, Object[] parameters) { case "setGMNotes": setGMNotes(parameters[0].toString()); break; + case "setInitiative": + setInitiative((String) parameters[0]); + break; + case "setInitiativeHold": + setInitiativeHold((boolean) parameters[0]); + break; case "setX": setX((int) parameters[0]); if (hasVBL()) zone.tokenTopologyChanged(); // update VBL if token has any @@ -2062,6 +2153,45 @@ public void updateProperty(Zone zone, String methodName, Object[] parameters) { setY((int) parameters[1]); if (hasVBL()) zone.tokenTopologyChanged(); // update VBL if token has any break; + case "setHaloColor": + setHaloColor((Color) parameters[0]); + break; + case "setLabel": + setLabel((String) parameters[0]); + break; + case "setName": + setName((String) parameters[0]); + break; + case "setGMName": + setGMName((String) parameters[0]); + break; + case "setVisible": + setVisible((boolean) parameters[0]); + break; + case "setVisibleOnlyToOwner": + setVisibleOnlyToOwner((boolean) parameters[0]); + break; + case "setIsAlwaysVisible": + setIsAlwaysVisible((boolean) parameters[0]); + break; + case "setTokenOpacity": + setTokenOpacity((String) parameters[0]); + break; + case "setTerrainModifier": + setTerrainModifier((double) parameters[0]); + break; + case "setVBL": + setVBL((Area) parameters[0]); + break; + case "setImageAsset": + setImageAsset((String) parameters[0], (MD5Key) parameters[1]); + break; + case "setPortraitImage": + setPortraitImage((MD5Key) parameters[0]); + break; + case "setCharsheetImage": + setCharsheetImage((MD5Key) parameters[0]); + break; case "clearLightSources": clearLightSources(); break; diff --git a/src/main/java/net/rptools/maptool/model/drawing/DrawableNoise.java b/src/main/java/net/rptools/maptool/model/drawing/DrawableNoise.java new file mode 100644 index 0000000000..c712f51c73 --- /dev/null +++ b/src/main/java/net/rptools/maptool/model/drawing/DrawableNoise.java @@ -0,0 +1,181 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.model.drawing; + +import java.awt.Paint; +import java.awt.Rectangle; +import java.awt.TexturePaint; +import java.awt.image.BufferedImage; +import net.rptools.noiselib.PerlinNoise; + +/** + * This class is used to generate a image from a noise function that can be used to break up + * repeating patterns in other images. + */ +public class DrawableNoise { + + /** + * Small offset in the X direction in case width period of noise is a multiple of texture its used + * to break up. + */ + private static final int OFFFSET_X_TWEAK = 5; + + /** + * Small offset in the Y direction in case height period of noise is a multiple of texture its + * used to break up. + */ + private static final int OFFFSET_Y_TWEAK = 5; + + /** The number of times the noise pattern fits into the noise image width. */ + private static final double WIDTH_DIVISOR = 12.0; + /** The number of times the noise pattern fits into the noise image height. */ + private static final double HEIGHT_DIVISOR = 9.0; + + /** The width of the noise image. */ + private static final int WIDTH = 256 * (int) WIDTH_DIVISOR; + /** The height of the noise image. */ + private static final int HEIGHT = 256 * (int) HEIGHT_DIVISOR; + + /** The default seed to used for the generation of noise. */ + private static final long DEFAULT_SEED = 42; + + /** The default alpha to use when applying the noise to another image. */ + private static final float DEFAULT_ALPHA = 0.20f; + + /** Noise generator. */ + private PerlinNoise perlinNoise; + + /** The alpha used to apply this noise to other images. */ + private float noiseAlpha; + + /** Flags if the noise "image" needs recalculation or not. */ + private boolean needsRecalc = true; + + /** The seed used to generate the noise. */ + private long noiseSeed; + + /** The buffered image of the rendered noise. */ + private BufferedImage noiseImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB); + + /** Recalculate the noise image. */ + private void recalc() { + needsRecalc = false; + int[] array = new int[WIDTH * HEIGHT]; + int alpha = (int) (noiseAlpha * 255) << 24; + for (int x = 0; x < WIDTH; x++) { + for (int y = 0; y < HEIGHT; y++) { + double noiseVal = perlinNoise.noise(x / WIDTH_DIVISOR, y / HEIGHT_DIVISOR); + int colVal = (int) (255 * noiseVal); + array[y * WIDTH + x] = colVal | (colVal << 8) | (colVal << 16) | alpha; + } + } + noiseImage.setRGB(0, 0, WIDTH, HEIGHT, array, 0, WIDTH); + } + + /** + * Creates a new DrawableNoisePant object with the specified seed and alpha. + * + * @param seed The seed used to generate the noise. + * @param alpha The alpha value the noise will be applied with. + */ + public DrawableNoise(long seed, float alpha) { + noiseSeed = seed; + perlinNoise = new PerlinNoise(seed); + noiseAlpha = alpha; + recalc(); + } + + /** Creates a new DrawableNoisePant object with default seed and alpha values. */ + public DrawableNoise() { + this(DEFAULT_SEED, DEFAULT_ALPHA); + } + + /** + * Returns the alpha level that is used to apply the noise. + * + * @return The alpha level that is used to apply the noise. + */ + public float getNoiseAlpha() { + return noiseAlpha; + } + + /** + * Sets the alpha level that is used to apply the noise. + * + * @param alpha the alpha level that is used to apply the noise. + */ + public void setNoiseAlpha(float alpha) { + noiseAlpha = alpha; + needsRecalc = true; + recalc(); + } + + /** + * Returns the seed used to generate the noise.. + * + * @return The seed used to generate the noise. + */ + public long getNoiseSeed() { + return noiseSeed; + } + + /** + * Sets the seed that is used to generate the noise. + * + * @param seed the seed that is used to generate the noise. + */ + public void setNoiseSeed(long seed) { + noiseSeed = seed; + perlinNoise = new PerlinNoise(seed); + recalc(); + } + + /** + * Returns a {@link Paint} object of the noise. + * + * @param offsetX The x offset of the view. + * @param offsetY The y offset of the view. + * @param scale The scale of the view. + * @return a {@link Paint} object that can be used to paint the noise. + */ + public Paint getPaint(int offsetX, int offsetY, double scale) { + return new TexturePaint( + noiseImage, + new Rectangle( + offsetX + OFFFSET_X_TWEAK, + offsetY + OFFFSET_Y_TWEAK, + (int) (WIDTH * scale), + (int) (HEIGHT * scale))); + } + + /** + * Sets the seed and alpha value for the noise. + * + * @param seed The seed value used to generate the noise. + * @param alpha The alpha used to apply the noise. + */ + public void setNoiseValues(long seed, float alpha) { + if (seed != noiseSeed || alpha != noiseAlpha) { + needsRecalc = true; + } + + noiseSeed = seed; + noiseAlpha = alpha; + + if (needsRecalc) { + recalc(); + } + } +} diff --git a/src/main/java/net/rptools/maptool/model/drawing/DrawableNoisePaint.java b/src/main/java/net/rptools/maptool/model/drawing/DrawableNoisePaint.java new file mode 100644 index 0000000000..d72e37ddf5 --- /dev/null +++ b/src/main/java/net/rptools/maptool/model/drawing/DrawableNoisePaint.java @@ -0,0 +1,107 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.model.drawing; + +import java.awt.Paint; +import java.awt.Rectangle; +import java.awt.TexturePaint; +import java.awt.image.BufferedImage; +import net.rptools.noiselib.PerlinNoise; + +public class DrawableNoisePaint { + + private static final double WIDTH_DIVISOR = 12.0; + private static final double HEIGHT_DIVISOR = 9.0; + + private static final int WIDTH = 256 * (int) WIDTH_DIVISOR; + private static final int HEIGHT = 256 * (int) HEIGHT_DIVISOR; + + private static final long DEFAULT_SEED = 42; + private static final float DEFAULT_ALPHA = 0.20f; + + private PerlinNoise perlinNoise; + + private float noiseAlpha; + + private boolean needsRecalc = true; + + private long noiseSeed; + + private BufferedImage noiseImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB); + + private void recalc() { + needsRecalc = false; + int[] array = new int[WIDTH * HEIGHT]; + int alpha = (int) (noiseAlpha * 255) << 24; + for (int x = 0; x < WIDTH; x++) { + for (int y = 0; y < HEIGHT; y++) { + double noiseVal = perlinNoise.noise(x / WIDTH_DIVISOR, y / HEIGHT_DIVISOR); + int colVal = (int) (255 * noiseVal); + array[y * WIDTH + x] = colVal | (colVal << 8) | (colVal << 16) | alpha; + } + } + noiseImage.setRGB(0, 0, WIDTH, HEIGHT, array, 0, WIDTH); + } + + public DrawableNoisePaint(long seed, float alpha) { + noiseSeed = seed; + perlinNoise = new PerlinNoise(seed); + noiseAlpha = alpha; + recalc(); + } + + public DrawableNoisePaint() { + this(DEFAULT_SEED, DEFAULT_ALPHA); + } + + public float getNoiseAlpha() { + return noiseAlpha; + } + + public void setNoiseAlpha(float alpha) { + noiseAlpha = alpha; + needsRecalc = true; + recalc(); + } + + public long getNoiseSeed() { + return noiseSeed; + } + + public void setNoiseSeed(long seed) { + noiseSeed = seed; + perlinNoise = new PerlinNoise(seed); + recalc(); + } + + public Paint getPaint(int offsetX, int offsetY, double scale) { + return new TexturePaint( + noiseImage, + new Rectangle(offsetX + 2, offsetY + 2, (int) (WIDTH * scale), (int) (HEIGHT * scale))); + } + + public void setNoiseValues(long seed, float alpha) { + if (seed != noiseSeed || alpha != noiseAlpha) { + needsRecalc = true; + } + + noiseSeed = seed; + noiseAlpha = alpha; + + if (needsRecalc) { + recalc(); + } + } +} diff --git a/src/main/java/net/rptools/maptool/server/ServerCommand.java b/src/main/java/net/rptools/maptool/server/ServerCommand.java index 9c781c12e4..77802fdd9f 100644 --- a/src/main/java/net/rptools/maptool/server/ServerCommand.java +++ b/src/main/java/net/rptools/maptool/server/ServerCommand.java @@ -41,6 +41,7 @@ public static enum COMMAND { // @formatter:off bootPlayer, setCampaign, + setCampaignName, getZone, putZone, removeZone, @@ -56,6 +57,7 @@ public static enum COMMAND { setZoneGridSize, message, execLink, + execFunction, undoDraw, showPointer, movePointer, @@ -85,6 +87,7 @@ public static enum COMMAND { updateTokenInitiative, setVisionType, updateCampaignMacros, + updateGmMacros, setTokenLocation, // NOTE: This is to support third party token placement and shouldn't be // depended on for general purpose token movement setLiveTypingLabel, // Experimental @@ -118,6 +121,8 @@ public static enum COMMAND { public void setCampaign(Campaign campaign); + public void setCampaignName(String name); + public void getZone(GUID zoneGUID); public void putZone(Zone zone); @@ -155,7 +160,9 @@ public void updateTokenProperty( public void message(TextMessage message); - public void execLink(String link, String target); + public void execFunction(String functionText, String target, String source); + + public void execLink(String link, String target, String source); public void showPointer(String player, Pointer pointer); @@ -196,6 +203,8 @@ public void updateTokenInitiative( public void updateCampaignMacros(List properties); + public void updateGmMacros(List properties); + public void setBoard(GUID zoneGUID, MD5Key mapAsset, int X, int Y); public void setLiveTypingLabel(String name, boolean show); diff --git a/src/main/java/net/rptools/maptool/server/ServerMethodHandler.java b/src/main/java/net/rptools/maptool/server/ServerMethodHandler.java index 8dd06a862f..e10f2985ef 100644 --- a/src/main/java/net/rptools/maptool/server/ServerMethodHandler.java +++ b/src/main/java/net/rptools/maptool/server/ServerMethodHandler.java @@ -125,8 +125,11 @@ public void handleMethod(String id, String method, Object... parameters) { case message: message((TextMessage) context.get(0)); break; + case execFunction: + execFunction((String) context.get(0), (String) context.get(1), (String) context.get(2)); + break; case execLink: - execLink((String) context.get(0), (String) context.get(1)); + execLink((String) context.get(0), (String) context.get(1), (String) context.get(2)); break; case putAsset: putAsset((Asset) context.get(0)); @@ -163,6 +166,9 @@ public void handleMethod(String id, String method, Object... parameters) { case setCampaign: setCampaign((Campaign) context.get(0)); break; + case setCampaignName: + setCampaignName((String) context.get(0)); + break; case setZoneGridSize: setZoneGridSize( context.getGUID(0), @@ -249,6 +255,9 @@ public void handleMethod(String id, String method, Object... parameters) { case updateCampaignMacros: updateCampaignMacros((List) context.get(0)); break; + case updateGmMacros: + updateGmMacros((List) context.get(0)); + break; case setTokenLocation: setTokenLocation( context.getGUID(0), context.getGUID(1), context.getInt(2), context.getInt(3)); @@ -287,14 +296,38 @@ private void forwardToAllClients() { new String[] {}, RPCContext.getCurrent().method, RPCContext.getCurrent().parameters); } + /** + * Broadcast a method to all clients excluding one client + * + * @param exclude the client to exclude + * @param method the method to send + * @param parameters an array of parameters related to the method + */ private void broadcastToClients(String exclude, String method, Object... parameters) { server.getConnection().broadcastCallMethod(new String[] {exclude}, method, parameters); } + /** + * Broadcast a method to all clients + * + * @param method the method to send + * @param parameters an array of parameters related to the method + */ private void broadcastToAllClients(String method, Object... parameters) { server.getConnection().broadcastCallMethod(new String[] {}, method, parameters); } + /** + * Broadcast a method to a single client + * + * @param client the client to send the method to + * @param method the method to send + * @param parameters an array of parameters related to the method + */ + private void broadcastToClient(String client, String method, Object... parameters) { + server.getConnection().callMethod(client, method, parameters); + } + //// // SERVER COMMAND public void setVisionType(GUID zoneGUID, VisionType visionType) { @@ -519,8 +552,13 @@ public void message(TextMessage message) { } @Override - public void execLink(String link, String target) { - forwardToAllClients(); + public void execFunction(String functionText, String target, String source) { + forwardToClients(); + } + + @Override + public void execLink(String link, String target, String source) { + forwardToClients(); } public void putAsset(Asset asset) { @@ -536,19 +574,23 @@ public void putLabel(GUID zoneGUID, Label label) { public void putToken(GUID zoneGUID, Token token) { Zone zone = server.getCampaign().getZone(zoneGUID); + int zOrder = 0; boolean newToken = zone.getToken(token.getId()) == null; synchronized (MUTEX) { // Set z-order for new tokens if (newToken) { - token.setZOrder(zone.getLargestZOrder() + 1); + zOrder = zone.getLargestZOrder() + 1; + token.setZOrder(zOrder); } zone.putToken(token); } if (newToken) { - forwardToAllClients(); - } else { - forwardToClients(); + // don't send whole token back to sender, instead just send new ZOrder + Object[] parameters = {zoneGUID, token.getId(), "setZOrder", new Object[] {zOrder}}; + broadcastToClient( + RPCContext.getCurrent().id, ClientCommand.COMMAND.updateTokenProperty.name(), parameters); } + forwardToClients(); } public void putZone(Zone zone) { @@ -631,6 +673,11 @@ public void setCampaign(Campaign campaign) { forwardToClients(); } + public void setCampaignName(String name) { + server.getCampaign().setName(name); + forwardToClients(); + } + public void setZoneGridSize(GUID zoneGUID, int offsetX, int offsetY, int size, int color) { Zone zone = server.getCampaign().getZone(zoneGUID); Grid grid = zone.getGrid(); @@ -746,6 +793,13 @@ public void updateCampaignMacros(List properties) { forwardToClients(); } + public void updateGmMacros(List properties) { + ArrayList campaignMacros = new ArrayList(properties); + MapTool.getCampaign().setGmMacroButtonPropertiesArray(campaignMacros); + server.getCampaign().setGmMacroButtonPropertiesArray(campaignMacros); + forwardToClients(); + } + public void setBoard(GUID zoneGUID, MD5Key mapId, int x, int y) { forwardToClients(); } diff --git a/src/main/java/net/rptools/maptool/server/ServerPolicy.java b/src/main/java/net/rptools/maptool/server/ServerPolicy.java index a09f330dd2..60da694f45 100644 --- a/src/main/java/net/rptools/maptool/server/ServerPolicy.java +++ b/src/main/java/net/rptools/maptool/server/ServerPolicy.java @@ -212,7 +212,7 @@ public JSONObject toJSON() { sinfo.put("timeDate", getTimeDate()); sinfo.put("gm", MapTool.getGMs()); - sinfo.put("hosting server", MapTool.isHostingServer()); + sinfo.put("hosting server", MapTool.isHostingServer() ? BigDecimal.ONE : BigDecimal.ZERO); InitiativePanel ip = MapTool.getFrame().getInitiativePanel(); if (ip != null) { diff --git a/src/main/java/net/rptools/maptool/util/FunctionUtil.java b/src/main/java/net/rptools/maptool/util/FunctionUtil.java index 5a6073c766..3154fcbc6f 100644 --- a/src/main/java/net/rptools/maptool/util/FunctionUtil.java +++ b/src/main/java/net/rptools/maptool/util/FunctionUtil.java @@ -20,6 +20,7 @@ import net.rptools.maptool.client.MapToolVariableResolver; import net.rptools.maptool.client.functions.FindTokenFunctions; import net.rptools.maptool.client.functions.JSONMacroFunctions; +import net.rptools.maptool.client.ui.zone.ZoneRenderer; import net.rptools.maptool.language.I18N; import net.rptools.maptool.model.Token; import net.rptools.parser.ParserException; @@ -124,6 +125,34 @@ public static Token getTokenFromParam( return token; } + /** + * Gets the ZoneRender from the specified index or returns the current ZoneRender. This method + * will check the list size before trying to retrieve the token so it is safe to use for functions + * that have the map as a optional argument. + * + * @param functionName the function name (used for generating exception messages). + * @param param the parameters for the function + * @param indexMap the index to find the map name at. If -1, use current map instead. + * @return the ZoneRenderer. + * @throws ParserException if the map cannot be found + */ + public static ZoneRenderer getZoneRendererFromParam( + String functionName, List param, int indexMap) throws ParserException { + + String map = indexMap >= 0 && param.size() > indexMap ? param.get(indexMap).toString() : null; + + ZoneRenderer zoneRenderer; + if (map == null) { + zoneRenderer = MapTool.getFrame().getCurrentZoneRenderer(); + } else { + zoneRenderer = MapTool.getFrame().getZoneRenderer(map); + if (zoneRenderer == null) { + throw new ParserException(I18N.getText(KEY_UNKNOWN_MAP, functionName, map)); + } + } + return zoneRenderer; + } + /** * Return the BigDecimal value of a parameter. Throws a ParserException if the * parameter can't be converted to BigDecimal. @@ -321,4 +350,29 @@ public static JSONArray paramAsJsonArray(String functionName, List param throw new ParserException(I18N.getText(KEY_NOT_JSON_ARRAY, functionName, index + 1)); } else return (JSONArray) obj; } + + /** + * Convert an object into a boolean value. Never returns an error. + * + * @param value Convert this object. Must be {@link Boolean}, {@link BigDecimal}, or will have its + * string value be converted to one of those types. + * @return The boolean value of the object + */ + public static boolean getBooleanValue(Object value) { + boolean set = false; + if (value instanceof Boolean) { + set = ((Boolean) value).booleanValue(); + } else if (value instanceof Number) { + set = ((Number) value).doubleValue() != 0; + } else if (value == null) { + set = false; + } else { + try { + set = !new BigDecimal(value.toString()).equals(BigDecimal.ZERO); + } catch (NumberFormatException e) { + set = Boolean.parseBoolean(value.toString()); + } // endif + } // endif + return set; + } } diff --git a/src/main/resources/github.properties b/src/main/resources/github.properties index 10d0a10ef7..6938483c13 100644 --- a/src/main/resources/github.properties +++ b/src/main/resources/github.properties @@ -1,2 +1,2 @@ -github.api.url=https://api.github.com/repos/RPTools/maptool/releases/latest?access_token= +github.api.releases = https://api.github.com/repos/RPTools/maptool/releases?access_token= github.api.oauth.token=28276e82af1de2f79f0c38ceb17d2b23640784eb \ No newline at end of file diff --git a/src/main/resources/net/rptools/maptool/client/ui/chat/smileyMap.xml b/src/main/resources/net/rptools/maptool/client/ui/chat/smileyMap.xml index f7e7432642..ca381a4599 100644 --- a/src/main/resources/net/rptools/maptool/client/ui/chat/smileyMap.xml +++ b/src/main/resources/net/rptools/maptool/client/ui/chat/smileyMap.xml @@ -14,12 +14,12 @@ --> Smilies - net/rptools/maptool/client/image/smiley/emsmile.png|:) - net/rptools/maptool/client/image/smiley/emfrown.png|:( - net/rptools/maptool/client/image/smiley/emcry.png|:'( - net/rptools/maptool/client/image/smiley/emcurl.png|:s - net/rptools/maptool/client/image/smiley/emgasp.png|:o - net/rptools/maptool/client/image/smiley/emgrin.png|:D - net/rptools/maptool/client/image/smiley/emwink.png|;) - net/rptools/maptool/client/image/smiley/emtongue.png|:P + net/rptools/maptool/client/image/smiley/emsmile.png|:) + net/rptools/maptool/client/image/smiley/emfrown.png|:( + net/rptools/maptool/client/image/smiley/emcry.png|:'( + net/rptools/maptool/client/image/smiley/emcurl.png|:s + net/rptools/maptool/client/image/smiley/emgasp.png|:o + net/rptools/maptool/client/image/smiley/emgrin.png|:D + net/rptools/maptool/client/image/smiley/emwink.png|;) + net/rptools/maptool/client/image/smiley/emtongue.png|:P diff --git a/src/main/resources/net/rptools/maptool/client/ui/forms/preferencesDialog.xml b/src/main/resources/net/rptools/maptool/client/ui/forms/preferencesDialog.xml index 1e15ed7a0a..9c0f72c6df 100644 --- a/src/main/resources/net/rptools/maptool/client/ui/forms/preferencesDialog.xml +++ b/src/main/resources/net/rptools/maptool/client/ui/forms/preferencesDialog.xml @@ -25,7 +25,7 @@ com.jeta.forms.gui.form.FormComponent C:\code\rptools\mtfx\maptool\src\main\resources\net\rptools\maptool\client\ui\forms\preferencesDialog.xml - preferencesDialog.xml + resources\net\rptools\maptool\client\ui\forms\preferencesDialog.xml CENTER:DEFAULT:NONE,CENTER:4DLU:NONE,CENTER:DEFAULT:NONE,CENTER:4DLU:NONE FILL:15DLU:GROW(1.0),FILL:DEFAULT:NONE @@ -165,7 +165,7 @@ com.jeta.forms.gui.form.FormComponent - embedded.2103712098 + embedded.491560192 CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:4DLU:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE FILL:8DLU:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE @@ -187,7 +187,7 @@ com.jeta.forms.gui.form.FormComponent - embedded.504157065 + embedded.1194202324 CENTER:2DLU:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:2DLU:NONE FILL:DEFAULT:NONE,FILL:DEFAULT:GROW(1.0),FILL:DEFAULT:GROW(1.0),FILL:DEFAULT:NONE,FILL:DEFAULT:NONE @@ -236,8 +236,8 @@ - 164 + Start Snap to Grid @@ -591,8 +591,8 @@ - 164 + Show Numbering on @@ -1471,8 +1471,8 @@ - 164 + Show Portrait on mouseover @@ -1703,8 +1703,8 @@ - 164 + Snap Token while dragging @@ -1820,8 +1820,8 @@ - 164 + Force Token Facing Arrow @@ -1881,8 +1881,8 @@ - 164 + Hide Mouse Pointer while dragging @@ -1992,7 +1992,7 @@ - + fill @@ -2164,7 +2164,7 @@ com.jeta.forms.gui.form.FormComponent - embedded.404397693 + embedded.977701466 CENTER:2DLU:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:2DLU:NONE FILL:DEFAULT:NONE,FILL:DEFAULT:GROW(1.0),FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE @@ -2533,7 +2533,7 @@ com.jeta.forms.gui.form.FormComponent - embedded.397491210 + embedded.944509219 CENTER:2DLU:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:2DLU:NONE FILL:DEFAULT:NONE,FILL:DEFAULT:GROW(1.0),FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE @@ -2902,7 +2902,7 @@ com.jeta.forms.gui.form.FormComponent - embedded.351125917 + embedded.1211241971 CENTER:2DLU:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:2DLU:NONE FILL:DEFAULT:NONE,FILL:DEFAULT:GROW(1.0),FILL:DEFAULT:GROW(1.0),FILL:23DLU:NONE,FILL:DEFAULT:NONE @@ -3646,13 +3646,13 @@ Disabled: <b>[ ]</b> acts like <b>[h: ]</b>&nbsp; 176 - Show Chat Notification Background + Typing Notification Background fill - Color of the text for typing notifications + If enabled, shows a background frame underneath the typing notification. 14 @@ -3705,7 +3705,7 @@ Disabled: <b>[ ]</b> acts like <b>[h: ]</b>&nbsp; - Show Chat Notification Background + Typing Notification Background chatNotificationShowBackground 31 @@ -4043,7 +4043,7 @@ Disabled (default): show tooltips for macroLinks com.jeta.forms.gui.form.FormComponent - embedded.1883581011 + embedded.1797045169 CENTER:2DLU:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:2DLU:NONE FILL:DEFAULT:NONE,FILL:DEFAULT:GROW(1.0),FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE @@ -4412,7 +4412,7 @@ Disabled (default): show tooltips for macroLinks com.jeta.forms.gui.form.FormComponent - embedded.915782330 + embedded.1666878610 CENTER:2DLU:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:2DLU:NONE FILL:DEFAULT:NONE,FILL:DEFAULT:GROW(1.0),FILL:DEFAULT:GROW(1.0),FILL:DEFAULT:NONE,FILL:DEFAULT:NONE @@ -5164,8 +5164,8 @@ Disabled (default): show tooltips for macroLinks - 133 + New Map Vision Distance @@ -5273,7 +5273,7 @@ Disabled (default): show tooltips for macroLinks - + fill @@ -5397,7 +5397,7 @@ Disabled (default): show tooltips for macroLinks com.jeta.forms.gui.form.FormComponent - embedded.928651145 + embedded.2134171981 CENTER:2DLU:NONE,CENTER:DEFAULT:NONE,CENTER:2DLU:NONE FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:23DLU:GROW(1.0),FILL:DEFAULT:NONE @@ -5681,7 +5681,7 @@ Disabled (default): show tooltips for macroLinks - + fill @@ -5824,7 +5824,7 @@ Disabled (default): show tooltips for macroLinks com.jeta.forms.gui.form.FormComponent - embedded.631796383 + embedded.449334682 CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE @@ -6307,7 +6307,7 @@ Disabled (default): show tooltips for macroLinks com.jeta.forms.gui.form.FormComponent - embedded.887221587 + embedded.461910486 CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:4DLU:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE FILL:DEFAULT:NONE,FILL:DEFAULT:GROW(1.0),FILL:DEFAULT:NONE,FILL:DEFAULT:GROW(1.0),FILL:DEFAULT:NONE @@ -6329,7 +6329,7 @@ Disabled (default): show tooltips for macroLinks com.jeta.forms.gui.form.FormComponent - embedded.1089791227 + embedded.312307659 CENTER:2DLU:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:2DLU:NONE FILL:DEFAULT:NONE,FILL:DEFAULT:GROW(1.0),FILL:MIN(20DLU;DEFAULT):NONE,FILL:40DLU:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:15DLU:NONE,FILL:5DLU:NONE,FILL:MIN(20DLU;DEFAULT):NONE @@ -6378,7 +6378,7 @@ Disabled (default): show tooltips for macroLinks - 493 + 342 Campaign autosave every @@ -6473,7 +6473,7 @@ Disabled (default): show tooltips for macroLinks - 493 + 342 Save reminder on close @@ -6590,7 +6590,7 @@ Disabled (default): show tooltips for macroLinks - 493 + 342 Time between chat log autosaves @@ -6685,7 +6685,7 @@ Disabled (default): show tooltips for macroLinks - 493 + 342 Autosave chat log filename @@ -6800,7 +6800,7 @@ Disabled (default): show tooltips for macroLinks - 493 + 342 File Sync Directory @@ -7202,7 +7202,7 @@ Disabled (default): show tooltips for macroLinks com.jeta.forms.gui.form.FormComponent - embedded.1178909893 + embedded.686438157 CENTER:DEFAULT:NONE,CENTER:4DLU:NONE,CENTER:DEFAULT:NONE,CENTER:4DLU:NONE,CENTER:DEFAULT:NONE,CENTER:4DLU:NONE,CENTER:DEFAULT:NONE,CENTER:4DLU:NONE,CENTER:DEFAULT:NONE,CENTER:4DLU:NONE,CENTER:DEFAULT:NONE FILL:DEFAULT:GROW(1.0) @@ -7224,8 +7224,8 @@ Disabled (default): show tooltips for macroLinks com.jeta.forms.gui.form.FormComponent - embedded.1716756801 - CENTER:2DLU:NONE,CENTER:DEFAULT:NONE,CENTER:2DLU:NONE + embedded.1035759935 + CENTER:2DLU:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:2DLU:NONE FILL:DEFAULT:NONE,FILL:DEFAULT:GROW(1.0),FILL:DEFAULT:NONE,FILL:40DLU:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE @@ -7328,8 +7328,8 @@ Disabled (default): show tooltips for macroLinks - 532 - + + 382 Fill selection box @@ -7345,6 +7345,121 @@ Disabled (default): show tooltips for macroLinks + + + + + + + 2 + 3 + 1 + 1 + default + default + 0,0,0,0 + + + com.jeta.forms.gui.form.StandardComponent + + com.jeta.forms.gui.beans.JETABean + com.jeta.forms.components.label.JETALabel + + + com.jeta.forms.components.label.JETALabel + + + + + + border + + + + + + + + border + + + + + + + + + + 382 + Frame Rate Cap + + + fill + + + Frame rate cap for map renderer in FPS. + 14 + + + + + + + + + + + + + + 4 + 3 + 1 + 1 + default + default + 0,0,0,0 + + + com.jeta.forms.gui.form.StandardComponent + + com.jeta.forms.gui.beans.JETABean + javax.swing.JTextField + + + javax.swing.JTextField + + + + + + border + + + + + + + + border + + + + + + + + + frameRateCapTextField + 56 + 20 + + + + + + + @@ -7383,7 +7498,7 @@ Disabled (default): show tooltips for macroLinks - + fill @@ -7423,7 +7538,7 @@ Disabled (default): show tooltips for macroLinks - + @@ -7433,6 +7548,9 @@ Disabled (default): show tooltips for macroLinks + + + @@ -7471,7 +7589,7 @@ Disabled (default): show tooltips for macroLinks com.jeta.forms.gui.form.FormComponent - embedded.205572378 + embedded.718890966 CENTER:2DLU:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:2DLU:NONE FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:40DLU:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE @@ -7529,7 +7647,7 @@ Disabled (default): show tooltips for macroLinks If enabled, NPCs will not appear in the players views of the Initiative panel. - 13 + 12 @@ -7584,7 +7702,7 @@ Disabled (default): show tooltips for macroLinks Hide NPCs from players on new maps hideNPCs 56 - 13 + 12 @@ -7645,7 +7763,7 @@ Disabled (default): show tooltips for macroLinks Owner Permission allows players to perform certain actions on their tokens in the Initiative panel. - 13 + 12 @@ -7700,7 +7818,7 @@ Disabled (default): show tooltips for macroLinks Give owners Permission in new campaigns ownerPermission 56 - 13 + 12 @@ -7761,7 +7879,7 @@ Disabled (default): show tooltips for macroLinks When enabled, players will only be able to move their token when that token has initiative. - 13 + 12 @@ -7816,7 +7934,7 @@ Disabled (default): show tooltips for macroLinks Lock Player Movement in new campaigns lockMovement 56 - 13 + 12 @@ -7877,7 +7995,7 @@ Disabled (default): show tooltips for macroLinks When enabled, a message is sent to chat when a token gains initative. - 13 + 12 @@ -7932,7 +8050,7 @@ Disabled (default): show tooltips for macroLinks Lock Player Movement in new campaigns showInitGainMessage 56 - 13 + 12 @@ -8084,7 +8202,7 @@ Disabled (default): show tooltips for macroLinks com.jeta.forms.gui.form.FormComponent - embedded.465856328 + embedded.620465298 CENTER:DEFAULT:NONE,CENTER:2DLU:NONE FILL:DEFAULT:NONE,FILL:DEFAULT:GROW(1.0),FILL:DEFAULT:NONE,FILL:40DLU:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE @@ -8133,7 +8251,7 @@ Disabled (default): show tooltips for macroLinks - 532 + 382 Fit GM view @@ -8328,7 +8446,7 @@ Disabled (default): show tooltips for macroLinks com.jeta.forms.gui.form.FormComponent - embedded.1924890465 + embedded.1935831964 CENTER:2DLU:NONE,CENTER:DEFAULT:NONE,CENTER:2DLU:NONE FILL:DEFAULT:NONE,FILL:DEFAULT:GROW(1.0),FILL:DEFAULT:NONE,FILL:40DLU:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE @@ -8377,7 +8495,7 @@ Disabled (default): show tooltips for macroLinks - 532 + 382 Default: Allow players to edit macros @@ -8575,7 +8693,7 @@ Disabled (default): show tooltips for macroLinks com.jeta.forms.gui.form.FormComponent - embedded.782031811 + embedded.2066852607 CENTER:2DLU:NONE,CENTER:DEFAULT:NONE,CENTER:2DLU:NONE FILL:DEFAULT:NONE,FILL:DEFAULT:GROW(1.0),FILL:DEFAULT:NONE,FILL:40DLU:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE @@ -8624,7 +8742,7 @@ Disabled (default): show tooltips for macroLinks - 532 + 382 Enable External Macro Access @@ -8822,7 +8940,7 @@ Disabled (default): show tooltips for macroLinks com.jeta.forms.gui.form.FormComponent - embedded.1854433302 + embedded.1405892137 CENTER:2DLU:NONE,CENTER:DEFAULT:NONE,CENTER:2DLU:NONE,CENTER:2DLU:NONE FILL:DEFAULT:NONE,FILL:DEFAULT:GROW(1.0),FILL:DEFAULT:NONE,FILL:40DLU:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE @@ -8925,7 +9043,7 @@ Disabled (default): show tooltips for macroLinks - 532 + 382 Discovery Timeout @@ -9183,7 +9301,7 @@ Disabled (default): show tooltips for macroLinks com.jeta.forms.gui.form.FormComponent - embedded.465421144 + embedded.2066240389 CENTER:2DLU:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:2DLU:NONE FILL:DEFAULT:NONE,FILL:DEFAULT:GROW(1.0),FILL:DEFAULT:NONE,FILL:40DLU:NONE,FILL:DEFAULT:NONE,FILL:15DLU:NONE,FILL:DEFAULT:NONE @@ -9232,7 +9350,7 @@ Disabled (default): show tooltips for macroLinks - 615 + 464 Halo line width @@ -9361,7 +9479,7 @@ Disabled (default): show tooltips for macroLinks - 615 + 464 Halo opacity @@ -9422,7 +9540,7 @@ Disabled (default): show tooltips for macroLinks - 615 + 464 Auto-expose fog on token movement (GM Only) @@ -9538,7 +9656,7 @@ Disabled (default): show tooltips for macroLinks - 615 + 464 Aura opacity @@ -9599,7 +9717,7 @@ Disabled (default): show tooltips for macroLinks - 615 + 464 Light opacity @@ -9728,7 +9846,7 @@ Disabled (default): show tooltips for macroLinks - 615 + 464 Fog opacity @@ -9823,7 +9941,7 @@ Disabled (default): show tooltips for macroLinks - 615 + 464 Use halo color for vision @@ -10167,7 +10285,7 @@ Disabled (default): show tooltips for macroLinks com.jeta.forms.gui.form.FormComponent - embedded.1531837263 + embedded.1394430965 CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE @@ -10272,7 +10390,7 @@ Disabled (default): show tooltips for macroLinks 150 - + Play system sounds @@ -10334,8 +10452,8 @@ Disabled (default): show tooltips for macroLinks Only when window not focused soundsOnlyWhenNotFocused - 14 - 14 + 15 + 15 @@ -10422,7 +10540,7 @@ Disabled (default): show tooltips for macroLinks com.jeta.forms.gui.form.StandardComponent com.jeta.forms.gui.beans.JETABean - com.jeta.forms.components.label.JETALabel + javax.swing.JCheckBox javax.swing.JCheckBox @@ -10448,10 +10566,14 @@ Disabled (default): show tooltips for macroLinks - Only when window not focused syrinscapeActive - 14 - 14 + 15 + + + fill + + + 15 @@ -10505,8 +10627,8 @@ Disabled (default): show tooltips for macroLinks Play streams playStreams - 14 - 14 + 15 + 15 @@ -10558,8 +10680,8 @@ Disabled (default): show tooltips for macroLinks - - 199 + 150 + Play streams @@ -10567,7 +10689,7 @@ Disabled (default): show tooltips for macroLinks Turn this off to prevent streams from playing - 17 + 14 @@ -10620,7 +10742,7 @@ Disabled (default): show tooltips for macroLinks 150 - + Only when window not focused @@ -10682,8 +10804,8 @@ Disabled (default): show tooltips for macroLinks Only when window not focused playStreams - 14 - 14 + 15 + 15 @@ -10827,7 +10949,7 @@ Disabled (default): show tooltips for macroLinks com.jeta.forms.gui.form.FormComponent - embedded.1493813688 + embedded.1838430994 CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE,FILL:DEFAULT:NONE @@ -10849,7 +10971,7 @@ Disabled (default): show tooltips for macroLinks com.jeta.forms.gui.form.FormComponent - embedded.410399626 + embedded.340552215 CENTER:2DLU:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:2DLU:NONE FILL:DEFAULT:NONE,FILL:DEFAULT:GROW(1.0),FILL:DEFAULT:GROW(1.0),FILL:MAX(40DLU;DEFAULT):NONE,FILL:DEFAULT:NONE @@ -11352,7 +11474,7 @@ Disabled (default): show tooltips for macroLinks com.jeta.forms.gui.form.FormComponent - embedded.2059718838 + embedded.1449083417 CENTER:2DLU:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:2DLU:NONE FILL:DEFAULT:NONE,FILL:DEFAULT:GROW(1.0),FILL:DEFAULT:GROW(1.0),CENTER:MAX(40DLU;DEFAULT):NONE,FILL:DEFAULT:NONE @@ -11957,7 +12079,7 @@ Disabled (default): show tooltips for macroLinks com.jeta.forms.gui.form.FormComponent - embedded.870025466 + embedded.637056756 CENTER:2DLU:NONE,CENTER:DEFAULT:NONE,CENTER:2DLU:NONE FILL:DEFAULT:NONE,FILL:DEFAULT:GROW(1.0),FILL:DEFAULT:GROW(1.0),FILL:MAX(40DLU;DEFAULT):NONE,FILL:DEFAULT:NONE @@ -12241,7 +12363,7 @@ Disabled (default): show tooltips for macroLinks com.jeta.forms.gui.form.FormComponent - embedded.1006479711 + embedded.1386084778 CENTER:2DLU:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:DEFAULT:NONE,CENTER:2DLU:NONE FILL:DEFAULT:NONE,FILL:DEFAULT:GROW(1.0),FILL:DEFAULT:GROW(1.0),LEFT:MAX(80DLU;DEFAULT):NONE,FILL:DEFAULT:NONE @@ -12604,7 +12726,7 @@ Disabled (default): show tooltips for macroLinks - 1552 + 1251 5 832 @@ -12631,7 +12753,7 @@ Disabled (default): show tooltips for macroLinks - + fill diff --git a/src/main/resources/net/rptools/maptool/client/ui/ilayout.xml b/src/main/resources/net/rptools/maptool/client/ui/ilayout.xml index e0815a3ae5..3cb85d6f13 100644 --- a/src/main/resources/net/rptools/maptool/client/ui/ilayout.xml +++ b/src/main/resources/net/rptools/maptool/client/ui/ilayout.xml @@ -148,6 +148,21 @@ sideDockAllowed="true" sideTitle="Campaign" slidingAutohide="true" tabDockAllowed="true" tabTitle="Campaign" title="Campaign" undockedBounds="10; 10; 150; 200"/> +