// Static handler responsible for some special stuff // save version holder Class SWWMSaveVerData : SWWMStaticThinker { String ver; int uid; } Class SWWMStaticHandler : StaticEventHandler { // crash handler ui bool wasinmap; ui int timer, msgpick; bool isvkdoom; // broccoli doccoli bool isbd; String bdname; // versioning bool tainted; String taintver; int uid; int checktic; int maptime; bool unloading; ui Dictionary menustate; // used by Demolitionist Menu to restore old menu positions // title stuff ui bool titlefirst; // map title stuff int mttics; // warnings bool mpwarned; // checks ThinkerIterator sti; // for intermissions, to prevent repetition ui Array lasttip, lastart; // stupid dumb thing ui bool aprilfools; ui Font aprfnt; override void NewGame() { // set save version every new session let svd = new("SWWMSaveVerData"); svd.ChangeStatNum(Thinker.STAT_STATIC); svd.ver = StringTable.Localize("$SWWM_SHORTVER"); uid = 0; } override void WorldUnloaded( WorldEvent e ) { SWWMHandler.ClearAllShaders(); unloading = true; } override void WorldTick() { if ( mttics > 0 ) { mttics--; if ( mttics == 0 ) EventHandler.SendInterfaceEvent(consoleplayer,"swwmmaptitle"); } maptime++; // in case we start late? if ( multiplayer && !mpwarned ) { mpwarned = true; Console.Printf("\cgWARNING:\c- Multiplayer is no longer supported, desyncs and other issues WILL happen. You are on your own."); S_StartSound("compat/warn",CHAN_YABLEWIT,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE); } // sanity check Array stinkers; if ( !sti ) sti = ThinkerIterator.Create("SWWMStaticThinker"); else sti.Reinit(); foreach ( t:sti ) stinkers.Push(t); if ( stinkers.Size() > 0 ) { foreach ( s:stinkers ) Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"%s is not STAT_STATIC!",s.GetClassName()); ThrowAbortException("Panic! %d static thinker%s been tampered with!",stinkers.Size(),(stinkers.Size()==1)?" has":"s have"); } } override void WorldLoaded( WorldEvent e ) { if ( gamestate != GS_TITLELEVEL ) mttics = 10; // count down to show the "area name" unloading = false; maptime = 0; if ( e.IsSavegame || e.IsReopen ) { // restore underwater sounds for players for ( int i=0; i types; for ( lmp = Wads.FindLumpFullName("swwmvoicepack",0,true); lmp != -1; lmp = Wads.FindLumpFullName("swwmvoicepack",lmp+1,true) ) { Array lst; lst.Clear(); String dat = Wads.ReadLump(lmp); dat.Split(lst,"\n",0); foreach ( l:lst ) { if ( (l.Length() <= 0) || (l.GetNextCodePoint(0) == 0) || (l.Left(1) == "\n") || (l.Left(1) == "#") ) continue; types.Push(l); } } let cv = CVar.FindCVar('swwm_voicetype'); if ( types.Find(cv.GetString()) >= types.Size() ) cv.SetString("default"); // load up the achievements if ( swwm_achievementstate == "" ) MigrateAchievements(); else LoadAchievements(); // precache fonts Array fonts; for ( lmp = Wads.FindLumpFullName("precachefonts",0,true); lmp != -1; lmp = Wads.FindLumpFullName("precachefonts",lmp+1,true) ) { Array lst; lst.Clear(); String dat = Wads.ReadLump(lmp); dat.Split(lst,"\n",0); foreach ( l:lst ) { if ( (l.Length() <= 0) || (l.GetNextCodePoint(0) == 0) || (l.Left(1) == "\n") || (l.Left(1) == "#") ) continue; fonts.Push(l); } } foreach ( f:fonts ) Font.GetFont(f); // detect vkdoom (naive, may find a better method later) lmp = Wads.FindLumpFullName("graphics/bootlogo.png",0,false); isvkdoom = (lmp != -1); // warn: mp no longer officially maintained if ( multiplayer ) { mpwarned = true; Console.Printf("\cgWARNING:\c- Multiplayer is no longer supported, desyncs and other issues WILL happen. You are on your own."); S_StartSound("compat/warn",CHAN_YABLEWIT,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE); } // warning for unsupported if ( Wads.FindLumpFullName("swwmgamesupported",0,true) != -1 ) return; Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY, "\cx┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\c-\n" "\cx┃ \cr[\cgWARNING\cr] \cx┃\c-\n" "\cx┃ \chSWWM \czGZ \cjis \cfNOT\cj compatible with the loaded IWAD. \cx┃\c-\n" "\cx┃ \cjOnly \cfDoom\cj, \cfHeretic\cj and \cfHexen\cj are supported. \cx┃\c-\n" "\cx┃ \cjIssues \cfCAN\cj and \cfWILL\cj happen. \cx┃\c-\n" "\cx┃ \cr[\cgYOU ARE ON YOUR OWN\cr] \cx┃\c-\n" "\cx┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\c-"); S_StartSound("compat/warn",CHAN_YABLEWIT,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE); } override void RenderOverlay( RenderEvent e ) { // silly april fools thing if ( aprilfools && (gamestate == GS_LEVEL) ) { String str = "Unregistered Ultracam"; if ( !aprfnt ) aprfnt = Font.GetFont("TewiFontOutline"); Screen.DrawText(aprfnt,Font.CR_WHITE,(Screen.GetWidth()-aprfnt.StringWidth(str)*CleanXFac_1)/2,2*CleanYFac_1,str,DTA_CleanNoMove_1,true); } } override void InterfaceProcess( ConsoleEvent e ) { if ( e.IsManual ) return; if ( e.Name ~== "swwmmaptitle" ) { if ( (gamestate != GS_LEVEL) || !swwm_showmaptitle ) return; StatusBar.AttachMessage(new("DSMapTitle").Init(),-7777); } else if ( e.Name ~== "swwmflushhud" ) { if ( !(StatusBar is 'SWWMStatusBar') ) return; SWWMStatusBar(StatusBar).Flush(); } else if ( e.Name ~== "swwmaprcheck" ) { if ( gamestate != GS_LEVEL ) return; if ( SystemTime.Format("%d%m",SystemTime.Now()) == "0104" ) { if ( !aprilfools ) SWWMDialogues.StartSeq("FOOL"); aprilfools = true; } else aprilfools = false; } } override void ConsoleProcess( ConsoleEvent e ) { if ( e.Name ~== "swwmresetcvars" ) { Array cvarlist; SWWMUtility.GetCVars(cvarlist); foreach ( cv:cvarlist ) { // don't reset these if ( (cv == "swwm_playtime") || (cv == "swwm_achievementstate") || (cv == "swwm_achievementprogress") ) continue; CVar.FindCVar(cv).ResetToDefault(); } } else if ( e.Name ~== "swwmresettooltips" ) { CVar.FindCVar('swwm_tooltipshown').ResetToDefault(); CVar.FindCVar('swwm_tooltipnote').ResetToDefault(); } else if ( e.Name ~== "swwmlistcvars" ) { // debug Array cvarlist; SWWMUtility.GetCVars(cvarlist); foreach ( cv:cvarlist ) { let rcv = CVar.FindCVar(cv); Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,cv.." = "..rcv.GetString()); } } else if ( e.Name ~== "swwmgetplaytime" ) { int val = swwm_playtime; int sec = (val%60); int min = ((val/60)%60); int hour = ((val/3600)%24); int day = val/86400; String str = ""; if ( day ) str.AppendFormat("%d days",day); if ( hour ) { if ( str != "" ) str = str..", "; str.AppendFormat("%d hours",hour); } if ( min ) { if ( str != "" ) str = str..", "; str.AppendFormat("%d minutes",min); } if ( sec ) { if ( str != "" ) str = str..", "; str.AppendFormat("%d seconds",sec); } if ( str == "" ) Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"No Data"); else Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,str); } else if ( e.Name ~== "swwmresetachievements" ) { foreach ( inf:achievementinfo ) { achievementstate.Insert(inf.basename,"0"); if ( inf.maxval ) achievementprogress.Insert(inf.basename,"0"); } } else if ( e.Name ~== "swwmdumpachievements" ) { Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"---STATE---"); let di = DictionaryIterator.Create(achievementstate); while ( di.Next() ) Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"%s = %s",di.Key(),di.Value()); Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"---PROGRESS---"); di = DictionaryIterator.Create(achievementprogress); while ( di.Next() ) Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"%s = %s",di.Key(),di.Value()); } else if ( e.Name ~== "swwmgetversion" ) { let ti = ThinkerIterator.Create("SWWMSaveVerData",Thinker.STAT_STATIC); let svd = SWWMSaveVerData(ti.Next()); if ( svd ) Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"\cj%s\c-",svd.ver); else Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"\cg(no version data)\c-"); if ( tainted ) Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"\cgversion mismatched\c-"); else Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"\cdversion not mismatched\c-"); } else if ( e.Name ~== "swwmdumpthinkers" ) { Array > sdefs; foreach ( cls : AllClasses ) { if ( !(cls is 'Thinker') || (cls is 'Actor') || (cls == 'Thinker') ) continue; sdefs.Push((Class)(cls)); } if ( !e.Args[0] ) { // trim default gzdoom thinkers for ( int i=0; i stink; stink.Resize(sdefs.Size()); for ( int i=Thinker.STAT_INFO; i= sdefs.Size() ) continue; stink[p]++; } ti.Destroy(); } for ( int i=0; i1)?String.Format(" [%d]",stink[i]):""); } else if ( e.Name ~== "swwmdumphandlers" ) { foreach ( cls:AllClasses ) { if ( !(cls is 'StaticEventHandler') || (cls == 'StaticEventHandler') || (cls == 'EventHandler') ) continue; bool reg = (cls is 'EventHandler')?EventHandler.Find((Class)(cls)):StaticEventHandler.Find((Class)(cls)); Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"%s%s\c-",reg?"\cj":"\cu",cls.GetClassName()); } } else if ( e.Name ~== "swwmtestdlgsize" ) { let f = Font.GetFont('TewiFont'); let lmp = Wads.FindLumpFullName("language.def_dlg"); Array lst; lst.Clear(); String dat = Wads.ReadLump(lmp); dat.Split(lst,"\n",0); bool skipme = true; Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"[default]"); bool fail = false; foreach ( l:lst ) { if ( l.Left(7) == "// E1M8" ) skipme = false; if ( l.Left(5) != "SWWM_" ) continue; if ( skipme ) continue; // extract string int st = l.IndexOf("\"")+1; int en = l.RightIndexOf("\""); String line = l.Mid(st,en-st); //line.Filter(); // DOES NOT WORK, FOR SOME REASON line.Substitute("\\\"","\""); line.Substitute("\\c","\c"); BrokenLines bl = f.BreakLines(line,220); if ( bl.Count() > 4 ) { Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"\cg%s [%d]\c-",l.Left(st-4),bl.Count()); fail = true; } bl.Destroy(); } if ( !fail ) Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"ALL OK"); lmp = Wads.FindLumpFullName("language.es_dlg"); lst.Clear(); dat = Wads.ReadLump(lmp); dat.Split(lst,"\n",0); skipme = true; Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"[es]"); foreach ( l:lst ) { if ( l.Left(7) == "// E1M8" ) skipme = false; if ( l.Left(5) != "SWWM_" ) continue; if ( skipme ) continue; // extract string int st = l.IndexOf("\"")+1; int en = l.RightIndexOf("\""); String line = l.Mid(st,en-st); //line.Filter(); // DOES NOT WORK, FOR SOME REASON line.Substitute("\\\"","\""); line.Substitute("\\c","\c"); BrokenLines bl = f.BreakLines(line,220); if ( bl.Count() > 4 ) { Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"\cg%s [%d]\c-",l.Left(st-4),bl.Count()); fail = true; } bl.Destroy(); } if ( !fail ) Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"ALL OK"); } else if ( e.Name ~== "swwmvalidatedlgfiles" ) { for ( int lmp = Wads.FindLumpFullName("swwmdialogue",0,true); lmp != -1; lmp = Wads.FindLumpFullName("swwmdialogue",lmp+1,true) ) { Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"\ce-- PARSING FILE \cf'%s'\ce...\c-",Wads.GetLumpFullName(lmp)); Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,""); String dat = Wads.ReadLump(lmp); dat.Replace("\r",""); // just in case Array lines; lines.Clear(); dat.Split(lines,"\n",0); // strip comments and trim whitespace for ( int i=0; i 0 ) Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY," \cqdelay: \cd%d\c-",sdelay); if ( sstartdelay > 0 ) Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY," \cqstartdelay: \cd%d\c-",sstartdelay); if ( senddelay > 0 ) Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY," \cqenddelay: \cd%d\c-",senddelay); if ( schardelay > 0 ) Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY," \cqchardelay: \cd%d\c-",schardelay); if ( spausedelay > 0 ) Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY," \cqpausedelay: \cd%d\c-",spausedelay); if ( sznvspecial ) Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY," \cq+\cdznvspecial\c-"); if ( sindirect ) Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY," \cq+\cdindirect\c-"); Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY," \cqcount: \cd%d\c-\n\cd---\c-",scnt); for ( int i=0; i 0 ) ThrowAbortException("dialogue '%s', line %d, sequence %d, duplicate 'CNT' parameter.",sdlg,cur+1,nseq); scnt = lines[cur].Mid(4).ToInt(); } else if ( lines[cur].Left(6) == "DELAY " ) { if ( sdelay > 0 ) ThrowAbortException("dialogue '%s', line %d, sequence %d, duplicate 'DELAY' parameter.",sdlg,cur+1,nseq); sdelay = lines[cur].Mid(6).ToInt(); } else if ( lines[cur].Left(11) == "STARTDELAY " ) { if ( sstartdelay > 0 ) ThrowAbortException("dialogue '%s', line %d, sequence %d, duplicate 'STARTDELAY' parameter.",sdlg,cur+1,nseq); sstartdelay = lines[cur].Mid(11).ToInt(); } else if ( lines[cur].Left(9) == "ENDDELAY " ) { if ( senddelay > 0 ) ThrowAbortException("dialogue '%s', line %d, sequence %d, duplicate 'ENDDELAY' parameter.",sdlg,cur+1,nseq); senddelay = lines[cur].Mid(9).ToInt(); } else if ( lines[cur].Left(10) == "CHARDELAY " ) { if ( schardelay > 0 ) ThrowAbortException("dialogue '%s', line %d, sequence %d, duplicate 'CHARDELAY' parameter.",sdlg,cur+1,nseq); schardelay = lines[cur].Mid(10).ToInt(); } else if ( lines[cur].Left(11) == "PAUSEDELAY " ) { if ( spausedelay > 0 ) ThrowAbortException("dialogue '%s', line %d, sequence %d, duplicate 'PAUSEDELAY' parameter.",sdlg,cur+1,nseq); spausedelay = lines[cur].Mid(11).ToInt(); } else if ( lines[cur].Left(8) == "INDIRECT" ) { if ( sindirect ) ThrowAbortException("dialogue '%s', line %d, sequence %d, duplicate 'INDIRECT' parameter.",sdlg,cur+1,nseq); sindirect = true; } else if ( lines[cur].Left(10) == "ZNVSPECIAL" ) { if ( sznvspecial ) ThrowAbortException("dialogue '%s', line %d, sequence %d, duplicate 'ZNVSPECIAL' parameter.",sdlg,cur+1,nseq); sznvspecial = true; } else ThrowAbortException("dialogue '%s', line %d, sequence %d, parameter not recognized",sdlg,cur+1,nseq); cur++; continue; } if ( lines[cur].Left(4) == "SEQ " ) { // begin dialogue inseq = true; schr = lines[cur].Mid(4); // wipe params sname = ""; scnt = 0; sdelay = 0; sstartdelay = 0; senddelay = 0; schardelay = 0; spausedelay = 0; sindirect = false; sznvspecial = false; cur++; continue; } ThrowAbortException("dialogue '%s', line %d, expected 'SEQ' directive",sdlg,cur+1); return; } if ( indlg ) ThrowAbortException("line %d, premature end of file reached for dialogue '%s'",cur+1,sdlg); if ( inseq ) ThrowAbortException("dialogue '%s', line %d, premature end of file reached for sequence %d",sdlg,cur+1,nseq); Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"\ce-- END OF FILE \cf'%s'\ce...\c-",Wads.GetLumpFullName(lmp)); Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,""); } } else if ( e.Name ~== "swwmdumpmonsters" ) { int i = 0; bool bLastVanilla = false; foreach ( cls:AllActorClasses ) { if ( cls.IsAbstract() ) continue; if ( cls == 'ChexSoul' ) // last defined in gzdoom.pk3 { bLastVanilla = true; continue; } if ( !bLastVanilla ) continue; let def = GetDefaultByType(cls); if ( !def.bISMONSTER && !def.bCOUNTKILL ) continue; Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"%s [%s]",def.GetClassName(),def.GetTag("NO TAG")); i++; } Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"\n%d estimated monsters defined.",i); } } override void NetworkProcess( ConsoleEvent e ) { if ( e.IsManual ) return; if ( e.Name.Left(16) ~== "swwmachievement." ) { let c = Actor.Spawn("PartyTime",players[e.Args[0]].mo.pos); c.bSTANDSTILL = true; if ( e.Args[0] == consoleplayer ) { c.A_StartSound("misc/achievement",CHAN_ITEM,CHANF_UI|CHANF_OVERLAP,attenuation:0.); c.A_StartSound("misc/achievement2",CHAN_VOICE,CHANF_UI|CHANF_OVERLAP,attenuation:0.); } else { Console.Printf(String.Format(StringTable.Localize("$SWWM_CHEEVOREM"),players[e.Args[0]].GetUserName(),StringTable.Localize(e.Name.Mid(16)))); c.A_StartSound("misc/achievement",CHAN_ITEM,CHANF_UI|CHANF_OVERLAP); c.A_StartSound("misc/achievement2",CHAN_ITEM,CHANF_UI|CHANF_OVERLAP); } } else if ( e.Name ~== "swwmsessionid" ) { let ti = ThinkerIterator.Create("SWWMSaveVerData",Thinker.STAT_STATIC); let svd = SWWMSaveVerData(ti.Next()); if ( !uid ) uid = e.Args[0]; if ( svd && !svd.uid ) svd.uid = e.Args[0]; } } override void PostUiTick() { if ( !uid ) EventHandler.SendNetworkEvent("swwmsessionid",SystemTime.Now()); if ( gamestate != GS_TITLELEVEL ) titlefirst = true; // we skip it if ( (gametic > 0) && !(gametic%GameTicRate) ) { let pt = CVar.FindCVar('swwm_playtime'); int ct = pt.GetInt(); pt.SetInt(ct+1); } if ( gamestate != GS_LEVEL ) return; CheckAllAchievements(); if ( gametic != checktic ) return; String cver = StringTable.Localize("$SWWM_SHORTVER"); if ( tainted ) { let ti = ThinkerIterator.Create("SWWMSaveVerData",Thinker.STAT_STATIC); let svd = SWWMSaveVerData(ti.Next()); if ( !svd ) Console.Printf("\cgWARNING:\n \cjSave contains no version data. Issues may happen.\c-"); else { Console.Printf("\cgWARNING:\n \cjVersion mismatch with save data. Issues may happen.\c-"); Console.Printf("\cgSaved:\n \cj"..svd.ver.."\c-"); Console.Printf("\cgCurrent:\n \cj"..cver.."\c-"); } } } override void UiTick() { // Fancy crash effect if ( (gamestate == GS_LEVEL) || (gamestate == GS_TITLELEVEL) ) { wasinmap = true; timer = 0; } else if ( (gamestate == GS_FULLCONSOLE) && ((wasinmap && !players[consoleplayer].viewheight) || (timer > 0)) ) { wasinmap = false; if ( timer == 1 ) { msgpick = Random[UIStuff](1,8); String str = StringTable.Localize("$CRASHMSG"..msgpick.."A"); if ( isvkdoom ) str.Replace("GZDoom","VKDoom"); Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"\cf%s\c-",str); let hnd = SWWMBrutalHandler(StaticEventHandler.Find("SWWMBrutalHandler")); if ( hnd && hnd.detected ) { S_StartSound("crash/glass",CHAN_YABLEWIT,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE); S_StartSound("crash/glass",CHAN_YABLEWIT,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE); } else S_StartSound("crash/crash",CHAN_YABLEWIT,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE); } else if ( timer == 70 ) { String str = StringTable.Localize("$CRASHMSG"..msgpick.."B"); if ( isvkdoom ) str.Replace("GZDoom","VKDoom"); Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"\cf%s\c-",str); S_StartSound("crash/curb",CHAN_YABLEWIT,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE); } else if ( timer == 140 ) { if ( isbd ) Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"\cfYou shouldn't have tried running this with "..bdname..".\c-"); else Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"\cfYou should probably screenshot this error and show it to Marisa.\c-"); Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"\cfLoaded Version:\n \cj%s\c-",StringTable.Localize("$SWWM_SHORTVER")); if ( tainted ) Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"\cfSavegame Version:\n \cj%s\c-",taintver); } timer++; } } }