version "4.0"

#include "jmod/ZForms/Include.zsc"

enum JModSpawnFlags{
	JMOD_FRIENDLY = 1,
	JMOD_DORMANT = 2,
	JMOD_TOSSED = 4
}

Class JModGlobalHandler : EventHandler{
	Array<Service> services;
	Array<string> servicecategories;
	Array<Class<Actor> > serviceactors;
	Array<int> serviceamounts;
	Array<Class<Actor> > abstracts;
	Array<Class<Actor> > abstractactors;
	Array<Class<Actor> > monsters;
	Array<Class<Actor> > missiles;
	Array<Class<Actor> > weapons;
	Array<Class<Actor> > ammo;
	Array<Class<Actor> > items;
	Array<Class<Actor> > misc;
	ui int category;
	
	Override void OnRegister(){
		services.Clear(); servicecategories.Clear(); serviceactors.Clear(); serviceamounts.Clear();
		abstracts.Clear(); abstractactors.Clear();
		monsters.Clear(); missiles.Clear(); weapons.Clear(); ammo.Clear(); items.Clear(); misc.Clear();
		let iter = ServiceIterator.Find("JModService");
		Service serv;
		While(serv = iter.Next()){
			services.Push(serv);
		}
		For(int i=0; i<services.Size(); i++){
			let s = services[i];
			int index;
			While(s){
				let ret = s.GetString("JModCategory","",index);
				If(ret){servicecategories.Push(ret); index++;}
				Else{index=0; Break;}
			}
			For(int j=0; j<servicecategories.Size(); j++){
				While(s){
					let ret = (Class<Actor>)(s.GetString("JModActor",servicecategories[j],index));
					If(ret){serviceactors.Push(ret); index++;}
					Else{serviceamounts.Push(index); index=0; Break;}
				}
			}
		}
		For(int i=0; i<AllActorClasses.Size(); i++){
			If(AllActorClasses[i].IsAbstract()){abstracts.Push(AllActorClasses[i]); Continue;}
		}
		For(int i=0; i<AllActorClasses.Size(); i++){
			let thing = AllActorClasses[i];
			let def = GetDefaultByType(thing);
			If(serviceactors.Find(thing)!=serviceactors.Size()){Continue;}
			If(!def.spawnstate.ValidateSpriteFrame()){Continue;}
			If(def.spawnstate.GetSpriteTexture(0)==TexMan.CheckForTexture("TNT1A0")){Continue;}
			If(def.spawnstate.GetSpriteTexture(0)==TexMan.CheckForTexture("TLGLA0")){Continue;}
			If(def is "Weapon"&&(!def.FindState("Ready")||!def.FindState("Ready").ValidateSpriteFrame())){Continue;}
			If(def.bSTEALTH||def is "ScriptedMarine"||def is "BlueArmorForMegasphere"){Continue;}
			If(!(GameInfo.GameType&GAME_Hexen)&&def is "FighterBoss"){Continue;}
			//Sorry Chex
			If(def is "FlemoidusCommonus"||def is "FlemoidusBipedicus"||def is "ArmoredFlemoidusBipedicus"
				||def is "FlemoidusCycloptisCommonus"||def is "Flembrane"||def is "ChexSoul"
				||def is "ChexBlueCard"||def is "ChexYellowCard"||def is "ChexRedCard"
				||def is "Bootspoon"||def is "SuperBootspork"||def is "MiniZorcher"
				||def is "LargeZorcher"||def is "SuperLargeZorcher"||def is "RapidZorcher"
				||def is "ZorchPropulsor"||def is "PhasingZorcher"||def is "LAZDevice"
				||def is "PropulsorMissile"||def is "PhaseZorchMissile"||def is "LAZBall"
				||def is "MiniZorchRecharge"||def is "MiniZorchPack"||def is "LargeZorchRecharge"
				||def is "LargeZorchPack"||def is "PropulsorZorch"||def is "PropulsorZorchPack"
				||def is "PhasingZorch"||def is "PhasingZorchPack"||def is "GlassOfWater"
				||def is "BowlOfFruit"||def is "BowlOfVegetables"||def is "SuperchargeBreakfast"
				||def is "SlimeRepellent"||def is "ChexArmor"||def is "SuperChexArmor"
				||def is "ComputerAreaMap"||def is "SlimeProofSuit"||def is "Zorchpack"
				||def is "ChexCivilian1"||def is "ChexCivilian2"||def is "ChexCivilian3"
				||def is "ChexLandingLight"||def is "ChexSpaceship"||def is "ChexAppleTree"
				||def is "ChexBananaTree"||def is "ChexOrangeTree"||def is "ChexSubmergedPlant"
				||def is "ChexTallFlower"||def is "ChexTallFlower2"||def is "ChexSlimeFountain"
				||def is "ChexCavernColumn"||def is "ChexCavernStalagmite"||def is "ChexChemicalBurner"
				||def is "ChexChemicalFlask"||def is "ChexFlagOnPole"||def is "ChexGasTank"
				||def is "ChexLightColumn"||def is "ChexMineCart"
			){Continue;}
			For(int j=0; j<abstracts.Size(); j++){
				If(def is abstracts[j]){abstractactors.Push(thing); Break;}
			}
			If(abstractactors.Find(thing)!=abstractactors.Size()){Continue;}
			If(CVar.GetCVar("jmod_replace").GetBool()&&Actor.GetReplacement(thing)){thing=Actor.GetReplacement(thing);}
			If(monsters.Find(thing)!=monsters.Size()||missiles.Find(thing)!=missiles.Size()||weapons.Find(thing)!=weapons.Size()
				||ammo.Find(thing)!=ammo.Size()||items.Find(thing)!=items.Size()||misc.Find(thing)!=misc.Size()
			){Continue;}
			If(def.bISMONSTER||def is "BossBrain"){monsters.Push(thing); Continue;}
			If(def.bMISSILE){missiles.Push(thing); Continue;}
			If(def is "Weapon"){weapons.Push(thing); Continue;}
			If(def is "Ammo"){ammo.Push(thing); Continue;}
			If(def is "Inventory"){items.Push(thing); Continue;}
			If(!def.bNOBLOCKMAP&&!(thing is "PlayerPawn")){misc.Push(thing); Continue;}
		}
		For(int i=0; i<services.Size(); i++){
			let s = services[i];
			int index;
			While(s){
				let ret = (Class<Actor>)(s.GetString("JModExclude","",index));
				If(ret){
					If(abstractactors.Find(ret)!=abstractactors.Size()){abstractactors.Delete(abstractactors.Find(ret));}
					Else If(monsters.Find(ret)!=monsters.Size()){monsters.Delete(monsters.Find(ret));}
					Else If(missiles.Find(ret)!=missiles.Size()){missiles.Delete(missiles.Find(ret));}
					Else If(weapons.Find(ret)!=weapons.Size()){weapons.Delete(weapons.Find(ret));}
					Else If(ammo.Find(ret)!=ammo.Size()){ammo.Delete(ammo.Find(ret));}
					Else If(items.Find(ret)!=items.Size()){items.Delete(items.Find(ret));}
					Else If(misc.Find(ret)!=misc.Size()){misc.Delete(misc.Find(ret));}
					index++;
				}
				Else{index=0; Break;}
			}
		}
		For(int i=0; i<abstracts.Size(); i++){
			bool delete = 1;
			For(int j=0; j<abstractactors.Size(); j++){
				If(abstractactors[j] is abstracts[i]){delete=0; Break;}
			}
			If(delete){abstracts.Delete(i); i--;}
		}
		/*
		//Array debugging
		console.printf("All actors: "..AllActorClasses.Size());
		console.printf("Services: "..services.Size());
		console.printf("Service Categories: "..servicecategories.Size());
		console.printf("Service Actors: "..serviceactors.Size());
		console.printf("Service Amounts: "..serviceamounts.Size());
		console.printf("Abstract actors: "..abstracts.Size());
		console.printf("Abstract inheritors: "..abstractactors.Size());
		console.printf("Monsters: "..monsters.Size());
		console.printf("Projectiles: "..missiles.Size());
		console.printf("Weapons: "..weapons.Size());
		console.printf("Ammo items: "..ammo.Size());
		console.printf("Inventory items: "..items.Size());
		console.printf("Other actors: "..misc.Size());
		*/
	}
}

