MapMove.crpl, a tool for editing maps

Started by st00pid_n00b, June 26, 2017, 02:23:44 AM

Previous topic - Next topic

st00pid_n00b

Update: Now operates on mouse click, easier to undo. Attachment updated.

This script basically cuts and paste a rectangular area, including terrain, digitalis, walls and units.

It's useful for when you make an elaborate structure but then realize it should be a few coordinates to the left, or when you resize a map but want to recenter it. So it allows for mistakes without starting all over again.

It's inspired from the more elaborate LandCopier.prpl by planetfall (thanks for the help).

This is my first script, I probably used too many variables and didn't take advantage of the stack. I needed some clarity to make up for the lack of familiarity with CRPL. Please let me know if you have any suggestion or remark. :)

If you need to duplicate a structure, the script could easily be modified to just copy terrain without erasing, but units can't be simply duplicated, so I didn't include this option.

Instructions (updated)


  • Attach script to a core, then save and reload to activate OperateWhilePaused status (you usually don't want to unpause while editing).

  • Set the variables for source and destination coordinates. You can also change the replacement terrain height.

  • Close editor tab and click on the core to execute. It might take a while if you move a big area, and the system might complain about the program not responding, that's normal! (I got execution times of around 30s.) At that point you can reload the map to undo the move.

  • After the execution you need to save and reload again to update the map properly.
    Explanation:
    Spoiler

    After executing, you will see the terrain and units moved, but not the walls and digitalis. Also, the map occupied array is not updated, so the previous unit positions are still considered occupied (can't land/build units) and the new positions are not (can stack units).
    The walls and digitalis are correctly displayed after unpausing, but the map occupied array is still not updated. All of these issues go away after saving and reloading.
    [close]

  • You can now change the variables for another move, or delete the core. It's possible to do several moves without saving, even moving the same terrain, all glitches disapear after save/reload.

Detailled instructions (updated)

For people who know nothing about CRPL, like me a few days ago :)


  • Copy this file in the "scripts" folder of the map you're editing. The path should be something like:
    <USER>\Documents\creeperworld3\WorldEditor\<MAP NAME>\scripts

  • In the editor, click on "Scripts" in the "Unit" tab. You should see the script name. Click "COMPILE ALL". You should see a message below "1 Scripts Successfully compiled into 181 opcodes." If you see an error, come back to scream at me.

  • Build a core anywhere on the map, then double click on it to display its properties. Click on "ADD" to add the MapMove.crpl script to the list of scripts attached to this core.

  • Save and reload your map so that the script can work while paused.

  • Double click on the core, then on the script name. An "Input variables" box appears, that's where you enter the source and destination coordinates for moving the map. The editor shows the mouse coordinates, so you can use it to look them up.

  • Click "APPLY VALUES & RECOMPILE", then "CLOSE".

  • See instructions above (the detailled instructions explain the first 2 points).



GoodMorning

Your code is well structured, with descriptive variables. This is more than I can say for some of us - my code regularly uses stack swaps aplenty and puns for variable names, making it less than readable.

Do note that there's an issue (probably with object pooling) that can cause random carryover of OperateWhilePaused status - dangerous in any script that does not allow for it.

Rather than working on save/load (which makes it hard to undo), I suggest having it work when the mapmaker clicks the Core. OperateWhilePaused is useful here, but you eliminate save/load, and can repeatedly use it more easily...
A narrative is a lightly-marked path to another reality.

st00pid_n00b

Thanks for the remarks!

I'm under the impression that playing with the stack allows for more efficient/concise code at the expense of readability. Maybe it's a question of habit... In fact I've started doing funny stuff using the stack in another script I'm working on.

Also my code seemed hard to read to me because of the reverse polish notation, but I'm kind of getting used to it. (And you should be ashamed to use puns as variable names ;))

Quote from: GoodMorning on June 26, 2017, 03:28:35 AM
Do note that there's an issue (probably with object pooling) that can cause random carryover of OperateWhilePaused status - dangerous in any script that does not allow for it.

What do you mean by that? Can the flag carryover to other scripts?

Quote
Rather than working on save/load (which makes it hard to undo), I suggest having it work when the mapmaker clicks the Core. OperateWhilePaused is useful here, but you eliminate save/load, and can repeatedly use it more easily...

What? You can do that? I didn't find anything in the documentation, how do you execute a script on click? By the way, the script works when unpausing too, which allows undo. But then there is the cleanup of delete creeper/digitalis, reset time, and remake the spore towers that have started building.

I have another question concerning the script: I'm making big lists with AppendToList, and I wonder if CRPL stores lists internally as chained lists or arrays. If it's the latter, it would be better to create a big list with an initial size, and use SetListElement.

GoodMorning

For executing on click, you can use something like the following:

0 GetMouseButtonDown if
GetMouseCell CurrentCoords Distance 1.5 lt if
#Functionality
endif
endif


It is an efficiency/readability trade-off, but you're already using SetTerrain over potentially hundreds or thousands of cells (each such call requires a milliseconds-long GPU texture push). Readability is usually considered the first goal, and it's much easier to sacrifice later than the other way around. Speaking of efficiency, there is CreateListStartingSize, but I do not know what the efficiency gain is. It is quite possible that it depends on the underlying C# library.

OperateWhilePaused can carry over between Cores or scripts, but such is rare enough that it's not major (see some of the early Sleeper maps, however, for a situation in which it is sometimes tactically relevant).
A narrative is a lightly-marked path to another reality.

st00pid_n00b

Ok I was confused because to check for mouse buttons, the script has to be running in the first place... I now understand that a script with OperateWhilePaused status starts running when you close the editor panel. In fact, I realized the script I posted works this way too, no need to save/reload.

However, waiting for a mouse click might be better as you suggested. I'll change that and update the original post.

For the lists, I don't know C# but it seems to have 2 types of lists: LinkedList<T> and List<T>.
The latter is based on an array, that doubles its size when it needs to reallocate. After some testing, it appears CRPL uses the array-based list, which give the computational complexities:

Constant O(1): GetListCount GetListElement SetListElement
Amortized constant O(1): AppendToList
Linear O(n): PrependToList InsertListElement* RemoveListElement*
(* Constant time if at the end of list)

Sorry, I have some background in CS, I couldn't help it :)

For example, doing 100000 times the following instructions gave me the execution times:
SetListElement 78 ms
AppendToList 68 ms
PrependToList 3080 ms
GetListElement 97 ms

In conclusion, using CreateListStartingSize and SetListElement  doesn't give efficiency gains over AppendToList, it even seems slower for some reason. When optimization is necessary, focus should be on the linear time functions (that is, the functions that need to shift the elements in the array).

GoodMorning

#5
I'm glad I could interest you. It would seem that CreateListStartingSize would preallocate memory (i.e. the appropriate size of array), and changing elements would remain constant-time. So the difference may not be that great.
A narrative is a lightly-marked path to another reality.

st00pid_n00b

Yes, maybe I wasn't clear but I compared CreateListStartingSize+SetListElement  with AppendToList , and there's no performance gain. So CreateListStartingSize is only usefull if you don't fill the list in order.

So I updated the original post, using your code for mouse click. It's still necessary to save/reload to call the awake function and set OperateWhilePaused though (unless I missed something?)

About the Sleeper map bug you mentioned, yes I remember it building digitalis while paused. I've set OperateWhilePaused to FALSE in the destroyed function, which seems to get rid of this bug.

(Note: All I wanted was to recenter a map... How did I end up testing the complexity of C# lists functions? Damn this took too much time...)

GoodMorning

It happens to us all. If we have any kind of spare time. The term "rabbit hole" is often used.

If you are unwilling to advance one frame while paused, then yes, save/load is required.

That is about all I would/could do, either.
A narrative is a lightly-marked path to another reality.