mirror of
https://github.com/ClassiCube/MCGalaxy.git
synced 2025-10-04 02:52:21 -04:00
879 lines
42 KiB
C#
879 lines
42 KiB
C#
/*
|
|
Copyright 2015 MCGalaxy
|
|
|
|
Dual-licensed under the Educational Community License, Version 2.0 and
|
|
the GNU General Public License, Version 3 (the "Licenses"); you may
|
|
not use this file except in compliance with the Licenses. You may
|
|
obtain a copy of the Licenses at
|
|
|
|
http://www.opensource.org/licenses/ecl2.php
|
|
http://www.gnu.org/licenses/gpl-3.0.html
|
|
|
|
Unless required by applicable law or agreed to in writing,
|
|
software distributed under the Licenses are distributed on an "AS IS"
|
|
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
|
or implied. See the Licenses for the specific language governing
|
|
permissions and limitations under the Licenses.
|
|
*/
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using MCGalaxy.Blocks;
|
|
using MCGalaxy.Commands.Building;
|
|
using BlockID = System.UInt16;
|
|
using BlockRaw = System.Byte;
|
|
|
|
namespace MCGalaxy.Commands.CPE {
|
|
internal static class CustomBlockCommand {
|
|
|
|
public static void Execute(Player p, string message, CommandData data, bool global, string cmd) {
|
|
string[] parts = message.SplitSpaces(4);
|
|
for (int i = 0; i < Math.Min(parts.Length, 3); i++)
|
|
parts[i] = parts[i].ToLower();
|
|
|
|
if (message.Length == 0) {
|
|
if (GetBD(p, global) != null) {
|
|
SendStepHelp(p, global);
|
|
} else {
|
|
Help(p, cmd);
|
|
}
|
|
return;
|
|
}
|
|
|
|
switch (parts[0]) {
|
|
case "add":
|
|
case "create":
|
|
AddHandler(p, parts, global, cmd); break;
|
|
case "copyall":
|
|
case "copyfrom":
|
|
CopyAllHandler(p, parts, data, global, cmd); break;
|
|
case "copy":
|
|
case "clone":
|
|
case "duplicate":
|
|
CopyHandler(p, parts, data, global, cmd); break;
|
|
case "delete":
|
|
case "remove":
|
|
RemoveHandler(p, parts, global, cmd); break;
|
|
case "info":
|
|
case "about":
|
|
InfoHandler(p, parts, global, cmd); break;
|
|
case "list":
|
|
case "ids":
|
|
ListHandler(p, parts, global, cmd); break;
|
|
case "abort":
|
|
p.Message("Aborted the custom block creation process.");
|
|
SetBD(p, global, null); break;
|
|
case "edit":
|
|
EditHandler(p, parts, global, cmd); break;
|
|
default:
|
|
if (GetBD(p, global) != null)
|
|
DefineBlockStep(p, message, global, cmd);
|
|
else
|
|
Help(p, cmd);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void AddHandler(Player p, string[] parts, bool global, string cmd) {
|
|
BlockID target;
|
|
if (parts.Length >= 2 ) {
|
|
string id = parts[1];
|
|
if (!CheckBlock(p, id, out target)) return;
|
|
BlockDefinition[] defs = global ? BlockDefinition.GlobalDefs : p.level.CustomBlockDefs;
|
|
BlockDefinition old = defs[target];
|
|
|
|
if (ExistsInScope(old, target, global)) {
|
|
p.Message("There is already a custom block with the id " + id +
|
|
", you must either use a different id or use \"" + cmd + " remove " + id + "\"");
|
|
return;
|
|
}
|
|
} else {
|
|
target = GetFreeBlock(global, p.IsSuper ? null : p.level);
|
|
if (target == Block.Invalid) {
|
|
p.Message("There are no custom block ids left, you must "
|
|
+ cmd + " remove a custom block first.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
SetBD(p, global, new BlockDefinition());
|
|
BlockDefinition def = GetBD(p, global);
|
|
def.SetBlock(target);
|
|
|
|
p.Message("Use %T{0} abort %Sat anytime to abort the creation process.", cmd);
|
|
p.Message(" Use %T{0} revert %Sto go back a step", cmd);
|
|
p.Message(" Use %T{0} [input] %Sto provide input", cmd);
|
|
p.Message("&f----------------------------------------------------------");
|
|
|
|
SetStep(p, global, 2);
|
|
SendStepHelp(p, global);
|
|
}
|
|
|
|
static BlockDefinition[] GetDefs(Player p, CommandData data, string map, ref string coloredMap) {
|
|
map = Matcher.FindMaps(p, map);
|
|
if (map == null) return null;
|
|
|
|
Level lvl = null;
|
|
LevelConfig cfg = LevelInfo.GetConfig(map, out lvl);
|
|
AccessController visit = new LevelAccessController(cfg, map, true);
|
|
|
|
if (!visit.CheckDetailed(p, data.Rank)) {
|
|
p.Message("Hence, you cannot copy custom blocks from that level");
|
|
return null;
|
|
}
|
|
|
|
coloredMap = cfg.Color + map;
|
|
string path = Paths.MapBlockDefs(map);
|
|
return BlockDefinition.Load(path);
|
|
}
|
|
|
|
static bool DoCopy(Player p, bool global, string cmd, bool keepOrder,
|
|
BlockDefinition srcDef, BlockID src, BlockID dst) {
|
|
if (srcDef == null && src < Block.CpeCount) {
|
|
srcDef = DefaultSet.MakeCustomBlock(src);
|
|
}
|
|
if (srcDef == null) { MessageNoBlock(p, src, global, cmd); return false; }
|
|
|
|
BlockDefinition[] defs = global ? BlockDefinition.GlobalDefs : p.level.CustomBlockDefs;
|
|
BlockDefinition dstDef = defs[dst];
|
|
if (ExistsInScope(dstDef, dst, global)) { MessageAlreadyBlock(p, dst, global, cmd); return false; }
|
|
|
|
BlockProps props = global ? Block.Props[src] : p.level.Props[src];
|
|
dstDef = srcDef.Copy();
|
|
dstDef.SetBlock(dst);
|
|
if (!keepOrder) dstDef.InventoryOrder = -1;
|
|
|
|
AddBlock(p, dstDef, global, cmd, props);
|
|
return true;
|
|
}
|
|
|
|
static void CopyAllHandler(Player p, string[] parts, CommandData data, bool global, string cmd) {
|
|
if (parts.Length < 2) { Help(p, cmd); return; }
|
|
string coloredMap = null;
|
|
int copied = 0;
|
|
|
|
BlockDefinition[] defs = GetDefs(p, data, parts[1], ref coloredMap);
|
|
if (defs == null) return;
|
|
|
|
for (int i = 0; i < defs.Length; i++) {
|
|
if (defs[i] == null) continue;
|
|
|
|
BlockID b = (BlockID)i;
|
|
if (!DoCopy(p, global, cmd, true, defs[i], b, b)) continue;
|
|
copied++;
|
|
|
|
string scope = global ? "global" : "level";
|
|
p.Message("Copied the {0} custom block with id \"{1}\".", scope, Block.ToRaw(b));
|
|
}
|
|
|
|
string prefix = copied > 0 ? copied.ToString() : "No";
|
|
p.Message("{0} custom blocks were copied from level {1}", prefix, coloredMap);
|
|
}
|
|
|
|
static void CopyHandler(Player p, string[] parts, CommandData data, bool global, string cmd) {
|
|
if (parts.Length < 2) { Help(p, cmd); return; }
|
|
BlockID min, max, dst;
|
|
if (!CheckBlocks(p, parts[1], out min, out max, true)) return;
|
|
|
|
BlockDefinition[] defs = global ? BlockDefinition.GlobalDefs : p.level.CustomBlockDefs;
|
|
if (parts.Length > 2) {
|
|
if (!CheckBlock(p, parts[2], out dst)) return;
|
|
|
|
if (parts.Length > 3) {
|
|
string coloredMap = null;
|
|
defs = GetDefs(p, data, parts[3], ref coloredMap);
|
|
if (defs == null) return;
|
|
}
|
|
} else {
|
|
dst = GetFreeBlock(global, p.IsSuper ? null : p.level);
|
|
if (dst == Block.Invalid) {
|
|
p.Message("There are no custom block ids left, you must "
|
|
+ cmd + " remove a custom block first.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (int i = min; i <= max && Block.ToRaw(dst) < Block.MaxRaw; i++, dst++) {
|
|
if (!DoCopy(p, global, cmd, false, defs[i], (BlockID)i, dst)) continue;
|
|
string scope = global ? "global" : "level";
|
|
|
|
p.Message("Duplicated the {0} custom block with id \"{1}\" to \"{2}\".",
|
|
scope, Block.ToRaw((BlockID)i), Block.ToRaw(dst));
|
|
}
|
|
}
|
|
|
|
static void DoInfo(Player p, BlockID block, bool global, string cmd) {
|
|
BlockDefinition[] defs = global ? BlockDefinition.GlobalDefs : p.level.CustomBlockDefs;
|
|
BlockDefinition def = defs[block];
|
|
if (def == null) { MessageNoBlock(p, block, global, cmd); return; }
|
|
|
|
p.Message("About {0} ({1})", def.Name, Block.ToRaw(block));
|
|
p.Message(" Draw type: {0}, Blocks light: {1}, collide type: {2}",
|
|
def.BlockDraw, def.BlocksLight, def.CollideType);
|
|
p.Message(" Fallback ID: {0}, Sound: {1}, Speed: {2}",
|
|
def.FallBack, def.WalkSound, def.Speed.ToString("F2"));
|
|
|
|
if (def.FogDensity == 0) {
|
|
p.Message(" Block does not use fog");
|
|
} else {
|
|
p.Message(" Fog density: {0}, color: {1}",
|
|
def.FogDensity, Utils.Hex(def.FogR, def.FogG, def.FogB));
|
|
}
|
|
|
|
bool tinted = (def.FogR != 0 || def.FogG != 0 || def.FogB != 0) && def.Name.IndexOf('#') >= 0;
|
|
if (tinted) {
|
|
p.Message(" Tint color: {0}", Utils.Hex(def.FogR, def.FogG, def.FogB));
|
|
}
|
|
|
|
if (def.Shape == 0) {
|
|
p.Message(" Block is a sprite");
|
|
p.Message(" Texture ID: {0}", def.RightTex);
|
|
} else {
|
|
p.Message(" Block is a cube from ({0}, {1}, {2}) to ({3}, {4}, {5})",
|
|
def.MinX, def.MinZ, def.MinY, def.MaxX, def.MaxZ, def.MaxY);
|
|
p.Message(" Texture IDs (left: {0}, right: {1}, front: {2}, back: {3}, top: {4}, bottom: {5})",
|
|
def.LeftTex, def.RightTex, def.FrontTex, def.BackTex, def.TopTex, def.BottomTex);
|
|
}
|
|
|
|
if (def.InventoryOrder < 0) {
|
|
p.Message(" Order: None");
|
|
} else if (def.InventoryOrder == 0) {
|
|
p.Message(" Order: Hidden from inventory");
|
|
} else {
|
|
p.Message(" Order: " + def.InventoryOrder);
|
|
}
|
|
}
|
|
|
|
static void InfoHandler(Player p, string[] parts, bool global, string cmd) {
|
|
if (parts.Length == 1) { Help(p, cmd); return; }
|
|
BlockID min, max;
|
|
if (!CheckBlocks(p, parts[1], out min, out max)) return;
|
|
|
|
for (int i = min; i <= max; i++) {
|
|
DoInfo(p, (BlockID)i, global, cmd);
|
|
}
|
|
}
|
|
|
|
static void ListHandler(Player p, string[] parts, bool global, string cmd) {
|
|
string modifier = parts.Length > 1 ? parts[1] : "";
|
|
BlockDefinition[] defs = global ? BlockDefinition.GlobalDefs : p.level.CustomBlockDefs;
|
|
List<BlockDefinition> defsInScope = new List<BlockDefinition>();
|
|
|
|
for (int i = 0; i < defs.Length; i++) {
|
|
BlockDefinition def = defs[i];
|
|
if (def == null) continue;
|
|
BlockID block = def.GetBlock();
|
|
|
|
if (!ExistsInScope(def, block, global)) continue;
|
|
defsInScope.Add(def);
|
|
}
|
|
MultiPageOutput.Output(p, defsInScope, FormatBlock, cmd.Substring(1) + " list",
|
|
"custom blocks", modifier, true);
|
|
}
|
|
|
|
static string FormatBlock(BlockDefinition def) {
|
|
return "Custom block %T" + def.RawID + " %Shas name %T" + def.Name;
|
|
}
|
|
|
|
static void DoRemove(Player p, BlockID block, bool global, string cmd) {
|
|
BlockDefinition[] defs = global ? BlockDefinition.GlobalDefs : p.level.CustomBlockDefs;
|
|
BlockDefinition def = defs[block];
|
|
if (!ExistsInScope(def, block, global)) { MessageNoBlock(p, block, global, cmd); return; }
|
|
|
|
BlockDefinition.Remove(def, defs, p.IsSuper ? null : p.level);
|
|
ResetProps(global, block, p);
|
|
|
|
string scope = global ? "global" : "level";
|
|
p.Message("Removed " + scope + " custom block " + def.Name + "(" + def.RawID + ")");
|
|
|
|
BlockDefinition globalDef = BlockDefinition.GlobalDefs[block];
|
|
if (!global && globalDef != null)
|
|
BlockDefinition.Add(globalDef, defs, p.level);
|
|
}
|
|
|
|
static void RemoveHandler(Player p, string[] parts, bool global, string cmd) {
|
|
if (parts.Length <= 1) { Help(p, cmd); return; }
|
|
BlockID min, max;
|
|
if (!CheckBlocks(p, parts[1], out min, out max)) return;
|
|
|
|
for (int i = min; i <= max; i++) {
|
|
DoRemove(p, (BlockID)i, global, cmd);
|
|
}
|
|
}
|
|
|
|
static void DefineBlockStep(Player p, string value, bool global, string cmd) {
|
|
string opt = value.ToLower();
|
|
int step = GetStep(p, global);
|
|
BlockDefinition bd = GetBD(p, global);
|
|
bool temp = false;
|
|
|
|
if (opt == "revert" && step > 2) {
|
|
if (step == 17 && bd.FogDensity == 0) step -= 2;
|
|
else if (step == 9 && bd.Shape == 0) step -= 5;
|
|
else step--;
|
|
|
|
SetStep(p, global, step);
|
|
SendStepHelp(p, global);
|
|
return;
|
|
}
|
|
|
|
if (step == 2) {
|
|
bd.Name = value;
|
|
step++;
|
|
} else if (step == 3) {
|
|
if (CommandParser.GetBool(p, value, ref temp)) {
|
|
bd.Shape = temp ? (byte)0 : (byte)16;
|
|
step++;
|
|
}
|
|
} else if (step == 4) {
|
|
if (CommandParser.GetUShort(p, value, "Texture ID", ref bd.TopTex)) {
|
|
step += (bd.Shape == 0 ? 5 : 1); // skip other texture steps for sprites
|
|
if (bd.Shape == 0) bd.SetAllTex(bd.TopTex);
|
|
}
|
|
} else if (step == 5) {
|
|
if (CommandParser.GetUShort(p, value, "Texture ID", ref bd.RightTex)) {
|
|
bd.SetSideTex(bd.RightTex);
|
|
step++;
|
|
}
|
|
} else if (step == 6) {
|
|
if (CommandParser.GetUShort(p, value, "Texture ID", ref bd.BottomTex))
|
|
step++;
|
|
} else if (step == 7) {
|
|
if (ParseCoords(p, value, ref bd.MinX, ref bd.MinY, ref bd.MinZ))
|
|
step++;
|
|
} else if (step == 8) {
|
|
if (ParseCoords(p, value, ref bd.MaxX, ref bd.MaxY, ref bd.MaxZ))
|
|
step++;
|
|
bd.Shape = bd.MaxY;
|
|
} else if (step == 9) {
|
|
if (CommandParser.GetByte(p, value, "Collide type", ref bd.CollideType, 0, 7))
|
|
step++;
|
|
} else if (step == 10) {
|
|
if (CommandParser.GetReal(p, value, "Movement speed", ref bd.Speed, 0.25f, 3.96f))
|
|
step++;
|
|
} else if (step == 11) {
|
|
if (CommandParser.GetBool(p, value, ref temp)) {
|
|
bd.BlocksLight = temp;
|
|
step++;
|
|
}
|
|
} else if (step == 12) {
|
|
if (CommandParser.GetByte(p, value, "Walk sound", ref bd.WalkSound, 0, 11))
|
|
step++;
|
|
} else if (step == 13) {
|
|
if (CommandParser.GetBool(p, value, ref bd.FullBright))
|
|
step++;
|
|
} else if (step == 14) {
|
|
if (CommandParser.GetByte(p, value, "Block draw", ref bd.BlockDraw, 0, 4))
|
|
step++;
|
|
} else if (step == 15) {
|
|
if (CommandParser.GetByte(p, value, "Fog density", ref bd.FogDensity)) {
|
|
step += (bd.FogDensity == 0 ? 2 : 1);
|
|
}
|
|
} else if (step == 16) {
|
|
ColorDesc rgb = default(ColorDesc);
|
|
if (CommandParser.GetHex(p, value, ref rgb)) {
|
|
bd.FogR = rgb.R; bd.FogG = rgb.G; bd.FogB = rgb.B;
|
|
step++;
|
|
}
|
|
} else if (step == 17) {
|
|
byte fallback = GetFallback(p, value);
|
|
if (fallback == Block.Invalid) { SendStepHelp(p, global); return; }
|
|
bd.FallBack = fallback;
|
|
|
|
BlockID block = bd.GetBlock();
|
|
if (!AddBlock(p, bd, global, cmd, Block.Props[block])) return;
|
|
|
|
SetBD(p, global, null);
|
|
SetStep(p, global, 0);
|
|
return;
|
|
}
|
|
|
|
SetStep(p, global, step);
|
|
SendStepHelp(p, global);
|
|
}
|
|
|
|
static void DoEdit(Player p, BlockID block, string[] parts, bool global, string cmd) {
|
|
BlockDefinition[] defs = global ? BlockDefinition.GlobalDefs : p.level.CustomBlockDefs;
|
|
BlockDefinition def = defs[block], globalDef = BlockDefinition.GlobalDefs[block];
|
|
|
|
if (def == null && block < Block.CpeCount) {
|
|
def = DefaultSet.MakeCustomBlock(block);
|
|
AddBlock(p, def, global, cmd, Block.Props[block]);
|
|
}
|
|
if (def != null && !global && def == globalDef) {
|
|
def = globalDef.Copy();
|
|
AddBlock(p, def, global, cmd, Block.Props[block]);
|
|
}
|
|
if (!ExistsInScope(def, block, global)) { MessageNoBlock(p, block, global, cmd); return; }
|
|
|
|
string value = parts[3], blockName = def.Name;
|
|
bool temp = false, changedFallback = false;
|
|
Level level = p.IsSuper ? null : p.level;
|
|
|
|
string arg = MapPropertyName(parts[2].ToLower());
|
|
switch (arg) {
|
|
case "name":
|
|
def.Name = value; break;
|
|
case "collide":
|
|
if (!EditByte(p, value, "Collide type", ref def.CollideType, arg)) return;
|
|
break;
|
|
case "speed":
|
|
if (!CommandParser.GetReal(p, value, "Movement speed", ref def.Speed, 0.25f, 3.96f)) {
|
|
SendEditHelp(p, arg); return;
|
|
} break;
|
|
case "toptex":
|
|
if (!EditUShort(p, value, "Top texture", ref def.TopTex, arg)) return;
|
|
break;
|
|
case "alltex":
|
|
if (!EditUShort(p, value, "All textures", ref def.RightTex, arg)) return;
|
|
def.SetAllTex(def.RightTex);
|
|
break;
|
|
case "sidetex":
|
|
if (!EditUShort(p, value, "Side texture", ref def.RightTex, arg)) return;
|
|
def.SetSideTex(def.RightTex);
|
|
break;
|
|
case "lefttex":
|
|
if (!EditUShort(p, value, "Left texture", ref def.LeftTex, arg)) return;
|
|
break;
|
|
case "righttex":
|
|
if (!EditUShort(p, value, "Right texture", ref def.RightTex, arg)) return;
|
|
break;
|
|
case "fronttex":
|
|
if (!EditUShort(p, value, "Front texture", ref def.FrontTex, arg)) return;
|
|
break;
|
|
case "backtex":
|
|
if (!EditUShort(p, value, "Back texture", ref def.BackTex, arg)) return;
|
|
break;
|
|
case "bottomtex":
|
|
if (!EditUShort(p, value, "Bottom texture", ref def.BottomTex, arg)) return;
|
|
break;
|
|
|
|
case "blockslight":
|
|
if (!CommandParser.GetBool(p, value, ref temp)) {
|
|
SendEditHelp(p, arg); return;
|
|
}
|
|
def.BlocksLight = temp;
|
|
break;
|
|
case "sound":
|
|
if (!EditByte(p, value, "Walk sound", ref def.WalkSound, arg)) return;
|
|
break;
|
|
case "fullbright":
|
|
if (!CommandParser.GetBool(p, value, ref temp)) {
|
|
SendEditHelp(p, arg); return;
|
|
}
|
|
def.FullBright = temp;
|
|
break;
|
|
|
|
case "shape":
|
|
if (!CommandParser.GetBool(p, value, ref temp)) {
|
|
SendEditHelp(p, arg); return;
|
|
}
|
|
def.Shape = temp ? (byte)0 : def.MaxZ;
|
|
break;
|
|
case "blockdraw":
|
|
if (!EditByte(p, value, "Block draw", ref def.BlockDraw, arg)) return;
|
|
break;
|
|
case "min":
|
|
if (!ParseCoords(p, value, ref def.MinX, ref def.MinY, ref def.MinZ)) {
|
|
SendEditHelp(p, arg); return;
|
|
} break;
|
|
case "max":
|
|
if (!ParseCoords(p, value, ref def.MaxX, ref def.MaxY, ref def.MaxZ)) {
|
|
SendEditHelp(p, arg); return;
|
|
} break;
|
|
|
|
case "fogdensity":
|
|
if (!EditByte(p, value, "Fog density", ref def.FogDensity, arg)) return;
|
|
break;
|
|
case "fogcolor":
|
|
ColorDesc rgb = default(ColorDesc);
|
|
if (!CommandParser.GetHex(p, value, ref rgb)) return;
|
|
def.FogR = rgb.R; def.FogG = rgb.G; def.FogB = rgb.B;
|
|
break;
|
|
case "fallback":
|
|
byte fallback = GetFallback(p, value);
|
|
if (fallback == Block.Invalid) return;
|
|
changedFallback = true;
|
|
|
|
value = Block.GetName(p, fallback);
|
|
def.FallBack = fallback; break;
|
|
|
|
case "order":
|
|
int order = 0;
|
|
if (!CommandParser.GetInt(p, value, "Inventory order", ref order, 0, Block.MaxRaw)) {
|
|
SendEditHelp(p, arg); return;
|
|
}
|
|
|
|
// Don't let multiple blocks be assigned to same order
|
|
if (order != def.RawID && order != 0) {
|
|
for (int i = 0; i < defs.Length; i++) {
|
|
if (defs[i] == null || defs[i].InventoryOrder != order) continue;
|
|
p.Message("Block {0} already had order {1}", defs[i].Name, order);
|
|
return;
|
|
}
|
|
}
|
|
|
|
def.InventoryOrder = order == def.RawID ? -1 : order;
|
|
BlockDefinition.UpdateOrder(def, global, level);
|
|
BlockDefinition.Save(global, level);
|
|
p.Message("Set inventory order for {0} to {1}", blockName,
|
|
order == def.RawID ? "default" : order.ToString());
|
|
return;
|
|
default:
|
|
p.Message("Unrecognised property: " + arg); return;
|
|
}
|
|
|
|
p.Message("Set {0} for {1} to {2}", arg, blockName, value);
|
|
BlockDefinition.Add(def, defs, level);
|
|
if (changedFallback) {
|
|
BlockDefinition.UpdateFallback(global, def.GetBlock(), level);
|
|
}
|
|
}
|
|
|
|
static void EditHandler(Player p, string[] parts, bool global, string cmd) {
|
|
if (parts.Length <= 3) {
|
|
if (parts.Length == 1) {
|
|
p.Message("Valid properties: " + helpSections.Keys.Join());
|
|
} else if (parts.Length == 3) {
|
|
Help(p, cmd, "edit " + parts[2]);
|
|
} else {
|
|
Help(p, cmd);
|
|
}
|
|
return;
|
|
}
|
|
|
|
BlockID min, max;
|
|
if (!CheckBlocks(p, parts[1], out min, out max)) return;
|
|
|
|
for (int i = min; i <= max; i++) {
|
|
DoEdit(p, (BlockID)i, parts, global, cmd);
|
|
}
|
|
}
|
|
|
|
|
|
static bool AddBlock(Player p, BlockDefinition def, bool global, string cmd, BlockProps props) {
|
|
BlockDefinition[] defs = global ? BlockDefinition.GlobalDefs : p.level.CustomBlockDefs;
|
|
BlockID block = def.GetBlock();
|
|
BlockDefinition old = defs[block];
|
|
if (!global && old == BlockDefinition.GlobalDefs[block]) old = null;
|
|
|
|
// in case the list is modified before we finish the command.
|
|
if (old != null) {
|
|
block = GetFreeBlock(global, p.IsSuper ? null : p.level);
|
|
if (block == Block.Invalid) {
|
|
p.Message("There are no custom block ids left, " +
|
|
"you must " + cmd + " remove a custom block first.");
|
|
if (!global) {
|
|
p.Message("You may also manually specify the same existing id of a global custom block.");
|
|
}
|
|
return false;
|
|
}
|
|
def.SetBlock(block);
|
|
}
|
|
|
|
string scope = global ? "global" : "level";
|
|
p.Message("Created a new " + scope + " custom block " + def.Name + "(" + def.RawID + ")");
|
|
|
|
block = def.GetBlock();
|
|
BlockDefinition.Add(def, defs, p.IsSuper ? null : p.level);
|
|
ResetProps(global, block, p);
|
|
return true;
|
|
}
|
|
|
|
static BlockRaw GetFallback(Player p, string value) {
|
|
BlockID block;
|
|
if (!CommandParser.GetBlock(p, value, out block)) return Block.Invalid;
|
|
|
|
if (block >= Block.Extended) {
|
|
p.Message("%WCustom blocks cannot be used as fallback blocks.");
|
|
return Block.Invalid;
|
|
}
|
|
if (Block.IsPhysicsType(block)) {
|
|
p.Message("%WPhysics block cannot be used as fallback blocks.");
|
|
return Block.Invalid;
|
|
}
|
|
return (BlockRaw)block;
|
|
}
|
|
|
|
|
|
static BlockID GetFreeBlock(bool global, Level lvl) {
|
|
// Start from opposite ends to avoid overlap.
|
|
if (global) {
|
|
BlockDefinition[] defs = BlockDefinition.GlobalDefs;
|
|
for (BlockID b = Block.CpeCount; b <= Block.MaxRaw; b++) {
|
|
BlockID block = Block.FromRaw(b);
|
|
if (defs[block] == null) return block;
|
|
}
|
|
} else {
|
|
BlockDefinition[] defs = lvl.CustomBlockDefs;
|
|
for (BlockID b = Block.MaxRaw; b >= Block.CpeCount; b--) {
|
|
BlockID block = Block.FromRaw(b);
|
|
if (defs[block] == null) return block;
|
|
}
|
|
}
|
|
return Block.Invalid;
|
|
}
|
|
|
|
static void MessageNoBlock(Player p, BlockID block, bool global, string cmd) {
|
|
string scope = global ? "global" : "level";
|
|
p.Message("%WThere is no {1} custom block with the id \"{0}\".", Block.ToRaw(block), scope);
|
|
p.Message("Type %T{0} list %Sto see a list of {1} custom blocks.", cmd, scope);
|
|
}
|
|
|
|
static void MessageAlreadyBlock(Player p, BlockID block, bool global, string cmd) {
|
|
string scope = global ? "global" : "level";
|
|
p.Message("%WThere is already a {1} custom block with the id \"{0}\".", Block.ToRaw(block), scope);
|
|
p.Message("Type %T{0} list %Sto see a list of {1} custom blocks.", cmd, scope);
|
|
}
|
|
|
|
static bool EditByte(Player p, string value, string propName, ref byte target, string help) {
|
|
if (!CommandParser.GetByte(p, value, propName, ref target)) {
|
|
SendEditHelp(p, help); return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool EditUShort(Player p, string value, string propName, ref ushort target, string help) {
|
|
if (!CommandParser.GetUShort(p, value, propName, ref target)) {
|
|
SendEditHelp(p, help); return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool ParseCoords(Player p, string parts, ref byte x, ref byte y, ref byte z) {
|
|
string[] coords = parts.SplitSpaces();
|
|
if (coords.Length != 3) return false;
|
|
|
|
int tx = 0, ty = 0, tz = 0;
|
|
if (!CommandParser.GetInt(p, coords[0], "X", ref tx, -127, 127)) return false;
|
|
if (!CommandParser.GetInt(p, coords[1], "Y", ref ty, -127, 127)) return false;
|
|
if (!CommandParser.GetInt(p, coords[2], "Z", ref tz, -127, 127)) return false;
|
|
|
|
x = (byte)tx; z = (byte)ty; y = (byte)tz; // blockdef files have z being height, we use y being height
|
|
return true;
|
|
}
|
|
|
|
static bool CheckBlock(Player p, string arg, out BlockID block, bool air = false) {
|
|
block = Block.Invalid;
|
|
BlockID raw = 0;
|
|
BlockID min = (BlockID)(air ? 0 : 1);
|
|
BlockID max = Block.MaxRaw;
|
|
bool success = CommandParser.GetUShort(p, arg, "Block ID", ref raw, min, max);
|
|
|
|
block = Block.FromRaw(raw);
|
|
return success;
|
|
}
|
|
|
|
static bool CheckBlocks(Player p, string arg, out BlockID min, out BlockID max, bool air = false) {
|
|
bool success;
|
|
// Either "[id]" or "[min]-[max]"
|
|
if (arg.IndexOf('-') == -1) {
|
|
success = CheckBlock(p, arg, out min, air);
|
|
max = min;
|
|
} else {
|
|
string[] bits = arg.Split(new char[] { '-' }, 2);
|
|
success = CheckBlock(p, bits[0], out min, air) & CheckBlock(p, bits[1], out max, air);
|
|
}
|
|
return success;
|
|
}
|
|
|
|
static void ResetProps(bool global, BlockID block, Player p) {
|
|
BlockProps[] scope = global ? Block.Props : p.level.Props;
|
|
int changed = scope[block].ChangedScope & BlockOptions.ScopeId(scope);
|
|
if (changed != 0) return;
|
|
|
|
// properties not manually modified, revert (e.g. make grass die in shadow again)
|
|
scope[block] = BlockOptions.DefaultProps(scope, p.level, block);
|
|
BlockOptions.ApplyChanges(scope, p.level, block, false);
|
|
}
|
|
|
|
|
|
static BlockDefinition GetBD(Player p, bool global) { return global ? p.gbBlock : p.lbBlock; }
|
|
static int GetStep(Player p, bool global) { return global ? p.gbStep : p.lbStep; }
|
|
|
|
static void SetBD(Player p, bool global, BlockDefinition bd) {
|
|
if (global) p.gbBlock = bd;
|
|
else p.lbBlock = bd;
|
|
}
|
|
|
|
static void SetStep(Player p, bool global, int step) {
|
|
if (global) p.gbStep = step;
|
|
else p.lbStep = step;
|
|
}
|
|
|
|
static bool ExistsInScope(BlockDefinition def, BlockID block, bool global) {
|
|
return def != null && (global ? true : def != BlockDefinition.GlobalDefs[block]);
|
|
}
|
|
|
|
|
|
static void SendStepHelp(Player p, bool global) {
|
|
int step = GetStep(p, global);
|
|
string[] help = helpSections[stepsHelp[step]];
|
|
|
|
BlockDefinition bd = GetBD(p, global);
|
|
if (step == 4 && bd.Shape == 0)
|
|
help[0] = help[0].Replace("top texture", "texture");
|
|
|
|
for (int i = 0; i < help.Length; i++)
|
|
p.Message(help[i]);
|
|
p.Message("&f--------------------------");
|
|
}
|
|
|
|
static void SendEditHelp(Player p, string section) {
|
|
string[] help = helpSections[section];
|
|
for (int i = 0; i < help.Length; i++)
|
|
p.Message(help[i].Replace("Type", "Use"));
|
|
}
|
|
|
|
static string MapPropertyName(string prop) {
|
|
if (prop == "side" || prop == "all" || prop == "top" || prop == "bottom"
|
|
|| prop == "left" || prop == "right" || prop == "front" || prop == "back") return prop + "tex";
|
|
|
|
if (prop == "sides" || prop == "sidestex") return "sidetex";
|
|
if (prop == "light") return "blockslight";
|
|
if (prop == "bright") return "fullbright";
|
|
if (prop == "walksound") return "sound";
|
|
if (prop == "draw") return "blockdraw";
|
|
if (prop == "mincoords") return "min";
|
|
if (prop == "maxcoords") return "max";
|
|
if (prop == "density") return "fogdensity";
|
|
if (prop == "col" || prop == "fogcol") return "fogcolor";
|
|
if (prop == "fogcolour") return "fogcolor";
|
|
if (prop == "fallbackid" || prop == "fallbackblock") return "fallback";
|
|
|
|
return prop;
|
|
}
|
|
|
|
|
|
static string[] stepsHelp = new string[] {
|
|
null, null, "name", "shape", "toptex", "sidetex", "bottomtex", "min", "max", "collide",
|
|
"speed", "blockslight", "sound", "fullbright", "blockdraw", "fogdensity", "fogcolor", "fallback" };
|
|
|
|
const string texLine = "Press F10 to see the numbers for each texture in terrain.png";
|
|
static Dictionary<string, string[]> helpSections = new Dictionary<string, string[]>() {
|
|
{ "name", new string[] { "Type the name for the block." } },
|
|
{ "shape", new string[] { "Type '0' if the block is a cube, '1' if a sprite (e.g roses)." } },
|
|
{ "blockslight", new string[] { "Type 'yes' if the block casts a shadow, 'no' if it doesn't." } },
|
|
{ "fullbright", new string[] { "Type 'yes' if the block is fully lit (e.g. lava), 'no' if not." } },
|
|
|
|
{ "alltex", new string[] { "Type a number for all textures.", texLine } },
|
|
{ "sidetex", new string[] { "Type a number for sides texture.", texLine } },
|
|
{ "lefttex", new string[] { "Type a number for the left side texture.", texLine } },
|
|
{ "righttex", new string[] { "Type a number for the right side texture.", texLine } },
|
|
{ "fronttex", new string[] { "Type a number for the front side texture.", texLine } },
|
|
{ "backtex", new string[] { "Type a number for the back side texture.", texLine } },
|
|
{ "toptex", new string[] { "Type a number for the top texture.", texLine } },
|
|
{ "bottomtex", new string[] { "Type a number for the bottom texture.", texLine } },
|
|
|
|
{ "min", new string[] { "Enter the three minimum coordinates of the cube in units (separated by spaces). 1 block = 16 units.",
|
|
"Minimum coordinates for a normal block are &40 &20 &10." } },
|
|
{ "max", new string[] { "Enter the three maximum coordinates of the cube in units (separated by spaces). 1 block = 16 units.",
|
|
"Maximum coordinates for a normal block are &416 &216 &116." } },
|
|
{ "collide", new string[] { "Type a number between '0' and '7' for collision type.",
|
|
"0 - block is walk-through (e.g. air).", "1 - block is swim-through/climbable (e.g. rope).",
|
|
"2 - block is solid (e.g. dirt).", "3 - block is solid, but slippery like ice",
|
|
"4 - block is solid, but even slipperier than ice", "5 - block is swim-through like water",
|
|
"6 - block is swim-through like lava", "7 - block is climbable like rope" } },
|
|
{ "speed", new string[] { "Type a number between '0.25' (25% speed) and '3.96' (396% speed).",
|
|
"This speed is used when inside or walking on the block. Default speed is 1" }
|
|
},
|
|
{ "sound", new string[] { "Type a number between '0' and '9' for the sound played when walking on it and breaking.",
|
|
"0 = None, 1 = Wood, 2 = Gravel, 3 = Grass, 4 = Stone",
|
|
"5 = Metal, 6 = Glass, 7 = Cloth, 8 = Sand, 9 = Snow" }
|
|
},
|
|
{ "blockdraw", new string[] { "Enter the block's draw method.", "0 = Opaque, 1 = Transparent (Like glass)",
|
|
"2 = Transparent (Like leaves), 3 = Translucent (Like ice), 4 = Gas (Like air)" }
|
|
},
|
|
{ "fogdensity", new string[] { "Enter the fog density for the block. 0 = No fog at all",
|
|
"1 - 255 = Increasing density (e.g. water has 12, lava 255)" }
|
|
},
|
|
{ "fogcolor", new string[] { "Enter the fog color (hex color)" } },
|
|
{ "fallback", new string[] { "Enter the fallback block (Block shown to players who can't see custom blocks).",
|
|
"You can use any block name or block ID from the normal blocks." }
|
|
},
|
|
{ "order", new string[] { "Enter the position/order of this block in the inventory.",
|
|
"The default position of a block is its ID.",
|
|
"A position of 0 hides the block from the inventory." }
|
|
},
|
|
};
|
|
|
|
|
|
internal static void Help(Player p, string cmd) {
|
|
p.Message("%H{0} help page 1:", cmd.Substring(1));
|
|
p.Message("%T{0} add [id] %H- begins creating a new custom block", cmd);
|
|
p.Message("%T{0} copy [id] <new id> %H- clones an existing custom block", cmd);
|
|
p.Message("%T{0} edit [id] [property] [value] %H- edits that custom block", cmd);
|
|
p.Message("%T{0} remove [id] %H- removes that custom block", cmd);
|
|
p.Message("%HTo see the list of editable properties, type %T{0} edit", cmd);
|
|
p.Message("%HTo read help page 2, type %T/help {0} 2", cmd.Substring(1));
|
|
}
|
|
|
|
internal static void Help(Player p, string cmd, string args) {
|
|
if (args.CaselessEq("2")) {
|
|
p.Message("%H{0} help page 2:", cmd.Substring(1));
|
|
p.Message("%T{0} copyall [level] %H- clones all custom blocks from [level]", cmd);
|
|
p.Message("%T{0} list <offset> %H- lists all custom blocks", cmd);
|
|
p.Message("%T{0} info [id] %H- shows info about that custom block", cmd);
|
|
p.Message("%HYou may edit, remove or see info for multiple IDs at once.");
|
|
p.Message("%HUse %T/help {0} 3 %Hfor multi explanation.", cmd.Substring(1));
|
|
return;
|
|
}
|
|
else if (args.CaselessEq("3")) {
|
|
p.Message("%H{0} help page 3:", cmd.Substring(1));
|
|
p.Message("%HTo work with multiple block IDs at once,");
|
|
p.Message("%Huse a start and end range seperated by a dash.");
|
|
p.Message("%HFor example, %T{0} remove 21-24", cmd);
|
|
p.Message("%Hwould remove blocks with ID 21, 22, 23, and 24.", cmd);
|
|
p.Message("%HMulti editing only works with %T{0} edit, remove, or info", cmd);
|
|
return;
|
|
}
|
|
if (!args.CaselessStarts("edit ")) { Help(p, cmd); return; }
|
|
string prop = args.Substring(args.IndexOf(' ') + 1);
|
|
prop = MapPropertyName(prop.ToLower());
|
|
|
|
if (!helpSections.ContainsKey(prop)) {
|
|
p.Message("Valid properties: " + helpSections.Keys.Join());
|
|
} else {
|
|
SendEditHelp(p, prop);
|
|
}
|
|
}
|
|
}
|
|
|
|
public sealed class CmdGlobalBlock : Command2 {
|
|
public override string name { get { return "GlobalBlock"; } }
|
|
public override string shortcut { get { return "gb"; } }
|
|
public override string type { get { return CommandTypes.Building; } }
|
|
public override LevelPermission defaultRank { get { return LevelPermission.Admin; } }
|
|
|
|
public override void Use(Player p, string message, CommandData data) {
|
|
CustomBlockCommand.Execute(p, message, data, true, "/gb");
|
|
}
|
|
|
|
public override void Help(Player p) {
|
|
CustomBlockCommand.Help(p, "/gb");
|
|
}
|
|
|
|
public override void Help(Player p, string message) {
|
|
CustomBlockCommand.Help(p, "/gb", message);
|
|
}
|
|
}
|
|
|
|
public sealed class CmdLevelBlock : Command2 {
|
|
public override string name { get { return "LevelBlock"; } }
|
|
public override string shortcut { get { return "lb"; } }
|
|
public override string type { get { return CommandTypes.Building; } }
|
|
public override LevelPermission defaultRank { get { return LevelPermission.Admin; } }
|
|
public override bool SuperUseable { get { return false; } }
|
|
|
|
public override void Use(Player p, string message, CommandData data) {
|
|
CustomBlockCommand.Execute(p, message, data, false, "/lb");
|
|
}
|
|
|
|
public override void Help(Player p) {
|
|
CustomBlockCommand.Help(p, "/lb");
|
|
}
|
|
|
|
public override void Help(Player p, string message) {
|
|
CustomBlockCommand.Help(p, "/lb", message);
|
|
}
|
|
}
|
|
} |