Class JModNetworkHandler : EventHandler{
	Override void NetworkProcess(ConsoleEvent e){
		If(e.Name=="JModSpawn"){
			let playe = players[e.Player];
			let camera = playe.camera;
			If(!camera){Return;}
			FLineTraceData h;
			camera.LineTrace(camera.angle,8192,camera.pitch,TRF_THRUACTORS,playe.viewz-camera.pos.z,data:h);
			If(h.HitType==TRACE_HitNone){Return;}
			let event = JModGlobalHandler(EventHandler.Find("JModGlobalHandler"));
			Array<Class<Actor> > items;
			If(e.Args[2]==0){items.Copy(event.monsters);}
			Else If(e.Args[2]==1){items.Copy(event.missiles);}
			Else If(e.Args[2]==2){items.Copy(event.weapons);}
			Else If(e.Args[2]==3){items.Copy(event.ammo);}
			Else If(e.Args[2]==4){items.Copy(event.items);}
			Else If(e.Args[2]==5){items.Copy(event.misc);}
			Else If(e.Args[2]<6+event.servicecategories.Size()){
				int start, end;
				For(int i=0; i<=e.Args[2]-6; i++){
					If(i==e.Args[2]-6){end=start+event.serviceamounts[i];}
					Else{start+=event.serviceamounts[i];}
				}
				For(int i=start; i<end; i++){
					If(i>=event.serviceactors.Size()){Break;}
					items.Push(event.serviceactors[i]);
				}
			}
			Else{
				For(int i=0; i<event.abstractactors.Size(); i++){
					If(event.abstractactors[i] is event.abstracts[e.Args[2]-event.servicecategories.Size()-6]){items.Push(event.abstractactors[i]);}
				}
			}
			If(e.Args[0]>=items.Size()){Return;}
			let thing = items[e.Args[0]];
			let def = GetDefaultByType(thing);
			If(def.bMISSILE){
				camera.SpawnPlayerMissile(thing);
			}Else{
				Vector3 spawnpos = h.HitLocation;
				If(h.HitType==TRACE_HitWall){spawnpos-=(h.HitDir*def.radius);}
				let spawn = Actor.Spawn(thing,spawnpos);
				If(e.Args[1]&JMOD_FRIENDLY){spawn.bFRIENDLY=1;}
				If(e.Args[1]&JMOD_DORMANT){spawn.Deactivate(null);}
				If(e.Args[1]&JMOD_TOSSED&&spawn is "Inventory"){Inventory(spawn).bTOSSED=1;}
			}
		}
		Else If(e.Name=="JModCheat"){
			let playe = players[e.Player];
			If(!playe){Return;}
			If(e.Args[0]==0){
				playe.mo.GiveBody(-100);
				console.printf("Player healed");
			}
			Else If(e.Args[0]==1){
				playe.mo.CheatGive("everything",1);
				console.printf("Everything given");
			}
			Else If(e.Args[0]==2){
				playe.cheats ^= CF_GODMODE;
				If(playe.cheats&CF_GODMODE){console.printf("God mode: enabled");}
				Else{console.printf("God mode: disabled");}
			}
			Else If(e.Args[0]==3){
				playe.cheats ^= CF_BUDDHA;
				If(playe.cheats&CF_BUDDHA){console.printf("Buddha mode: enabled");}
				Else{console.printf("Buddha mode: disabled");}
			}
			Else If(e.Args[0]==4){
				If(playe.playerstate==PST_DEAD){
					playe.Resurrect();
					console.printf("Player resurrected");
				}
			}
			Else If(e.Args[0]==5){
				playe.cheats ^= CF_NOTARGET;
				If(playe.cheats&CF_NOTARGET){console.printf("Notarget: enabled");}
				Else{console.printf("Notarget: disabled");}
			}
			Else If(e.Args[0]==6){
				playe.cheats ^= CF_NOCLIP;
				If(playe.cheats&CF_NOCLIP){console.printf("Noclip: enabled");}
				Else{console.printf("Noclip: disabled");}
			}
			Else If(e.Args[0]==7){
				playe.cheats ^= CF_FLY;
				playe.mo.bFLY=playe.cheats&CF_FLY;
				playe.mo.bNOGRAVITY=playe.cheats&CF_FLY;
				If(playe.cheats&CF_FLY){console.printf("Fly: enabled");}
				Else{console.printf("Fly: disabled");}
			}
			Else If(e.Args[0]==8){
				If(playe.playerstate==PST_LIVE){
					playe.mo.DamageMobj(null,null,playe.mo.health,"normal",DMG_FORCED);
					console.printf("Played killed");
				}
			}
			Else If(e.Args[0]==9){
				For(let m = BlockThingsIterator.Create(playe.mo,81920); m.Next();){
					If(!m.thing||!m.thing.bISMONSTER){Continue;}
					m.thing.DamageMobj(null,null,m.thing.health,"normal",DMG_FORCED);
				}
				console.printf("Monsters killed");
			}
			Else If(e.Args[0]==10){
				If(level.IsFrozen()){
					playe.timefreezer=0;
					level.SetFrozen(0);
					console.printf("Map unfrozen");
				}Else{
					playe.timefreezer=1;
					level.SetFrozen(1);
					console.printf("Map frozen");
				}
			}
		}
	}
}

