1: <?php
2: //# ## PMScript
3: //:
4: //: The PMScript module implements a simple [PHP](https://secure.php.net/)
5: //: based scripting engine. It can be used to enter multiple PocketMine
6: //: commands while allowing you to add PHP code to control the flow of
7: //: the script.
8: //:
9: //: While you can embed any arbitrary PHP code, for readability purposes
10: //: it is recommended that you use
11: //: [PHP's alternative syntax](http://php.net/manual/en/control-structures.alternative-syntax.php)
12: //:
13: //: By convention, PMScript's have a file extension of ".pms" and they are
14: //: just simple text file containing PHP console commands (without the "/").
15: //:
16: //: To control the execution you can use the following prefixes when
17: //: entering commands:
18: //:
19: //: * **+op:** - will give Op access to the player (temporarily) before executing
20: //: a command
21: //: * **+console:** - run the command as if it was run from the console.
22: //: * **+rcon:** - like **+console:** but the output is sent to the player.
23: //:
24: //: Also, before executing a command variable expansion (e.g. {vars}) and
25: //: command selector expansion (e.g. @a, @r, etc) takes place.
26: //:
27: //: Available variables depend on installed plugins, pocketmine.yml
28: //: settings, execution context, etc.
29: //:
30: //: It is possible to use PHP functions and variables in command lines by
31: //: surrounding PHP expressions with:
32: //:
33: //: '.(php expression).'
34: //:
35: //: For example:
36: //:
37: //: echo MaxPlayers: '.$interp->getServer()->getMaxPlayers().'
38: //:
39: //: ### Adding logic flow to PMScripts
40: //:
41: //: Arbitrary PHP code can be added to your pmscripts. Lines that start
42: //: with "@" are treated as PHP code. For your convenience,
43: //: you can ommit ";" at the end of the line.
44: //:
45: //: Any valid PHP code can be used, but for readability, the use of
46: //: alternative syntax is recommended.
47: //:
48: //: The execution context for this PHP code has the following variables
49: //: available:
50: //:
51: //: * **$interp** - reference to the running PMSCript object.
52: //: * **$context** - This is the CommandSender that is executing the script
53: //: * **$vars** - This is the variables array used for variable substitution
54: //: when executing commands.
55: //: * **$args** - Command line arguments.
56: //: * **$env** - execution environment. Empty by default but may be used
57: //: by third party plugins.
58: //: * **$v_xxxxx** - When posible the variables use for command variable
59: //: substitution are made available as **$v_xxxx**. For example, the
60: //: **{tps}** variable, will be available as **$v_tps**
61: //:
62: //: Example:
63: //:
64: //: # Sample PMScript
65: //: #
66: //: ; You can use ";" or "#" as comments
67: //: #
68: //: # Just place your commands as you would enter them on the console
69: //: # on your .pms file.
70: //: echo You have the following plugins:
71: //: plugins
72: //: echo {GOLD}Variable {RED}Expansions {BLUE}are {GREEN}possible
73: //: echo libcommon: {libcommon} MOTD: {MOTD}
74: //: #
75: //: # You can include in there PHP expressions...
76: //: say '.$context->getName().' is AWESOME!
77: //: # CommandSelectors are possible...
78: //: echo Greeting everybody
79: //: say Hello @a
80: //: ;
81: //: # Adding PHP control code is possible:
82: //: @if ($v_tps > 10):
83: //: echo Your TPS {tps} is greater than 10
84: //: @else:
85: //: echo Your TPS {tps} is less or equal to 10
86: //: @endif
87: //: ;
88: //: ;
89: //: echo The following variables are available in this context:
90: //: echo '.print_r($vars,true).'
91: //: echo You passed {#} arguments to this script.
92:
93:
94: namespace aliuly\common;
95:
96: use pocketmine\command\CommandSender;
97: use pocketmine\plugin\Plugin;
98: use pocketmine\Player;
99: use pocketmine\utils\TextFormat;
100:
101: use aliuly\common\CmdSelector;
102: use aliuly\common\ExpandVars;
103: use aliuly\common\Cmd;
104: /**
105: * Class that implements a PocketMine-MP scripting engine
106: */
107: class PMScript {
108: protected $owner;
109: protected $selector;
110: protected $perms;
111: protected $vars;
112: protected $globs;
113:
114: /**
115: * @param Plugin $owner - plugin that owns this interpreter
116: * @param bool|ExpandVars $vars - allow for standard variable expansion
117: * @param bool $perms - allow the use of Cmd::opexec
118: * @param int $selector - if 0, command selctors are not used, otherwise max commands
119: */
120: public function __construct(Plugin $owner, $vars = true, $perms = true, $selector = 100) {
121: $this->owner = $owner;
122: $this->selector = $selector;
123: $this->globs = [];
124:
125: if ($perms) {
126: $this->perms = [ __NAMESPACE__."\\Cmd" , "opexec" ];
127: } else {
128: $this->perms = [ $this->owner->getServer(), "dispatchCommand" ];
129: }
130:
131: if ($vars) {
132: if ($vars instanceof ExpandVars) {
133: $this->vars = $vars;
134: } else {
135: $this->vars = new ExpandVars($owner);
136: }
137: } else {
138: $this->vars = null;
139: }
140:
141: }
142: /**
143: * Execute a command
144: * @param CommandSender $ctx - Command context
145: * @param str $cmdline - Command to execute
146: * @param array $vars - Variables table for variable expansion
147: */
148: public function exec(CommandSender $ctx, $cmdline, $vars) {
149: $cmdline = strtr($cmdline,$vars);
150: if ($this->selector) {
151: $cmds = CmdSelector::expandSelectors($this->getServer(),$ctx, $cmdline, $this->selector);
152: if ($cmds == false) {
153: $cmds = [ $cmdline ];
154: }
155: } else {
156: $cmds = [ $cmdline ];
157: }
158: $cmdex = $this->perms;
159: foreach ($cmds as $ln) {
160: $cmdex($ctx,$ln);
161: }
162: }
163: /**
164: * If GrabBag is available, try to get a single shared instance of
165: * PMScript
166: */
167: static public function getCommonInterp(Plugin $owner) {
168: $pm = $owner->getServer()->getPluginManager();
169: if (($gb = $pm->getPlugin("GrabBag")) !== null) {
170: if ($gb->isEnabled() && MPMU::apiCheck($gb->getDescription()->getVersion(),"2.3")) {
171: $vars = $gb->api->getInterp();
172: if ($vars instanceof PMScript) return $vars;
173: }
174: }
175: return new PMScript($owner, ExpandVars::getCommonVars($owner));
176: }
177: /**
178: * Define additional constants on the fly...
179: * @param str $name
180: * @param mixed $value
181: */
182: public function define($str,$value) {
183: if ($this->vars !== null) $this->vars->define($str,$value);
184: }
185: /** Return plugin owner */
186: public function getOwner() {
187: return $this->owner;
188: }
189: /** Return server */
190: public function getServer() {
191: return $this->owner->getServer();
192: }
193:
194: /**
195: * @param str $label - global variable to get
196: * @param mixed $default - default value to return is no global found
197: * @return mixed
198: */
199: public function getGlob($label,$default) {
200: if (!isset($this->globs[$label])) return $default;
201: return $this->globs[$label];
202: }
203: /**
204: * Set global variable
205: *
206: * @param str $label - state variable to set
207: * @param mixed $val - value to set
208: * @return mixed
209: */
210: public function setGlob($label,$val) {
211: $this->globs[$label] = $val;
212: return $val;
213: }
214: /**
215: * Clears a global variable
216: *
217: * @param str $label - state variable to clear
218: */
219: public function unsetGlob($label) {
220: if (!isset($this->globs[$label])) return;
221: unset($this->globs[$label]);
222: }
223: ////////////////////////////////////////////////////////////////////////
224: // Main implementation
225: ////////////////////////////////////////////////////////////////////////
226:
227: /**
228: * Run a script file
229: * @param CommandSender $ctx - Command context
230: * @param callable $php - Loaded PMScript
231: * @param array $args - Command args
232: * @param array $opts - Some environemnt variables
233: */
234: public function runScriptFile(CommandSender $ctx, $path, array &$args, array &$opts) {
235: $php = $this->loadScriptFile($path);
236: if ($php === false) return false;
237: $this->executeScript($ctx,$php,$args,$opts);
238: return true;
239: }
240:
241: /**
242: * load a script from file (May implement a cache in the future...)
243: * @param str $path - path to file to load
244: * @param bool $cache - enable/disable caching
245: */
246: public function loadScriptFile($path,$cache = false) {
247: return $this->loadScriptCode(file_get_contents($path));
248: }
249: /**
250: * Execute a PMScript
251: *
252: * @param CommandSender $ctx - Command context
253: * @param callable $php - Loaded PMScript
254: * @param array $args - Command args
255: * @param array $opts - Some environemnt variables
256: */
257: public function runScriptCode(CommandSender $ctx,$pmscode,array &$args,array &$opts) {
258: $php = $this->loadScriptCode($pmscode);
259: if ($php === false) return false;
260: $this->executeScript($ctx,$php,$args,$opts);
261: return true;
262: }
263: /**
264: * Execute preloaded PHP code
265: * @param CommandSender $ctx - Command context
266: * @param callable $php - Loaded PMScript
267: * @param array $args - Command args
268: */
269: public function executeScript(CommandSender $ctx, $php, array &$args, array &$opts) {
270: if ($this->vars === null) {
271: $vars = [];
272: } else {
273: $vars = $this->vars->getConsts();
274: $this->vars->sysVars($vars);
275: if ($ctx instanceof Player) $this->vars->playerVars($ctx,$vars);
276: }
277: $vars["{#}"] = count($args);
278: $i = 0;
279: foreach ($args as $j) {
280: $vars["{".($i++)."}"] = $j;
281: }
282: foreach ($opts as $i=>&$j) {
283: if (is_string($j)) $vars["{".$i."}"] = $j;
284: }
285: try {
286: $php($this,$ctx,$vars,$args,$opts);
287: } catch (\Exception $e) {
288: $ctx->sendMessage(TextFormat::RED.mc::_("Exception: %1%",$e->getMessage()));
289: }
290: }
291: /**
292: * Prepare PMScript and convert into a PHP callable
293: * @param str $pmscript - text script
294: */
295: public function loadScriptCode($pmscript) {
296: $php = "";
297: // Prefix code ...
298: $php .= " return function (\$interp,\$context,&\$vars,&\$args,&\$env) {";
299: $php .= " foreach (\$vars as \$i=>\$j) {\n";
300: $php .= " if (preg_match(\"/^\\{([_a-zA-Z][_a-zA-Z0-9]*)\\}\\\$/\",\$i,\$mv)) {\n";
301: $php .= " eval(\"\\\$v_\" . \$mv[1] . \" = \\\$j;\\n\");\n";
302: $php .= " }\n";
303: $php .= " }\n";
304: foreach (explode("\n",$pmscript) as $ln) {
305: $ln = trim($ln);
306: if ($ln == "" || $ln{0} == "#" || $ln{0} == ";") continue;
307: if ($ln{0} == "@") {
308: $c = substr($ln,-1);
309: $q = ($c == ":" || $c == ";") ? "\n" : ";\n";
310: $php .= substr($ln,1).$q;
311: } else {
312: $php .= " \$interp->exec(\$context,'".$ln."',\$vars);\n";
313: }
314: }
315: $php .= "};";
316: return eval($php);
317: }
318: }
319: