├── .gitignore ├── README.md ├── bundles └── bundle.properties ├── icon.png ├── mod.hjson ├── scripts ├── folder.js ├── health.js ├── main.js ├── sandboxUtilities.js ├── seppuku.js ├── statusMenu.js ├── teamChanger.js └── vars.js └── sprites └── icons ├── clone.png ├── core.png ├── dump.png ├── fullSword.png ├── heal.png ├── invincibility.png ├── sandbox.png ├── seppuku.png └── survival.png /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Testing Utilities 2 | Discontinued, go to [MEEPofFaith/testing-utilities-java](https://github.com/MEEPofFaith/testing-utilities-java). 3 | 4 | Just some small utilities for sandbox/modding 5 | 6 | - Team Changer: Change teams easilty. (Disabled in campaign) 7 | - Seppuku Button: Kill yourself. Press and hold to commit crawler. A setting to make the death instant can be found in game settings. 8 | - Clone Button: Instantly clones your player unit. Press and hold to mass clone. (Disabled in campaign) 9 | - Heal Button: Sets your player unit's hp to its max. (Disabled in campaign) 10 | - Invincibility Button: Sets your player unit's hp to infinity. (Disabled in campaign) 11 | - Sandbox/Survival Button: Toggles infinite resources. (Disabled in campaign) 12 | - Fill/Dump Core: Fill or empty your core of all items. Hold to swap. (Disabled in campaign) 13 | - Status Menu: Apply/clear status effects to yourself. (Disabled in campaign) 14 | 15 | A setting for making the menu folded by default can be found in game settings. 16 | 17 | Also increases zooming range. 18 | -------------------------------------------------------------------------------- /bundles/bundle.properties: -------------------------------------------------------------------------------- 1 | tu-title = Testing Utilities 2 | 3 | tu-status-applier = Status Effect Applier 4 | tu.apply-effect = Apply Effect 5 | tu.apply-perma = Apply Permanently 6 | tu.clear-effects = Clear Effects 7 | 8 | setting.startfolded.name = Menu starts folded 9 | setting.instakill.name = Seppuku is instant -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-js/1c607dc7ad470869ebe13a04570f606203c0b9ea/icon.png -------------------------------------------------------------------------------- /mod.hjson: -------------------------------------------------------------------------------- 1 | name: test-utils 2 | displayName: Testing Utilities 3 | author: "[accent]MEEP of Faith" 4 | description: 5 | ''' 6 | Utilities for testing stuff. 7 | 8 | Team Changer: Change teams easilty. (Disabled in campaign) 9 | Seppuku Button: Kill yourself. Press and hold to mass kill. A setting to make the death instant can be found in game settings. 10 | Clone Button: Instantly clones your player unit. Press and hold to mass clone. (Disabled in campaign) 11 | Heal Button: Sets your player unit's hp to its max. (Disabled in campaign) 12 | Invincibility Button: Sets your player unit's hp to infinity. (Disabled in campaign) 13 | Sandbox/Survival Button: Toggles infinite resources. (Disabled in campaign) 14 | Fill/Dump Core: Fill or empty your core of all items. Hold to swap. (Disabled in campaign) 15 | Status Menu: Apply/clear status effects to yourself. (Disabled in campaign) 16 | 17 | A setting for making the menu folded by default can be found in game settings. 18 | 19 | Also increases zooming range. 20 | ''' 21 | version: 0.17 22 | minGameVersion: 126.2 23 | hideBrowser: true 24 | hidden: true 25 | -------------------------------------------------------------------------------- /scripts/folder.js: -------------------------------------------------------------------------------- 1 | const vars = require("vars"); 2 | 3 | function folding(t){ 4 | let b = new ImageButton(Icon.resize, Styles.logici); 5 | let bs = b.style; 6 | bs.down = Styles.flatDown; 7 | bs.over = Styles.flatOver; 8 | bs.imageDisabledColor = Color.gray; 9 | bs.imageUpColor = Color.white; 10 | 11 | b.clicked(() => { 12 | vars.folded = !vars.folded; 13 | }); 14 | 15 | return t.add(b).size(40, 40).pad(0).left(); 16 | } 17 | 18 | function folder(table){ 19 | table.table(Styles.black5, cons(t => { 20 | t.background(Tex.buttonEdge3); 21 | folding(t); 22 | })).padBottom(vars.TCOffset).padLeft(Vars.mobile ? 164 : 480); 23 | table.fillParent = true; 24 | table.visibility = () => { 25 | if(vars.folded) return false; 26 | if(!Vars.ui.hudfrag.shown) return false; 27 | if(Vars.ui.minimapfrag.shown()) return false; 28 | if(!Vars.mobile) return true; 29 | if(Vars.player.unit().isBuilding()) return false; 30 | if(Vars.control.input.block != null) return false; 31 | if(Vars.control.input.mode == PlaceMode.breaking) return false; 32 | if(!Vars.control.input.selectRequests.isEmpty() && Vars.control.input.lastSchematic != null && !Vars.control.input.selectRequests.isEmpty()) return false; 33 | return true; 34 | }; 35 | } 36 | 37 | function foldedFolder(table){ 38 | table.table(Styles.black5, cons(t => { 39 | t.background(Tex.buttonEdge3); 40 | folding(t); 41 | })).padBottom(vars.TCOffset).padLeft(Vars.mobile ? 176 : 252); 42 | table.fillParent = true; 43 | table.visibility = () => { 44 | if(!vars.folded) return false; 45 | if(!Vars.ui.hudfrag.shown) return false; 46 | if(Vars.ui.minimapfrag.shown()) return false; 47 | if(!Vars.mobile) return true; 48 | if(Vars.player.unit().isBuilding()) return false; 49 | if(Vars.control.input.block != null) return false; 50 | if(Vars.control.input.mode == PlaceMode.breaking) return false; 51 | if(!Vars.control.input.selectRequests.isEmpty() && Vars.control.input.lastSchematic != null && !Vars.control.input.selectRequests.isEmpty()) return false; 52 | return true; 53 | }; 54 | } 55 | 56 | module.exports = { 57 | add: folder, 58 | addFolded: foldedFolder 59 | } -------------------------------------------------------------------------------- /scripts/health.js: -------------------------------------------------------------------------------- 1 | const vars = require("vars"); 2 | 3 | function healButton(){ 4 | let b = new ImageButton(Core.atlas.find("test-utils-heal"), Styles.logici); 5 | let bs = b.style; 6 | bs.down = Styles.flatDown; 7 | bs.over = Styles.flatOver; 8 | bs.disabled = Tex.whiteui.tint(0.625, 0, 0, 0.8); 9 | bs.imageDisabledColor = Color.gray; 10 | bs.imageUpColor = Color.white; 11 | 12 | let offset = -4; 13 | bs.pressedOffsetX = offset; 14 | bs.unpressedOffsetX = offset; 15 | bs.checkedOffsetX = offset; 16 | 17 | b.setDisabled(() => Vars.state.isCampaign() || !Vars.player.unit() || !Vars.player.unit().type); 18 | 19 | b.label(() => b.isDisabled() ? "[gray]Heal[]" : "[white]Heal[]").padLeft(0); 20 | 21 | b.clicked(() => { 22 | vars.check(); 23 | if(Vars.net.client()){ 24 | vars.runServer("p.unit().dead=false"); 25 | vars.runServer("p.unit().maxHealth=p.unit().type.health"); 26 | vars.runServer("p.unit().health=p.unit().maxHealth"); 27 | }else{ 28 | let player = Vars.player; 29 | if(player.unit() != null && player.unit().type != null){ 30 | let u = player.unit(); 31 | u.dead = false; 32 | u.maxHealth = player.unit().type.health; 33 | u.health = player.unit().maxHealth; 34 | vars.spawnIconEffect("test-utils-heal"); 35 | } 36 | } 37 | }); 38 | 39 | b.update(() => { 40 | b.setColor(b.isDisabled() ? Color.white : Vars.player.team.color != null ? Vars.player.team.color : vars.curTeam.color); 41 | }); 42 | 43 | return b.left(); 44 | } 45 | 46 | function invincibilityButton(){ 47 | let b = new ImageButton(Core.atlas.find("test-utils-invincibility"), Styles.logici); 48 | let bs = b.style; 49 | bs.down = Styles.flatDown; 50 | bs.over = Styles.flatOver; 51 | bs.disabled = Tex.whiteui.tint(0.625, 0, 0, 0.8); 52 | bs.imageDisabledColor = Color.gray; 53 | bs.imageUpColor = Color.white; 54 | 55 | let offset = -4; 56 | bs.pressedOffsetX = offset; 57 | bs.unpressedOffsetX = offset; 58 | bs.checkedOffsetX = offset; 59 | 60 | b.setDisabled(() => Vars.state.isCampaign() || !Vars.player.unit() || !Vars.player.unit().type); 61 | 62 | b.label(() => b.isDisabled() ? "[gray]Invincibility[]" : "[white]Invincibility[]").padLeft(0); 63 | 64 | b.clicked(() => { 65 | vars.check(); 66 | if(Vars.net.client()){ 67 | vars.runServer("p.unit().dead=false"); 68 | vars.runServer("p.unit().maxHealth=Number.MAX_VALUE"); 69 | vars.runServer("p.unit().health=Number.MAX_VALUE"); 70 | }else{ 71 | let player = Vars.player; 72 | if(player.unit() != null && player.unit().type != null){ 73 | let u = player.unit(); 74 | u.dead = false; 75 | u.maxHealth = Number.MAX_VALUE; 76 | u.health = Number.MAX_VALUE; 77 | vars.spawnIconEffect("test-utils-invincibility"); 78 | } 79 | } 80 | }); 81 | 82 | b.update(() => { 83 | b.setColor(b.isDisabled() ? Color.white : Vars.player.team.color != null ? Vars.player.team.color : vars.curTeam.color); 84 | }); 85 | 86 | return b.left(); 87 | } 88 | 89 | module.exports = { 90 | heal: healButton, 91 | inv: invincibilityButton 92 | } -------------------------------------------------------------------------------- /scripts/main.js: -------------------------------------------------------------------------------- 1 | const vars = require("vars"); 2 | const folder = require("folder"); 3 | const changer = require("teamChanger"); 4 | const self = require("seppuku"); 5 | const health = require("health"); 6 | const sandbox = require("sandboxUtilities"); 7 | const status = require("statusMenu"); 8 | 9 | function add(source, t, ft){ 10 | source.add(t); 11 | Vars.ui.hudGroup.addChild(t); 12 | source.addFolded(ft); 13 | Vars.ui.hudGroup.addChild(ft); 14 | } 15 | 16 | if(!Vars.headless){ //Now this is what I call inefficient hell. 17 | let fold = new Table().bottom().left(); 18 | let fFold = new Table().bottom().left(); 19 | let change = new Table().bottom().left(); 20 | let fChange = new Table().bottom().left(); 21 | let sep = new Table().bottom().left(); 22 | let fSep = new Table().bottom().left(); 23 | let sand = new Table().bottom().left(); 24 | let fSand = new Table().bottom().left(); 25 | let stat = new Table().bottom().left(); 26 | let fStat = new Table().bottom().left(); 27 | 28 | let initialized = false; 29 | 30 | Events.on(ClientLoadEvent, () => { 31 | add(folder, fold, fFold); 32 | add(changer, change, fChange); 33 | add(self, sep, fSep); 34 | add(sandbox, sand, fSand); 35 | add(status, stat, fStat); 36 | 37 | //Settings 38 | const dialog = new BaseDialog("Testing Utilities"); 39 | dialog.addCloseButton(); 40 | dialog.cont.center().pane(p => { 41 | p.defaults().height(36); 42 | 43 | function addSetting(name, def){ 44 | p.check(Core.bundle.get("setting." + name + ".name"), Core.settings.getBool(name, def), () => { 45 | Core.settings.put(name, !Core.settings.getBool(name, def)); 46 | }).left(); 47 | p.row(); 48 | } 49 | 50 | addSetting("startfolded", true); //Start vars.folded 51 | addSetting("instakill", false); //Instakill 52 | }).growY().width(Vars.mobile ? Core.graphics.getWidth() : Core.graphics.getWidth() / 3); 53 | 54 | Vars.ui.settings.shown(() => { 55 | Vars.ui.settings.children.get(1).children.get(0).children.get(0).row(); 56 | Vars.ui.settings.children.get(1).children.get(0).children.get(0).button(Core.bundle.get("tu-title"), Styles.cleart, () => { 57 | dialog.show(); 58 | }); 59 | }); 60 | 61 | //Health/Invincibility buttons 62 | Events.on(WorldLoadEvent, () => { 63 | if(!initialized){ 64 | let m = Vars.mobile; 65 | let healthUI = Vars.ui.hudGroup.children.get(5).children.get(m ? 2 : 0).children.get(0).children.get(0).children.get(0); 66 | healthUI.row(); 67 | healthUI.add(health.heal()).size(96, 40).color(vars.curTeam.color).pad(0).left().padLeft(4); 68 | healthUI.add(health.inv()).size(164, 40).color(vars.curTeam.color).pad(0).left().padLeft(-20); 69 | initialized = true; 70 | } 71 | }); 72 | }); 73 | 74 | Events.on(WorldLoadEvent, () => { 75 | vars.folded = Core.settings.getBool("startfolded"); 76 | sandbox.mode = true; 77 | vars.curTeam = Vars.player.team(); 78 | changer.mode = changer.teams.indexOf(vars.curTeam); 79 | }); 80 | 81 | Core.app.post(() => { 82 | const meta = Vars.mods.locateMod("test-utils").meta; 83 | meta.displayName = "[#FCC21B]Testing Utilities"; 84 | meta.author = "[#FCC21B]MEEP of Faith"; 85 | meta.description = "Utilities for testing stuff" + 86 | "\n\n\n[#FCC21B]Team Changer:[] Change teams easilty. Hold to collapse or expand the list. [red](Disabled in campaign)" + 87 | "\n\n[#FCC21B]Seppuku Button:[] Instantly kill yourself. Press and hold to mass kill. A setting to make the death instant can be found in game settings." + 88 | "\n\n[#FCC21B]Clone Button:[] Instantly clones your player unit. Press and hold to mass clone. [red](Disabled in campaign)" + 89 | "\n\n[#FCC21B]Heal Button:[] Sets your player unit's hp to its max. [red](Disabled in campaign)" + 90 | "\n\n[#FCC21B]Invincibility Button:[] Sets your player unit's hp to infinity. [red](Disabled in campaign)" + 91 | "\n\n[#FCC21B]Sandbox/Survival Button:[] Toggles infinite resources. [red](Disabled in campaign)" + 92 | "\n\n[#FCC21B]Fill/Dump Core:[] Fill or empty your core of all items. Hold to swap. [red](Disabled in campaign)" + 93 | "\n\n\n[#FCC21B]Also increases zooming range.[]" 94 | }); 95 | 96 | Vars.renderer.minZoom = 0.667; //Zoom out farther 97 | Vars.renderer.maxZoom = 24; //Get a closer look at yourself 98 | } -------------------------------------------------------------------------------- /scripts/sandboxUtilities.js: -------------------------------------------------------------------------------- 1 | const vars = require("vars"); 2 | 3 | //NiChrosia's suggesion 4 | let fillMode = true; 5 | 6 | function toggleSandbox(){ 7 | vars.check(); 8 | vars.spawnIconEffect(Vars.state.rules.infiniteResources ? "test-utils-survival" : "test-utils-sandbox"); 9 | if(Vars.net.client()){ 10 | vars.runServer("Vars.state.rules.infiniteResources=!Vars.state.rules.infiniteResources"); 11 | } 12 | Vars.state.rules.infiniteResources = !Vars.state.rules.infiniteResources; 13 | }; 14 | 15 | // Fills/dumps the core 16 | function fillCore(){ 17 | vars.check(); 18 | vars.spawnIconEffect(fillMode ? "test-utils-core" : "test-utils-dump"); 19 | if(Vars.net.client()){ 20 | let code; 21 | if(fillMode){ 22 | vars.runServer("Vars.content.items().each(i=>{p.core().items.set(i,p.core().storageCapacity);})"); 23 | }else if(!fillMode){ 24 | vars.runServer("Vars.content.items().each(i=>{p.core().items.set(i,0);})"); 25 | } 26 | }else{ 27 | let core = Vars.player.core(); 28 | if(core != null){ 29 | Vars.content.items().each(i => { 30 | core.items.set(i, fillMode ? core.storageCapacity : 0); 31 | }); 32 | } 33 | } 34 | }; 35 | 36 | function addSandbox(t, mobile){ 37 | let b = new ImageButton(Core.atlas.find("test-utils-survival"), Styles.logici); 38 | let bs = b.style; 39 | bs.down = Styles.flatDown; 40 | bs.over = Styles.flatOver; 41 | bs.disabled = Tex.whiteui.tint(0.625, 0, 0, 0.8); 42 | bs.imageDisabledColor = Color.gray; 43 | bs.imageUpColor = Color.white; 44 | 45 | if(!mobile){ 46 | b.label(() => Vars.state.rules.infiniteResources && b.isDisabled() ? "[gray]Survival[]" : Vars.state.rules.infiniteResources && !b.isDisabled() ? "[white]Survival[]" : !Vars.state.rules.infiniteResources && b.isDisabled() ? "[gray]Sandbox[]" : "[white]Sandbox[]").padLeft(0); 47 | } 48 | 49 | b.setDisabled(() => Vars.state.isCampaign()); 50 | 51 | b.clicked(() => { 52 | toggleSandbox(); 53 | }); 54 | 55 | b.update(() => { 56 | //Update offset 57 | let offset = mobile ? 0 : Vars.state.rules.infiniteResources ? -3 : -2; 58 | bs.pressedOffsetX = offset; 59 | bs.unpressedOffsetX = offset; 60 | bs.checkedOffsetX = offset; 61 | 62 | b.setColor(b.isDisabled() ? Color.white : Vars.player.team.color != null ? Vars.player.team.color : vars.curTeam.color); 63 | 64 | if(!Vars.headless){ 65 | b.replaceImage(new Image(Vars.state.rules.infiniteResources ? Core.atlas.find("test-utils-survival") : Core.atlas.find("test-utils-sandbox")).setScaling(Scaling.bounded)); 66 | b.setColor(Vars.player.team.color != null ? Vars.player.team.color : vars.curTeam.color); 67 | } 68 | }); 69 | 70 | return t.add(b).color(vars.curTeam.color).pad(0).left(); 71 | } 72 | 73 | function addFillCore(t, mobile){ 74 | let b = new ImageButton(Core.atlas.find("test-utils-core"), Styles.logici); 75 | let bs = b.style; 76 | bs.down = Styles.flatDown; 77 | bs.over = Styles.flatOver; 78 | bs.disabled = Tex.whiteui.tint(0.625, 0, 0, 0.8); 79 | bs.imageDisabledColor = Color.gray; 80 | bs.imageUpColor = Color.white; 81 | 82 | if(!mobile){ 83 | b.label(() => fillMode && b.isDisabled() ? "[gray]Fill Core[]" : fillMode && !b.isDisabled() ? "[white]Fill Core[]" : !fillMode && b.isDisabled() ? "[gray]Dump Core[]" : "[white]Dump Core[]").padLeft(0); 84 | } 85 | 86 | let h5 = 0; 87 | let swap = true; 88 | 89 | b.setDisabled(() => Vars.state.isCampaign()); 90 | 91 | b.clicked(() => { 92 | if(swap) fillCore(); 93 | }); 94 | 95 | b.update(() => { 96 | if(b.isPressed() && !b.isDisabled()){ 97 | h5 += Core.graphics.getDeltaTime() * 60; 98 | if(h5 > vars.longPress && swap){ 99 | fillMode = !fillMode; 100 | swap = false; 101 | } 102 | }else{ 103 | h5 = 0; 104 | swap = true; 105 | } 106 | 107 | //Update Offset 108 | let offset = mobile ? 0 : fillMode ? -12 : -4; 109 | bs.pressedOffsetX = offset; 110 | bs.unpressedOffsetX = offset; 111 | bs.checkedOffsetX = offset; 112 | 113 | if(!Vars.headless){ 114 | b.replaceImage(new Image(fillMode ? Core.atlas.find("test-utils-core") : Core.atlas.find("test-utils-dump")).setScaling(Scaling.bounded)); 115 | b.setColor(Vars.player.team.color != null ? Vars.player.team.color : vars.curTeam.color); 116 | } 117 | }); 118 | 119 | return t.add(b).color(vars.curTeam.color).pad(0).left(); 120 | } 121 | 122 | function sandboxTable(table){ 123 | table.table(Styles.black5, cons(t => { 124 | t.background(Tex.buttonEdge3); 125 | if(Vars.mobile){ 126 | addSandbox(t, true).size(vars.iconWidth, 40); 127 | addFillCore(t, true).size(vars.iconWidth, 40); 128 | }else{ 129 | addSandbox(t, false).size(108 + vars.iconWidth, 40); 130 | addFillCore(t, false).size(120 + vars.iconWidth, 40); 131 | } 132 | })).padBottom(vars.buttonHeight + vars.TCOffset).padLeft(Vars.mobile ? 60 : 186); 133 | table.fillParent = true; 134 | table.visibility = () => { 135 | if(vars.folded) return false; 136 | if(!Vars.ui.hudfrag.shown) return false; 137 | if(Vars.ui.minimapfrag.shown()) return false; 138 | if(!Vars.mobile) return true; 139 | if(Vars.player.unit().isBuilding()) return false; 140 | if(Vars.control.input.block != null) return false; 141 | if(Vars.control.input.mode == PlaceMode.breaking) return false; 142 | if(!Vars.control.input.selectRequests.isEmpty() && Vars.control.input.lastSchematic != null && !Vars.control.input.selectRequests.isEmpty()) return false; 143 | return true; 144 | }; 145 | } 146 | 147 | function foldedSandboxTable(table){ 148 | table.table(Styles.black5, cons(t => { 149 | t.background(Tex.buttonEdge3); 150 | addSandbox(t, true).size(vars.iconWidth, 40); 151 | addFillCore(t, true).size(vars.iconWidth, 40); 152 | })).padBottom(vars.buttonHeight + vars.TCOffset).padLeft(vars.iconWidth + 20); 153 | table.fillParent = true; 154 | table.visibility = () => { 155 | if(!vars.folded) return false; 156 | if(!Vars.ui.hudfrag.shown) return false; 157 | if(Vars.ui.minimapfrag.shown()) return false; 158 | if(!Vars.mobile) return true; 159 | if(Vars.player.unit().isBuilding()) return false; 160 | if(Vars.control.input.block != null) return false; 161 | if(Vars.control.input.mode == PlaceMode.breaking) return false; 162 | if(!Vars.control.input.selectRequests.isEmpty() && Vars.control.input.lastSchematic != null && !Vars.control.input.selectRequests.isEmpty()) return false; 163 | return true; 164 | }; 165 | } 166 | 167 | module.exports = { 168 | add: sandboxTable, 169 | addFolded: foldedSandboxTable, 170 | mode: fillMode 171 | } -------------------------------------------------------------------------------- /scripts/seppuku.js: -------------------------------------------------------------------------------- 1 | const vars = require("vars"); 2 | 3 | const timers = new Interval(2); 4 | 5 | function addKill(t, mobile){ 6 | let b = new ImageButton(Vars.ui.getIcon("units"), Styles.logici); 7 | b.getImage().setScaling(Scaling.fit); 8 | let bs = b.style; 9 | bs.down = Styles.flatDown; 10 | bs.over = Styles.flatOver; 11 | bs.disabled = Tex.whiteui.tint(0.625, 0, 0, 0.8); 12 | bs.imageDisabledColor = Color.gray; 13 | bs.imageUpColor = Color.white; 14 | 15 | let offset = mobile ? 0 : -5; 16 | bs.pressedOffsetX = offset; 17 | bs.unpressedOffsetX = offset; 18 | bs.checkedOffsetX = offset; 19 | 20 | b.setDisabled(() => !Vars.player.unit() || Vars.player.unit().type == UnitTypes.block); 21 | 22 | b.image(Core.atlas.find("test-utils-seppuku")).size(40).padLeft(-60); 23 | if(!mobile){ 24 | b.label(() => b.isDisabled() ? "[gray]Seppuku[]" : "[white]Seppuku[]").padLeft(-8); 25 | } 26 | 27 | let h3 = 0; 28 | b.clicked(() => { 29 | if(h3 > vars.longPress) return; 30 | if(Vars.net.client()){ 31 | vars.runServer("p.unit().kill()"); 32 | }else{ 33 | let u = Vars.player.unit(); 34 | let type = u.type; 35 | u.kill(); 36 | if(Core.settings.getBool("instakill")){ // I n s t a n t l y d i e 37 | if(type != null){ 38 | Effect.shake(type.hitSize, type.hitSize, u); 39 | Fx.dynamicExplosion.at(u.x, u.y, type.hitSize / 5); 40 | } 41 | u.elevation = 0; 42 | u.health = -1; 43 | u.dead = true; 44 | u.destroy(); 45 | } 46 | } 47 | }); 48 | 49 | b.update(() => { 50 | if(b.isPressed()){ 51 | h3 += Core.graphics.getDeltaTime() * 60; 52 | if(h3 > vars.longPress){ 53 | if(Vars.net.client()){ 54 | vars.runServer("p.unit().kill()"); 55 | }else if(Vars.player.unit() != null){ 56 | let u = Vars.player.unit(); 57 | let type = u.type; 58 | u.kill(); 59 | if(Core.settings.getBool("instakill")){ // I n s t a n t l y d i e 60 | if(type != null){ 61 | Effect.shake(type.hitSize, type.hitSize, u); 62 | Fx.dynamicExplosion.at(u.x, u.y, type.hitSize / 5); 63 | } 64 | u.elevation = 0; 65 | u.health = -1; 66 | u.dead = true; 67 | u.destroy(); 68 | } 69 | } 70 | } 71 | }else{ 72 | h3 = 0; 73 | } 74 | 75 | b.setColor(b.isDisabled() ? Color.white : Vars.player.team.color != null ? Vars.player.team.color : vars.curTeam.color); 76 | 77 | if(!Vars.headless && Vars.player.unit().type != null && Vars.player.unit().type != UnitTypes.block && timers.get(0, 20)){ //Slight delay to reduce lag 78 | bs.imageUp = new TextureRegionDrawable(Vars.player.unit().type.icon(Cicon.full)); 79 | } 80 | }); 81 | 82 | return t.add(b).color(vars.curTeam.color).pad(0).left(); 83 | } 84 | 85 | function addClone(t, mobile){ 86 | let b = new ImageButton(Vars.ui.getIcon("units"), Styles.logici); 87 | b.getImage().setScaling(Scaling.fit); 88 | let bs = b.style; 89 | bs.down = Styles.flatDown; 90 | bs.over = Styles.flatOver; 91 | bs.disabled = Tex.whiteui.tint(0.625, 0, 0, 0.8); 92 | bs.imageDisabledColor = Color.gray; 93 | bs.imageUpColor = Color.white; 94 | 95 | let offset = mobile ? 0 : -4; 96 | bs.pressedOffsetX = offset; 97 | bs.unpressedOffsetX = offset; 98 | bs.checkedOffsetX = offset; 99 | 100 | b.setDisabled(() => Vars.state.isCampaign() || !Vars.player.unit() || !Vars.player.unit().type || Vars.player.unit().type == UnitTypes.block); 101 | 102 | b.image(Core.atlas.find("test-utils-clone")).size(40).padLeft(-60); 103 | if(!mobile){ 104 | b.label(() => b.isDisabled() ? "[gray]Clone[]" : "[white]Clone[]").padLeft(-8); 105 | } 106 | 107 | let h4 = 0; 108 | b.clicked(() => { 109 | vars.check(); 110 | if(h4 > vars.longPress) return; 111 | if(Vars.net.client()){ 112 | vars.runServer("p.unit().type.spawn(p.team(),p.getX(),p.getY())"); 113 | }else if(Vars.player.unit().type != null){ 114 | Tmp.v1.rnd(Mathf.random(Vars.player.unit().type.hitSize * 3)); 115 | let unit = Vars.player.unit().type.spawn(Vars.player.team(), Vars.player.getX() + Tmp.v1.x, Vars.player.getY() + Tmp.v1.y); 116 | 117 | unit.rotation = Mathf.random(360); 118 | unit.add(); 119 | Fx.spawn.at(Vars.player.getX() + Tmp.v1.x, Vars.player.getY() + Tmp.v1.y); 120 | } 121 | }); 122 | 123 | b.update(() => { 124 | if(b.isPressed()){ 125 | vars.check(); 126 | h4 += Core.graphics.getDeltaTime() * 60; 127 | if(h4 > vars.longPress){ 128 | if(Vars.net.client()){ 129 | vars.runServer("p.unit().type.spawn(p.team(),p.getX(),p.getY())"); 130 | }else if(Vars.player.unit().type != null){ 131 | Tmp.v1.rnd(Mathf.random(Vars.player.unit().type.hitSize * 3)); 132 | let unit = Vars.player.unit().type.spawn(Vars.player.team(), Vars.player.getX() + Tmp.v1.x, Vars.player.getY() + Tmp.v1.y); 133 | 134 | unit.rotation = Mathf.random(360); 135 | unit.add(); 136 | Fx.spawn.at(Vars.player.getX() + Tmp.v1.x, Vars.player.getY() + Tmp.v1.y); 137 | } 138 | } 139 | }else{ 140 | h4 = 0; 141 | } 142 | 143 | b.setColor(b.isDisabled() ? Color.white : Vars.player.team.color != null ? Vars.player.team.color : vars.curTeam.color); 144 | 145 | if(!Vars.headless && Vars.player.unit().type != null && Vars.player.unit().type != UnitTypes.block && timers.get(1, 20)){ //Slight delay to reduce lag 146 | bs.imageUp = new TextureRegionDrawable(Vars.player.unit().type.icon(Cicon.full)); 147 | } 148 | }); 149 | 150 | return t.add(b).color(vars.curTeam.color).pad(0).left(); 151 | } 152 | 153 | function selfTable(table){ 154 | table.table(Styles.black5, cons(t => { 155 | t.background(Tex.buttonEdge3); 156 | if(Vars.mobile){ 157 | addClone(t, true).size(vars.mobileWidth, 40); 158 | addKill(t, true).size(vars.mobileWidth, 40); 159 | }else{ 160 | addClone(t, false).size(104, 40); 161 | addKill(t, false).size(140, 40); 162 | } 163 | })).padBottom(2 * vars.buttonHeight + vars.TCOffset); 164 | table.fillParent = true; 165 | table.visibility = () => { 166 | if(vars.folded) return false; 167 | if(!Vars.ui.hudfrag.shown) return false; 168 | if(Vars.ui.minimapfrag.shown()) return false; 169 | if(!Vars.mobile) return true; 170 | if(Vars.player.unit().isBuilding()) return false; 171 | if(Vars.control.input.block != null) return false; 172 | if(Vars.control.input.mode == PlaceMode.breaking) return false; 173 | if(!Vars.control.input.selectRequests.isEmpty() && Vars.control.input.lastSchematic != null && !Vars.control.input.selectRequests.isEmpty()) return false; 174 | return true; 175 | }; 176 | } 177 | 178 | function foldedSelfTable(table){ 179 | let xOff = Vars.mobile ? 44 : 120; 180 | table.table(Styles.black5, cons(t => { 181 | t.background(Tex.pane); 182 | addClone(t, true).size(vars.mobileWidth, 40); 183 | addKill(t, true).size(vars.mobileWidth, 40); 184 | })).padBottom(vars.TCOffset).padLeft(xOff); 185 | table.fillParent = true; 186 | table.visibility = () => { 187 | if(!vars.folded) return false; 188 | if(!Vars.ui.hudfrag.shown) return false; 189 | if(Vars.ui.minimapfrag.shown()) return false; 190 | if(!Vars.mobile) return true; 191 | if(Vars.player.unit().isBuilding()) return false; 192 | if(Vars.control.input.block != null) return false; 193 | if(Vars.control.input.mode == PlaceMode.breaking) return false; 194 | if(!Vars.control.input.selectRequests.isEmpty() && Vars.control.input.lastSchematic != null && !Vars.control.input.selectRequests.isEmpty()) return false; 195 | return true; 196 | }; 197 | } 198 | 199 | module.exports = { 200 | add: selfTable, 201 | addFolded: foldedSelfTable 202 | } -------------------------------------------------------------------------------- /scripts/statusMenu.js: -------------------------------------------------------------------------------- 1 | const vars = require("vars"); 2 | 3 | // Status Data 4 | let status = StatusEffects.burning; 5 | let duration = 10; 6 | 7 | let minDur = 0.125; 8 | let maxDur = 60; 9 | 10 | function applyLocal(perma){ // Singleplayer 11 | let p = Vars.player.unit(); 12 | if(p != null){ 13 | p.apply(status, perma ? Number.MAX_VALUE : duration * 60); 14 | } 15 | } 16 | 17 | function applyRemote(perma){ // Multiplayer 18 | let eff = "Vars.content.statusEffects().find(b=>b.name===" + status.name + ")"; 19 | vars.runServer("(p.unit()!=null?p.unit().apply(" + eff + "," + (perma ? "2147483647" : duration * 60) + "):0)"); 20 | } 21 | 22 | function apply(){ 23 | vars.check(); 24 | (Vars.net.client() ? applyRemote : applyLocal)(false); 25 | } 26 | 27 | function applyPerma(){ 28 | vars.check(); 29 | (Vars.net.client() ? applyRemote : applyLocal)(true); 30 | } 31 | 32 | function clearStatuses(){ 33 | vars.check(); 34 | if(Vars.net.client()){ 35 | vars.runServer("(p.unit()!=null?p.unit().clearStatuses():0)"); 36 | }else{ 37 | let p = Vars.player.unit(); 38 | if(p != null){ 39 | p.clearStatuses(); 40 | } 41 | } 42 | } 43 | 44 | function addStatusMenu(t, mobile){ 45 | const dialog = new BaseDialog("$tu-status-applier"); 46 | const table = dialog.cont; 47 | const sButton = new ImageButton(status.icon(Cicon.full), Styles.logici); 48 | let bs = sButton.style; 49 | bs.down = Styles.flatDown; 50 | bs.over = Styles.flatOver; 51 | bs.imageDisabledColor = Color.gray; 52 | bs.imageUpColor = status.color; 53 | bs.disabled = Tex.whiteui.tint(0.625, 0, 0, 0.8); 54 | 55 | /* Title */ 56 | table.label(() => status.localizedName + (status.permanent ? " (Permanent effect)" : "")); 57 | table.row(); 58 | 59 | /* Effect selection */ 60 | table.pane(t => { 61 | let i = 0; 62 | 63 | Vars.content.statusEffects().each(e => { 64 | if(e == StatusEffects.none) return; //None does nothing, don't show it. 65 | 66 | if(i++ % 8 == 0){ 67 | t.row(); 68 | } 69 | 70 | const icon = new TextureRegionDrawable(e.icon(Cicon.full)).tint(e.color); 71 | t.button(icon, () => { 72 | status = e; 73 | bs.imageUp = icon; 74 | bs.imageUpColor = e.color; 75 | }).size(64); 76 | }); 77 | }).top().center(); 78 | table.row(); 79 | 80 | /* Duration Selection */ 81 | const d = table.table().center().bottom().get(); 82 | let dSlider, dField; 83 | d.defaults().left(); 84 | dSlider = d.slider(minDur, maxDur, 0.125, duration, n => { 85 | duration = n; 86 | dField.text = n; 87 | }).get(); 88 | d.add("Duration (seconds): ").padLeft(8); 89 | dField = d.field("" + duration, text => { 90 | duration = parseInt(text); 91 | dSlider.value = duration; 92 | }).get(); 93 | dField.validator = text => !isNaN(parseInt(text)); 94 | table.row(); 95 | 96 | /* Buttons */ 97 | table.table(null, b => { 98 | b.defaults().size(210, 64); 99 | 100 | b.button("$tu.apply-effect", Icon.add, apply).padRight(6); 101 | b.button("$tu.apply-perma", Icon.add, applyPerma); 102 | }).growX(); 103 | 104 | dialog.addCloseButton(); 105 | dialog.buttons.button("$tu.clear-effects", Icon.cancel, clearStatuses); 106 | 107 | /* Set clicky */ 108 | if(!mobile){ 109 | sButton.label(() => sButton.isDisabled() ? "[gray]Status Menu[]" : "[white]Status Menu[]").padLeft(0); 110 | } 111 | 112 | sButton.setDisabled(() => Vars.state.isCampaign() || !Vars.player.unit()); 113 | 114 | sButton.clicked(() => { 115 | dialog.show(); 116 | }); 117 | 118 | return t.add(sButton).pad(0).left(); 119 | } 120 | 121 | function statusTable(table){ 122 | table.table(Styles.black5, cons(t => { 123 | t.background(Tex.pane); 124 | if(Vars.mobile){ 125 | addStatusMenu(t, true).size(vars.iconWidth, 40); 126 | }else{ 127 | addStatusMenu(t, false).size(128 + vars.iconWidth, 40); 128 | } 129 | })).padBottom(vars.buttonHeight + vars.TCOffset); 130 | table.fillParent = true; 131 | table.visibility = () => { 132 | if(vars.folded) return false; 133 | if(!Vars.ui.hudfrag.shown) return false; 134 | if(Vars.ui.minimapfrag.shown()) return false; 135 | if(!Vars.mobile) return true; 136 | if(Vars.player.unit().isBuilding()) return false; 137 | if(Vars.control.input.block != null) return false; 138 | if(Vars.control.input.mode == PlaceMode.breaking) return false; 139 | if(!Vars.control.input.selectRequests.isEmpty() && Vars.control.input.lastSchematic != null && !Vars.control.input.selectRequests.isEmpty()) return false; 140 | return true; 141 | }; 142 | } 143 | 144 | function foldedStatusTable(table){ 145 | table.table(Styles.black5, cons(t => { 146 | t.background(Tex.pane); 147 | addStatusMenu(t, true).size(vars.iconWidth, 40); 148 | })).padBottom(vars.buttonHeight + vars.TCOffset); 149 | table.fillParent = true; 150 | table.visibility = () => { 151 | if(!vars.folded) return false; 152 | if(!Vars.ui.hudfrag.shown) return false; 153 | if(Vars.ui.minimapfrag.shown()) return false; 154 | if(!Vars.mobile) return true; 155 | if(Vars.player.unit().isBuilding()) return false; 156 | if(Vars.control.input.block != null) return false; 157 | if(Vars.control.input.mode == PlaceMode.breaking) return false; 158 | if(!Vars.control.input.selectRequests.isEmpty() && Vars.control.input.lastSchematic != null && !Vars.control.input.selectRequests.isEmpty()) return false; 159 | return true; 160 | }; 161 | } 162 | 163 | module.exports = { 164 | add: statusTable, 165 | addFolded: foldedStatusTable 166 | } 167 | -------------------------------------------------------------------------------- /scripts/teamChanger.js: -------------------------------------------------------------------------------- 1 | const vars = require("vars"); 2 | 3 | const teams = [Team.derelict, Team.sharded, Team.crux, Team.green, Team.purple, Team.blue]; 4 | const teamNames = ["Team.derelict", "Team.sharded", "Team.crux", "Team.green", "Team.purple", "Team.blue"]; 5 | const mainTeams = [0, 1, 2]; 6 | const titleList = ["[#4d4e58]Derelict[]", "[accent]Sharded[]", "[#f25555]Crux[]", "[#54d67d]Green[]", "[#995bb0]Purple[]", "[#5a4deb]Blue[]"]; 7 | const abbreList = ["[#4d4e58]D[]", "[accent]S[]", "[#f25555]C[]", "[#54d67d]G[]", "[#995bb0]P[]", "[#5a4deb]B[]"]; 8 | let mode = 1; 9 | 10 | function teamLocal(){ 11 | Vars.player.team(vars.curTeam); 12 | } 13 | 14 | function teamRemote(){ 15 | vars.runServer("p.team(" + teamNames[teams.indexOf(vars.curTeam)] + ")"); 16 | } 17 | 18 | function changeTeam(){ 19 | (Vars.net.client() ? teamRemote : teamLocal)(); 20 | } 21 | 22 | function addSingle(t, team, num, mobile){ 23 | let b = new Button(Styles.logict); 24 | let bs = b.style; 25 | bs.disabled = Tex.whiteui.tint(0.625, 0, 0, 0.8); 26 | 27 | b.setDisabled(() => Vars.state.isCampaign() || Vars.player.unit().type == UnitTypes.block); 28 | 29 | if(mobile){ 30 | b.label(() => (abbreList[teams.indexOf(team)])); 31 | }else{ 32 | b.label(() => (titleList[teams.indexOf(team)])); 33 | } 34 | 35 | b.clicked(() => { 36 | mode = num; 37 | vars.curTeam = team; 38 | changeTeam(); 39 | }); 40 | 41 | b.update(() => { 42 | b.setColor(b.isDisabled() ? Color.white : team.color); 43 | }); 44 | 45 | return t.add(b).size(40, 40).color(team.color).pad(0); 46 | } 47 | 48 | function addMini(t, teamList, mobile){ 49 | let b = new Button(Styles.logict); 50 | let bs = b.style; 51 | bs.disabled = Tex.whiteui.tint(0.625, 0, 0, 0.8); 52 | 53 | b.setDisabled(() => Vars.state.isCampaign() || Vars.player.unit().type == UnitTypes.block); 54 | 55 | if(mobile){ 56 | b.label(() => (abbreList[teams.indexOf(vars.curTeam)])); 57 | }else{ 58 | b.label(() => (titleList[teams.indexOf(vars.curTeam)])); 59 | } 60 | 61 | b.clicked(() => { 62 | do{ 63 | mode++; 64 | if(mode > teamList[teamList.length - 1]) mode = teamList[0]; 65 | }while(teamList.indexOf(mode) == -1); 66 | vars.curTeam = teams[mode]; 67 | changeTeam(); 68 | }); 69 | 70 | b.update(() => { 71 | b.setColor(b.isDisabled() ? Color.white : Vars.player.team.color != null ? Vars.player.team.color : vars.curTeam.color); 72 | }); 73 | 74 | return t.add(b).size(40, 40).color(vars.curTeam.color).pad(0).left(); 75 | } 76 | 77 | function teamChanger(table){ 78 | table.table(Styles.black5, cons(t => { 79 | t.background(Tex.pane); 80 | if(Vars.mobile){ 81 | for(let i = 0; i < teams.length; i++){ 82 | addSingle(t, teams[i], i, true).width(24); 83 | } 84 | }else{ 85 | let widths = [100, 100, 60, 68, 70, 60]; 86 | for(let i = 0; i < teams.length; i++){ 87 | addSingle(t, teams[i], i, false).width(widths[i]); 88 | } 89 | } 90 | })).padBottom(vars.TCOffset); 91 | table.fillParent = true; 92 | table.visibility = () => { 93 | if(vars.folded) return false; 94 | if(!Vars.ui.hudfrag.shown) return false; 95 | if(Vars.ui.minimapfrag.shown()) return false; 96 | if(!Vars.mobile) return true; 97 | if(Vars.player.unit().isBuilding()) return false; 98 | if(Vars.control.input.block != null) return false; 99 | if(Vars.control.input.mode == PlaceMode.breaking) return false; 100 | if(!Vars.control.input.selectRequests.isEmpty() && Vars.control.input.lastSchematic != null && !Vars.control.input.selectRequests.isEmpty()) return false; 101 | return true; 102 | }; 103 | } 104 | 105 | function foldedTeamChanger(table){ 106 | table.table(Styles.black5, cons(t => { 107 | t.background(Tex.pane); 108 | if(Vars.mobile){ 109 | addMini(t, mainTeams, true).width(24); 110 | }else{ 111 | addMini(t, mainTeams, false).width(100); 112 | } 113 | })).padBottom(vars.TCOffset); 114 | table.fillParent = true; 115 | table.visibility = () => { 116 | if(!vars.folded) return false; 117 | if(!Vars.ui.hudfrag.shown) return false; 118 | if(Vars.ui.minimapfrag.shown()) return false; 119 | if(!Vars.mobile) return true; 120 | if(Vars.player.unit().isBuilding()) return false; 121 | if(Vars.control.input.block != null) return false; 122 | if(Vars.control.input.mode == PlaceMode.breaking) return false; 123 | if(!Vars.control.input.selectRequests.isEmpty() && Vars.control.input.lastSchematic != null && !Vars.control.input.selectRequests.isEmpty()) return false; 124 | return true; 125 | }; 126 | } 127 | 128 | module.exports = { 129 | add: teamChanger, 130 | addFolded: foldedTeamChanger, 131 | mode: mode, 132 | teams: teams 133 | } -------------------------------------------------------------------------------- /scripts/vars.js: -------------------------------------------------------------------------------- 1 | let longPress = 30; 2 | 3 | let curTeam = Team.sharded; 4 | let folded = false; 5 | 6 | let TCOffset = Core.settings.getBool("mod-time-control-enabled", false) ? 62 : 0; 7 | let buttonHeight = 60; 8 | let mobileWidth = 56; 9 | let iconWidth = 40; 10 | 11 | const iconEffect = new Effect(60, e => { 12 | let rise = e.finpow() * 28; 13 | let opacity = Mathf.curve(e.fin(), 0, 0.2) - Mathf.curve(e.fin(), 0.9, 1); 14 | Draw.alpha(opacity); 15 | Draw.rect(Core.atlas.find(e.data), e.x, e.y + rise); 16 | }); 17 | iconEffect.layer = Layer.flyingUnit + 1; 18 | 19 | function spawnIconEffect(icon){ 20 | let player = Vars.player; 21 | iconEffect.at(player.getX(), player.getY(), 0, icon); 22 | } 23 | 24 | function check(){ // ;) 25 | if(!Vars.net.client() && Vars.state.isCampaign()){ 26 | /* 27 | Groups.build.each(b => { 28 | if(b.team == Team.sharded){ 29 | b.kill(); 30 | } 31 | }); 32 | */ 33 | Threads.throwAppException(new Throwable("No cheating! Don't use Testing Utilities in campaign!")); 34 | } 35 | } 36 | 37 | function runServer(script){ 38 | let name = Vars.player.name; 39 | let code = [ 40 | "Groups.player.each(p=>{p.name.includes(\"", 41 | name, 42 | "\")?", 43 | script, 44 | ":0})" 45 | ].join(""); 46 | Call.sendChatMessage("/js " + code); 47 | } 48 | 49 | module.exports = { 50 | check: check, 51 | spawnIconEffect: spawnIconEffect, 52 | longPress: longPress, 53 | team: curTeam, 54 | folded: folded, 55 | TCOffset: TCOffset, 56 | buttonHeight: buttonHeight, 57 | mobileWidth: mobileWidth, 58 | iconWidth: iconWidth, 59 | runServer: runServer 60 | } -------------------------------------------------------------------------------- /sprites/icons/clone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-js/1c607dc7ad470869ebe13a04570f606203c0b9ea/sprites/icons/clone.png -------------------------------------------------------------------------------- /sprites/icons/core.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-js/1c607dc7ad470869ebe13a04570f606203c0b9ea/sprites/icons/core.png -------------------------------------------------------------------------------- /sprites/icons/dump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-js/1c607dc7ad470869ebe13a04570f606203c0b9ea/sprites/icons/dump.png -------------------------------------------------------------------------------- /sprites/icons/fullSword.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-js/1c607dc7ad470869ebe13a04570f606203c0b9ea/sprites/icons/fullSword.png -------------------------------------------------------------------------------- /sprites/icons/heal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-js/1c607dc7ad470869ebe13a04570f606203c0b9ea/sprites/icons/heal.png -------------------------------------------------------------------------------- /sprites/icons/invincibility.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-js/1c607dc7ad470869ebe13a04570f606203c0b9ea/sprites/icons/invincibility.png -------------------------------------------------------------------------------- /sprites/icons/sandbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-js/1c607dc7ad470869ebe13a04570f606203c0b9ea/sprites/icons/sandbox.png -------------------------------------------------------------------------------- /sprites/icons/seppuku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-js/1c607dc7ad470869ebe13a04570f606203c0b9ea/sprites/icons/seppuku.png -------------------------------------------------------------------------------- /sprites/icons/survival.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-js/1c607dc7ad470869ebe13a04570f606203c0b9ea/sprites/icons/survival.png --------------------------------------------------------------------------------