Class JModButtonHandler : JModZFHandler{
	JModSpawnMenu link;
	Override void ButtonClickCommand(JModZFButton caller, Name command){
		let event = JModGlobalHandler(EventHandler.Find("JModGlobalHandler"));
		string commandstring = command;
		string comma = commandstring.Mid(0,5).MakeUpper(); //Why did some of them return lowercased?
		int commaid = commandstring.Mid(5,commandstring.Length()).ToInt(10);
		If(comma=="CATEG"){
			event.category=commaid;
			link.Refresh();
		}
		Else If(comma=="SPAWN"){
			EventHandler.SendNetworkEvent("JModSpawn",commaid,link.spawnflags,event.category);
		}
		Else If(comma=="CHEAT"){
			EventHandler.SendNetworkEvent("JModCheat",commaid);
		}
		Else If(comma=="SFLAG"){
			link.spawnflags^=commaid;
			If(link.spawnflags&commaid){caller.SetText(">          <");}
			Else{caller.SetText("");}
		}
	}
	Override void ElementHoverChanged(JModZFElement caller, Name command, bool unhovered){
		If(unhovered||!(caller is "JModZFButton")){Return;}
		link.SetFocus(caller,JModZFNavEventType_Tab);
	}
}

Class JModSpawnMenu : JModZFGenericMenu{
	JModButtonHandler handler;
	Font confont;
	PlayerInfo playe;
	int spawnflags, oldleft, oldright;
	JModZFLabel spawntag, spawnsubtag;
	JModZFScrollContainer centercontainer;
	Array<JModZFElement> leftbuttons, centerbuttons, rightbuttons;
	Array<string> leftstrings, centerstrings, centersubstrings, rightstrings;
	Override void Init(Menu parent){
		Super.Init(parent);
		playe = players[consoleplayer];
		SetBaseResolution((640,480));
		mainFrame.SetBox((0,40),(640,400));
		confont = Font.GetFont("CONFONT");
		handler = New("JModButtonHandler");
		handler.link = self;
		menuactive=Menu.OnNoPause;
		DrawMenu();
	}
	Override bool OnUIEvent(UIEvent ev){
		int evtype = UIEvent.Type_KeyDown;
		If(CVar.GetCVar("jmod_heldmenu",playe).GetBool()){evtype=UIEvent.Type_KeyUp;}
		If(ev.type==evtype){
			Array<int> keylist;
			Bindings.GetAllKeysForCommand(keylist,"JModSpawnMenu");
			Array<string> keys;
			Bindings.NameAllKeys(keylist,false).Split(keys,", ");
			string keystring = ev.KeyString;
			bool keypad;
			For(int i=0; i<keys.Size(); i++){
				If(keystring==keys[i]){HandleBack(); Return true;}
				If(!keypad&&i==keys.Size()-1){keystring="KP"..keystring; keypad=1; i=0;}
			}
		}
		Return Super.OnUIEvent(ev);
	}
	void Refresh(){
		If(!centercontainer){Return;}
		centercontainer.Unpack();
		centerbuttons.Clear(); centerstrings.Clear(); centersubstrings.Clear();
		SetupCenterContainer();
		SetupFocusNeighbors();
	}
	Override void SetupFocusIndicator(){
		let focus = JModZFBoxTextures.CreateTexturePixels("jmod/graphics/jmod_focus.png",(2,2),(6,6),false,false);
		SetFocusIndicator(JModZFBoxImage.Create((0,0),(0,0),focus));
		SetFocusPriority(JModZFFocusPriority_JustAboveFocused);
	}
	Override void ChangeFocusIndicator(JModZFElement oldFocus, JModZFElement newFocus){
		string tag, subtag;
		If(leftbuttons.Find(newFocus)!=leftbuttons.Size()){tag = leftstrings[leftbuttons.Find(newFocus)];}
		If(centerbuttons.Find(newFocus)!=centerbuttons.Size()){tag = centerstrings[centerbuttons.Find(newFocus)]; subtag = centersubstrings[centerbuttons.Find(newFocus)];}
		If(rightbuttons.Find(newFocus)!=rightbuttons.Size()){tag = rightstrings[rightbuttons.Find(newFocus)];}
		double tagscale = 2;
		If(tag.Length()>=24){tagscale = 2*(24./tag.Length());}
		spawntag.SetText(tag);
		spawntag.SetTextScale(tagscale);
		If(tag==subtag){subtag="";}
		If(subtag){
			spawntag.SetHeight(18);
			double subtagscale = 2;
			If(subtag.Length()>=24){subtagscale = 2*(24./subtag.Length());}
			spawnsubtag.SetText(subtag);
			spawnsubtag.SetTextScale(subtagscale);
		}Else{
			spawntag.SetHeight(36);
			spawnsubtag.SetText("");
		}
		If((leftbuttons.Find(newFocus)!=leftbuttons.Size()&&leftbuttons.Find(oldFocus)==leftbuttons.Size())
			||(centerbuttons.Find(newFocus)!=centerbuttons.Size()&&centerbuttons.Find(oldFocus)==centerbuttons.Size())
			||(rightbuttons.Find(newFocus)!=rightbuttons.Size()&&rightbuttons.Find(oldFocus)==rightbuttons.Size())
		){
			If(leftbuttons.Find(oldFocus)!=leftbuttons.Size()){oldleft=leftbuttons.Find(oldFocus);}
			If(rightbuttons.Find(oldFocus)!=rightbuttons.Size()){oldright=rightbuttons.Find(oldFocus);}
			SetupFocusNeighbors();
		}
	}
	Override void PositionFocusIndicator(Vector2 pos, Vector2 size){
		GetFocusIndicator().SetBox(pos,size);
	}
	void DrawMenu(){
		let event = JModGlobalHandler(EventHandler.Find("JModGlobalHandler"));
		JModZFElement elem;
		let box = JModZFBoxTextures.CreateTexturePixels("jmod/graphics/jmod_box.png",(2,2),(6,6),false,false);
		let button = JModZFBoxTextures.CreateTexturePixels("jmod/graphics/jmod_button.png",(2,2),(6,6),false,false);
		elem = JModZFImage.Create((0,0),(640,400),"jmod/graphics/jmod_back.png",tiled:1);
		elem.Pack(mainFrame);
		elem = JModZFBoxImage.Create((0,0),(120,400),box);
		elem.Pack(mainFrame);
		elem = JModZFBoxImage.Create((520,0),(120,400),box);
		elem.Pack(mainFrame);
		elem = JModZFBoxImage.Create((120,0),(400,40),box);
		elem.Pack(mainFrame);
		spawntag = JModZFLabel.Create((122,2),(396,36),"",confont,JModZFElement.AlignType_CenterLeft,textScale:2);
		spawntag.Pack(mainFrame);
		spawnsubtag = JModZFLabel.Create((122,20),(396,18),"",confont,JModZFElement.AlignType_CenterLeft,textScale:2);
		spawnsubtag.Pack(mainFrame);
		
		Array<Class<Actor> > abstracts;
		Array<string> services;
		abstracts.Copy(event.abstracts);
		services.Copy(event.servicecategories);
		double leftheight = 104;
		If(services.Size()>0){leftheight+=16+(16*services.Size());}
		If(abstracts.Size()>0){leftheight+=16+(16*abstracts.Size());}
		let leftcontainer = JModZFScrollContainer.Create((0,0),(120,400),8,leftheight,32,box);
		leftcontainer.Pack(mainFrame);
		let leftframe = JModZFFrame(leftcontainer.GetScrollArea());
		elem = JModZFLabel.Create((8,8),(96,16),"GENERAL",confont,textScale:1.5);
		elem.Pack(leftframe);
		double abstractoffset;
		If(services.Size()>0){
			elem = JModZFLabel.Create((8,120),(96,16),"SERVICE",confont,textScale:1.5);
			elem.Pack(leftframe);
			abstractoffset=16+(16*services.Size());
		}
		If(abstracts.Size()>0){
			elem = JModZFLabel.Create((8,120+abstractoffset),(96,16),"ABSTRACT",confont,textScale:1.5);
			elem.Pack(leftframe);
		}
		For(int i=0; i<6+services.Size()+abstracts.Size(); i++){
			string category;
			Vector2 catpos = (8,24+(16*i));
			If(i==0){category="Monsters";}
			Else If(i==1){category="Missiles";}
			Else If(i==2){category="Weapons";}
			Else If(i==3){category="Ammo";}
			Else If(i==4){category="Items";}
			Else If(i==5){category="Misc";}
			Else If(i<6+services.Size()){category=services[i-6]; catpos.y+=16;}
			Else{category=abstracts[i-services.Size()-6].GetClassName(); catpos.y+=16; If(services.Size()>0){catpos.y+=16;}}
			double catscale = 1;
			If(category.Length()>=10){catscale = 10./category.Length();}
			elem = JModZFLabel.Create(catpos,(96,12),category,confont,JModZFElement.AlignType_Center,textScale:catscale);
			elem.Pack(leftframe);
			leftstrings.Push(category);
			elem = JModZFButton.Create(catpos,(96,12),"",handler,"CATEG"..i,button,button,button);
			elem.Pack(leftframe);
			leftbuttons.Push(JModZFButton(elem));
		}
		
		SetupCenterContainer();
		
		let rightframe = JModZFFrame.Create((520,0),(120,400));
		rightframe.Pack(mainFrame);
		elem = JModZFLabel.Create((8,8),(96,16),"CONSOLE",confont,textScale:1.5);
		elem.Pack(rightframe);
		string cheats[11] = {"GIVE HEALTH","GIVE ALL","GOD","BUDDHA","RESURRECT","NOTARGET","NOCLIP","FLY","KILL SELF","KILL ALL","FREEZE"};
		string cheatstrings[11] = {"Heal the player","Give all items","Toggle god mode","Toggle buddha mode","Resurrect","Toggle notarget","Toggle noclip","Toggle flight","Kill the player","Kill all monsters","Freeze the map"};
		For(int i=0; i<11; i++){
			Vector2 cheatpos = (8,24+(16*i));
			elem = JModZFLabel.Create(cheatpos,(96,12),cheats[i],confont,JModZFElement.AlignType_Center);
			elem.Pack(rightframe);
			rightstrings.Push(cheatstrings[i]);
			elem = JModZFButton.Create(cheatpos,(96,12),"",handler,"CHEAT"..i,button,button,button);
			elem.Pack(rightframe);
			rightbuttons.Push(JModZFButton(elem));
		}
		elem = JModZFLabel.Create((8,200),(96,16),"FLAGS",confont,textScale:1.5);
		elem.Pack(rightframe);
		int flagnum = 1;
		double flagoffset = 16;
		While(true){
			Vector2 flagpos = (8,200+flagoffset);
			string flag, flagstring;
			If(flagnum&JMOD_FRIENDLY){flag="FRIENDLY"; flagstring="Spawned actor becomes friendly";}
			Else If(flagnum&JMOD_DORMANT){flag="DORMANT"; flagstring="Spawned actor becomes dormant";}
			Else If(flagnum&JMOD_TOSSED){flag="TOSSED"; flagstring="Spawned item becomes tossed";}
			Else{Break;}
			elem = JModZFLabel.Create(flagpos,(96,12),flag,confont,JModZFElement.AlignType_Center);
			elem.Pack(rightframe);
			rightstrings.Push(flagstring);
			elem = JModZFButton.Create(flagpos,(96,12),"",handler,"SFLAG"..flagnum,button,button,button,fnt:confont,alignment:JModZFElement.AlignType_Center);
			elem.Pack(rightframe);
			rightbuttons.Push(JModZFButton(elem));
			flagnum*=2;
			flagoffset+=16;
		}
		
		If(centerbuttons.Size()>0){SetFocus(centerbuttons[0],JModZFNavEventType_FocusChangeCount);}
		Else{SetFocus(leftbuttons[0],JModZFNavEventType_FocusChangeCount);}
		SetupFocusNeighbors();
	}
	void SetupCenterContainer(){
		let event = JModGlobalHandler(EventHandler.Find("JModGlobalHandler"));
		JModZFElement elem;
		let box = JModZFBoxTextures.CreateTexturePixels("jmod/graphics/jmod_box.png",(2,2),(6,6),false,false);
		let button = JModZFBoxTextures.CreateTexturePixels("jmod/graphics/jmod_button.png",(2,2),(6,6),false,false);
		Array<Class<Actor> > items;
		int select = event.category;
		int alignment = JModZFElement.AlignType_Center;
		If(select==0){items.Copy(event.monsters); alignment=JModZFElement.AlignType_TopCenter;}
		Else If(select==1){items.Copy(event.missiles);}
		Else If(select==2){items.Copy(event.weapons);}
		Else If(select==3){items.Copy(event.ammo);}
		Else If(select==4){items.Copy(event.items);}
		Else If(select==5){items.Copy(event.misc);}
		Else If(select<6+event.servicecategories.Size()){
			int start, end;
			For(int i=0; i<=select-6; i++){
				If(i==select-6){end=start+event.serviceamounts[i];}
				Else{start+=event.serviceamounts[i];}
			}
			For(int i=start; i<end; i++){
				If(i>=event.serviceactors.Size()){Break;}
				items.Push(event.serviceactors[i]);
			}
		}
		Else{
			For(int i=0; i<event.abstractactors.Size(); i++){
				If(event.abstractactors[i] is event.abstracts[select-event.servicecategories.Size()-6]){items.Push(event.abstractactors[i]);}
			}
		}
		double centerheight = 56+((items.Size()-1)/7)*56;
		centercontainer = JModZFScrollContainer.Create((120,40),(400,360),8,centerheight,32,box);
		centercontainer.Pack(mainFrame);
		let centerframe = JModZFFrame(centercontainer.GetScrollArea());
		Vector2 offset;
		For(int i=0; i<items.Size(); i++){
			Vector2 boxpos = (4,4)+offset;
			let thing = GetDefaultByType(items[i]);
			elem = JModZFBoxImage.Create(boxpos,(48,48),box);
			elem.Pack(centerframe);
			string image = TexMan.GetName(thing.spawnstate.GetSpriteTexture(0));
			elem = JModZFImage.Create(boxpos,(48,48),image,alignment);
			elem.Pack(centerframe);
			elem = JModZFBoxImage.Create(boxpos+(0,36),(48,12),box);
			elem.Pack(centerframe);
			string thingtag = thing.GetTag();
			double tagscale = 1;
			If(thingtag.Length()>=5){tagscale = 5./thingtag.Length();}
			elem = JModZFLabel.Create(boxpos+(0,36),(48,12),thingtag,confont,JModZFElement.AlignType_Center,textScale:tagscale);
			elem.Pack(centerframe);
			centerstrings.Push(thingtag);
			centersubstrings.Push(thing.GetClassName());
			elem = JModZFButton.Create(boxpos,(48,48),"",handler,"SPAWN"..i,button,button,button);
			elem.Pack(centerframe);
			centerbuttons.Push(JModZFButton(elem));
			offset.x+=56;
			If(offset.x>=392){
				offset.x=0;
				offset.y+=56;
			}
		}
	}
	void SetupFocusNeighbors(){
		For(int i=0; i<leftbuttons.Size(); i++){
			let button = leftbuttons[i];
			button.SetFocusNeighbor(JModZFNavEventType_Left,rightbuttons[oldright]);
			If(centerbuttons.Size()>0){button.SetFocusNeighbor(JModZFNavEventType_Right,centerbuttons[0]);}
			Else{button.SetFocusNeighbor(JModZFNavEventType_Right,rightbuttons[oldright]);}
			If(i>0){button.SetFocusNeighbor(JModZFNavEventType_Up,leftbuttons[i-1]);}
			Else{button.SetFocusNeighbor(JModZFNavEventType_Up,leftbuttons[leftbuttons.Size()-1]);}
			If(i<leftbuttons.Size()-1){button.SetFocusNeighbor(JModZFNavEventType_Down,leftbuttons[i+1]);}
			Else{button.SetFocusNeighbor(JModZFNavEventType_Down,leftbuttons[0]);}
		}
		For(int i=0; i<centerbuttons.Size(); i++){
			let button = centerbuttons[i];
			If(i%7==0){button.SetFocusNeighbor(JModZFNavEventType_Left,leftbuttons[oldleft]);}
			Else{button.SetFocusNeighbor(JModZFNavEventType_Left,centerbuttons[i-1]);}
			If(i%7==6||i==centerbuttons.Size()-1){button.SetFocusNeighbor(JModZFNavEventType_Right,rightbuttons[oldright]);}
			Else{button.SetFocusNeighbor(JModZFNavEventType_Right,centerbuttons[i+1]);}
			If(i==0){button.SetFocusNeighbor(JModZFNavEventType_Up,centerbuttons[centerbuttons.Size()-1]);}
			Else If(i-7>0){button.SetFocusNeighbor(JModZFNavEventType_Up,centerbuttons[i-7]);}
			Else{button.SetFocusNeighbor(JModZFNavEventType_Up,centerbuttons[0]);}
			If(i==centerbuttons.Size()-1){button.SetFocusNeighbor(JModZFNavEventType_Down,centerbuttons[0]);}
			Else If(i+7<centerbuttons.Size()){button.SetFocusNeighbor(JModZFNavEventType_Down,centerbuttons[i+7]);}
			Else{button.SetFocusNeighbor(JModZFNavEventType_Down,centerbuttons[centerbuttons.Size()-1]);}
		}
		For(int i=0; i<rightbuttons.Size(); i++){
			let button = rightbuttons[i];
			If(centerbuttons.Size()>7){button.SetFocusNeighbor(JModZFNavEventType_Left,centerbuttons[6]);}
			Else If(centerbuttons.Size()>0){button.SetFocusNeighbor(JModZFNavEventType_Left,centerbuttons[centerbuttons.Size()-1]);}
			Else{button.SetFocusNeighbor(JModZFNavEventType_Left,leftbuttons[oldleft]);}
			button.SetFocusNeighbor(JModZFNavEventType_Right,leftbuttons[oldleft]);
			If(i>0){button.SetFocusNeighbor(JModZFNavEventType_Up,rightbuttons[i-1]);}
			Else{button.SetFocusNeighbor(JModZFNavEventType_Up,rightbuttons[rightbuttons.Size()-1]);}
			If(i<rightbuttons.Size()-1){button.SetFocusNeighbor(JModZFNavEventType_Down,rightbuttons[i+1]);}
			Else{button.SetFocusNeighbor(JModZFNavEventType_Down,rightbuttons[0]);}
		}
	}
}

Class JModTestDummy : Actor{
	int dpstimer, dpsdamage;
	Default{
		Tag "Test Dummy";
		Radius 16; Height 56;
		+SOLID; +SHOOTABLE; +DONTTHRUST;
	}
	States{
	Spawn:
		TROO A -1;
		Stop;
	}
	Override int DamageMobj(Actor inflictor, Actor source, int damage, Name mod, int flags, double angle){
		console.printf(mod.." - "..damage);
		dpsdamage+=damage;
		If(dpstimer==0){dpstimer=35;}
		Return 0;
	}
	Override void Tick(){
		Super.Tick();
		If(dpstimer>0){
			dpstimer--;
			If(dpstimer==0){
				console.printf("DPS: "..dpsdamage);
				dpsdamage=0;
			}
		}
	}
}