12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777 |
- // leave this line at the top for all g_xxxx.cpp files...
- #include "g_headers.h"
- #include "g_local.h"
- #include "anims.h"
- #include "b_local.h"
- #include "bg_local.h"
- #include "g_functions.h"
- #include "wp_saber.h"
- #include "g_vehicles.h"
- #include "../client/fffx.h"
- #define JK2_RAGDOLL_GRIPNOHEALTH
- #define MAX_SABER_VICTIMS 16
- static int victimEntityNum[MAX_SABER_VICTIMS];
- static float totalDmg[MAX_SABER_VICTIMS];
- static vec3_t dmgDir[MAX_SABER_VICTIMS];
- static vec3_t dmgSpot[MAX_SABER_VICTIMS];
- static float dmgFraction[MAX_SABER_VICTIMS];
- static int hitLoc[MAX_SABER_VICTIMS];
- static qboolean hitDismember[MAX_SABER_VICTIMS];
- static int hitDismemberLoc[MAX_SABER_VICTIMS];
- static vec3_t saberHitLocation, saberHitNormal={0,0,1.0};
- static float saberHitFraction;
- static float sabersCrossed;
- static int saberHitEntity;
- static int numVictims = 0;
- #define SABER_PITCH_HACK 90
- extern cvar_t *g_sex;
- extern cvar_t *g_timescale;
- extern cvar_t *g_dismemberment;
- extern cvar_t *g_debugSaberLock;
- extern cvar_t *g_saberLockRandomNess;
- extern cvar_t *d_slowmodeath;
- extern cvar_t *g_cheats;
- extern cvar_t *g_debugMelee;
- extern cvar_t *g_saberRestrictForce;
- extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType );
- extern qboolean G_ClearViewEntity( gentity_t *ent );
- extern void G_SetViewEntity( gentity_t *self, gentity_t *viewEntity );
- extern qboolean G_ControlledByPlayer( gentity_t *self );
- extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
- extern void CG_ChangeWeapon( int num );
- extern void G_AngerAlert( gentity_t *self );
- extern void G_ReflectMissile( gentity_t *ent, gentity_t *missile, vec3_t forward );
- extern int G_CheckLedgeDive( gentity_t *self, float checkDist, const vec3_t checkVel, qboolean tryOpposite, qboolean tryPerp );
- extern void G_BounceMissile( gentity_t *ent, trace_t *trace );
- extern qboolean G_PointInBounds( const vec3_t point, const vec3_t mins, const vec3_t maxs );
- extern void NPC_UseResponse( gentity_t *self, gentity_t *user, qboolean useWhenDone );
- extern void WP_FireDreadnoughtBeam( gentity_t *ent );
- extern void G_MissileImpacted( gentity_t *ent, gentity_t *other, vec3_t impactPos, vec3_t normal, int hitLoc=HL_NONE );
- extern evasionType_t Jedi_SaberBlockGo( gentity_t *self, usercmd_t *cmd, vec3_t pHitloc, vec3_t phitDir, gentity_t *incoming, float dist = 0.0f );
- extern void Jedi_RageStop( gentity_t *self );
- extern int PM_PickAnim( gentity_t *self, int minAnim, int maxAnim );
- extern void NPC_SetPainEvent( gentity_t *self );
- extern qboolean PM_SwimmingAnim( int anim );
- extern qboolean PM_InAnimForSaberMove( int anim, int saberMove );
- extern qboolean PM_SpinningSaberAnim( int anim );
- extern qboolean PM_SaberInSpecialAttack( int anim );
- extern qboolean PM_SaberInAttack( int move );
- extern qboolean PM_SaberInTransition( int move );
- extern qboolean PM_SaberInStart( int move );
- extern qboolean PM_SaberInTransitionAny( int move );
- extern qboolean PM_SaberInReturn( int move );
- extern qboolean PM_SaberInBounce( int move );
- extern qboolean PM_SaberInParry( int move );
- extern qboolean PM_SaberInKnockaway( int move );
- extern qboolean PM_SaberInBrokenParry( int move );
- extern qboolean PM_SpinningSaberAnim( int anim );
- extern saberMoveName_t PM_SaberBounceForAttack( int move );
- extern saberMoveName_t PM_BrokenParryForAttack( int move );
- extern saberMoveName_t PM_KnockawayForParry( int move );
- extern qboolean PM_FlippingAnim( int anim );
- extern qboolean PM_RollingAnim( int anim );
- extern qboolean PM_CrouchAnim( int anim );
- extern qboolean PM_SaberInIdle( int move );
- extern qboolean PM_SaberInReflect( int move );
- extern qboolean PM_InSpecialJump( int anim );
- extern qboolean PM_InKnockDown( playerState_t *ps );
- extern qboolean PM_ForceUsingSaberAnim( int anim );
- extern qboolean PM_SuperBreakLoseAnim( int anim );
- extern qboolean PM_SuperBreakWinAnim( int anim );
- extern qboolean PM_SaberLockBreakAnim( int anim );
- extern qboolean PM_InOnGroundAnim ( playerState_t *ps );
- extern qboolean PM_KnockDownAnim( int anim );
- extern qboolean PM_SaberInKata( saberMoveName_t saberMove );
- extern qboolean PM_StabDownAnim( int anim );
- extern int PM_PowerLevelForSaberAnim( playerState_t *ps, int saberNum = 0 );
- extern void PM_VelocityForSaberMove( playerState_t *ps, vec3_t throwDir );
- extern qboolean PM_VelocityForBlockedMove( playerState_t *ps, vec3_t throwDir );
- extern int Jedi_ReCalcParryTime( gentity_t *self, evasionType_t evasionType );
- extern qboolean Jedi_DodgeEvasion( gentity_t *self, gentity_t *shooter, trace_t *tr, int hitLoc );
- extern void Jedi_PlayDeflectSound( gentity_t *self );
- extern void Jedi_PlayBlockedPushSound( gentity_t *self );
- extern qboolean Jedi_WaitingAmbush( gentity_t *self );
- extern void Jedi_Ambush( gentity_t *self );
- extern qboolean Jedi_SaberBusy( gentity_t *self );
- extern qboolean Jedi_CultistDestroyer( gentity_t *self );
- extern qboolean Boba_Flying( gentity_t *self );
- extern void JET_FlyStart( gentity_t *self );
- extern void Boba_DoFlameThrower( gentity_t *self );
- extern Vehicle_t *G_IsRidingVehicle( gentity_t *ent );
- extern int SaberDroid_PowerLevelForSaberAnim( gentity_t *self );
- extern qboolean G_ValidEnemy( gentity_t *self, gentity_t *enemy );
- extern void G_StartMatrixEffect( gentity_t *ent, int meFlags = 0, int length = 1000, float timeScale = 0.0f, int spinTime = 0 );
- extern int PM_AnimLength( int index, animNumber_t anim );
- extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock );
- extern void G_KnockOffVehicle( gentity_t *pRider, gentity_t *self, qboolean bPull );
- extern qboolean PM_LockedAnim( int anim );
- extern qboolean Rosh_BeingHealed( gentity_t *self );
- extern qboolean G_OkayToLean( playerState_t *ps, usercmd_t *cmd, qboolean interruptOkay );
- int WP_AbsorbConversion(gentity_t *attacked, int atdAbsLevel, gentity_t *attacker, int atPower, int atPowerLevel, int atForceSpent);
- void WP_ForcePowerStart( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
- void WP_ForcePowerStop( gentity_t *self, forcePowers_t forcePower );
- qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
- void WP_SaberInFlightReflectCheck( gentity_t *self, usercmd_t *ucmd );
- void WP_SaberDrop( gentity_t *self, gentity_t *saber );
- qboolean WP_SaberLose( gentity_t *self, vec3_t throwDir );
- void WP_SaberReturn( gentity_t *self, gentity_t *saber );
- void WP_SaberBlock( gentity_t *saber, vec3_t hitloc, qboolean missleBlock );
- void WP_SaberBlockNonRandom( gentity_t *self, vec3_t hitloc, qboolean missileBlock );
- qboolean WP_ForcePowerAvailable( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
- void WP_ForcePowerDrain( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
- void WP_DeactivateSaber( gentity_t *self, qboolean clearLength = qfalse );
- qboolean FP_ForceDrainGrippableEnt( gentity_t *victim );
- extern cvar_t *g_saberAutoBlocking;
- extern cvar_t *g_saberRealisticCombat;
- extern cvar_t *g_saberDamageCapping;
- extern cvar_t *g_saberNewControlScheme;
- extern int g_crosshairEntNum;
- qboolean g_saberNoEffects = qfalse;
- int g_saberFlashTime = 0;
- vec3_t g_saberFlashPos = {0,0,0};
- int forcePowerDarkLight[NUM_FORCE_POWERS] = //0 == neutral
- { //nothing should be usable at rank 0..
- FORCE_LIGHTSIDE,//FP_HEAL,//instant
- 0,//FP_LEVITATION,//hold/duration
- 0,//FP_SPEED,//duration
- 0,//FP_PUSH,//hold/duration
- 0,//FP_PULL,//hold/duration
- FORCE_LIGHTSIDE,//FP_TELEPATHY,//instant
- FORCE_DARKSIDE,//FP_GRIP,//hold/duration
- FORCE_DARKSIDE,//FP_LIGHTNING,//hold/duration
- 0,//FP_SABERATTACK,
- 0,//FP_SABERDEFEND,
- 0,//FP_SABERTHROW,
- //new Jedi Academy powers
- FORCE_DARKSIDE,//FP_RAGE,//duration
- FORCE_LIGHTSIDE,//FP_PROTECT,//duration
- FORCE_LIGHTSIDE,//FP_ABSORB,//duration
- FORCE_DARKSIDE,//FP_DRAIN,//hold/duration
- 0,//FP_SEE,//duration
- //NUM_FORCE_POWERS
- };
- int forcePowerNeeded[NUM_FORCE_POWERS] =
- {
- 0,//FP_HEAL,//instant
- 10,//FP_LEVITATION,//hold/duration
- 50,//FP_SPEED,//duration
- 15,//FP_PUSH,//hold/duration
- 15,//FP_PULL,//hold/duration
- 20,//FP_TELEPATHY,//instant
- 1,//FP_GRIP,//hold/duration - FIXME: 30?
- 1,//FP_LIGHTNING,//hold/duration
- 20,//FP_SABERTHROW,
- 1,//FP_SABER_DEFENSE,
- 0,//FP_SABER_OFFENSE,
- //new Jedi Academy powers
- 50,//FP_RAGE,//duration - speed, invincibility and extra damage for short period, drains your health and leaves you weak and slow afterwards.
- 30,//FP_PROTECT,//duration - protect against physical/energy (level 1 stops blaster/energy bolts, level 2 stops projectiles, level 3 protects against explosions)
- 30,//FP_ABSORB,//duration - protect against dark force powers (grip, lightning, drain)
- 1,//FP_DRAIN,//hold/duration - drain force power for health
- 20//FP_SEE,//duration - detect/see hidden enemies
- //NUM_FORCE_POWERS
- };
- float forceJumpStrength[NUM_FORCE_POWER_LEVELS] =
- {
- JUMP_VELOCITY,//normal jump
- 420,
- 590,
- 840
- };
- float forceJumpHeight[NUM_FORCE_POWER_LEVELS] =
- {
- 32,//normal jump (+stepheight+crouchdiff = 66)
- 96,//(+stepheight+crouchdiff = 130)
- 192,//(+stepheight+crouchdiff = 226)
- 384//(+stepheight+crouchdiff = 418)
- };
- float forceJumpHeightMax[NUM_FORCE_POWER_LEVELS] =
- {
- 66,//normal jump (32+stepheight(18)+crouchdiff(24) = 74)
- 130,//(96+stepheight(18)+crouchdiff(24) = 138)
- 226,//(192+stepheight(18)+crouchdiff(24) = 234)
- 418//(384+stepheight(18)+crouchdiff(24) = 426)
- };
- float forcePushPullRadius[NUM_FORCE_POWER_LEVELS] =
- {
- 0,//none
- 384,//256,
- 448,//384,
- 512
- };
- float forcePushCone[NUM_FORCE_POWER_LEVELS] =
- {
- 1.0f,//none
- 1.0f,
- 0.8f,
- 0.6f
- };
- float forcePullCone[NUM_FORCE_POWER_LEVELS] =
- {
- 1.0f,//none
- 1.0f,
- 1.0f,
- 0.8f
- };
- float forceSpeedValue[NUM_FORCE_POWER_LEVELS] =
- {
- 1.0f,//none
- 0.75f,
- 0.5f,
- 0.25f
- };
- float forceSpeedRangeMod[NUM_FORCE_POWER_LEVELS] =
- {
- 0.0f,//none
- 30.0f,
- 45.0f,
- 60.0f
- };
- float forceSpeedFOVMod[NUM_FORCE_POWER_LEVELS] =
- {
- 0.0f,//none
- 20.0f,
- 30.0f,
- 40.0f
- };
- int forceGripDamage[NUM_FORCE_POWER_LEVELS] =
- {
- 0,//none
- 0,
- 6,
- 9
- };
- int mindTrickTime[NUM_FORCE_POWER_LEVELS] =
- {
- 0,//none
- 10000,//5000,
- 15000,//10000,
- 30000//15000
- };
- //NOTE: keep in synch with table below!!!
- int saberThrowDist[NUM_FORCE_POWER_LEVELS] =
- {
- 0,//none
- 256,
- 400,
- 400
- };
- //NOTE: keep in synch with table above!!!
- int saberThrowDistSquared[NUM_FORCE_POWER_LEVELS] =
- {
- 0,//none
- 65536,
- 160000,
- 160000
- };
- int parryDebounce[NUM_FORCE_POWER_LEVELS] =
- {
- 500,//if don't even have defense, can't use defense!
- 300,
- 150,
- 50
- };
- float saberAnimSpeedMod[NUM_FORCE_POWER_LEVELS] =
- {
- 0.0f,//if don't even have offense, can't use offense!
- 0.75f,
- 1.0f,
- 2.0f
- };
- stringID_table_t SaberStyleTable[] =
- {
- "NULL",SS_NONE,
- ENUM2STRING(SS_FAST),
- "fast",SS_FAST,
- ENUM2STRING(SS_MEDIUM),
- "medium",SS_MEDIUM,
- ENUM2STRING(SS_STRONG),
- "strong",SS_STRONG,
- ENUM2STRING(SS_DESANN),
- "desann",SS_DESANN,
- ENUM2STRING(SS_TAVION),
- "tavion",SS_TAVION,
- ENUM2STRING(SS_DUAL),
- "dual",SS_DUAL,
- ENUM2STRING(SS_STAFF),
- "staff",SS_STAFF,
- "", NULL
- };
- //SABER INITIALIZATION======================================================================
- void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *psWeaponModel, int boltNum, int weaponNum )
- {
- if (!psWeaponModel)
- {
- assert (psWeaponModel);
- return;
- }
- if ( ent->playerModel == -1 )
- {
- return;
- }
- if ( boltNum == -1 )
- {
- return;
- }
- if ( ent && ent->client && ent->client->NPC_class == CLASS_GALAKMECH )
- {//hack for galakmech, no weaponmodel
- ent->weaponModel[0] = ent->weaponModel[1] = -1;
- return;
- }
- if ( weaponNum < 0 || weaponNum >= MAX_INHAND_WEAPONS )
- {
- return;
- }
- char weaponModel[64];
- strcpy (weaponModel, psWeaponModel);
- if (char *spot = strstr(weaponModel, ".md3") ) {
- *spot = 0;
- spot = strstr(weaponModel, "_w");//i'm using the in view weapon array instead of scanning the item list, so put the _w back on
- if (!spot&&!strstr(weaponModel, "noweap"))
- {
- strcat (weaponModel, "_w");
- }
- strcat (weaponModel, ".glm"); //and change to ghoul2
- }
- // give us a saber model
- int wModelIndex = G_ModelIndex( weaponModel );
- if ( wModelIndex )
- {
- ent->weaponModel[weaponNum] = gi.G2API_InitGhoul2Model(ent->ghoul2, weaponModel, wModelIndex );
- if ( ent->weaponModel[weaponNum] != -1 )
- {
- // attach it to the hand
- gi.G2API_AttachG2Model(&ent->ghoul2[ent->weaponModel[weaponNum]], &ent->ghoul2[ent->playerModel],
- boltNum, ent->playerModel);
- // set up a bolt on the end so we can get where the sabre muzzle is - we can assume this is always bolt 0
- gi.G2API_AddBolt(&ent->ghoul2[ent->weaponModel[weaponNum]], "*flash");
- //gi.G2API_SetLodBias( &ent->ghoul2[ent->weaponModel[weaponNum]], 0 );
- }
- }
- }
- void WP_SaberAddG2SaberModels( gentity_t *ent, int specificSaberNum )
- {
- int saberNum = 0, maxSaber = 1;
- if ( specificSaberNum != -1 && specificSaberNum <= maxSaber )
- {
- saberNum = maxSaber = specificSaberNum;
- }
- for ( ; saberNum <= maxSaber; saberNum++ )
- {
- if ( ent->weaponModel[saberNum] > 0 )
- {//we already have a weapon model in this slot
- //remove it
- gi.G2API_SetSkin( &ent->ghoul2[ent->weaponModel[saberNum]], -1, 0 );
- gi.G2API_RemoveGhoul2Model( ent->ghoul2, ent->weaponModel[saberNum] );
- ent->weaponModel[saberNum] = -1;
- }
- if ( saberNum > 0 )
- {//second saber
- if ( !ent->client->ps.dualSabers
- || G_IsRidingVehicle( ent ) )
- {//only have one saber or riding a vehicle and can only use one saber
- return;
- }
- }
- else if ( saberNum == 0 )
- {//first saber
- if ( ent->client->ps.saberInFlight )
- {//it's still out there somewhere, don't add it
- //FIXME: call it back?
- continue;
- }
- }
- int handBolt = ((saberNum == 0) ? ent->handRBolt : ent->handLBolt);
- G_CreateG2AttachedWeaponModel( ent, ent->client->ps.saber[saberNum].model, handBolt, saberNum );
- if ( ent->client->ps.saber[saberNum].skin != NULL )
- {//if this saber has a customSkin, use it
- // lets see if it's out there
- int saberSkin = gi.RE_RegisterSkin( ent->client->ps.saber[saberNum].skin );
- if ( saberSkin )
- {
- // put it in the config strings
- // and set the ghoul2 model to use it
- gi.G2API_SetSkin( &ent->ghoul2[ent->weaponModel[saberNum]], G_SkinIndex( ent->client->ps.saber[saberNum].skin ), saberSkin );
- }
- }
- }
- }
- //----------------------------------------------------------
- void G_Throw( gentity_t *targ, const vec3_t newDir, float push )
- //----------------------------------------------------------
- {
- vec3_t kvel;
- float mass;
- if ( targ
- && targ->client
- && ( targ->client->NPC_class == CLASS_ATST
- || targ->client->NPC_class == CLASS_RANCOR
- || targ->client->NPC_class == CLASS_SAND_CREATURE ) )
- {//much to large to *ever* throw
- return;
- }
- if ( targ->physicsBounce > 0 ) //overide the mass
- {
- mass = targ->physicsBounce;
- }
- else
- {
- mass = 200;
- }
- if ( g_gravity->value > 0 )
- {
- VectorScale( newDir, g_knockback->value * (float)push / mass * 0.8, kvel );
- if ( !targ->client || targ->client->ps.groundEntityNum != ENTITYNUM_NONE )
- {//give them some z lift to get them off the ground
- kvel[2] = newDir[2] * g_knockback->value * (float)push / mass * 1.5;
- }
- }
- else
- {
- VectorScale( newDir, g_knockback->value * (float)push / mass, kvel );
- }
- if ( targ->client )
- {
- VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity );
- }
- else if ( targ->s.pos.trType != TR_STATIONARY && targ->s.pos.trType != TR_LINEAR_STOP && targ->s.pos.trType != TR_NONLINEAR_STOP )
- {
- VectorAdd( targ->s.pos.trDelta, kvel, targ->s.pos.trDelta );
- VectorCopy( targ->currentOrigin, targ->s.pos.trBase );
- targ->s.pos.trTime = level.time;
- }
- // set the timer so that the other client can't cancel
- // out the movement immediately
- if ( targ->client && !targ->client->ps.pm_time )
- {
- int t;
- t = push * 2;
- if ( t < 50 )
- {
- t = 50;
- }
- if ( t > 200 )
- {
- t = 200;
- }
- targ->client->ps.pm_time = t;
- targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
- }
- }
- int WP_SetSaberModel( gclient_t *client, class_t npcClass )
- {//FIXME: read from NPCs.cfg
- if ( client )
- {
- switch ( npcClass )
- {
- case CLASS_DESANN://Desann
- client->ps.saber[0].model = "models/weapons2/saber_desann/saber_w.glm";
- break;
- case CLASS_LUKE://Luke
- client->ps.saber[0].model = "models/weapons2/saber_luke/saber_w.glm";
- break;
- case CLASS_PLAYER://Kyle NPC and player
- case CLASS_KYLE://Kyle NPC and player
- client->ps.saber[0].model = "models/weapons2/saber/saber_w.glm";
- break;
- default://reborn and tavion and everyone else
- client->ps.saber[0].model = "models/weapons2/saber_reborn/saber_w.glm";
- break;
- }
- return ( G_ModelIndex( client->ps.saber[0].model ) );
- }
- else
- {
- switch ( npcClass )
- {
- case CLASS_DESANN://Desann
- return ( G_ModelIndex( "models/weapons2/saber_desann/saber_w.glm" ) );
- break;
- case CLASS_LUKE://Luke
- return ( G_ModelIndex( "models/weapons2/saber_luke/saber_w.glm" ) );
- break;
- case CLASS_PLAYER://Kyle NPC and player
- case CLASS_KYLE://Kyle NPC and player
- return ( G_ModelIndex( "models/weapons2/saber/saber_w.glm" ) );
- break;
- default://reborn and tavion and everyone else
- return ( G_ModelIndex( "models/weapons2/saber_reborn/saber_w.glm" ) );
- break;
- }
- }
- }
- void WP_SetSaberEntModelSkin( gentity_t *ent, gentity_t *saberent )
- {
- int saberModel = 0;
- qboolean newModel = qfalse;
- //FIXME: get saberModel from NPCs.cfg
- if ( !ent->client->ps.saber[0].model )
- {
- saberModel = WP_SetSaberModel( ent->client, ent->client->NPC_class );
- }
- else
- {
- //got saberModel from NPCs.cfg
- saberModel = G_ModelIndex( ent->client->ps.saber[0].model );
- }
- if ( saberModel && saberent->s.modelindex != saberModel )
- {
- if ( saberent->playerModel >= 0 )
- {//remove the old one, if there is one
- gi.G2API_RemoveGhoul2Model( saberent->ghoul2, saberent->playerModel );
- }
- //add the new one
- saberent->playerModel = gi.G2API_InitGhoul2Model( saberent->ghoul2, ent->client->ps.saber[0].model, saberModel);
- saberent->s.modelindex = saberModel;
- newModel = qtrue;
- }
- //set skin, too
- if ( ent->client->ps.saber[0].skin == NULL )
- {
- gi.G2API_SetSkin( &saberent->ghoul2[0], -1, 0 );
- }
- else
- {//if this saber has a customSkin, use it
- // lets see if it's out there
- int saberSkin = gi.RE_RegisterSkin( ent->client->ps.saber[0].skin );
- if ( saberSkin && (newModel || saberent->s.modelindex2 != saberSkin) )
- {
- // put it in the config strings
- // and set the ghoul2 model to use it
- gi.G2API_SetSkin( &saberent->ghoul2[0], G_SkinIndex( ent->client->ps.saber[0].skin ), saberSkin );
- saberent->s.modelindex2 = saberSkin;
- }
- }
- }
- void WP_SaberSwingSound( gentity_t *ent, int saberNum, swingType_t swingType )
- {
- int index = 1;
- if ( !ent || !ent->client )
- {
- return;
- }
- if ( swingType == SWING_FAST )
- {
- index = Q_irand( 1, 3 );
- }
- else if ( swingType == SWING_MEDIUM )
- {
- index = Q_irand( 4, 6 );
- }
- else if ( swingType == SWING_STRONG )
- {
- index = Q_irand( 7, 9 );
- }
- #ifdef _IMMERSION
- if ( ent->client->ps.saber[saberNum].type == SABER_SITH_SWORD )
- {
- G_SoundOnEnt( ent, CHAN_WEAPON, va( "sound/weapons/sword/swing%d.wav", Q_irand( 1, 4 ) ) );
- }
- else
- {
- G_SoundOnEnt( ent, CHAN_WEAPON, va( "sound/weapons/saber/saberhup%d.wav", index ) );
- }
- G_Force( ent, G_ForceIndex( va( "fffx/weapons/saber/saberhup%d", index ), FF_CHANNEL_WEAPON ) );
- #else
- if ( ent->client->ps.saber[saberNum].type == SABER_SITH_SWORD )
- {
- G_SoundOnEnt( ent, CHAN_WEAPON, va( "sound/weapons/sword/swing%d.wav", Q_irand( 1, 4 ) ) );
- }
- else
- {
- G_SoundOnEnt( ent, CHAN_WEAPON, va( "sound/weapons/saber/saberhup%d.wav", index ) );
- }
- #endif // _IMMERSION
- }
- void WP_SaberHitSound( gentity_t *ent, int saberNum )
- {
- int index = 1;
- if ( !ent || !ent->client )
- {
- return;
- }
- #ifdef _IMMERSION
- index = Q_irand( 1, 3 );
- if ( ent->client->ps.saber[saberNum].type == SABER_SITH_SWORD )
- {
- G_Sound( ent, G_SoundIndex( va( "sound/weapons/sword/stab%d.wav", Q_irand( 1, 4 ) ) ) );
- }
- else
- {
- G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberhit%d.wav", index ) ) );
- }
- G_Force( ent, G_ForceIndex( va( "fffx/weapons/saber/saberhit%d", index), FF_CHANNEL_WEAPON ) );
- #else
- if ( ent->client->ps.saber[saberNum].type == SABER_SITH_SWORD )
- {
- G_Sound( ent, G_SoundIndex( va( "sound/weapons/sword/stab%d.wav", Q_irand( 1, 4 ) ) ) );
- }
- else
- {
- G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberhit%d.wav", Q_irand( 1, 3 ) ) ) );
- }
- #endif // _IMMERSION
- if(ent->client && ent->client->ps.clientNum == 0)
- FF_Play(fffx_Laser1);
- }
- int WP_SaberInitBladeData( gentity_t *ent )
- {
- if ( !ent->client )
- {
- return 0;
- }
- if ( 1 )
- {
- VectorClear( ent->client->renderInfo.muzzlePoint );
- VectorClear( ent->client->renderInfo.muzzlePointOld );
- //VectorClear( ent->client->renderInfo.muzzlePointNext );
- VectorClear( ent->client->renderInfo.muzzleDir );
- VectorClear( ent->client->renderInfo.muzzleDirOld );
- //VectorClear( ent->client->renderInfo.muzzleDirNext );
- for ( int saberNum = 0; saberNum < MAX_SABERS; saberNum++ )
- {
- for ( int bladeNum = 0; bladeNum < MAX_BLADES; bladeNum++ )
- {
- VectorClear( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint );
- VectorClear( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePointOld );
- VectorClear( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDir );
- VectorClear( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDirOld );
- ent->client->ps.saber[saberNum].blade[bladeNum].lengthOld = ent->client->ps.saber[saberNum].blade[bladeNum].length = 0;
- if ( !ent->client->ps.saber[saberNum].blade[bladeNum].lengthMax )
- {
- if ( ent->client->NPC_class == CLASS_DESANN )
- {//longer saber
- ent->client->ps.saber[saberNum].blade[bladeNum].lengthMax = 48;
- }
- else if ( ent->client->NPC_class == CLASS_REBORN )
- {//shorter saber
- ent->client->ps.saber[saberNum].blade[bladeNum].lengthMax = 32;
- }
- else
- {//standard saber length
- ent->client->ps.saber[saberNum].blade[bladeNum].lengthMax = 40;
- }
- }
- }
- }
- ent->client->ps.saberLockEnemy = ENTITYNUM_NONE;
- ent->client->ps.saberLockTime = 0;
- if ( ent->s.number )
- {
- if ( !ent->client->ps.saberAnimLevel )
- {
- if ( ent->client->NPC_class == CLASS_DESANN )
- {
- ent->client->ps.saberAnimLevel = SS_DESANN;
- }
- else if ( ent->client->NPC_class == CLASS_TAVION )
- {
- ent->client->ps.saberAnimLevel = SS_TAVION;
- }
- else if ( ent->client->NPC_class == CLASS_ALORA )
- {
- ent->client->ps.saberAnimLevel = SS_DUAL;
- }
- //FIXME: CLASS_CULTIST instead of this Q_stricmpn?
- else if ( !Q_stricmpn( "cultist", ent->NPC_type, 7 ) )
- {//should already be set in the .npc file
- ent->client->ps.saberAnimLevel = Q_irand( SS_FAST, SS_STRONG );
- }
- else if ( ent->NPC && ent->client->playerTeam == TEAM_ENEMY && (ent->NPC->rank == RANK_CIVILIAN || ent->NPC->rank == RANK_LT_JG) )
- {//grunt and fencer always uses quick attacks
- ent->client->ps.saberAnimLevel = SS_FAST;
- }
- else if ( ent->NPC && ent->client->playerTeam == TEAM_ENEMY && (ent->NPC->rank == RANK_CREWMAN || ent->NPC->rank == RANK_ENSIGN) )
- {//acrobat & force-users always use medium attacks
- ent->client->ps.saberAnimLevel = SS_MEDIUM;
- }
- else if ( ent->client->playerTeam == TEAM_ENEMY && ent->client->NPC_class == CLASS_SHADOWTROOPER )
- {//shadowtroopers
- ent->client->ps.saberAnimLevel = Q_irand( SS_FAST, SS_STRONG );
- }
- else if ( ent->NPC && ent->client->playerTeam == TEAM_ENEMY && ent->NPC->rank == RANK_LT )
- {//boss always starts with strong attacks
- ent->client->ps.saberAnimLevel = SS_STRONG;
- }
- else if ( ent->client->NPC_class == CLASS_PLAYER )
- {
- ent->client->ps.saberAnimLevel = g_entities[0].client->ps.saberAnimLevel;
- }
- else
- {//?
- ent->client->ps.saberAnimLevel = Q_irand( SS_FAST, SS_STRONG );
- }
- }
- }
- else
- {
- if ( !ent->client->ps.saberAnimLevel )
- {//initialize, but don't reset
- if (ent->s.number < MAX_CLIENTS)
- {
- if (!ent->client->ps.saberStylesKnown)
- {
- ent->client->ps.saberStylesKnown = (1<<SS_MEDIUM);
- }
- if (ent->client->ps.saberStylesKnown & (1<<SS_FAST))
- {
- ent->client->ps.saberAnimLevel = SS_FAST;
- }
- else if (ent->client->ps.saberStylesKnown & (1<<SS_STRONG))
- {
- ent->client->ps.saberAnimLevel = SS_STRONG;
- }
- else
- {
- ent->client->ps.saberAnimLevel = SS_MEDIUM;
- }
- }
- else
- {
- ent->client->ps.saberAnimLevel = SS_MEDIUM;
- }
- }
- cg.saberAnimLevelPending = ent->client->ps.saberAnimLevel;
- if ( ent->client->sess.missionStats.weaponUsed[WP_SABER] <= 0 )
- {//let missionStats know that we actually do have the saber, even if we never use it
- ent->client->sess.missionStats.weaponUsed[WP_SABER] = 1;
- }
- }
- ent->client->ps.saberAttackChainCount = 0;
- if ( ent->client->ps.saberEntityNum <= 0 || ent->client->ps.saberEntityNum >= ENTITYNUM_WORLD )
- {//FIXME: if you do have a saber already, be sure to re-set the model if it's changed (say, via a script).
- gentity_t *saberent = G_Spawn();
- ent->client->ps.saberEntityNum = saberent->s.number;
- saberent->classname = "lightsaber";
-
- saberent->s.eType = ET_GENERAL;
- saberent->svFlags = SVF_USE_CURRENT_ORIGIN;
- saberent->s.weapon = WP_SABER;
- saberent->owner = ent;
- saberent->s.otherEntityNum = ent->s.number;
- //clear the enemy
- saberent->enemy = NULL;
- saberent->clipmask = MASK_SOLID | CONTENTS_LIGHTSABER;
- saberent->contents = CONTENTS_LIGHTSABER;//|CONTENTS_SHOTCLIP;
- VectorSet( saberent->mins, -3.0f, -3.0f, -3.0f );
- VectorSet( saberent->maxs, 3.0f, 3.0f, 3.0f );
- saberent->mass = 10;//necc?
- saberent->s.eFlags |= EF_NODRAW;
- saberent->svFlags |= SVF_NOCLIENT;
- /*
- Ghoul2 Insert Start
- */
- saberent->playerModel = -1;
- WP_SetSaberEntModelSkin( ent, saberent );
- // set up a bolt on the end so we can get where the sabre muzzle is - we can assume this is always bolt 0
- gi.G2API_AddBolt( &saberent->ghoul2[0], "*flash" );
- //gi.G2API_SetLodBias( &saberent->ghoul2[0], 0 );
- if ( ent->client->ps.dualSabers )
- {
- //int saber2 =
- G_ModelIndex( ent->client->ps.saber[1].model );
- //gi.G2API_InitGhoul2Model( saberent->ghoul2, ent->client->ps.saber[1].model, saber2 );
- // set up a bolt on the end so we can get where the sabre muzzle is - we can assume this is always bolt 0
- //gi.G2API_AddBolt( &saberent->ghoul2[0], "*flash" );
- //gi.G2API_SetLodBias( &saberent->ghoul2[0], 0 );
- }
-
- /*
- Ghoul2 Insert End
- */
- ent->client->ps.saberInFlight = qfalse;
- ent->client->ps.saberEntityDist = 0;
- ent->client->ps.saberEntityState = SES_LEAVING;
- ent->client->ps.saberMove = ent->client->ps.saberMoveNext = LS_NONE;
- //FIXME: need a think function to create alerts when turned on or is on, etc.
- }
- else
- {//already have one, might just be changing sabers, register the model and skin and use them if different from what we're using now.
- WP_SetSaberEntModelSkin( ent, &g_entities[ent->client->ps.saberEntityNum] );
- }
- }
- else
- {
- ent->client->ps.saberEntityNum = ENTITYNUM_NONE;
- ent->client->ps.saberInFlight = qfalse;
- ent->client->ps.saberEntityDist = 0;
- ent->client->ps.saberEntityState = SES_LEAVING;
- }
- if ( ent->client->ps.dualSabers )
- {
- return 2;
- }
-
- return 1;
- }
- void WP_SaberUpdateOldBladeData( gentity_t *ent )
- {
- if ( ent->client )
- {
- qboolean didEvent = qfalse;
- for ( int saberNum = 0; saberNum < 2; saberNum++ )
- {
- for ( int bladeNum = 0; bladeNum < ent->client->ps.saber[saberNum].numBlades; bladeNum++ )
- {
- VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePointOld );
- VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDirOld );
- if ( !didEvent )
- {
- if ( ent->client->ps.saber[saberNum].blade[bladeNum].lengthOld <= 0 && ent->client->ps.saber[saberNum].blade[bladeNum].length > 0 )
- {//just turned on
- //do sound event
- vec3_t saberOrg;
- VectorCopy( g_entities[ent->client->ps.saberEntityNum].currentOrigin, saberOrg );
- if ( (!ent->client->ps.saberInFlight && ent->client->ps.groundEntityNum == ENTITYNUM_WORLD)//holding saber and on ground
- || g_entities[ent->client->ps.saberEntityNum].s.pos.trType == TR_STATIONARY )//saber out there somewhere and on ground
- {//a ground alert
- AddSoundEvent( ent, saberOrg, 256, AEL_SUSPICIOUS, qfalse, qtrue );
- }
- else
- {//an in-air alert
- AddSoundEvent( ent, saberOrg, 256, AEL_SUSPICIOUS );
- }
- didEvent = qtrue;
- }
- }
- ent->client->ps.saber[saberNum].blade[bladeNum].lengthOld = ent->client->ps.saber[saberNum].blade[bladeNum].length;
- }
- }
- VectorCopy( ent->client->renderInfo.muzzlePoint, ent->client->renderInfo.muzzlePointOld );
- VectorCopy( ent->client->renderInfo.muzzleDir, ent->client->renderInfo.muzzleDirOld );
- }
- }
- //SABER DAMAGE==============================================================================
- //SABER DAMAGE==============================================================================
- //SABER DAMAGE==============================================================================
- //SABER DAMAGE==============================================================================
- //SABER DAMAGE==============================================================================
- //SABER DAMAGE==============================================================================
- int WPDEBUG_SaberColor( saber_colors_t saberColor )
- {
- switch( (int)(saberColor) )
- {
- case SABER_RED:
- return 0x000000ff;
- break;
- case SABER_ORANGE:
- return 0x000088ff;
- break;
- case SABER_YELLOW:
- return 0x0000ffff;
- break;
- case SABER_GREEN:
- return 0x0000ff00;
- break;
- case SABER_BLUE:
- return 0x00ff0000;
- break;
- case SABER_PURPLE:
- return 0x00ff00ff;
- break;
- default:
- return 0x00ffffff;//white
- break;
- }
- }
- qboolean WP_GetSaberDeflectionAngle( gentity_t *attacker, gentity_t *defender )
- {
- vec3_t temp, att_SaberBase, att_StartPos, saberMidNext, att_HitDir, att_HitPos, def_BladeDir;
- float att_SaberHitLength, hitDot;
- if ( !attacker || !attacker->client || attacker->client->ps.saberInFlight || attacker->client->ps.SaberLength() <= 0 )
- {
- return qfalse;
- }
- if ( !defender || !defender->client || defender->client->ps.saberInFlight || defender->client->ps.SaberLength() <= 0 )
- {
- return qfalse;
- }
- if ( PM_SuperBreakLoseAnim( attacker->client->ps.torsoAnim )
- || PM_SuperBreakWinAnim( attacker->client->ps.torsoAnim ) )
- {
- return qfalse;
- }
- attacker->client->ps.saberBounceMove = LS_NONE;
- //get the attacker's saber base pos at time of impact
- VectorSubtract( attacker->client->renderInfo.muzzlePoint, attacker->client->renderInfo.muzzlePointOld, temp );
- VectorMA( attacker->client->renderInfo.muzzlePointOld, saberHitFraction, temp, att_SaberBase );
- //get the position along the length of the blade where the hit occured
- att_SaberHitLength = Distance( saberHitLocation, att_SaberBase )/attacker->client->ps.SaberLength();
- //now get the start of that midpoint in the swing and the actual impact point in the swing (shouldn't the latter just be saberHitLocation?)
- VectorMA( attacker->client->renderInfo.muzzlePointOld, att_SaberHitLength, attacker->client->renderInfo.muzzleDirOld, att_StartPos );
- VectorMA( attacker->client->renderInfo.muzzlePoint, att_SaberHitLength, attacker->client->renderInfo.muzzleDir, saberMidNext );
- VectorSubtract( saberMidNext, att_StartPos, att_HitDir );
- VectorMA( att_StartPos, saberHitFraction, att_HitDir, att_HitPos );
- VectorNormalize( att_HitDir );
- //get the defender's saber dir at time of impact
- VectorSubtract( defender->client->renderInfo.muzzleDirOld, defender->client->renderInfo.muzzleDir, temp );
- VectorMA( defender->client->renderInfo.muzzleDirOld, saberHitFraction, temp, def_BladeDir );
- //now compare
- hitDot = DotProduct( att_HitDir, def_BladeDir );
- if ( hitDot < 0.25f && hitDot > -0.25f )
- {//hit pretty much perpendicular, pop straight back
- attacker->client->ps.saberBounceMove = PM_SaberBounceForAttack( attacker->client->ps.saberMove );
- return qfalse;
- }
- else
- {//a deflection
- vec3_t att_Right, att_Up, att_DeflectionDir;
- float swingRDot, swingUDot;
- //get the direction of the deflection
- VectorScale( def_BladeDir, hitDot, att_DeflectionDir );
- //get our bounce straight back direction
- VectorScale( att_HitDir, -1.0f, temp );
- //add the bounce back and deflection
- VectorAdd( att_DeflectionDir, temp, att_DeflectionDir );
- //normalize the result to determine what direction our saber should bounce back toward
- VectorNormalize( att_DeflectionDir );
- //need to know the direction of the deflectoin relative to the attacker's facing
- VectorSet( temp, 0, attacker->client->ps.viewangles[YAW], 0 );//presumes no pitch!
- AngleVectors( temp, NULL, att_Right, att_Up );
- swingRDot = DotProduct( att_Right, att_DeflectionDir );
- swingUDot = DotProduct( att_Up, att_DeflectionDir );
- if ( swingRDot > 0.25f )
- {//deflect to right
- if ( swingUDot > 0.25f )
- {//deflect to top
- attacker->client->ps.saberBounceMove = LS_D1_TR;
- }
- else if ( swingUDot < -0.25f )
- {//deflect to bottom
- attacker->client->ps.saberBounceMove = LS_D1_BR;
- }
- else
- {//deflect horizontally
- attacker->client->ps.saberBounceMove = LS_D1__R;
- }
- }
- else if ( swingRDot < -0.25f )
- {//deflect to left
- if ( swingUDot > 0.25f )
- {//deflect to top
- attacker->client->ps.saberBounceMove = LS_D1_TL;
- }
- else if ( swingUDot < -0.25f )
- {//deflect to bottom
- attacker->client->ps.saberBounceMove = LS_D1_BL;
- }
- else
- {//deflect horizontally
- attacker->client->ps.saberBounceMove = LS_D1__L;
- }
- }
- else
- {//deflect in middle
- if ( swingUDot > 0.25f )
- {//deflect to top
- attacker->client->ps.saberBounceMove = LS_D1_T_;
- }
- else if ( swingUDot < -0.25f )
- {//deflect to bottom
- attacker->client->ps.saberBounceMove = LS_D1_B_;
- }
- else
- {//deflect horizontally? Well, no such thing as straight back in my face, so use top
- if ( swingRDot > 0 )
- {
- attacker->client->ps.saberBounceMove = LS_D1_TR;
- }
- else if ( swingRDot < 0 )
- {
- attacker->client->ps.saberBounceMove = LS_D1_TL;
- }
- else
- {
- attacker->client->ps.saberBounceMove = LS_D1_T_;
- }
- }
- }
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer )
- {
- gi.Printf( S_COLOR_BLUE"%s deflected from %s to %s\n", attacker->targetname, saberMoveData[attacker->client->ps.saberMove].name, saberMoveData[attacker->client->ps.saberBounceMove].name );
- }
- #endif
- return qtrue;
- }
- }
- void WP_SaberClearDamageForEntNum( int entityNum )
- {
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer )
- {
- if ( entityNum )
- {
- Com_Printf( "clearing damage for entnum %d\n", entityNum );
- }
- }
- #endif// FINAL_BUILD
- if ( g_saberRealisticCombat->integer > 1 )
- {
- return;
- }
- for ( int i = 0; i < numVictims; i++ )
- {
- if ( victimEntityNum[i] == entityNum )
- {
- totalDmg[i] = 0;//no damage
- hitLoc[i] = HL_NONE;
- hitDismemberLoc[i] = HL_NONE;
- hitDismember[i] = qfalse;
- victimEntityNum[i] = ENTITYNUM_NONE;//like we never hit him
- }
- }
- }
- extern float damageModifier[];
- extern float hitLocHealthPercentage[];
- qboolean WP_SaberApplyDamage( gentity_t *ent, float baseDamage, int baseDFlags, qboolean brokenParry, saberType_t saberType, qboolean thrownSaber )
- {
- qboolean didDamage = qfalse;
- gentity_t *victim;
- int dFlags = baseDFlags;
- float maxDmg;
- if ( !numVictims )
- {
- return qfalse;
- }
- for ( int i = 0; i < numVictims; i++ )
- {
- dFlags = baseDFlags|DAMAGE_DEATH_KNOCKBACK|DAMAGE_NO_HIT_LOC;
- if ( victimEntityNum[i] != ENTITYNUM_NONE && &g_entities[victimEntityNum[i]] != NULL )
- { // Don't bother with this damage if the fraction is higher than the saber's fraction
- if ( dmgFraction[i] < saberHitFraction || brokenParry )
- {
- victim = &g_entities[victimEntityNum[i]];
- if ( !victim )
- {
- continue;
- }
-
- if ( victim->e_DieFunc == dieF_maglock_die )
- {//*sigh*, special check for maglocks
- vec3_t testFrom;
- if ( ent->client->ps.saberInFlight )
- {
- VectorCopy( g_entities[ent->client->ps.saberEntityNum].currentOrigin, testFrom );
- }
- else
- {
- VectorCopy( ent->currentOrigin, testFrom );
- }
- testFrom[2] = victim->currentOrigin[2];
- trace_t testTrace;
- gi.trace( &testTrace, testFrom, vec3_origin, vec3_origin, victim->currentOrigin, ent->s.number, MASK_SHOT );
- if ( testTrace.entityNum != victim->s.number )
- {//can only damage maglocks if have a clear trace to the thing's origin
- continue;
- }
- }
- if ( totalDmg[i] > 0 )
- {//actually want to do *some* damage here
- if ( victim->client
- && victim->client->NPC_class==CLASS_WAMPA
- && victim->activator == ent )
- {
- }
- else if ( PM_SuperBreakWinAnim( ent->client->ps.torsoAnim )
- || PM_StabDownAnim( ent->client->ps.torsoAnim ) )
- {//never cap the superbreak wins
- }
- else
- {
- if ( victim->client
- && (victim->s.weapon == WP_SABER || (victim->client->NPC_class==CLASS_REBORN) || (victim->client->NPC_class==CLASS_WAMPA))
- && !g_saberRealisticCombat->integer )
- {//dmg vs other saber fighters is modded by hitloc and capped
- totalDmg[i] *= damageModifier[hitLoc[i]];
- if ( hitLoc[i] == HL_NONE )
- {
- maxDmg = 33*baseDamage;
- }
- else
- {
- maxDmg = 50*hitLocHealthPercentage[hitLoc[i]]*baseDamage;//*victim->client->ps.stats[STAT_MAX_HEALTH]*2.0f;
- }
- if ( maxDmg < totalDmg[i] )
- {
- totalDmg[i] = maxDmg;
- }
- //dFlags |= DAMAGE_NO_HIT_LOC;
- }
- //clamp the dmg
- if ( victim->s.weapon != WP_SABER )
- {//clamp the dmg between 25 and maxhealth
- /*
- if ( totalDmg[i] > victim->max_health )
- {
- totalDmg[i] = victim->max_health;
- }
- else */if ( totalDmg[i] < 25 )
- {
- totalDmg[i] = 25;
- }
- if ( totalDmg[i] > 100 )//+(50*g_spskill->integer) )
- {//clamp using same adjustment as in NPC_Begin
- totalDmg[i] = 100;//+(50*g_spskill->integer);
- }
- }
- else
- {//clamp the dmg between 5 and 100
- if ( !victim->s.number && totalDmg[i] > 50 )
- {//never do more than half full health damage to player
- //prevents one-hit kills
- totalDmg[i] = 50;
- }
- else if ( totalDmg[i] > 100 )
- {
- totalDmg[i] = 100;
- }
- else
- {
- if ( totalDmg[i] < 5 )
- {
- totalDmg[i] = 5;
- }
- }
- }
- }
- if ( totalDmg[i] > 0 )
- {
- gentity_t *inflictor = ent;
- didDamage = qtrue;
- if ( baseDamage <= 0.1f )
- {//just get their attention?
- dFlags |= DAMAGE_NO_DAMAGE;
- }
- if( victim->client )
- {
- if ( victim->client->ps.pm_time > 0 && victim->client->ps.pm_flags & PMF_TIME_KNOCKBACK && victim->client->ps.velocity[2] > 0 )
- {//already being knocked around
- dFlags |= DAMAGE_NO_KNOCKBACK;
- }
- if ( g_dismemberment->integer >= 11381138 || g_saberRealisticCombat->integer )
- {
- dFlags |= DAMAGE_DISMEMBER;
- if ( hitDismember[i] )
- {
- victim->client->dismembered = false;
- }
- }
- else if ( hitDismember[i] )
- {
- dFlags |= DAMAGE_DISMEMBER;
- }
- if ( baseDamage <= 1.0f )
- {//very mild damage
- if ( victim->s.number == 0 || victim->client->ps.weapon == WP_SABER || victim->client->NPC_class == CLASS_GALAKMECH )
- {//if it's the player or a saber-user, don't kill them with this blow
- dFlags |= DAMAGE_NO_KILL;
- }
- }
- }
- else
- {
- if ( victim->takedamage )
- {//some other breakable thing
- //create a flash here
- g_saberFlashTime = level.time-50;
- VectorCopy( dmgSpot[i], g_saberFlashPos );
- }
- }
- if ( !PM_SuperBreakWinAnim( ent->client->ps.torsoAnim )
- && !PM_StabDownAnim( ent->client->ps.torsoAnim )
- && !g_saberRealisticCombat->integer
- && g_saberDamageCapping->integer )
- {//never cap the superbreak wins
- if ( victim->client
- && victim->s.number >= MAX_CLIENTS )
- {
- if ( victim->client->NPC_class == CLASS_SHADOWTROOPER
- || ( victim->NPC && (victim->NPC->aiFlags&NPCAI_BOSS_CHARACTER) ) )
- {//hit a boss character
- int maxDmg = ((3-g_spskill->integer)*5)+10;
- if ( totalDmg[i] > maxDmg )
- {
- totalDmg[i] = maxDmg;
- }
- }
- else if ( victim->client->ps.weapon == WP_SABER
- || victim->client->NPC_class == CLASS_REBORN
- || victim->client->NPC_class == CLASS_JEDI )
- {//hit a non-boss saber-user
- int maxDmg = ((3-g_spskill->integer)*15)+30;
- if ( totalDmg[i] > maxDmg )
- {
- totalDmg[i] = maxDmg;
- }
- }
- }
- if ( victim->s.number < MAX_CLIENTS
- && ent->NPC )
- {
- if ( (ent->NPC->aiFlags&NPCAI_BOSS_CHARACTER)
- || (ent->NPC->aiFlags&NPCAI_SUBBOSS_CHARACTER)
- || ent->client->NPC_class == CLASS_SHADOWTROOPER )
- {//player hit by a boss character
- int maxDmg = ((g_spskill->integer+1)*4)+3;
- if ( totalDmg[i] > maxDmg )
- {
- totalDmg[i] = maxDmg;
- }
- }
- else if ( g_spskill->integer < 3 ) //was < 2
- {//player hit by any enemy //on easy or medium?
- int maxDmg = ((g_spskill->integer+1)*10)+20;
- if ( totalDmg[i] > maxDmg )
- {
- totalDmg[i] = maxDmg;
- }
- }
- }
- }
- //victim->hitLoc = hitLoc[i];
- dFlags |= DAMAGE_NO_KNOCKBACK;//okay, let's try no knockback whatsoever...
- dFlags &= ~DAMAGE_DEATH_KNOCKBACK;
- if ( g_saberRealisticCombat->integer )
- {
- dFlags |= DAMAGE_NO_KNOCKBACK;
- dFlags &= ~DAMAGE_DEATH_KNOCKBACK;
- dFlags &= ~DAMAGE_NO_KILL;
- }
- if ( ent->client && !ent->s.number )
- {
- switch( hitLoc[i] )
- {
- case HL_FOOT_RT:
- case HL_FOOT_LT:
- case HL_LEG_RT:
- case HL_LEG_LT:
- ent->client->sess.missionStats.legAttacksCnt++;
- break;
- case HL_WAIST:
- case HL_BACK_RT:
- case HL_BACK_LT:
- case HL_BACK:
- case HL_CHEST_RT:
- case HL_CHEST_LT:
- case HL_CHEST:
- ent->client->sess.missionStats.torsoAttacksCnt++;
- break;
- case HL_ARM_RT:
- case HL_ARM_LT:
- case HL_HAND_RT:
- case HL_HAND_LT:
- ent->client->sess.missionStats.armAttacksCnt++;
- break;
- default:
- ent->client->sess.missionStats.otherAttacksCnt++;
- break;
- }
- }
- if ( saberType == SABER_SITH_SWORD )
- {//do knockback
- dFlags &= ~(DAMAGE_NO_KNOCKBACK|DAMAGE_DEATH_KNOCKBACK);
- }
- if ( thrownSaber )
- {
- inflictor = &g_entities[ent->client->ps.saberEntityNum];
- }
- G_Damage( victim, inflictor, ent, dmgDir[i], dmgSpot[i], ceil(totalDmg[i]), dFlags, MOD_SABER, hitDismemberLoc[i] );
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer )
- {
- if ( (dFlags&DAMAGE_NO_DAMAGE) )
- {
- gi.Printf( S_COLOR_RED"damage: fake, hitLoc %d\n", hitLoc[i] );
- }
- else
- {
- gi.Printf( S_COLOR_RED"damage: %4.2f, hitLoc %d\n", totalDmg[i], hitLoc[i] );
- }
- }
- #endif
- //do the effect
- //G_PlayEffect( G_EffectIndex( "blood_sparks" ), dmgSpot[i], dmgDir[i] );
- if ( ent->s.number == 0 )
- {
- AddSoundEvent( victim->owner, dmgSpot[i], 256, AEL_DISCOVERED );
- AddSightEvent( victim->owner, dmgSpot[i], 512, AEL_DISCOVERED, 50 );
- }
- if ( ent->client )
- {
- if ( ent->enemy && ent->enemy == victim )
- {//just so Jedi knows that he hit his enemy
- ent->client->ps.saberEventFlags |= SEF_HITENEMY;
- }
- else
- {
- ent->client->ps.saberEventFlags |= SEF_HITOBJECT;
- }
- }
- }
- }
- }
- }
- }
- return didDamage;
- }
- void WP_SaberDamageAdd( float trDmg, int trVictimEntityNum, vec3_t trDmgDir, vec3_t trDmgSpot, float dmg, float fraction, int trHitLoc, qboolean trDismember, int trDismemberLoc )
- {
- int curVictim = 0;
- if ( trVictimEntityNum < 0 || trVictimEntityNum >= ENTITYNUM_WORLD )
- {
- return;
- }
- if ( trDmg * dmg < 10.0f )
- {//too piddly an amount of damage to really count?
- //FIXME: but already did the effect, didn't we... sigh...
- //return;
- }
- if ( trDmg )
- {//did some damage to something
- for ( int i = 0; i < numVictims; i++ )
- {
- if ( victimEntityNum[i] == trVictimEntityNum )
- {//already hit this guy before
- curVictim = i;
- break;
- }
- }
- if ( i == numVictims )
- {//haven't hit his guy before
- if ( numVictims + 1 >= MAX_SABER_VICTIMS )
- {//can't add another victim at this time
- return;
- }
- //add a new victim to the list
- curVictim = numVictims;
- victimEntityNum[numVictims++] = trVictimEntityNum;
- }
- float addDmg = trDmg*dmg;
- if ( trHitLoc!=HL_NONE && (hitLoc[curVictim]==HL_NONE||hitLocHealthPercentage[trHitLoc]>hitLocHealthPercentage[hitLoc[curVictim]]) )
- {//this hitLoc is more critical than the previous one this frame
- hitLoc[curVictim] = trHitLoc;
- }
-
- totalDmg[curVictim] += addDmg;
- if ( !VectorLengthSquared( dmgDir[curVictim] ) )
- {
- VectorCopy( trDmgDir, dmgDir[curVictim] );
- }
- if ( !VectorLengthSquared( dmgSpot[curVictim] ) )
- {
- VectorCopy( trDmgSpot, dmgSpot[curVictim] );
- }
- // Make sure we keep track of the fraction. Why?
- // Well, if the saber hits something that stops it, the damage isn't done past that point.
- dmgFraction[curVictim] = fraction;
- if ( (trDismemberLoc != HL_NONE && hitDismemberLoc[curVictim] == HL_NONE)
- || (!hitDismember[curVictim] && trDismember) )
- {//either this is the first dismember loc we got or we got a loc before, but it wasn't a dismember loc, so take the new one
- hitDismemberLoc[curVictim] = trDismemberLoc;
- }
- if ( trDismember )
- {//we scored a dismemberment hit...
- hitDismember[curVictim] = trDismember;
- }
- }
- }
- /*
- WP_SabersIntersect
- Breaks the two saber paths into 2 tris each and tests each tri for the first saber path against each of the other saber path's tris
- FIXME: subdivide the arc into a consistant increment
- FIXME: test the intersection to see if the sabers really did intersect (weren't going in the same direction and/or passed through same point at different times)?
- */
- extern qboolean tri_tri_intersect(vec3_t V0,vec3_t V1,vec3_t V2,vec3_t U0,vec3_t U1,vec3_t U2);
- qboolean WP_SabersIntersect( gentity_t *ent1, int ent1SaberNum, int ent1BladeNum, gentity_t *ent2, qboolean checkDir )
- {
- vec3_t saberBase1, saberTip1, saberBaseNext1, saberTipNext1;
- vec3_t saberBase2, saberTip2, saberBaseNext2, saberTipNext2;
- int ent2SaberNum = 0, ent2BladeNum = 0;
- vec3_t dir;
- /*
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer )
- {
- gi.Printf( S_COLOR_GREEN"Doing precise saber intersection check\n" );
- }
- #endif
- */
- if ( !ent1 || !ent2 )
- {
- return qfalse;
- }
- if ( !ent1->client || !ent2->client )
- {
- return qfalse;
- }
- if ( ent1->client->ps.SaberLength() <= 0 || ent2->client->ps.SaberLength() <= 0 )
- {
- return qfalse;
- }
- for ( ent2SaberNum = 0; ent2SaberNum < MAX_SABERS; ent2SaberNum++ )
- {
- for ( ent2BladeNum = 0; ent2BladeNum < ent2->client->ps.saber[ent2SaberNum].numBlades; ent2BladeNum++ )
- {
- if ( ent2->client->ps.saber[ent2SaberNum].type != SABER_NONE
- && ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].length > 0 )
- {//valid saber and this blade is on
- //if ( ent1->client->ps.saberInFlight )
- {
- VectorCopy( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointOld, saberBase1 );
- VectorCopy( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, saberBaseNext1 );
- VectorSubtract( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointOld, dir );
- VectorNormalize( dir );
- VectorMA( saberBaseNext1, SABER_EXTRAPOLATE_DIST, dir, saberBaseNext1 );
- VectorMA( saberBase1, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].length, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzleDirOld, saberTip1 );
- VectorMA( saberBaseNext1, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].length, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, saberTipNext1 );
- VectorSubtract( saberTipNext1, saberTip1, dir );
- VectorNormalize( dir );
- VectorMA( saberTipNext1, SABER_EXTRAPOLATE_DIST, dir, saberTipNext1 );
- }
- /*
- else
- {
- VectorCopy( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, saberBase1 );
- VectorCopy( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointNext, saberBaseNext1 );
- VectorMA( saberBase1, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].length, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, saberTip1 );
- VectorMA( saberBaseNext1, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].length, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzleDirNext, saberTipNext1 );
- }
- */
- //if ( ent2->client->ps.saberInFlight )
- {
- VectorCopy( ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointOld, saberBase2 );
- VectorCopy( ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, saberBaseNext2 );
- VectorSubtract( ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointOld, dir );
- VectorNormalize( dir );
- VectorMA( saberBaseNext2, SABER_EXTRAPOLATE_DIST, dir, saberBaseNext2 );
- VectorMA( saberBase2, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].length, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzleDirOld, saberTip2 );
- VectorMA( saberBaseNext2, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].length, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir, saberTipNext2 );
- VectorSubtract( saberTipNext2, saberTip2, dir );
- VectorNormalize( dir );
- VectorMA( saberTipNext2, SABER_EXTRAPOLATE_DIST, dir, saberTipNext2 );
- }
- /*
- else
- {
- VectorCopy( ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, saberBase2 );
- VectorCopy( ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointNext, saberBaseNext2 );
- VectorMA( saberBase2, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].length, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir, saberTip2 );
- VectorMA( saberBaseNext2, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].length, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzleDirNext, saberTipNext2 );
- }
- */
- if ( checkDir )
- {//check the direction of the two swings to make sure the sabers are swinging towards each other
- vec3_t saberDir1, saberDir2;
- VectorSubtract( saberTipNext1, saberTip1, saberDir1 );
- VectorSubtract( saberTipNext2, saberTip2, saberDir2 );
- VectorNormalize( saberDir1 );
- VectorNormalize( saberDir2 );
- if ( DotProduct( saberDir1, saberDir2 ) > 0.6f )
- {//sabers moving in same dir, probably didn't actually hit
- continue;
- }
- //now check orientation of sabers, make sure they're not parallel or close to it
- float dot = DotProduct( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir );
- if ( dot > 0.9f || dot < -0.9f )
- {//too parallel to really block effectively?
- continue;
- }
- }
- if ( tri_tri_intersect( saberBase1, saberTip1, saberBaseNext1, saberBase2, saberTip2, saberBaseNext2 ) )
- {
- return qtrue;
- }
- if ( tri_tri_intersect( saberBase1, saberTip1, saberBaseNext1, saberBase2, saberTip2, saberTipNext2 ) )
- {
- return qtrue;
- }
- if ( tri_tri_intersect( saberBase1, saberTip1, saberTipNext1, saberBase2, saberTip2, saberBaseNext2 ) )
- {
- return qtrue;
- }
- if ( tri_tri_intersect( saberBase1, saberTip1, saberTipNext1, saberBase2, saberTip2, saberTipNext2 ) )
- {
- return qtrue;
- }
- }
- }
- }
- return qfalse;
- }
- extern float ShortestLineSegBewteen2LineSegs( vec3_t start1, vec3_t end1, vec3_t start2, vec3_t end2, vec3_t close_pnt1, vec3_t close_pnt2 );
- float WP_SabersDistance( gentity_t *ent1, gentity_t *ent2 )
- {
- vec3_t saberBaseNext1, saberTipNext1, saberPoint1;
- vec3_t saberBaseNext2, saberTipNext2, saberPoint2;
- /*
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer )
- {
- gi.Printf( S_COLOR_GREEN"Doing precise saber intersection check\n" );
- }
- #endif
- */
- if ( !ent1 || !ent2 )
- {
- return qfalse;
- }
- if ( !ent1->client || !ent2->client )
- {
- return qfalse;
- }
- if ( ent1->client->ps.SaberLength() <= 0 || ent2->client->ps.SaberLength() <= 0 )
- {
- return qfalse;
- }
- //FIXME: UGH, how do we make this work for multiply-bladed sabers?
- //if ( ent1->client->ps.saberInFlight )
- {
- VectorCopy( ent1->client->ps.saber[0].blade[0].muzzlePoint, saberBaseNext1 );
- VectorMA( saberBaseNext1, ent1->client->ps.saber[0].blade[0].length, ent1->client->ps.saber[0].blade[0].muzzleDir, saberTipNext1 );
- }
- /*
- else
- {
- VectorCopy( ent1->client->ps.saber[0].blade[0].muzzlePointNext, saberBaseNext1 );
- VectorMA( saberBaseNext1, ent1->client->ps.saber[0].blade[0].length, ent1->client->ps.saber[0].blade[0].muzzleDirNext, saberTipNext1 );
- }
- */
- //if ( ent2->client->ps.saberInFlight )
- {
- VectorCopy( ent2->client->ps.saber[0].blade[0].muzzlePoint, saberBaseNext2 );
- VectorMA( saberBaseNext2, ent2->client->ps.saber[0].blade[0].length, ent2->client->ps.saber[0].blade[0].muzzleDir, saberTipNext2 );
- }
- /*
- else
- {
- VectorCopy( ent2->client->ps.saber[0].blade[0].muzzlePointNext, saberBaseNext2 );
- VectorMA( saberBaseNext2, ent2->client->ps.saber[0].blade[0].length, ent2->client->ps.saber[0].blade[0].muzzleDirNext, saberTipNext2 );
- }
- */
- float sabersDist = ShortestLineSegBewteen2LineSegs( saberBaseNext1, saberTipNext1, saberBaseNext2, saberTipNext2, saberPoint1, saberPoint2 );
- //okay, this is a super hack, but makes saber collisions look better from the player point of view
- /*
- if ( sabersDist < 16.0f )
- {
- vec3_t saberDistDir, saberMidPoint, camLookDir;
- VectorSubtract( saberPoint2, saberPoint1, saberDistDir );
- VectorMA( saberPoint1, 0.5f, saberDistDir, saberMidPoint );
- VectorSubtract( saberMidPoint, cg.refdef.vieworg, camLookDir );
- VectorNormalize( saberDistDir );
- VectorNormalize( camLookDir );
- float dot = fabs(DotProduct( camLookDir, saberDistDir ));
- sabersDist -= 8.0f*dot;
- if ( sabersDist < 0.0f )
- {
- sabersDist = 0.0f;
- }
- }
- */
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer > 2 )
- {
- G_DebugLine( saberPoint1, saberPoint2, FRAMETIME, 0x00ffffff, qtrue );
- }
- #endif
- return sabersDist;
- }
- qboolean WP_SabersIntersection( gentity_t *ent1, gentity_t *ent2, vec3_t intersect )
- {
- vec3_t saberBaseNext1, saberTipNext1, saberPoint1;
- vec3_t saberBaseNext2, saberTipNext2, saberPoint2;
- int saberNum1, saberNum2, bladeNum1, bladeNum2;
- float lineSegLength, bestLineSegLength = Q3_INFINITE;
- if ( !ent1 || !ent2 )
- {
- return qfalse;
- }
- if ( !ent1->client || !ent2->client )
- {
- return qfalse;
- }
- if ( ent1->client->ps.SaberLength() <= 0 || ent2->client->ps.SaberLength() <= 0 )
- {
- return qfalse;
- }
- //UGH, had to make this work for multiply-bladed sabers
- for ( saberNum1 = 0; saberNum1 < MAX_SABERS; saberNum1++ )
- {
- for ( bladeNum1 = 0; bladeNum1 < ent1->client->ps.saber[saberNum1].numBlades; bladeNum1++ )
- {
- if ( ent1->client->ps.saber[saberNum1].type != SABER_NONE
- && ent1->client->ps.saber[saberNum1].blade[bladeNum1].length > 0 )
- {//valid saber and this blade is on
- for ( saberNum2 = 0; saberNum2 < MAX_SABERS; saberNum2++ )
- {
- for ( bladeNum2 = 0; bladeNum2 < ent2->client->ps.saber[saberNum2].numBlades; bladeNum2++ )
- {
- if ( ent2->client->ps.saber[saberNum2].type != SABER_NONE
- && ent2->client->ps.saber[saberNum2].blade[bladeNum2].length > 0 )
- {//valid saber and this blade is on
- VectorCopy( ent1->client->ps.saber[saberNum1].blade[bladeNum1].muzzlePoint, saberBaseNext1 );
- VectorMA( saberBaseNext1, ent1->client->ps.saber[saberNum1].blade[bladeNum1].length, ent1->client->ps.saber[saberNum1].blade[bladeNum1].muzzleDir, saberTipNext1 );
- VectorCopy( ent2->client->ps.saber[saberNum2].blade[bladeNum2].muzzlePoint, saberBaseNext2 );
- VectorMA( saberBaseNext2, ent2->client->ps.saber[saberNum2].blade[bladeNum2].length, ent2->client->ps.saber[saberNum2].blade[bladeNum2].muzzleDir, saberTipNext2 );
- lineSegLength = ShortestLineSegBewteen2LineSegs( saberBaseNext1, saberTipNext1, saberBaseNext2, saberTipNext2, saberPoint1, saberPoint2 );
- if ( lineSegLength < bestLineSegLength )
- {
- bestLineSegLength = lineSegLength;
- VectorAdd( saberPoint1, saberPoint2, intersect );
- VectorScale( intersect, 0.5, intersect );
- }
- }
- }
- }
- }
- }
- }
- if(ent1->client && ent1->client->ps.clientNum == 0)
- FF_Play(fffx_Laser1);
- return qtrue;
- }
- const char *hit_blood_sparks = "sparks/blood_sparks2"; // could have changed this effect directly, but this is just safer in case anyone anywhere else is using the old one for something?
- const char *hit_sparks = "saber/saber_cut";
- //extern char *hitLocName[];
- qboolean WP_SaberDamageEffects( trace_t *tr, const vec3_t start, float length, float dmg, vec3_t dmgDir, vec3_t bladeDir, int enemyTeam, saberType_t saberType )
- {
- int hitEntNum[MAX_G2_COLLISIONS];
- for ( int hen = 0; hen < MAX_G2_COLLISIONS; hen++ )
- {
- hitEntNum[hen] = ENTITYNUM_NONE;
- }
- //NOTE: = {0} does NOT work on anything but bytes?
- float hitEntDmgAdd[MAX_G2_COLLISIONS] = {0};
- float hitEntDmgSub[MAX_G2_COLLISIONS] = {0};
- vec3_t hitEntPoint[MAX_G2_COLLISIONS];
- vec3_t hitEntDir[MAX_G2_COLLISIONS];
- float hitEntStartFrac[MAX_G2_COLLISIONS] = {0};
- int trHitLoc[MAX_G2_COLLISIONS] = {HL_NONE};//same as 0
- int trDismemberLoc[MAX_G2_COLLISIONS] = {HL_NONE};//same as 0
- qboolean trDismember[MAX_G2_COLLISIONS] = {qfalse};//same as 0
- int i,z;
- int numHitEnts = 0;
- float distFromStart,doDmg;
- const char *hitEffect, *trSurfName;
- gentity_t *hitEnt;
-
- for (z=0; z < MAX_G2_COLLISIONS; z++)
- {
- if ( tr->G2CollisionMap[z].mEntityNum == -1 )
- {//actually, completely break out of this for loop since nothing after this in the aray should ever be valid either
- continue;//break;//
- }
- CCollisionRecord &coll = tr->G2CollisionMap[z];
- //distFromStart = Distance( start, coll.mCollisionPosition );
- distFromStart = coll.mDistance;
- /*
- //FIXME: (distFromStart/length) is not guaranteed to be from 0 to 1... *sigh*...
- if ( length && saberHitFraction < 1.0f && (distFromStart/length) < 1.0f && (distFromStart/length) > saberHitFraction )
- {//a saber was hit before this point, don't count it
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer )
- {
- gi.Printf( S_COLOR_MAGENTA"rejecting G2 collision- %4.2f farther than saberHitFraction %4.2f\n", (distFromStart/length), saberHitFraction );
- }
- #endif
- continue;
- }
- */
- for ( i = 0; i < numHitEnts; i++ )
- {
- if ( hitEntNum[i] == coll.mEntityNum )
- {//we hit this ent before
- //we'll want to add this dist
- hitEntDmgAdd[i] = distFromStart;
- break;
- }
- }
- if ( i == numHitEnts )
- {//first time we hit this ent
- if ( numHitEnts == MAX_G2_COLLISIONS )
- {//hit too many damn ents!
- continue;
- }
- hitEntNum[numHitEnts] = coll.mEntityNum;
- if ( !coll.mFlags )
- {//hmm, we came out first, so we must have started inside
- //we'll want to subtract this dist
- hitEntDmgAdd[numHitEnts] = distFromStart;
- }
- else
- {//we're entering the model
- //we'll want to subtract this dist
- hitEntDmgSub[numHitEnts] = distFromStart;
- }
- //keep track of how far in the damage was done
- hitEntStartFrac[numHitEnts] = hitEntDmgSub[numHitEnts]/length;
- //remember the entrance point
- VectorCopy( coll.mCollisionPosition, hitEntPoint[numHitEnts] );
- //remember the entrance dir
- VectorCopy( coll.mCollisionNormal, hitEntDir[numHitEnts] );
- VectorNormalize( hitEntDir[numHitEnts] );
-
- //do the effect
- //FIXME: check material rather than team?
- hitEnt = &g_entities[hitEntNum[numHitEnts]];
- hitEffect = hit_blood_sparks;
- if ( hitEnt != NULL )
- {
- if ( hitEnt->client )
- {
- class_t npc_class = hitEnt->client->NPC_class;
- if ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE || npc_class == CLASS_REMOTE ||
- npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 ||
- npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 || npc_class == CLASS_MARK2 ||
- npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST || npc_class == CLASS_SENTRY )
- {
- hitEffect = hit_sparks;
- }
- }
- else
- {
- // So sue me, this is the easiest way to check to see if this is the turbo laser from t2_wedge,
- // in which case I don't want the saber effects goin off on it.
- if ( (hitEnt->flags&FL_DMG_BY_HEAVY_WEAP_ONLY)
- && hitEnt->takedamage == qfalse
- && Q_stricmp( hitEnt->classname, "misc_turret" ) == 0 )
- {
- continue;
- }
- else
- {
- if ( dmg )
- {//only do these effects if actually trying to damage the thing...
- if ( (hitEnt->svFlags&SVF_BBRUSH)//a breakable brush
- && ( (hitEnt->spawnflags&1)//INVINCIBLE
- ||(hitEnt->flags&FL_DMG_BY_HEAVY_WEAP_ONLY) )//HEAVY weapon damage only
- )
- {//no hit effect (besides regular client-side one)
- hitEffect = NULL;
- }
- else
- {
- hitEffect = hit_sparks;
- }
- }
- }
- }
- }
- //FIXME: play less if damage is less?
- if ( !g_saberNoEffects )
- {
- if ( hitEffect != NULL )
- {
- G_PlayEffect( hitEffect, coll.mCollisionPosition, coll.mCollisionNormal );
- // if(ent->client && ent->client->ps.clientNum == 0)
- // FF_Play(fffx_Laser1);
- }
- /*
- if ( hitEnt && hitEnt->client )
- {
- CG_AddGhoul2Mark( PGORE_DECAL02, Q_flrand(3.5, 4.0), coll.mCollisionPosition, hitEntDir[numHitEnts], hitEnt->s.number,
- hitEnt->client->ps.origin, hitEnt->client->renderInfo.legsYaw, hitEnt->ghoul2 );
- CG_AddGhoul2Mark( PGORE_DECAL03, Q_flrand(3.5, 4.0), coll.mCollisionPosition, hitEntDir[numHitEnts], hitEnt->s.number,
- hitEnt->client->ps.origin, hitEnt->client->renderInfo.legsYaw, hitEnt->ghoul2 );
- }
- */
- }
- //Get the hit location based on surface name
- if ( (hitLoc[hitEntNum[numHitEnts]] == HL_NONE && trHitLoc[numHitEnts] == HL_NONE)
- || (hitDismemberLoc[hitEntNum[numHitEnts]] == HL_NONE && trDismemberLoc[numHitEnts] == HL_NONE)
- || (!hitDismember[hitEntNum[numHitEnts]] && !trDismember[numHitEnts]) )
- {//no hit loc set for this ent this damage cycle yet
- //FIXME: find closest impact surf *first* (per ent), then call G_GetHitLocFromSurfName?
- //FIXED: if hit multiple ents in this collision record, these trSurfName, trDismember and trDismemberLoc will get stomped/confused over the multiple ents I hit
- trSurfName = gi.G2API_GetSurfaceName( &g_entities[coll.mEntityNum].ghoul2[coll.mModelIndex], coll.mSurfaceIndex );
- trDismember[numHitEnts] = G_GetHitLocFromSurfName( &g_entities[coll.mEntityNum], trSurfName, &trHitLoc[numHitEnts], coll.mCollisionPosition, dmgDir, bladeDir, MOD_SABER, saberType );
- if ( trDismember[numHitEnts] )
- {
- trDismemberLoc[numHitEnts] = trHitLoc[numHitEnts];
- }
- /*
- if ( trDismember[numHitEnts] )
- {
- Com_Printf( S_COLOR_RED"Okay to dismember %s on ent %d\n", hitLocName[trDismemberLoc[numHitEnts]], hitEntNum[numHitEnts] );
- }
- else
- {
- Com_Printf( "Hit (no dismember) %s on ent %d\n", hitLocName[trHitLoc[numHitEnts]], hitEntNum[numHitEnts] );
- }
- */
- }
- numHitEnts++;
- }
- }
- //now go through all the ents we hit and do the damage
- for ( i = 0; i < numHitEnts; i++ )
- {
- doDmg = dmg;
- if ( hitEntNum[i] != ENTITYNUM_NONE )
- {
- if ( doDmg < 10 )
- {//base damage is less than 10
- if ( hitEntNum[i] != 0 )
- {//not the player
- hitEnt = &g_entities[hitEntNum[i]];
- if ( !hitEnt->client || (hitEnt->client->ps.weapon!=WP_SABER&&hitEnt->client->NPC_class!=CLASS_GALAKMECH&&hitEnt->client->playerTeam==enemyTeam) )
- {//did *not* hit a jedi and did *not* hit the player
- //make sure the base damage is high against non-jedi, feels better
- doDmg = 10;
- }
- }
- }
- if ( !hitEntDmgAdd[i] && !hitEntDmgSub[i] )
- {//spent entire time in model
- //NOTE: will we even get a collision then?
- doDmg *= length;
- }
- else if ( hitEntDmgAdd[i] && hitEntDmgSub[i] )
- {//we did enter and exit
- doDmg *= hitEntDmgAdd[i] - hitEntDmgSub[i];
- }
- else if ( !hitEntDmgAdd[i] )
- {//we didn't exit, just entered
- doDmg *= length - hitEntDmgSub[i];
- }
- else if ( !hitEntDmgSub[i] )
- {//we didn't enter, only exited
- doDmg *= hitEntDmgAdd[i];
- }
- if ( doDmg > 0 )
- {
- WP_SaberDamageAdd( 1.0, hitEntNum[i], hitEntDir[i], hitEntPoint[i], ceil(doDmg), hitEntStartFrac[i], trHitLoc[i], trDismember[i], trDismemberLoc[i] );
- }
- }
- }
- return (numHitEnts>0);
- }
- void WP_SaberKnockaway( gentity_t *attacker, trace_t *tr )
- {
- WP_SaberDrop( attacker, &g_entities[attacker->client->ps.saberEntityNum] );
- G_Sound( &g_entities[attacker->client->ps.saberEntityNum], G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
- G_PlayEffect( "saber/saber_block", tr->endpos );
-
- saberHitFraction = tr->fraction;
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer )
- {
- gi.Printf( S_COLOR_MAGENTA"WP_SaberKnockaway: saberHitFraction %4.2f\n", saberHitFraction );
- }
- #endif
- VectorCopy( tr->endpos, saberHitLocation );
- saberHitEntity = tr->entityNum;
- g_saberFlashTime = level.time-50;
- VectorCopy( saberHitLocation, g_saberFlashPos );
- //FIXME: make hitEnt play an attack anim or some other special anim when this happens
- //gentity_t *hitEnt = &g_entities[tr->entityNum];
- //NPC_SetAnim( hitEnt, SETANIM_BOTH, BOTH_KNOCKSABER, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- qboolean G_InCinematicSaberAnim( gentity_t *self )
- {
- if ( self->NPC
- && self->NPC->behaviorState == BS_CINEMATIC
- && (self->client->ps.torsoAnim == BOTH_CIN_16 ||self->client->ps.torsoAnim == BOTH_CIN_17) )
- {
- return qtrue;
- }
- return qfalse;
- }
- #define SABER_COLLISION_DIST 6//was 2//was 4//was 8//was 16
- extern qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold = 0.0f );
- qboolean WP_SaberDamageForTrace( int ignore, vec3_t start, vec3_t end, float dmg,
- vec3_t bladeDir, qboolean noGhoul, int attackStrength,
- saberType_t saberType, qboolean extrapolate,
- int saberNum, int bladeNum )
- {
- trace_t tr;
- vec3_t dir;
- int mask = (MASK_SHOT|CONTENTS_LIGHTSABER);
- gentity_t *attacker = &g_entities[ignore];
- vec3_t end2;
- VectorCopy( end, end2 );
- if ( extrapolate )
- {
- //NOTE: since we can no longer use the predicted point, extrapolate the trace some.
- // this may allow saber hits that aren't actually hits, but it doesn't look too bad
- vec3_t diff;
- VectorSubtract( end, start, diff );
- VectorNormalize( diff );
- VectorMA( end2, SABER_EXTRAPOLATE_DIST, diff, end2 );
- }
- if ( !noGhoul )
- {
- if ( !attacker->s.number
- || (attacker->client
- && (attacker->client->playerTeam==TEAM_PLAYER
- || attacker->client->NPC_class==CLASS_SHADOWTROOPER
- || attacker->client->NPC_class==CLASS_ALORA
- || (attacker->NPC && (attacker->NPC->aiFlags&NPCAI_BOSS_CHARACTER))
- )
- )
- )//&&attackStrength==FORCE_LEVEL_3)
- {//player,. player allies, shadowtroopers, tavion and desann use larger traces
- vec3_t traceMins = {-2,-2,-2}, traceMaxs = {2,2,2};
- gi.trace( &tr, start, traceMins, traceMaxs, end2, ignore, mask, G2_COLLIDE, 10 );//G2_SUPERSIZEDBBOX
- }
- /*
- else if ( !attacker->s.number )
- {
- vec3_t traceMins = {-1,-1,-1}, traceMaxs = {1,1,1};
- gi.trace( &tr, start, traceMins, traceMaxs, end2, ignore, mask, G2_COLLIDE, 10 );//G2_SUPERSIZEDBBOX
- }
- */
- else
- {//reborn use smaller traces
- gi.trace( &tr, start, NULL, NULL, end2, ignore, mask, G2_COLLIDE, 10 );//G2_SUPERSIZEDBBOX
- }
- }
- else
- {
- gi.trace( &tr, start, NULL, NULL, end2, ignore, mask, G2_NOCOLLIDE, 10 );
- }
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer > 1 )
- {
- if ( attacker != NULL && attacker->client != NULL )
- {
- G_DebugLine(start, end2, FRAMETIME, WPDEBUG_SaberColor( attacker->client->ps.saber[0].blade[0].color ), qtrue);
- }
- }
- #endif
- if ( tr.entityNum == ENTITYNUM_NONE )
- {
- return qfalse;
- }
- if ( tr.entityNum == ENTITYNUM_WORLD )
- {
- return qtrue;
- }
- if ( &g_entities[tr.entityNum] )
- {
- gentity_t *hitEnt = &g_entities[tr.entityNum];
- gentity_t *owner = g_entities[tr.entityNum].owner;
- if ( hitEnt->contents & CONTENTS_LIGHTSABER )
- {
- if ( attacker && attacker->client && attacker->client->ps.saberInFlight )
- {//thrown saber hit something
- if ( owner
- && owner->s.number
- && owner->client
- && owner->NPC
- && owner->health > 0 )
- {
- if ( owner->client->NPC_class == CLASS_ALORA )
- {//alora takes less damage
- dmg *= 0.25f;
- }
- else if ( owner->client->NPC_class == CLASS_TAVION
- /*|| (owner->client->NPC_class == CLASS_SHADOWTROOPER && !Q_irand( 0, g_spskill->integer*3 ))
- || (Q_irand( -5, owner->NPC->rank ) > RANK_CIVILIAN && !Q_irand( 0, g_spskill->integer*3 ))*/ )
- {//Tavion can toss a blocked thrown saber aside
- WP_SaberKnockaway( attacker, &tr );
- Jedi_PlayDeflectSound( owner );
- return qfalse;
- }
- }
- }
- //FIXME: take target FP_SABER_DEFENSE and attacker FP_SABER_OFFENSE into account here somehow?
- qboolean sabersIntersect = WP_SabersIntersect( attacker, saberNum, bladeNum, owner, qfalse );//qtrue );
- float sabersDist;
- if ( attacker && attacker->client && attacker->client->ps.saberInFlight
- && owner && owner->s.number == 0 && (g_saberAutoBlocking->integer||attacker->client->ps.saberBlockingTime>level.time) )//NPC flying saber hit player's saber bounding box
- {//players have g_saberAutoBlocking, do the more generous check against flying sabers
- //FIXME: instead of hitting the player's saber bounding box
- //and picking an anim afterwards, have him use AI similar
- //to the AI the jedi use for picking a saber melee block...?
- sabersDist = 0;
- }
- else
- {//sabers must actually collide with the attacking saber
- sabersDist = WP_SabersDistance( attacker, owner );
- if ( attacker && attacker->client && attacker->client->ps.saberInFlight )
- {
- sabersDist /= 2.0f;
- if ( sabersDist <= 16.0f )
- {
- sabersIntersect = qtrue;
- }
- }
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer > 1 )
- {
- gi.Printf( "sabersDist: %4.2f\n", sabersDist );
- }
- #endif//FINAL_BUILD
- }
- if ( sabersCrossed == -1 || sabersCrossed > sabersDist )
- {
- sabersCrossed = sabersDist;
- }
- float collisionDist;
- if ( g_saberRealisticCombat->integer )
- {
- collisionDist = SABER_COLLISION_DIST;
- }
- else
- {
- collisionDist = SABER_COLLISION_DIST+6+g_spskill->integer*4;
- }
- {
- if ( G_InCinematicSaberAnim( owner )
- && G_InCinematicSaberAnim( attacker ) )
- {
- sabersIntersect = qtrue;
- }
- }
- if ( owner && owner->client && (attacker != NULL)
- && (sabersDist > collisionDist )//|| !InFront( attacker->currentOrigin, owner->currentOrigin, owner->client->ps.viewangles, 0.35f ))
- && !sabersIntersect )//was qtrue, but missed too much?
- {//swing came from behind and/or was not stopped by a lightsaber
- //re-try the trace without checking for lightsabers
- gi.trace ( &tr, start, NULL, NULL, end2, ignore, mask&~CONTENTS_LIGHTSABER, G2_NOCOLLIDE, 10 );
- if ( tr.entityNum == ENTITYNUM_WORLD )
- {
- return qtrue;
- }
- if ( tr.entityNum == ENTITYNUM_NONE || &g_entities[tr.entityNum] == NULL )
- {//didn't hit the owner
- /*
- if ( attacker
- && attacker->client
- && (PM_SaberInAttack( attacker->client->ps.saberMove ) || PM_SaberInStart( attacker->client->ps.saberMove ))
- && DistanceSquared( tr.endpos, owner->currentOrigin ) < 10000 )
- {
- if ( owner->NPC
- && !owner->client->ps.saberInFlight
- && owner->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN
- && !Jedi_SaberBusy( owner ) )
- {//owner parried, just make sure they're saber is in the right spot - only does this if they're not already doing something with saber
- if ( g_spskill->integer && (g_spskill->integer > 1 || Q_irand( 0, 1 )))
- {//if on easy, they don't cheat like this, if on medium, they cheat 50% of the time, if on hard, they always cheat
- //FIXME: also take into account the owner's FP_DEFENSE?
- if ( Q_irand( 0, owner->NPC->rank ) >= RANK_CIVILIAN )
- {//lower-rank Jedi aren't as good blockers
- vec3_t attDir;
- VectorSubtract( end2, start, attDir );
- VectorNormalize( attDir );
- Jedi_SaberBlockGo( owner, owner->NPC->last_ucmd, start, attDir, NULL );
- }
- }
- }
- }
- */
- return qfalse; // Exit, but we didn't hit the wall.
- }
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer > 1 )
- {
- if ( !attacker->s.number )
- {
- gi.Printf( S_COLOR_MAGENTA"%d saber hit owner through saber %4.2f, dist = %4.2f\n", level.time, saberHitFraction, sabersDist );
- }
- }
- #endif//FINAL_BUILD
- hitEnt = &g_entities[tr.entityNum];
- owner = g_entities[tr.entityNum].owner;
- }
- else
- {//hit a lightsaber
- if ( (tr.fraction < saberHitFraction || tr.startsolid)
- && sabersDist < (8.0f+g_spskill->value)*4.0f// 50.0f//16.0f
- && (sabersIntersect || sabersDist < (4.0f+g_spskill->value)*2.0f) )//32.0f) )
- { // This saber hit closer than the last one.
- if ( (tr.allsolid || tr.startsolid) && owner && owner->client )
- {//tr.fraction will be 0, unreliable... so calculate actual
- float dist = Distance( start, end2 );
- if ( dist )
- {
- float hitFrac = WP_SabersDistance( attacker, owner )/dist;
- if ( hitFrac > 1.0f )
- {//umm... minimum distance between sabers was longer than trace...?
- hitFrac = 1.0f;
- }
- if ( hitFrac < saberHitFraction )
- {
- saberHitFraction = hitFrac;
- }
- }
- else
- {
- saberHitFraction = 0.0f;
- }
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer > 1 )
- {
- if ( !attacker->s.number )
- {
- gi.Printf( S_COLOR_GREEN"%d saber hit saber dist %4.2f allsolid %4.2f\n", level.time, sabersDist, saberHitFraction );
- }
- }
- #endif//FINAL_BUILD
- }
- else
- {
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer > 1 )
- {
- if ( !attacker->s.number )
- {
- gi.Printf( S_COLOR_BLUE"%d saber hit saber dist %4.2f, frac %4.2f\n", level.time, sabersDist, saberHitFraction );
- }
- saberHitFraction = tr.fraction;
- }
- #endif//FINAL_BUILD
- }
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer )
- {
- gi.Printf( S_COLOR_MAGENTA"hit saber: saberHitFraction %4.2f, allsolid %d, startsolid %d\n", saberHitFraction, tr.allsolid, tr.startsolid );
- }
- #endif//FINAL_BUILD
- VectorCopy(tr.endpos, saberHitLocation);
- saberHitEntity = tr.entityNum;
- }
- /*
- if ( owner
- && owner->client
- && attacker
- && attacker->client
- && (PM_SaberInAttack( attacker->client->ps.saberMove ) || PM_SaberInStart( attacker->client->ps.saberMove ))
- && DistanceSquared( tr.endpos, owner->currentOrigin ) < 10000 )
- {
- if ( owner->NPC
- && !owner->client->ps.saberInFlight
- && owner->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN
- && !Jedi_SaberBusy( owner ) )
- {//owner parried, just make sure they're saber is in the right spot - only does this if they're not already doing something with saber
- if ( g_spskill->integer && (g_spskill->integer > 1 || Q_irand( 0, 1 )))
- {//if on easy, they don't cheat like this, if on medium, they cheat 50% of the time, if on hard, they always cheat
- //FIXME: also take into account the owner's FP_DEFENSE?
- if ( Q_irand( 0, owner->NPC->rank ) >= RANK_CIVILIAN )
- {//lower-rank Jedi aren't as good blockers
- vec3_t attDir;
- VectorSubtract( end2, start, attDir );
- VectorNormalize( attDir );
- Jedi_SaberBlockGo( owner, owner->NPC->last_ucmd, start, attDir, NULL );
- }
- }
- }
- }
- */
- //FIXME: check to see if we broke the saber
- // go through the impacted surfaces and call WP_BreakSaber
- // PROBLEM: saberEnt doesn't actually have a saber g2 model
- // and/or isn't in same location as saber model attached
- // to the client. We'd have to fake it somehow...
- return qfalse; // Exit, but we didn't hit the wall.
- }
- }
- else
- {
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer > 1 )
- {
- if ( !attacker->s.number )
- {
- gi.Printf( S_COLOR_RED"%d saber hit owner directly %4.2f\n", level.time, saberHitFraction );
- }
- }
- #endif//FINAL_BUILD
- }
- if ( attacker && attacker->client && attacker->client->ps.saberInFlight )
- {//thrown saber hit something
- if ( ( hitEnt && hitEnt->client && hitEnt->health > 0 && ( hitEnt->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",hitEnt->NPC_type) || hitEnt->client->NPC_class == CLASS_LUKE || hitEnt->client->NPC_class == CLASS_BOBAFETT || (hitEnt->client->ps.powerups[PW_GALAK_SHIELD] > 0) ) ) ||
- ( owner && owner->client && owner->health > 0 && ( owner->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",owner->NPC_type) || owner->client->NPC_class == CLASS_LUKE || owner->client->NPC_class == CLASS_BOBAFETT || (owner->client->ps.powerups[PW_GALAK_SHIELD] > 0) ) ) )
- {//Luke and Desann slap thrown sabers aside
- //FIXME: control the direction of the thrown saber... if hit Galak's shield, bounce directly away from his origin?
- WP_SaberKnockaway( attacker, &tr );
- if ( hitEnt->client )
- {
- Jedi_PlayDeflectSound( hitEnt );
- }
- else
- {
- Jedi_PlayDeflectSound( owner );
- }
- return qfalse; // Exit, but we didn't hit the wall.
- }
- }
- if ( hitEnt->takedamage )
- {
- //no team damage: if ( !hitEnt->client || attacker == NULL || !attacker->client || (hitEnt->client->playerTeam != attacker->client->playerTeam) )
- {
- //multiply the damage by the total distance of the swipe
- VectorSubtract( end2, start, dir );
- float len = VectorNormalize( dir );//VectorLength( dir );
- if ( noGhoul || !hitEnt->ghoul2.size() )
- {//we weren't doing a ghoul trace
- const char *hitEffect = NULL;
- if ( dmg >= 1.0 && hitEnt->bmodel )
- {
- dmg = 1.0;
- }
- if ( len > 1 )
- {
- dmg *= len;
- }
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer > 1 )
- {
- if ( !(hitEnt->contents & CONTENTS_LIGHTSABER) )
- {
- gi.Printf( S_COLOR_GREEN"Hit ent, but no ghoul collisions\n" );
- }
- }
- #endif
- float trFrac, dmgFrac;
- if ( tr.allsolid )
- {//totally inside them
- trFrac = 1.0;
- dmgFrac = 0.0;
- }
- else if ( tr.startsolid )
- {//started inside them
- //we don't know how much was inside, we know it's less than all, so use half?
- trFrac = 0.5;
- dmgFrac = 0.0;
- }
- else
- {//started outside them and hit them
- //yeah. this doesn't account for coming out the other wide, but we can worry about that later (use ghoul2)
- trFrac = (1.0f - tr.fraction);
- dmgFrac = tr.fraction;
- }
- WP_SaberDamageAdd( trFrac, tr.entityNum, dir, tr.endpos, dmg, dmgFrac, HL_NONE, qfalse, HL_NONE );
- if ( !tr.allsolid && !tr.startsolid )
- {
- VectorScale( dir, -1, dir );
- }
- if ( hitEnt != NULL )
- {
- if ( hitEnt->client )
- {
- //don't do blood sparks on non-living things
- class_t npc_class = hitEnt->client->NPC_class;
- if ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE ||
- npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 || npc_class == CLASS_REMOTE ||
- npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 || npc_class == CLASS_MARK2 ||
- npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST || npc_class == CLASS_SENTRY )
- {
- hitEffect = hit_sparks;
- }
- }
- else
- {
- if ( dmg )
- {//only do these effects if actually trying to damage the thing...
- if ( (hitEnt->svFlags&SVF_BBRUSH)//a breakable brush
- && ( (hitEnt->spawnflags&1)//INVINCIBLE
- ||(hitEnt->flags&FL_DMG_BY_HEAVY_WEAP_ONLY)//HEAVY weapon damage only
- ||(hitEnt->NPC_targetname&&attacker&&attacker->targetname&&Q_stricmp(attacker->targetname,hitEnt->NPC_targetname)) ) )//only breakable by an entity who is not the attacker
- {//no hit effect (besides regular client-side one)
- }
- else
- {
- hitEffect = hit_sparks;
- }
- }
- }
- }
- if ( !g_saberNoEffects && hitEffect != NULL )
- {
- G_PlayEffect( hitEffect, tr.endpos, dir );//"saber_cut"
- // if(ent->client && ent->client->ps.clientNum == 0)
- // FF_Play(fffx_Laser1);
- }
- }
- else
- {//we were doing a ghoul trace
- if ( !WP_SaberDamageEffects( &tr, start, len, dmg, dir, bladeDir, attacker->client->enemyTeam, saberType ) )
- {//didn't hit a ghoul ent
- /*
- if ( && hitEnt->ghoul2.size() )
- {//it was a ghoul2 model so we should have hit it
- return qfalse;
- }
- */
- }
- }
- }
- }
- }
- return qfalse;
- }
- #define LOCK_IDEAL_DIST_TOP 32.0f
- #define LOCK_IDEAL_DIST_CIRCLE 48.0f
- #define LOCK_IDEAL_DIST_JKA 46.0f//all of the new saberlocks are 46.08 from each other because Richard Lico is da MAN
- extern void PM_SetAnimFrame( gentity_t *gent, int frame, qboolean torso, qboolean legs );
- extern qboolean ValidAnimFileIndex ( int index );
- int G_SaberLockAnim( int attackerSaberStyle, int defenderSaberStyle, int topOrSide, int lockOrBreakOrSuperBreak, int winOrLose )
- {
- int baseAnim = -1;
- if ( lockOrBreakOrSuperBreak == SABERLOCK_LOCK )
- {//special case: if we're using the same style and locking
- if ( attackerSaberStyle == defenderSaberStyle
- || (attackerSaberStyle>=SS_FAST&&attackerSaberStyle<=SS_TAVION&&defenderSaberStyle>=SS_FAST&&defenderSaberStyle<=SS_TAVION) )
- {//using same style
- if ( winOrLose == SABERLOCK_LOSE )
- {//you want the defender's stance...
- switch ( defenderSaberStyle )
- {
- case SS_DUAL:
- if ( topOrSide == SABERLOCK_TOP )
- {
- baseAnim = BOTH_LK_DL_DL_T_L_2;
- }
- else
- {
- baseAnim = BOTH_LK_DL_DL_S_L_2;
- }
- break;
- case SS_STAFF:
- if ( topOrSide == SABERLOCK_TOP )
- {
- baseAnim = BOTH_LK_ST_ST_T_L_2;
- }
- else
- {
- baseAnim = BOTH_LK_ST_ST_S_L_2;
- }
- break;
- default:
- if ( topOrSide == SABERLOCK_TOP )
- {
- baseAnim = BOTH_LK_S_S_T_L_2;
- }
- else
- {
- baseAnim = BOTH_LK_S_S_S_L_2;
- }
- break;
- }
- }
- }
- }
- if ( baseAnim == -1 )
- {
- switch ( attackerSaberStyle )
- {
- case SS_DUAL:
- switch ( defenderSaberStyle )
- {
- case SS_DUAL:
- baseAnim = BOTH_LK_DL_DL_S_B_1_L;
- break;
- case SS_STAFF:
- baseAnim = BOTH_LK_DL_ST_S_B_1_L;
- break;
- default://single
- baseAnim = BOTH_LK_DL_S_S_B_1_L;
- break;
- }
- break;
- case SS_STAFF:
- switch ( defenderSaberStyle )
- {
- case SS_DUAL:
- baseAnim = BOTH_LK_ST_DL_S_B_1_L;
- break;
- case SS_STAFF:
- baseAnim = BOTH_LK_ST_ST_S_B_1_L;
- break;
- default://single
- baseAnim = BOTH_LK_ST_S_S_B_1_L;
- break;
- }
- break;
- default://single
- switch ( defenderSaberStyle )
- {
- case SS_DUAL:
- baseAnim = BOTH_LK_S_DL_S_B_1_L;
- break;
- case SS_STAFF:
- baseAnim = BOTH_LK_S_ST_S_B_1_L;
- break;
- default://single
- baseAnim = BOTH_LK_S_S_S_B_1_L;
- break;
- }
- break;
- }
- //side lock or top lock?
- if ( topOrSide == SABERLOCK_TOP )
- {
- baseAnim += 5;
- }
- //lock, break or superbreak?
- if ( lockOrBreakOrSuperBreak == SABERLOCK_LOCK )
- {
- baseAnim += 2;
- }
- else
- {//a break or superbreak
- if ( lockOrBreakOrSuperBreak == SABERLOCK_SUPERBREAK )
- {
- baseAnim += 3;
- }
- //winner or loser?
- if ( winOrLose == SABERLOCK_WIN )
- {
- baseAnim += 1;
- }
- }
- }
- return baseAnim;
- }
- qboolean G_CheckIncrementLockAnim( int anim, int winOrLose )
- {
- qboolean increment = qfalse;//???
- //RULE: if you are the first style in the lock anim, you advance from LOSING position to WINNING position
- // if you are the second style in the lock anim, you advance from WINNING position to LOSING position
- switch ( anim )
- {
- //increment to win:
- case BOTH_LK_DL_DL_S_L_1: //lock if I'm using dual vs. dual and I initiated
- case BOTH_LK_DL_DL_S_L_2: //lock if I'm using dual vs. dual and other initiated
- case BOTH_LK_DL_DL_T_L_1: //lock if I'm using dual vs. dual and I initiated
- case BOTH_LK_DL_DL_T_L_2: //lock if I'm using dual vs. dual and other initiated
- case BOTH_LK_DL_S_S_L_1: //lock if I'm using dual vs. a single
- case BOTH_LK_DL_S_T_L_1: //lock if I'm using dual vs. a single
- case BOTH_LK_DL_ST_S_L_1: //lock if I'm using dual vs. a staff
- case BOTH_LK_DL_ST_T_L_1: //lock if I'm using dual vs. a staff
- case BOTH_LK_S_S_S_L_1: //lock if I'm using single vs. a single and I initiated
- case BOTH_LK_S_S_T_L_2: //lock if I'm using single vs. a single and other initiated
- case BOTH_LK_ST_S_S_L_1: //lock if I'm using staff vs. a single
- case BOTH_LK_ST_S_T_L_1: //lock if I'm using staff vs. a single
- case BOTH_LK_ST_ST_T_L_1: //lock if I'm using staff vs. a staff and I initiated
- case BOTH_LK_ST_ST_T_L_2: //lock if I'm using staff vs. a staff and other initiated
- if ( winOrLose == SABERLOCK_WIN )
- {
- increment = qtrue;
- }
- else
- {
- increment = qfalse;
- }
- break;
- //decrement to win:
- case BOTH_LK_S_DL_S_L_1: //lock if I'm using single vs. a dual
- case BOTH_LK_S_DL_T_L_1: //lock if I'm using single vs. a dual
- case BOTH_LK_S_S_S_L_2: //lock if I'm using single vs. a single and other intitiated
- case BOTH_LK_S_S_T_L_1: //lock if I'm using single vs. a single and I initiated
- case BOTH_LK_S_ST_S_L_1: //lock if I'm using single vs. a staff
- case BOTH_LK_S_ST_T_L_1: //lock if I'm using single vs. a staff
- case BOTH_LK_ST_DL_S_L_1: //lock if I'm using staff vs. dual
- case BOTH_LK_ST_DL_T_L_1: //lock if I'm using staff vs. dual
- case BOTH_LK_ST_ST_S_L_1: //lock if I'm using staff vs. a staff and I initiated
- case BOTH_LK_ST_ST_S_L_2: //lock if I'm using staff vs. a staff and other initiated
- if ( winOrLose == SABERLOCK_WIN )
- {
- increment = qfalse;
- }
- else
- {
- increment = qtrue;
- }
- break;
- default:
- #ifndef FINAL_BUILD
- Com_Printf( S_COLOR_RED"ERROR: unknown Saber Lock Anim: %s!!!\n", animTable[anim].name );
- #endif
- break;
- }
- return increment;
- }
- qboolean WP_SabersCheckLock2( gentity_t *attacker, gentity_t *defender, sabersLockMode_t lockMode )
- {
- animation_t *anim;
- int attAnim, defAnim, advance = 0;
- float attStart = 0.5f, defStart = 0.5f;
- float idealDist = 48.0f;
- //FIXME: this distances need to be modified by the lengths of the sabers involved...
- //MATCH ANIMS
- if ( lockMode == LOCK_KYLE_GRAB1
- || lockMode == LOCK_KYLE_GRAB2
- || lockMode == LOCK_KYLE_GRAB3 )
- {
- float numSpins = 1.0f;
- idealDist = 46.0f;//42.0f;
- attStart = defStart = 0.0f;
- switch ( lockMode )
- {
- default:
- case LOCK_KYLE_GRAB1:
- attAnim = BOTH_KYLE_PA_1;
- defAnim = BOTH_PLAYER_PA_1;
- numSpins = 2.0f;
- break;
- case LOCK_KYLE_GRAB2:
- attAnim = BOTH_KYLE_PA_3;
- defAnim = BOTH_PLAYER_PA_3;
- numSpins = 1.0f;
- break;
- case LOCK_KYLE_GRAB3:
- attAnim = BOTH_KYLE_PA_2;
- defAnim = BOTH_PLAYER_PA_2;
- defender->forcePushTime = level.time + PM_AnimLength( defender->client->clientInfo.animFileIndex, BOTH_PLAYER_PA_2 );
- numSpins = 3.0f;
- break;
- }
- attacker->client->ps.SaberDeactivate();
- defender->client->ps.SaberDeactivate();
- if ( d_slowmodeath->integer > 3
- && ( defender->s.number < MAX_CLIENTS
- || attacker->s.number < MAX_CLIENTS ) )
- {
- if( ValidAnimFileIndex( attacker->client->clientInfo.animFileIndex ) )
- {
- int effectTime = PM_AnimLength( attacker->client->clientInfo.animFileIndex, (animNumber_t)attAnim );
- int spinTime = floor((float)effectTime/numSpins);
- int meFlags = (MEF_MULTI_SPIN);//MEF_NO_TIMESCALE|MEF_NO_VERTBOB|
- if ( Q_irand( 0, 1 ) )
- {
- meFlags |= MEF_REVERSE_SPIN;
- }
- G_StartMatrixEffect( attacker, meFlags, effectTime, 0.75f, spinTime );
- }
- }
- }
- else if ( lockMode == LOCK_FORCE_DRAIN )
- {
- idealDist = 46.0f;//42.0f;
- attStart = defStart = 0.0f;
- attAnim = BOTH_FORCE_DRAIN_GRAB_START;
- defAnim = BOTH_FORCE_DRAIN_GRABBED;
- attacker->client->ps.SaberDeactivate();
- defender->client->ps.SaberDeactivate();
- }
- else
- {
- if ( lockMode == LOCK_RANDOM )
- {
- lockMode = (sabersLockMode_t)Q_irand( (int)LOCK_FIRST, (int)(LOCK_RANDOM)-1 );
- }
- //FIXME: attStart% and idealDist will change per saber lock anim pairing... do we need a big table like in bg_panimate.cpp?
- if ( attacker->client->ps.saberAnimLevel >= SS_FAST
- && attacker->client->ps.saberAnimLevel <= SS_TAVION
- && defender->client->ps.saberAnimLevel >= SS_FAST
- && defender->client->ps.saberAnimLevel <= SS_TAVION )
- {//2 single sabers? Just do it the old way...
- switch ( lockMode )
- {
- case LOCK_TOP:
- attAnim = BOTH_BF2LOCK;// - starts in middle
- defAnim = BOTH_BF1LOCK;// - starts in middle
- attStart = defStart = 0.5f;
- idealDist = LOCK_IDEAL_DIST_TOP;
- break;
- case LOCK_DIAG_TR:
- attAnim = BOTH_CCWCIRCLELOCK; //- starts in middle
- defAnim = BOTH_CWCIRCLELOCK;// - starts in middle
- attStart = defStart = 0.5f;
- idealDist = LOCK_IDEAL_DIST_CIRCLE;
- break;
- case LOCK_DIAG_TL:
- attAnim = BOTH_CWCIRCLELOCK;// - starts in middle
- defAnim = BOTH_CCWCIRCLELOCK;// - starts in middle
- attStart = defStart = 0.5f;
- idealDist = LOCK_IDEAL_DIST_CIRCLE;
- break;
- case LOCK_DIAG_BR:
- attAnim = BOTH_CWCIRCLELOCK;// - starts on left, to right
- defAnim = BOTH_CCWCIRCLELOCK;// - starts on right, to left
- attStart = defStart = 0.85f;//move to end of anim
- idealDist = LOCK_IDEAL_DIST_CIRCLE;
- break;
- case LOCK_DIAG_BL:
- attAnim = BOTH_CCWCIRCLELOCK;// - starts on right, to left
- defAnim = BOTH_CWCIRCLELOCK;// - starts on left, to right
- attStart = defStart = 0.85f;//move to end of anim
- idealDist = LOCK_IDEAL_DIST_CIRCLE;
- break;
- case LOCK_R:
- attAnim = BOTH_CWCIRCLELOCK;// - starts on left, to right
- defAnim = BOTH_CCWCIRCLELOCK;// - starts on right, to left
- attStart = defStart = 0.75f;//move to end of anim
- idealDist = LOCK_IDEAL_DIST_CIRCLE;
- break;
- case LOCK_L:
- attAnim = BOTH_CCWCIRCLELOCK;// - starts on right, to left
- defAnim = BOTH_CWCIRCLELOCK;// - starts on left, to right
- attStart = defStart = 0.75f;//move to end of anim
- idealDist = LOCK_IDEAL_DIST_CIRCLE;
- break;
- default:
- return qfalse;
- break;
- }
- }
- else
- {//use the new system
- idealDist = LOCK_IDEAL_DIST_JKA;//all of the new saberlocks are 46.08 from each other because Richard Lico is da MAN
- if ( lockMode == LOCK_TOP )
- {//top lock
- attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_TOP, SABERLOCK_LOCK, SABERLOCK_WIN );
- defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_TOP, SABERLOCK_LOCK, SABERLOCK_LOSE );
- attStart = defStart = 0.5f;
- }
- else
- {//side lock
- switch ( lockMode )
- {
- case LOCK_DIAG_TR:
- attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
- defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
- attStart = defStart = 0.5f;
- break;
- case LOCK_DIAG_TL:
- attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
- defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
- attStart = defStart = 0.5f;
- break;
- case LOCK_DIAG_BR:
- attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
- defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
- if ( G_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
- {
- attStart = 0.85f;//move to end of anim
- }
- else
- {
- attStart = 0.15f;//start at beginning of anim
- }
- if ( G_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
- {
- defStart = 0.85f;//start at end of anim
- }
- else
- {
- defStart = 0.15f;//start at beginning of anim
- }
- break;
- case LOCK_DIAG_BL:
- attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
- defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
- if ( G_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
- {
- attStart = 0.85f;//move to end of anim
- }
- else
- {
- attStart = 0.15f;//start at beginning of anim
- }
- if ( G_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
- {
- defStart = 0.85f;//start at end of anim
- }
- else
- {
- defStart = 0.15f;//start at beginning of anim
- }
- break;
- case LOCK_R:
- attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
- defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
- if ( G_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
- {
- attStart = 0.75f;//move to end of anim
- }
- else
- {
- attStart = 0.25f;//start at beginning of anim
- }
- if ( G_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
- {
- defStart = 0.75f;//start at end of anim
- }
- else
- {
- defStart = 0.25f;//start at beginning of anim
- }
- break;
- case LOCK_L:
- attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
- defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
- //attacker starts with advantage
- if ( G_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
- {
- attStart = 0.75f;//move to end of anim
- }
- else
- {
- attStart = 0.25f;//start at beginning of anim
- }
- if ( G_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
- {
- defStart = 0.75f;//start at end of anim
- }
- else
- {
- defStart = 0.25f;//start at beginning of anim
- }
- break;
- default:
- return qfalse;
- break;
- }
- }
- }
- }
- //set the proper anims
- NPC_SetAnim( attacker, SETANIM_BOTH, attAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- NPC_SetAnim( defender, SETANIM_BOTH, defAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- //don't let them store a kick for the whole saberlock....
- attacker->client->ps.saberMoveNext = defender->client->ps.saberMoveNext = LS_NONE;
- //
- if ( attStart > 0.0f )
- {
- if( ValidAnimFileIndex( attacker->client->clientInfo.animFileIndex ) )
- {
- anim = &level.knownAnimFileSets[attacker->client->clientInfo.animFileIndex].animations[attAnim];
- advance = floor( anim->numFrames*attStart );
- PM_SetAnimFrame( attacker, anim->firstFrame + advance, qtrue, qtrue );
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer )
- {
- Com_Printf( "%s starting saber lock, anim = %s, %d frames to go!\n", attacker->NPC_type, animTable[attAnim].name, anim->numFrames-advance );
- }
- #endif
- }
- }
- if ( defStart > 0.0f )
- {
- if( ValidAnimFileIndex( defender->client->clientInfo.animFileIndex ) )
- {
- anim = &level.knownAnimFileSets[defender->client->clientInfo.animFileIndex].animations[defAnim];
- advance = ceil( anim->numFrames*defStart );
- PM_SetAnimFrame( defender, anim->firstFrame + advance, qtrue, qtrue );//was anim->firstFrame + anim->numFrames - advance, but that's wrong since they are matched anims
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer )
- {
- Com_Printf( "%s starting saber lock, anim = %s, %d frames to go!\n", defender->NPC_type, animTable[defAnim].name, advance );
- }
- #endif
- }
- }
- VectorClear( attacker->client->ps.velocity );
- VectorClear( attacker->client->ps.moveDir );
- VectorClear( defender->client->ps.velocity );
- VectorClear( defender->client->ps.moveDir );
- if ( lockMode == LOCK_KYLE_GRAB1
- || lockMode == LOCK_KYLE_GRAB2
- || lockMode == LOCK_KYLE_GRAB3
- || lockMode == LOCK_FORCE_DRAIN )
- {//not a real lock, just freeze them both in place
- //can't move or attack
- attacker->client->ps.pm_time = attacker->client->ps.weaponTime = attacker->client->ps.legsAnimTimer;
- attacker->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
- attacker->painDebounceTime = level.time + attacker->client->ps.pm_time;
- if ( lockMode != LOCK_FORCE_DRAIN )
- {
- defender->client->ps.torsoAnimTimer += 200;
- defender->client->ps.legsAnimTimer += 200;
- }
- defender->client->ps.pm_time = defender->client->ps.weaponTime = defender->client->ps.legsAnimTimer;
- defender->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
- if ( lockMode != LOCK_FORCE_DRAIN )
- {
- attacker->aimDebounceTime = level.time + attacker->client->ps.pm_time;
- }
- }
- else
- {
- attacker->client->ps.saberLockTime = defender->client->ps.saberLockTime = level.time + SABER_LOCK_TIME;
- attacker->client->ps.legsAnimTimer = attacker->client->ps.torsoAnimTimer = defender->client->ps.legsAnimTimer = defender->client->ps.torsoAnimTimer = SABER_LOCK_TIME;
- //attacker->client->ps.weaponTime = defender->client->ps.weaponTime = SABER_LOCK_TIME;
- attacker->client->ps.saberLockEnemy = defender->s.number;
- defender->client->ps.saberLockEnemy = attacker->s.number;
- }
- //MATCH ANGLES
- if ( lockMode == LOCK_KYLE_GRAB1
- || lockMode == LOCK_KYLE_GRAB2
- || lockMode == LOCK_KYLE_GRAB3 )
- {//not a real lock, just set pitch to 0
- attacker->client->ps.viewangles[PITCH] = defender->client->ps.viewangles[PITCH] = 0;
- }
- else
- {
- //FIXME: if zDiff in elevation, make lower look up and upper look down and move them closer?
- float defPitchAdd = 0, zDiff = ((attacker->currentOrigin[2]+attacker->client->standheight)-(defender->currentOrigin[2]+defender->client->standheight));
- if ( zDiff > 24 )
- {
- defPitchAdd = -30;
- }
- else if ( zDiff < -24 )
- {
- defPitchAdd = 30;
- }
- else
- {
- defPitchAdd = zDiff/24.0f*-30.0f;
- }
- if ( attacker->NPC && defender->NPC )
- {//if 2 NPCs, just set pitch to 0
- attacker->client->ps.viewangles[PITCH] = -defPitchAdd;
- defender->client->ps.viewangles[PITCH] = defPitchAdd;
- }
- else
- {//if a player is involved, clamp player's pitch and match NPC's to player
- if ( !attacker->s.number )
- {
- //clamp to defPitch
- if ( attacker->client->ps.viewangles[PITCH] > -defPitchAdd + 10 )
- {
- attacker->client->ps.viewangles[PITCH] = -defPitchAdd + 10;
- }
- else if ( attacker->client->ps.viewangles[PITCH] < -defPitchAdd-10 )
- {
- attacker->client->ps.viewangles[PITCH] = -defPitchAdd-10;
- }
- //clamp to sane numbers
- if ( attacker->client->ps.viewangles[PITCH] > 50 )
- {
- attacker->client->ps.viewangles[PITCH] = 50;
- }
- else if ( attacker->client->ps.viewangles[PITCH] < -50 )
- {
- attacker->client->ps.viewangles[PITCH] = -50;
- }
- defender->client->ps.viewangles[PITCH] = attacker->client->ps.viewangles[PITCH]*-1;
- defPitchAdd = defender->client->ps.viewangles[PITCH];
- }
- else if ( !defender->s.number )
- {
- //clamp to defPitch
- if ( defender->client->ps.viewangles[PITCH] > defPitchAdd + 10 )
- {
- defender->client->ps.viewangles[PITCH] = defPitchAdd + 10;
- }
- else if ( defender->client->ps.viewangles[PITCH] < defPitchAdd-10 )
- {
- defender->client->ps.viewangles[PITCH] = defPitchAdd-10;
- }
- //clamp to sane numbers
- if ( defender->client->ps.viewangles[PITCH] > 50 )
- {
- defender->client->ps.viewangles[PITCH] = 50;
- }
- else if ( defender->client->ps.viewangles[PITCH] < -50 )
- {
- defender->client->ps.viewangles[PITCH] = -50;
- }
- defPitchAdd = defender->client->ps.viewangles[PITCH];
- attacker->client->ps.viewangles[PITCH] = defender->client->ps.viewangles[PITCH]*-1;
- }
- }
- }
- vec3_t attAngles, defAngles, defDir;
- VectorSubtract( defender->currentOrigin, attacker->currentOrigin, defDir );
- VectorCopy( attacker->client->ps.viewangles, attAngles );
- attAngles[YAW] = vectoyaw( defDir );
- SetClientViewAngle( attacker, attAngles );
- defAngles[PITCH] = attAngles[PITCH]*-1;
- defAngles[YAW] = AngleNormalize180( attAngles[YAW] + 180);
- defAngles[ROLL] = 0;
- SetClientViewAngle( defender, defAngles );
-
- //MATCH POSITIONS
- vec3_t newOrg;
- /*
- idealDist -= fabs(defPitchAdd)/8.0f;
- */
- float scale = (attacker->s.modelScale[0]+attacker->s.modelScale[1])*0.5f;
- if ( scale && scale != 1.0f )
- {
- idealDist += 8*(scale-1.0f);
- }
- scale = (defender->s.modelScale[0]+defender->s.modelScale[1])*0.5f;
- if ( scale && scale != 1.0f )
- {
- idealDist += 8*(scale-1.0f);
- }
- float diff = VectorNormalize( defDir ) - idealDist;//diff will be the total error in dist
- //try to move attacker half the diff towards the defender
- VectorMA( attacker->currentOrigin, diff*0.5f, defDir, newOrg );
- trace_t trace;
- gi.trace( &trace, attacker->currentOrigin, attacker->mins, attacker->maxs, newOrg, attacker->s.number, attacker->clipmask );
- if ( !trace.startsolid && !trace.allsolid )
- {
- G_SetOrigin( attacker, trace.endpos );
- gi.linkentity( attacker );
- }
- //now get the defender's dist and do it for him too
- vec3_t attDir;
- VectorSubtract( attacker->currentOrigin, defender->currentOrigin, attDir );
- diff = VectorNormalize( attDir ) - idealDist;//diff will be the total error in dist
- //try to move defender all of the remaining diff towards the attacker
- VectorMA( defender->currentOrigin, diff, attDir, newOrg );
- gi.trace( &trace, defender->currentOrigin, defender->mins, defender->maxs, newOrg, defender->s.number, defender->clipmask );
- if ( !trace.startsolid && !trace.allsolid )
- {
- G_SetOrigin( defender, trace.endpos );
- gi.linkentity( defender );
- }
- //DONE!
- return qtrue;
- }
- qboolean WP_SabersCheckLock( gentity_t *ent1, gentity_t *ent2 )
- {
- if ( ent1->client->playerTeam == ent2->client->playerTeam )
- {
- return qfalse;
- }
- if ( ent1->client->NPC_class == CLASS_SABER_DROID
- || ent2->client->NPC_class == CLASS_SABER_DROID )
- {//they don't have saberlock anims
- return qfalse;
- }
- if ( ent1->client->ps.groundEntityNum == ENTITYNUM_NONE ||
- ent2->client->ps.groundEntityNum == ENTITYNUM_NONE )
- {
- return qfalse;
- }
- if ( !ent1->client->ps.saber[0].lockable || !ent2->client->ps.saber[0].lockable )
- {//one of these sabers cannot lock (like a lance)
- //FIXME: check the second sabers too?
- return qfalse;
- }
- if ( ent1->painDebounceTime > level.time-1000 || ent2->painDebounceTime > level.time-1000 )
- {//can't saberlock if you're not ready
- return qfalse;
- }
- if ( fabs( ent1->currentOrigin[2]-ent2->currentOrigin[2]) > 18 )
- {
- return qfalse;
- }
- float dist = DistanceSquared(ent1->currentOrigin,ent2->currentOrigin);
- if ( dist < 64 || dist > 6400 )//( dist < 128 || dist > 2304 )
- {//between 8 and 80 from each other//was 16 and 48
- return qfalse;
- }
- if ( !InFOV( ent1, ent2, 40, 180 ) || !InFOV( ent2, ent1, 40, 180 ) )
- {
- return qfalse;
- }
- //Check for certain anims that *cannot* lock
- //FIXME: there should probably be a whole *list* of these, but I'll put them in as they come up
- if ( ent1->client->ps.torsoAnim == BOTH_A2_STABBACK1 && ent1->client->ps.torsoAnimTimer > 300 )
- {//can't lock when saber is behind you
- return qfalse;
- }
- if ( ent2->client->ps.torsoAnim == BOTH_A2_STABBACK1 && ent2->client->ps.torsoAnimTimer > 300 )
- {//can't lock when saber is behind you
- return qfalse;
- }
- if ( PM_LockedAnim( ent1->client->ps.torsoAnim )
- || PM_LockedAnim( ent2->client->ps.torsoAnim ) )
- {//stuck doing something else
- return qfalse;
- }
- if ( PM_SaberLockBreakAnim( ent1->client->ps.torsoAnim )
- || PM_SaberLockBreakAnim( ent2->client->ps.torsoAnim ) )
- {//still finishing the last lock break!
- return qfalse;
- }
- //BR to TL lock
- if ( ent1->client->ps.torsoAnim == BOTH_A1_BR_TL ||
- ent1->client->ps.torsoAnim == BOTH_A2_BR_TL ||
- ent1->client->ps.torsoAnim == BOTH_A3_BR_TL ||
- ent1->client->ps.torsoAnim == BOTH_A4_BR_TL ||
- ent1->client->ps.torsoAnim == BOTH_A5_BR_TL ||
- ent1->client->ps.torsoAnim == BOTH_A6_BR_TL ||
- ent1->client->ps.torsoAnim == BOTH_A7_BR_TL )
- {//ent1 is attacking in the opposite diagonal
- return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BR );
- }
- if ( ent2->client->ps.torsoAnim == BOTH_A1_BR_TL ||
- ent2->client->ps.torsoAnim == BOTH_A2_BR_TL ||
- ent2->client->ps.torsoAnim == BOTH_A3_BR_TL ||
- ent2->client->ps.torsoAnim == BOTH_A4_BR_TL ||
- ent2->client->ps.torsoAnim == BOTH_A5_BR_TL ||
- ent2->client->ps.torsoAnim == BOTH_A6_BR_TL ||
- ent2->client->ps.torsoAnim == BOTH_A7_BR_TL )
- {//ent2 is attacking in the opposite diagonal
- return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BR );
- }
- //BL to TR lock
- if ( ent1->client->ps.torsoAnim == BOTH_A1_BL_TR ||
- ent1->client->ps.torsoAnim == BOTH_A2_BL_TR ||
- ent1->client->ps.torsoAnim == BOTH_A3_BL_TR ||
- ent1->client->ps.torsoAnim == BOTH_A4_BL_TR ||
- ent1->client->ps.torsoAnim == BOTH_A5_BL_TR ||
- ent1->client->ps.torsoAnim == BOTH_A6_BL_TR ||
- ent1->client->ps.torsoAnim == BOTH_A7_BL_TR )
- {//ent1 is attacking in the opposite diagonal
- return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BL );
- }
- if ( ent2->client->ps.torsoAnim == BOTH_A1_BL_TR ||
- ent2->client->ps.torsoAnim == BOTH_A2_BL_TR ||
- ent2->client->ps.torsoAnim == BOTH_A3_BL_TR ||
- ent2->client->ps.torsoAnim == BOTH_A4_BL_TR ||
- ent2->client->ps.torsoAnim == BOTH_A5_BL_TR ||
- ent2->client->ps.torsoAnim == BOTH_A6_BL_TR ||
- ent2->client->ps.torsoAnim == BOTH_A7_BL_TR )
- {//ent2 is attacking in the opposite diagonal
- return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BL );
- }
- //L to R lock
- if ( ent1->client->ps.torsoAnim == BOTH_A1__L__R ||
- ent1->client->ps.torsoAnim == BOTH_A2__L__R ||
- ent1->client->ps.torsoAnim == BOTH_A3__L__R ||
- ent1->client->ps.torsoAnim == BOTH_A4__L__R ||
- ent1->client->ps.torsoAnim == BOTH_A5__L__R ||
- ent1->client->ps.torsoAnim == BOTH_A6__L__R ||
- ent1->client->ps.torsoAnim == BOTH_A7__L__R )
- {//ent1 is attacking l to r
- return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
- /*
- if ( ent2BlockingPlayer )
- {//player will block this anyway
- return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
- }
- if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
- ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
- ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
- ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
- ent2->client->ps.torsoAnim == BOTH_A5_TL_BR ||
- ent2->client->ps.torsoAnim == BOTH_A6_TL_BR ||
- ent2->client->ps.torsoAnim == BOTH_A7_TL_BR ||
- ent2->client->ps.torsoAnim == BOTH_P1_S1_TR ||
- ent2->client->ps.torsoAnim == BOTH_P1_S1_BL )
- {//ent2 is attacking or blocking on the r
- return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
- }
- if ( ent2Boss && !Q_irand( 0, 3 ) )
- {
- return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
- }
- */
- }
- if ( ent2->client->ps.torsoAnim == BOTH_A1__L__R ||
- ent2->client->ps.torsoAnim == BOTH_A2__L__R ||
- ent2->client->ps.torsoAnim == BOTH_A3__L__R ||
- ent2->client->ps.torsoAnim == BOTH_A4__L__R ||
- ent2->client->ps.torsoAnim == BOTH_A5__L__R ||
- ent2->client->ps.torsoAnim == BOTH_A6__L__R ||
- ent2->client->ps.torsoAnim == BOTH_A7__L__R )
- {//ent2 is attacking l to r
- return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
- /*
- if ( ent1BlockingPlayer )
- {//player will block this anyway
- return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
- }
- if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
- ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
- ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
- ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
- ent1->client->ps.torsoAnim == BOTH_A5_TL_BR ||
- ent1->client->ps.torsoAnim == BOTH_A6_TL_BR ||
- ent1->client->ps.torsoAnim == BOTH_A7_TL_BR ||
- ent1->client->ps.torsoAnim == BOTH_P1_S1_TR ||
- ent1->client->ps.torsoAnim == BOTH_P1_S1_BL )
- {//ent1 is attacking or blocking on the r
- return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
- }
- if ( ent1Boss && !Q_irand( 0, 3 ) )
- {
- return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
- }
- */
- }
- //R to L lock
- if ( ent1->client->ps.torsoAnim == BOTH_A1__R__L ||
- ent1->client->ps.torsoAnim == BOTH_A2__R__L ||
- ent1->client->ps.torsoAnim == BOTH_A3__R__L ||
- ent1->client->ps.torsoAnim == BOTH_A4__R__L ||
- ent1->client->ps.torsoAnim == BOTH_A5__R__L ||
- ent1->client->ps.torsoAnim == BOTH_A6__R__L ||
- ent1->client->ps.torsoAnim == BOTH_A7__R__L )
- {//ent1 is attacking r to l
- return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
- /*
- if ( ent2BlockingPlayer )
- {//player will block this anyway
- return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
- }
- if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
- ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
- ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
- ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
- ent2->client->ps.torsoAnim == BOTH_A5_TR_BL ||
- ent2->client->ps.torsoAnim == BOTH_A6_TR_BL ||
- ent2->client->ps.torsoAnim == BOTH_A7_TR_BL ||
- ent2->client->ps.torsoAnim == BOTH_P1_S1_TL ||
- ent2->client->ps.torsoAnim == BOTH_P1_S1_BR )
- {//ent2 is attacking or blocking on the l
- return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
- }
- if ( ent2Boss && !Q_irand( 0, 3 ) )
- {
- return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
- }
- */
- }
- if ( ent2->client->ps.torsoAnim == BOTH_A1__R__L ||
- ent2->client->ps.torsoAnim == BOTH_A2__R__L ||
- ent2->client->ps.torsoAnim == BOTH_A3__R__L ||
- ent2->client->ps.torsoAnim == BOTH_A4__R__L ||
- ent2->client->ps.torsoAnim == BOTH_A5__R__L ||
- ent2->client->ps.torsoAnim == BOTH_A6__R__L ||
- ent2->client->ps.torsoAnim == BOTH_A7__R__L )
- {//ent2 is attacking r to l
- return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
- /*
- if ( ent1BlockingPlayer )
- {//player will block this anyway
- return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
- }
- if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
- ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
- ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
- ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
- ent1->client->ps.torsoAnim == BOTH_A5_TR_BL ||
- ent1->client->ps.torsoAnim == BOTH_A6_TR_BL ||
- ent1->client->ps.torsoAnim == BOTH_A7_TR_BL ||
- ent1->client->ps.torsoAnim == BOTH_P1_S1_TL ||
- ent1->client->ps.torsoAnim == BOTH_P1_S1_BR )
- {//ent1 is attacking or blocking on the l
- return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
- }
- if ( ent1Boss && !Q_irand( 0, 3 ) )
- {
- return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
- }
- */
- }
- //TR to BL lock
- if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
- ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
- ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
- ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
- ent1->client->ps.torsoAnim == BOTH_A5_TR_BL ||
- ent1->client->ps.torsoAnim == BOTH_A6_TR_BL ||
- ent1->client->ps.torsoAnim == BOTH_A7_TR_BL )
- {//ent1 is attacking diagonally
- return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
- /*
- if ( ent2BlockingPlayer )
- {//player will block this anyway
- return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
- }
- if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
- ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
- ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
- ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
- ent2->client->ps.torsoAnim == BOTH_A5_TR_BL ||
- ent2->client->ps.torsoAnim == BOTH_A6_TR_BL ||
- ent2->client->ps.torsoAnim == BOTH_A7_TR_BL ||
- ent2->client->ps.torsoAnim == BOTH_P1_S1_TL )
- {//ent2 is attacking in the opposite diagonal
- return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
- }
- if ( ent2->client->ps.torsoAnim == BOTH_A1_BR_TL ||
- ent2->client->ps.torsoAnim == BOTH_A2_BR_TL ||
- ent2->client->ps.torsoAnim == BOTH_A3_BR_TL ||
- ent2->client->ps.torsoAnim == BOTH_A4_BR_TL ||
- ent2->client->ps.torsoAnim == BOTH_A5_BR_TL ||
- ent2->client->ps.torsoAnim == BOTH_A6_BR_TL ||
- ent2->client->ps.torsoAnim == BOTH_A7_BR_TL ||
- ent2->client->ps.torsoAnim == BOTH_P1_S1_BL )
- {//ent2 is attacking in the opposite diagonal
- return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BL );
- }
- if ( ent2Boss && !Q_irand( 0, 3 ) )
- {
- return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
- }
- */
- }
- if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
- ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
- ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
- ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
- ent2->client->ps.torsoAnim == BOTH_A5_TR_BL ||
- ent2->client->ps.torsoAnim == BOTH_A6_TR_BL ||
- ent2->client->ps.torsoAnim == BOTH_A7_TR_BL )
- {//ent2 is attacking diagonally
- return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
- /*
- if ( ent1BlockingPlayer )
- {//player will block this anyway
- return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
- }
- if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
- ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
- ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
- ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
- ent1->client->ps.torsoAnim == BOTH_A5_TR_BL ||
- ent1->client->ps.torsoAnim == BOTH_A6_TR_BL ||
- ent1->client->ps.torsoAnim == BOTH_A7_TR_BL ||
- ent1->client->ps.torsoAnim == BOTH_P1_S1_TL )
- {//ent1 is attacking in the opposite diagonal
- return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
- }
- if ( ent1->client->ps.torsoAnim == BOTH_A1_BR_TL ||
- ent1->client->ps.torsoAnim == BOTH_A2_BR_TL ||
- ent1->client->ps.torsoAnim == BOTH_A3_BR_TL ||
- ent1->client->ps.torsoAnim == BOTH_A4_BR_TL ||
- ent1->client->ps.torsoAnim == BOTH_A5_BR_TL ||
- ent1->client->ps.torsoAnim == BOTH_A6_BR_TL ||
- ent1->client->ps.torsoAnim == BOTH_A7_BR_TL ||
- ent1->client->ps.torsoAnim == BOTH_P1_S1_BL )
- {//ent1 is attacking in the opposite diagonal
- return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BL );
- }
- if ( ent1Boss && !Q_irand( 0, 3 ) )
- {
- return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
- }
- */
- }
- //TL to BR lock
- if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
- ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
- ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
- ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
- ent1->client->ps.torsoAnim == BOTH_A5_TL_BR ||
- ent1->client->ps.torsoAnim == BOTH_A6_TL_BR ||
- ent1->client->ps.torsoAnim == BOTH_A7_TL_BR )
- {//ent1 is attacking diagonally
- return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
- /*
- if ( ent2BlockingPlayer )
- {//player will block this anyway
- return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
- }
- if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
- ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
- ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
- ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
- ent2->client->ps.torsoAnim == BOTH_A5_TL_BR ||
- ent2->client->ps.torsoAnim == BOTH_A6_TL_BR ||
- ent2->client->ps.torsoAnim == BOTH_A7_TL_BR ||
- ent2->client->ps.torsoAnim == BOTH_P1_S1_TR )
- {//ent2 is attacking in the opposite diagonal
- return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
- }
- if ( ent2->client->ps.torsoAnim == BOTH_A1_BL_TR ||
- ent2->client->ps.torsoAnim == BOTH_A2_BL_TR ||
- ent2->client->ps.torsoAnim == BOTH_A3_BL_TR ||
- ent2->client->ps.torsoAnim == BOTH_A4_BL_TR ||
- ent2->client->ps.torsoAnim == BOTH_A5_BL_TR ||
- ent2->client->ps.torsoAnim == BOTH_A6_BL_TR ||
- ent2->client->ps.torsoAnim == BOTH_A7_BL_TR ||
- ent2->client->ps.torsoAnim == BOTH_P1_S1_BR )
- {//ent2 is attacking in the opposite diagonal
- return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BR );
- }
- if ( ent2Boss && !Q_irand( 0, 3 ) )
- {
- return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
- }
- */
- }
- if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
- ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
- ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
- ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
- ent2->client->ps.torsoAnim == BOTH_A5_TL_BR ||
- ent2->client->ps.torsoAnim == BOTH_A6_TL_BR ||
- ent2->client->ps.torsoAnim == BOTH_A7_TL_BR )
- {//ent2 is attacking diagonally
- return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
- /*
- if ( ent1BlockingPlayer )
- {//player will block this anyway
- return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
- }
- if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
- ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
- ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
- ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
- ent1->client->ps.torsoAnim == BOTH_A5_TL_BR ||
- ent1->client->ps.torsoAnim == BOTH_A6_TL_BR ||
- ent1->client->ps.torsoAnim == BOTH_A7_TL_BR ||
- ent1->client->ps.torsoAnim == BOTH_P1_S1_TR )
- {//ent1 is attacking in the opposite diagonal
- return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
- }
- if ( ent1->client->ps.torsoAnim == BOTH_A1_BL_TR ||
- ent1->client->ps.torsoAnim == BOTH_A2_BL_TR ||
- ent1->client->ps.torsoAnim == BOTH_A3_BL_TR ||
- ent1->client->ps.torsoAnim == BOTH_A4_BL_TR ||
- ent1->client->ps.torsoAnim == BOTH_A5_BL_TR ||
- ent1->client->ps.torsoAnim == BOTH_A6_BL_TR ||
- ent1->client->ps.torsoAnim == BOTH_A7_BL_TR ||
- ent1->client->ps.torsoAnim == BOTH_P1_S1_BR )
- {//ent1 is attacking in the opposite diagonal
- return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BR );
- }
- if ( ent1Boss && !Q_irand( 0, 3 ) )
- {
- return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
- }
- */
- }
- //T to B lock
- if ( ent1->client->ps.torsoAnim == BOTH_A1_T__B_ ||
- ent1->client->ps.torsoAnim == BOTH_A2_T__B_ ||
- ent1->client->ps.torsoAnim == BOTH_A3_T__B_ ||
- ent1->client->ps.torsoAnim == BOTH_A4_T__B_ ||
- ent1->client->ps.torsoAnim == BOTH_A5_T__B_ ||
- ent1->client->ps.torsoAnim == BOTH_A6_T__B_ ||
- ent1->client->ps.torsoAnim == BOTH_A7_T__B_ )
- {//ent1 is attacking top-down
- /*
- if ( ent2->client->ps.torsoAnim == BOTH_P1_S1_T_ ||
- ent2->client->ps.torsoAnim == BOTH_K1_S1_T_ )
- */
- {//ent2 is blocking at top
- return WP_SabersCheckLock2( ent1, ent2, LOCK_TOP );
- }
- }
- if ( ent2->client->ps.torsoAnim == BOTH_A1_T__B_ ||
- ent2->client->ps.torsoAnim == BOTH_A2_T__B_ ||
- ent2->client->ps.torsoAnim == BOTH_A3_T__B_ ||
- ent2->client->ps.torsoAnim == BOTH_A4_T__B_ ||
- ent2->client->ps.torsoAnim == BOTH_A5_T__B_ ||
- ent2->client->ps.torsoAnim == BOTH_A6_T__B_ ||
- ent2->client->ps.torsoAnim == BOTH_A7_T__B_ )
- {//ent2 is attacking top-down
- /*
- if ( ent1->client->ps.torsoAnim == BOTH_P1_S1_T_ ||
- ent1->client->ps.torsoAnim == BOTH_K1_S1_T_ )
- */
- {//ent1 is blocking at top
- return WP_SabersCheckLock2( ent2, ent1, LOCK_TOP );
- }
- }
- /*
- if ( !Q_irand( 0, 10 ) )
- {
- return WP_SabersCheckLock2( ent1, ent2, LOCK_RANDOM );
- }
- */
- return qfalse;
- }
- qboolean WP_SaberParry( gentity_t *victim, gentity_t *attacker )
- {
- if ( !victim || !victim->client || !attacker )
- {
- return qfalse;
- }
- if ( Rosh_BeingHealed( victim ) )
- {
- return qfalse;
- }
- if ( G_InCinematicSaberAnim( victim ) )
- {
- return qfalse;
- }
- if ( PM_SuperBreakLoseAnim( victim->client->ps.torsoAnim )
- || PM_SuperBreakWinAnim( victim->client->ps.torsoAnim ) )
- {
- return qfalse;
- }
- if ( victim->s.number || g_saberAutoBlocking->integer || victim->client->ps.saberBlockingTime > level.time )
- {//either an NPC or a player who is blocking
- if ( !PM_SaberInTransitionAny( victim->client->ps.saberMove )
- && !PM_SaberInBounce( victim->client->ps.saberMove )
- && !PM_SaberInKnockaway( victim->client->ps.saberMove ) )
- {//I'm not attacking, in transition or in a bounce or knockaway, so play a parry
- WP_SaberBlockNonRandom( victim, saberHitLocation, qfalse );
- }
- victim->client->ps.saberEventFlags |= SEF_PARRIED;
- //since it was parried, take away any damage done
- //FIXME: what if the damage was done before the parry?
- WP_SaberClearDamageForEntNum( victim->s.number );
- //tell the victim to get mad at me
- if ( victim->enemy != attacker && victim->client->playerTeam != attacker->client->playerTeam )
- {//they're not mad at me and they're not on my team
- G_ClearEnemy( victim );
- G_SetEnemy( victim, attacker );
- }
- return qtrue;
- }
- return qfalse;
- }
- qboolean WP_BrokenParryKnockDown( gentity_t *victim )
- {
- if ( !victim || !victim->client )
- {
- return qfalse;
- }
- if ( PM_SuperBreakLoseAnim( victim->client->ps.torsoAnim )
- || PM_SuperBreakWinAnim( victim->client->ps.torsoAnim ) )
- {
- return qfalse;
- }
- if ( victim->client->ps.saberMove == LS_PARRY_UP
- || victim->client->ps.saberMove == LS_PARRY_UR
- || victim->client->ps.saberMove == LS_PARRY_UL
- || victim->client->ps.saberMove == LS_H1_BR
- || victim->client->ps.saberMove == LS_H1_B_
- || victim->client->ps.saberMove == LS_H1_BL )
- {//knock their asses down!
- int knockAnim = BOTH_KNOCKDOWN1;
- if ( PM_CrouchAnim( victim->client->ps.legsAnim ) )
- {
- knockAnim = BOTH_KNOCKDOWN4;
- }
- NPC_SetAnim( victim, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- G_AddEvent( victim, EV_PAIN, victim->health );
- return qtrue;
- }
- return qfalse;
- }
- qboolean G_TryingKataAttack( gentity_t *self, usercmd_t *cmd )
- {
- if ( g_saberNewControlScheme->integer )
- {//use the new control scheme: force focus button
- if ( (cmd->buttons&BUTTON_FORCE_FOCUS) )
- {
- return qtrue;
- }
- else
- {
- return qfalse;
- }
- }
- else //if ( self && self->client )
- {//use the old control scheme
- if ( (cmd->buttons&BUTTON_ALT_ATTACK) )
- {//pressing alt-attack
- //if ( !(self->client->ps.pm_flags&PMF_ALT_ATTACK_HELD) )
- {//haven't been holding alt-attack
- if ( (cmd->buttons&BUTTON_ATTACK) )
- {//pressing attack
- return qtrue;
- }
- }
- }
- }
- return qfalse;
- }
- qboolean G_TryingPullAttack( gentity_t *self, usercmd_t *cmd, qboolean amPulling )
- {
- if ( g_saberNewControlScheme->integer )
- {//use the new control scheme: force focus button
- if ( (cmd->buttons&BUTTON_FORCE_FOCUS) )
- {
- if ( self && self->client )
- {
- if ( self->client->ps.forcePowerLevel[FP_PULL] >= FORCE_LEVEL_3 )
- {//force pull 3
- if ( amPulling
- || (self->client->ps.forcePowersActive&(1<<FP_PULL))
- || self->client->ps.forcePowerDebounce[FP_PULL] > level.time ) //force-pulling
- {//pulling
- return qtrue;
- }
- }
- }
- }
- else
- {
- return qfalse;
- }
- }
- else
- {//use the old control scheme
- if ( (cmd->buttons&BUTTON_ATTACK) )
- {//pressing attack
- if ( self && self->client )
- {
- if ( self->client->ps.forcePowerLevel[FP_PULL] >= FORCE_LEVEL_3 )
- {//force pull 3
- if ( amPulling
- || (self->client->ps.forcePowersActive&(1<<FP_PULL))
- || self->client->ps.forcePowerDebounce[FP_PULL] > level.time ) //force-pulling
- {//pulling
- return qtrue;
- }
- }
- }
- }
- }
- return qfalse;
- }
- qboolean G_TryingCartwheel( gentity_t *self, usercmd_t *cmd )
- {
- if ( g_saberNewControlScheme->integer )
- {//use the new control scheme: force focus button
- if ( (cmd->buttons&BUTTON_FORCE_FOCUS) )
- {
- return qtrue;
- }
- else
- {
- return qfalse;
- }
- }
- else
- {//use the old control scheme
- if ( (cmd->buttons&BUTTON_ATTACK) )
- {//pressing attack
- if ( cmd->rightmove )
- {
- if ( self && self->client )
- {
- if ( cmd->upmove>0 //)
- && self->client->ps.groundEntityNum != ENTITYNUM_NONE )
- {//on ground, pressing jump
- return qtrue;
- }
- else
- {//just jumped?
- if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE
- && level.time - self->client->ps.lastOnGround <= 250 //50//250
- && (self->client->ps.pm_flags&PMF_JUMPING) )//jumping
- {//just jumped this or last frame
- return qtrue;
- }
- }
- }
- }
- }
- }
- return qfalse;
- }
- qboolean G_TryingSpecial( gentity_t *self, usercmd_t *cmd )
- {
- if ( g_saberNewControlScheme->integer )
- {//use the new control scheme: force focus button
- if ( (cmd->buttons&BUTTON_FORCE_FOCUS) )
- {
- return qtrue;
- }
- else
- {
- return qfalse;
- }
- }
- else
- {//use the old control scheme
- return qfalse;
- }
- }
- qboolean G_TryingJumpAttack( gentity_t *self, usercmd_t *cmd )
- {
- if ( g_saberNewControlScheme->integer )
- {//use the new control scheme: force focus button
- if ( (cmd->buttons&BUTTON_FORCE_FOCUS) )
- {
- return qtrue;
- }
- else
- {
- return qfalse;
- }
- }
- else
- {//use the old control scheme
- if ( (cmd->buttons&BUTTON_ATTACK) )
- {//pressing attack
- if ( cmd->upmove>0 )
- {//pressing jump
- return qtrue;
- }
- else if ( self && self->client )
- {//just jumped?
- if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE
- && level.time - self->client->ps.lastOnGround <= 250
- && (self->client->ps.pm_flags&PMF_JUMPING) )//jumping
- {//jumped within the last quarter second
- return qtrue;
- }
- }
- }
- }
- return qfalse;
- }
- qboolean G_TryingJumpForwardAttack( gentity_t *self, usercmd_t *cmd )
- {
- if ( g_saberNewControlScheme->integer )
- {//use the new control scheme: force focus button
- if ( (cmd->buttons&BUTTON_FORCE_FOCUS) )
- {
- return qtrue;
- }
- else
- {
- return qfalse;
- }
- }
- else
- {//use the old control scheme
- if ( (cmd->buttons&BUTTON_ATTACK) )
- {//pressing attack
- if ( cmd->forwardmove > 0 )
- {//moving forward
- if ( self && self->client )
- {
- if ( cmd->upmove>0
- && self->client->ps.groundEntityNum != ENTITYNUM_NONE )
- {//pressing jump
- return qtrue;
- }
- else
- {//no slop on forward jumps - must be precise!
- if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE
- && level.time - self->client->ps.lastOnGround <= 250 //50
- && (self->client->ps.pm_flags&PMF_JUMPING) )//jumping
- {//just jumped this or last frame
- return qtrue;
- }
- }
- }
- }
- }
- }
- return qfalse;
- }
- qboolean G_TryingLungeAttack( gentity_t *self, usercmd_t *cmd )
- {
- if ( g_saberNewControlScheme->integer )
- {//use the new control scheme: force focus button
- if ( (cmd->buttons&BUTTON_FORCE_FOCUS) )
- {
- return qtrue;
- }
- else
- {
- return qfalse;
- }
- }
- else
- {//use the old control scheme
- if ( (cmd->buttons&BUTTON_ATTACK) )
- {//pressing attack
- if ( cmd->upmove<0 )
- {//pressing crouch
- return qtrue;
- }
- else if ( self && self->client )
- {//just unducked?
- if ( (self->client->ps.pm_flags&PMF_DUCKED) )
- {//just unducking
- return qtrue;
- }
- }
- }
- }
- return qfalse;
- }
- //FIXME: for these below funcs, maybe in the old control scheme some moves should still cost power... if so, pass in the saberMove and use a switch statement
- qboolean G_EnoughPowerForSpecialMove( int forcePower, int cost, qboolean kataMove )
- {
- if ( g_saberNewControlScheme->integer || kataMove )
- {//special moves cost power
- if ( forcePower >= cost )
- {
- return qtrue;
- }
- else
- {
- cg.forceHUDTotalFlashTime = level.time + 1000;
- return qfalse;
- }
- }
- else
- {//old control scheme: uses no power, so just do it
- return qtrue;
- }
- }
- void G_DrainPowerForSpecialMove( gentity_t *self, forcePowers_t fp, int cost, qboolean kataMove )
- {
- if ( !self || !self->client || self->s.number >= MAX_CLIENTS )
- {
- return;
- }
- if ( g_saberNewControlScheme->integer || kataMove )
- {//special moves cost power
- WP_ForcePowerDrain( self, fp, cost );//drain the required force power
- }
- else
- {//old control scheme: uses no power, so just do it
- }
- }
- int G_CostForSpecialMove( int cost, qboolean kataMove )
- {
- if ( g_saberNewControlScheme->integer || kataMove )
- {//special moves cost power
- return cost;
- }
- else
- {//old control scheme: uses no power, so just do it
- return 0;
- }
- }
- /*
- ---------------------------------------------------------
- void WP_SaberDamageTrace( gentity_t *ent, int saberNum, int bladeNum )
- Constantly trace from the old blade pos to new, down the saber beam and do damage
- FIXME: if the dot product of the old muzzle dir and the new muzzle dir is < 0.75, subdivide it and do multiple traces so we don't flatten out the arc!
- ---------------------------------------------------------
- */
- #define MAX_SABER_SWING_INC 0.33f
- void WP_SaberDamageTrace( gentity_t *ent, int saberNum, int bladeNum )
- {
- vec3_t mp1, mp2, md1, md2, baseOld, baseNew, baseDiff, endOld, endNew, bladePointOld, bladePointNew;
- float tipDmgMod = 1.0f;
- float baseDamage;
- int baseDFlags = 0;
- qboolean hit_wall = qfalse;
- qboolean brokenParry = qfalse;
- for ( int ven = 0; ven < MAX_SABER_VICTIMS; ven++ )
- {
- victimEntityNum[ven] = ENTITYNUM_NONE;
- }
- memset( totalDmg, 0, sizeof( totalDmg) );
- memset( dmgDir, 0, sizeof( dmgDir ) );
- memset( dmgSpot, 0, sizeof( dmgSpot ) );
- memset( dmgFraction, 0, sizeof( dmgFraction ) );
- memset( hitLoc, HL_NONE, sizeof( hitLoc ) );
- memset( hitDismemberLoc, HL_NONE, sizeof( hitDismemberLoc ) );
- memset( hitDismember, qfalse, sizeof( hitDismember ) );
- numVictims = 0;
- VectorClear(saberHitLocation);
- VectorClear(saberHitNormal);
- saberHitFraction = 1.0; // Closest saber hit. The saber can do no damage past this point.
- saberHitEntity = ENTITYNUM_NONE;
- sabersCrossed = -1;
- if ( !ent->client )
- {
- return;
- }
- if ( !ent->s.number )
- {//player never uses these
- ent->client->ps.saberEventFlags &= ~SEF_EVENTS;
- }
- if ( ent->client->ps.saber[saberNum].blade[bladeNum].length <= 1 )//cen get down to 1 when in a wall
- {//saber is not on
- return;
- }
- if ( VectorCompare( ent->client->renderInfo.muzzlePointOld, vec3_origin ) || VectorCompare( ent->client->renderInfo.muzzleDirOld, vec3_origin ) )
- {
- //just started up the saber?
- return;
- }
- int saberContents = gi.pointcontents( ent->client->renderInfo.muzzlePoint, ent->client->ps.saberEntityNum );
- if ( (saberContents&CONTENTS_WATER)||
- (saberContents&CONTENTS_SLIME)||
- (saberContents&CONTENTS_LAVA) )
- {//um... turn off? Or just set length to 1?
- //FIXME: short-out effect/sound?
- ent->client->ps.saber[saberNum].blade[bladeNum].active = qfalse;
- return;
- }
- else if (!g_saberNoEffects && gi.WE_IsOutside(ent->client->renderInfo.muzzlePoint))
- {
- float chanceOfFizz = gi.WE_GetChanceOfSaberFizz();
- if (chanceOfFizz>0 && Q_flrand(0.0f, 1.0f)<chanceOfFizz)
- {
- vec3_t end; /*normal = {0,0,1};//FIXME: opposite of rain angles?*/
- VectorMA( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, ent->client->ps.saber[saberNum].blade[bladeNum].length*Q_flrand(0, 1), ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, end );
- G_PlayEffect( "saber/fizz", end );
- // if(ent->client && ent->client->ps.clientNum == 0)
- // FF_Play(fffx_Laser1);
- }
- }
- //FIXMEFIXMEFIXME: When in force speed (esp. lvl 3), need to interpolate this because
- // we animate so much faster that the arc is pretty much flat...
- int entPowerLevel = 0;
- if ( ent->client->NPC_class == CLASS_SABER_DROID )
- {
- entPowerLevel = SaberDroid_PowerLevelForSaberAnim( ent );
- }
- else if ( !ent->s.number && (ent->client->ps.forcePowersActive&(1<<FP_SPEED)) )
- {
- entPowerLevel = FORCE_LEVEL_3;
- }
- else
- {
- entPowerLevel = PM_PowerLevelForSaberAnim( &ent->client->ps, saberNum );
- }
- if ( entPowerLevel )
- {
- if ( ent->client->ps.forceRageRecoveryTime > level.time )
- {
- entPowerLevel = FORCE_LEVEL_1;
- }
- else if ( ent->client->ps.forcePowersActive & (1 << FP_RAGE) )
- {
- entPowerLevel += ent->client->ps.forcePowerLevel[FP_RAGE];
- }
- }
- if ( ent->client->ps.saberInFlight )
- {//flying sabers are much more deadly
- //unless you're dead
- if ( ent->health <= 0 && g_saberRealisticCombat->integer < 2 )
- {//so enemies don't keep trying to block it
- //FIXME: still do damage, just not to humanoid clients who should try to avoid it
- //baseDamage = 0.0f;
- return;
- }
- //or unless returning
- else if ( ent->client->ps.saberEntityState == SES_RETURNING
- && ent->client->ps.saber[0].returnDamage == qfalse )//type != SABER_STAR )
- {//special case, since we're returning, chances are if we hit something
- //it's going to be butt-first. So do less damage.
- baseDamage = 0.1f;
- }
- else
- {
- if ( !ent->s.number )
- {//cheat for player
- baseDamage = 10.0f;
- }
- else
- {
- baseDamage = 2.5f;
- }
- }
- //Use old to current since can't predict it
- VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePointOld, mp1 );
- VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDirOld, md1 );
- VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, mp2 );
- VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, md2 );
- }
- else
- {
- if ( ent->client->ps.torsoAnim == BOTH_A7_HILT )
- {//no effects, no damage
- return;
- }
- else if ( G_InCinematicSaberAnim( ent ) )
- {
- baseDFlags = DAMAGE_NO_KILL;
- baseDamage = 0.1f;
- }
- else if ( ent->client->ps.saberMove == LS_READY
- && !PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) )
- {//just do effects
- if ( g_saberRealisticCombat->integer < 2 )
- {//don't kill with this hit
- baseDFlags = DAMAGE_NO_KILL;
- }
- baseDamage = 0;
- }
- else if ( ent->client->ps.saberLockTime > level.time )
- {//just do effects
- baseDamage = 0;
- }
- else if ( ent->client->ps.saberBlocked > BLOCKED_NONE
- || ( !PM_SaberInAttack( ent->client->ps.saberMove )
- && !PM_SaberInSpecialAttack( ent->client->ps.torsoAnim )
- && !PM_SaberInTransitionAny( ent->client->ps.saberMove )
- )
- )
- {//don't do damage if parrying/reflecting/bouncing/deflecting or not actually attacking or in a transition to/from/between attacks
- baseDamage = 0;
- }
- else
- {//okay, in a saberMove that does damage
- //make sure we're in the right anim
- if ( !PM_SaberInSpecialAttack( ent->client->ps.torsoAnim )
- && !PM_InAnimForSaberMove( ent->client->ps.torsoAnim, ent->client->ps.saberMove ) )
- {//forced into some other animation somehow, like a pain or death?
- baseDamage = 0;
- }
- else if ( ent->client->ps.weaponstate == WEAPON_FIRING && ent->client->ps.saberBlocked == BLOCKED_NONE &&
- ( PM_SaberInAttack(ent->client->ps.saberMove) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) || PM_SpinningSaberAnim(ent->client->ps.torsoAnim) || entPowerLevel > FORCE_LEVEL_2 ) )// || ent->client->ps.saberAnimLevel == SS_STAFF ) )
- {//normal attack swing swinging/spinning (or if using strong set), do normal damage //FIXME: or if using staff?
- //FIXME: more damage for higher attack power levels?
- // More damage based on length/color of saber?
- //FIXME: Desann does double damage?
- if ( g_saberRealisticCombat->integer )
- {
- switch ( entPowerLevel )
- {
- default:
- case FORCE_LEVEL_3:
- baseDamage = 10.0f;
- break;
- case FORCE_LEVEL_2:
- baseDamage = 5.0f;
- break;
- case FORCE_LEVEL_0:
- case FORCE_LEVEL_1:
- baseDamage = 2.5f;
- break;
- }
- }
- else
- {
- if ( g_spskill->integer > 0
- && ent->s.number < MAX_CLIENTS
- && ( ent->client->ps.torsoAnim == BOTH_ROLL_STAB
- || ent->client->ps.torsoAnim == BOTH_SPINATTACK6
- || ent->client->ps.torsoAnim == BOTH_SPINATTACK7
- || ent->client->ps.torsoAnim == BOTH_LUNGE2_B__T_ ) )
- {//*sigh*, these anim do less damage since they're so easy to do
- baseDamage = 2.5f;
- }
- else
- {
- baseDamage = 2.5f * (float)entPowerLevel;
- }
- }
- }
- else
- {//saber is transitioning, defending or idle, don't do as much damage
- //FIXME: strong attacks and returns should do damage and be unblockable
- if ( g_timescale->value < 1.0 )
- {//in slow mo or force speed, we need to do damage during the transitions
- if ( g_saberRealisticCombat->integer )
- {
- switch ( entPowerLevel )
- {
- case FORCE_LEVEL_3:
- baseDamage = 10.0f;
- break;
- case FORCE_LEVEL_2:
- baseDamage = 5.0f;
- break;
- default:
- case FORCE_LEVEL_1:
- baseDamage = 2.5f;
- break;
- }
- }
- else
- {
- baseDamage = 2.5f * (float)entPowerLevel;
- }
- }
- else// if ( !ent->s.number )
- {//I have to do *some* damage in transitions or else you feel like a total gimp
- baseDamage = 0.1f;
- }
- /*
- else
- {
- baseDamage = 0;//was 1.0f;//was 0.25
- }
- */
- }
- }
- //Use current to next since can predict it
- //FIXME: if they're closer than the saber blade start, we don't want the
- // arm to pass through them without any damage... so check the radius
- // and push them away (do pain & knockback)
- //FIXME: if going into/coming from a parry/reflection or going into a deflection, don't use old mp & dir? Otherwise, deflections will cut through?
- //VectorCopy( ent->client->renderInfo.muzzlePoint, mp1 );
- //VectorCopy( ent->client->renderInfo.muzzleDir, md1 );
- //VectorCopy( ent->client->renderInfo.muzzlePointNext, mp2 );
- //VectorCopy( ent->client->renderInfo.muzzleDirNext, md2 );
- //prediction was causing gaps in swing (G2 problem) so *don't* predict
- VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePointOld, mp1 );
- VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDirOld, md1 );
- VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, mp2 );
- VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, md2 );
- //NOTE: this is a test, may not be necc, as I can still swing right through someone without hitting them, somehow...
- //see if anyone is so close that they're within the dist from my origin to the start of the saber
- if ( ent->health > 0 && !ent->client->ps.saberLockTime && saberNum == 0 && bladeNum == 0
- && !G_InCinematicSaberAnim( ent ) )
- {//only do once - for first blade
- trace_t trace;
- gi.trace( &trace, ent->currentOrigin, vec3_origin, vec3_origin, mp1, ent->s.number, (MASK_SHOT&~(CONTENTS_CORPSE|CONTENTS_ITEM)) );
- if ( trace.entityNum < ENTITYNUM_WORLD && (trace.entityNum > 0||ent->client->NPC_class == CLASS_DESANN) )//NPCs don't push player away, unless it's Desann
- {//a valid ent
- gentity_t *traceEnt = &g_entities[trace.entityNum];
- if ( traceEnt
- && traceEnt->client
- && traceEnt->client->NPC_class != CLASS_RANCOR
- && traceEnt->client->NPC_class != CLASS_ATST
- && traceEnt->client->NPC_class != CLASS_WAMPA
- && traceEnt->client->NPC_class != CLASS_SAND_CREATURE
- && traceEnt->health > 0
- && traceEnt->client->playerTeam != ent->client->playerTeam
- && !PM_SuperBreakLoseAnim( traceEnt->client->ps.legsAnim )
- && !PM_SuperBreakLoseAnim( traceEnt->client->ps.torsoAnim )
- && !PM_SuperBreakWinAnim( traceEnt->client->ps.legsAnim )
- && !PM_SuperBreakWinAnim( traceEnt->client->ps.torsoAnim )
- && !PM_InKnockDown( &traceEnt->client->ps )
- && !PM_LockedAnim( traceEnt->client->ps.legsAnim )
- && !PM_LockedAnim( traceEnt->client->ps.torsoAnim )
- && !G_InCinematicSaberAnim( traceEnt ))
- {//enemy client, push them away
- if ( !traceEnt->client->ps.saberLockTime
- && !traceEnt->message
- && !(traceEnt->flags&FL_NO_KNOCKBACK)
- && (!traceEnt->NPC||traceEnt->NPC->jumpState!=JS_JUMPING) )
- {//don't push people in saberlock or with security keys or who are in BS_JUMP
- vec3_t hitDir;
- VectorSubtract( trace.endpos, ent->currentOrigin, hitDir );
- float totalDist = Distance( mp1, ent->currentOrigin );
- float knockback = (totalDist-VectorNormalize( hitDir ))/totalDist * 200.0f;
- hitDir[2] = 0;
- //FIXME: do we need to call G_Throw? Seems unfair to put actual knockback on them, stops the attack
- //G_Throw( traceEnt, hitDir, knockback );
- VectorMA( traceEnt->client->ps.velocity, knockback, hitDir, traceEnt->client->ps.velocity );
- traceEnt->client->ps.pm_time = 200;
- traceEnt->client->ps.pm_flags |= PMF_TIME_NOFRICTION;
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer )
- {
- gi.Printf( "%s pushing away %s at %s\n", ent->NPC_type, traceEnt->NPC_type, vtos( traceEnt->client->ps.velocity ) );
- }
- #endif
- }
- }
- }
- }
- }
- //the thicker the blade, the more damage... the thinner, the less damage
- baseDamage *= ent->client->ps.saber[saberNum].blade[bladeNum].radius/SABER_RADIUS_STANDARD;
- if ( g_saberRealisticCombat->integer > 1 )
- {//always do damage, and lots of it
- if ( g_saberRealisticCombat->integer > 2 )
- {//always do damage, and lots of it
- baseDamage = 25.0f;
- }
- else if ( baseDamage > 0.1f )
- {//only do super damage if we would have done damage according to normal rules
- baseDamage = 25.0f;
- }
- }
- else if ( ((!ent->s.number&&ent->client->ps.forcePowersActive&(1<<FP_SPEED))||ent->client->ps.forcePowersActive&(1<<FP_RAGE))
- && g_timescale->value < 1.0f )
- {
- baseDamage *= (1.0f-g_timescale->value);
- }
- if ( baseDamage > 0.1f )
- {
- if ( (ent->client->ps.forcePowersActive&(1<<FP_RAGE)) )
- {//add some damage if raged
- baseDamage += ent->client->ps.forcePowerLevel[FP_RAGE] * 5.0f;
- }
- else if ( ent->client->ps.forceRageRecoveryTime )
- {//halve it if recovering
- baseDamage *= 0.5f;
- }
- }
- // Get the old state of the blade
- VectorCopy( mp1, baseOld );
- VectorMA( baseOld, ent->client->ps.saber[saberNum].blade[bladeNum].length, md1, endOld );
- // Get the future state of the blade
- VectorCopy( mp2, baseNew );
- VectorMA( baseNew, ent->client->ps.saber[saberNum].blade[bladeNum].length, md2, endNew );
- sabersCrossed = -1;
- if ( VectorCompare2( baseOld, baseNew ) && VectorCompare2( endOld, endNew ) )
- {
- hit_wall = WP_SaberDamageForTrace( ent->s.number, mp2, endNew, baseDamage*4, md2,
- qfalse, entPowerLevel, ent->client->ps.saber[saberNum].type, qfalse,
- saberNum, bladeNum );
- }
- else
- {
- float aveLength, step = 8, stepsize = 8;
- vec3_t ma1, ma2, md2ang, curBase1, curBase2;
- int xx;
- //do the trace at the base first
- hit_wall = WP_SaberDamageForTrace( ent->s.number, baseOld, baseNew, baseDamage, md2,
- qfalse, entPowerLevel, ent->client->ps.saber[saberNum].type, qtrue,
- saberNum, bladeNum );
-
- //if hit a saber, shorten rest of traces to match
- if ( saberHitFraction < 1.0 )
- {
- //adjust muzzleDir...
- vec3_t ma1, ma2;
- vectoangles( md1, ma1 );
- vectoangles( md2, ma2 );
- for ( xx = 0; xx < 3; xx++ )
- {
- md2ang[xx] = LerpAngle( ma1[xx], ma2[xx], saberHitFraction );
- }
- AngleVectors( md2ang, md2, NULL, NULL );
- //shorten the base pos
- VectorSubtract( mp2, mp1, baseDiff );
- VectorMA( mp1, saberHitFraction, baseDiff, baseNew );
- VectorMA( baseNew, ent->client->ps.saber[saberNum].blade[bladeNum].length, md2, endNew );
- }
- //If the angle diff in the blade is high, need to do it in chunks of 33 to avoid flattening of the arc
- float dirInc, curDirFrac;
- if ( PM_SaberInAttack( ent->client->ps.saberMove )
- || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim )
- || PM_SpinningSaberAnim( ent->client->ps.torsoAnim )
- || PM_InSpecialJump( ent->client->ps.torsoAnim )
- || (g_timescale->value<1.0f&&PM_SaberInTransitionAny( ent->client->ps.saberMove )) )
- {
- curDirFrac = DotProduct( md1, md2 );
- }
- else
- {
- curDirFrac = 1.0f;
- }
- //NOTE: if saber spun at least 180 degrees since last damage trace, this is not reliable...!
- if ( fabs(curDirFrac) < 1.0f - MAX_SABER_SWING_INC )
- {//the saber blade spun more than 33 degrees since the last damage trace
- curDirFrac = dirInc = 1.0f/((1.0f - curDirFrac)/MAX_SABER_SWING_INC);
- }
- else
- {
- curDirFrac = 1.0f;
- dirInc = 0.0f;
- }
- qboolean hit_saber = qfalse;
- vectoangles( md1, ma1 );
- vectoangles( md2, ma2 );
- vec3_t curMD1, curMD2;//, mdDiff, dirDiff;
- //VectorSubtract( md2, md1, mdDiff );
- VectorCopy( md1, curMD2 );
- VectorCopy( baseOld, curBase2 );
- while ( 1 )
- {
- VectorCopy( curMD2, curMD1 );
- VectorCopy( curBase2, curBase1 );
- if ( curDirFrac >= 1.0f )
- {
- VectorCopy( md2, curMD2 );
- VectorCopy( baseNew, curBase2 );
- }
- else
- {
- for ( xx = 0; xx < 3; xx++ )
- {
- md2ang[xx] = LerpAngle( ma1[xx], ma2[xx], curDirFrac );
- }
- AngleVectors( md2ang, curMD2, NULL, NULL );
- //VectorMA( md1, curDirFrac, mdDiff, curMD2 );
- VectorSubtract( baseNew, baseOld, baseDiff );
- VectorMA( baseOld, curDirFrac, baseDiff, curBase2 );
- }
- // Move up the blade in intervals of stepsize
- for ( step = stepsize; step < ent->client->ps.saber[saberNum].blade[bladeNum].length && step < ent->client->ps.saber[saberNum].blade[bladeNum].lengthOld; step+=12 )
- {
- VectorMA( curBase1, step, curMD1, bladePointOld );
- VectorMA( curBase2, step, curMD2, bladePointNew );
- if ( WP_SaberDamageForTrace( ent->s.number, bladePointOld, bladePointNew, baseDamage, curMD2,
- qfalse, entPowerLevel, ent->client->ps.saber[saberNum].type, qtrue,
- saberNum, bladeNum ) )
- {
- hit_wall = qtrue;
- }
- //if hit a saber, shorten rest of traces to match
- if ( saberHitFraction < 1.0 )
- {
- //adjust muzzle endpoint
- VectorSubtract( mp2, mp1, baseDiff );
- VectorMA( mp1, saberHitFraction, baseDiff, baseNew );
- VectorMA( baseNew, ent->client->ps.saber[saberNum].blade[bladeNum].length, curMD2, endNew );
- //adjust muzzleDir...
- vec3_t curMA1, curMA2;
- vectoangles( curMD1, curMA1 );
- vectoangles( curMD2, curMA2 );
- for ( xx = 0; xx < 3; xx++ )
- {
- md2ang[xx] = LerpAngle( curMA1[xx], curMA2[xx], saberHitFraction );
- }
- AngleVectors( md2ang, curMD2, NULL, NULL );
- /*
- VectorSubtract( curMD2, curMD1, dirDiff );
- VectorMA( curMD1, saberHitFraction, dirDiff, curMD2 );
- */
- hit_saber = qtrue;
- }
- if (hit_wall)
- {
- break;
- }
- }
- if ( hit_wall || hit_saber )
- {
- break;
- }
- if ( curDirFrac >= 1.0f )
- {
- break;
- }
- else
- {
- curDirFrac += dirInc;
- if ( curDirFrac >= 1.0f )
- {
- curDirFrac = 1.0f;
- }
- }
- }
- //do the trace at the end last
- //Special check- adjust for length of blade not being a multiple of 12
- aveLength = (ent->client->ps.saber[saberNum].blade[bladeNum].lengthOld + ent->client->ps.saber[saberNum].blade[bladeNum].length)/2;
- if ( step > aveLength )
- {//less dmg if the last interval was not stepsize
- tipDmgMod = (stepsize-(step-aveLength))/stepsize;
- }
- //NOTE: since this is the tip, we do not extrapolate the extra 16
- if ( WP_SaberDamageForTrace( ent->s.number, endOld, endNew, tipDmgMod*baseDamage, md2,
- qfalse, entPowerLevel, ent->client->ps.saber[saberNum].type, qfalse,
- saberNum, bladeNum ) )
- {
- hit_wall = qtrue;
- }
- }
- if ( (saberHitFraction < 1.0f||(sabersCrossed>=0&&sabersCrossed<=32.0f)) && (ent->client->ps.weaponstate == WEAPON_FIRING || ent->client->ps.saberInFlight || G_InCinematicSaberAnim( ent ) ) )
- {// The saber (in-hand) hit another saber, mano.
- qboolean inFlightSaberBlocked = qfalse;
- qboolean collisionResolved = qfalse;
- qboolean deflected = qfalse;
- gentity_t *hitEnt = &g_entities[saberHitEntity];
- gentity_t *hitOwner = NULL;
- int hitOwnerPowerLevel = FORCE_LEVEL_0;
- if ( hitEnt )
- {
- hitOwner = hitEnt->owner;
- }
- if ( hitOwner && hitOwner->client )
- {
- hitOwnerPowerLevel = PM_PowerLevelForSaberAnim( &hitOwner->client->ps );
- /*
- if ( entPowerLevel >= FORCE_LEVEL_3
- && PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) )
- {//a special "unblockable" attack
- if ( hitOwner->client->NPC_class == CLASS_ALORA
- || hitOwner->client->NPC_class == CLASS_SHADOWTROOPER
- || (hitOwner->NPC&&(hitOwner->NPC->aiFlags&NPCAI_BOSS_CHARACTER)) )
- {//these masters can even block unblockables (stops cheap kills)
- entPowerLevel = FORCE_LEVEL_2;
- }
- }
- */
- }
- //FIXME: check for certain anims, facing, etc, to make them lock into a sabers-locked pose
- //SEF_LOCKED
- if ( ent->client->ps.saberInFlight && saberNum == 0 &&
- ent->client->ps.saber[saberNum].blade[bladeNum].active &&
- ent->client->ps.saberEntityNum != ENTITYNUM_NONE &&
- ent->client->ps.saberEntityState != SES_RETURNING )
- {//saber was blocked, return it
- inFlightSaberBlocked = qtrue;
- }
- //FIXME: based on strength, position and angle of attack & defense, decide if:
- // defender and attacker lock sabers
- // *defender's parry should hold and attack bounces (or deflects, based on angle of sabers)
- // *defender's parry is somewhat broken and both bounce (or deflect)
- // *defender's parry is broken and they bounce while attacker's attack deflects or carries through (especially if they're dead)
- // defender is knocked down and attack goes through
- //Check deflections and broken parries
- if ( hitOwner && hitOwner->health > 0 && ent->health > 0 //both are alive
- && !inFlightSaberBlocked && hitOwner->client && !hitOwner->client->ps.saberInFlight && !ent->client->ps.saberInFlight//both have sabers in-hand
- && ent->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN
- && ent->client->ps.saberLockTime < level.time
- && hitOwner->client->ps.saberLockTime < level.time )
- {//2 in-hand sabers hit
- //FIXME: defender should not parry or block at all if not in a saber anim... like, if in a roll or knockdown...
- if ( baseDamage )
- {//there is damage involved, not just effects
- qboolean entAttacking = qfalse;
- qboolean hitOwnerAttacking = qfalse;
- qboolean entDefending = qfalse;
- qboolean hitOwnerDefending = qfalse;
- qboolean forceLock = qfalse;
- if ( (ent->client->NPC_class == CLASS_KYLE && (ent->spawnflags&1) && hitOwner->s.number < MAX_CLIENTS )
- || (hitOwner->client->NPC_class == CLASS_KYLE && (hitOwner->spawnflags&1) && ent->s.number < MAX_CLIENTS ) )
- {//Player vs. Kyle Boss == lots of saberlocks
- if ( !Q_irand( 0, 2 ) )
- {
- forceLock = qtrue;
- }
- }
-
- if ( PM_SaberInAttack( ent->client->ps.saberMove ) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) )
- {
- entAttacking = qtrue;
- }
- else if ( entPowerLevel > FORCE_LEVEL_2 )
- {//stronger styles count as attacking even if in a transition
- if ( PM_SaberInTransitionAny( ent->client->ps.saberMove ) )
- {
- entAttacking = qtrue;
- }
- }
- if ( PM_SaberInParry( ent->client->ps.saberMove )
- || ent->client->ps.saberMove == LS_READY )
- {
- entDefending = qtrue;
- }
- if ( ent->client->ps.torsoAnim == BOTH_A1_SPECIAL
- || ent->client->ps.torsoAnim == BOTH_A2_SPECIAL
- || ent->client->ps.torsoAnim == BOTH_A3_SPECIAL )
- {//parry/block/break-parry bonus for single-style kata moves
- entPowerLevel++;
- }
- if ( entAttacking )
- {//add twoHanded bonus and breakParryBonus to entPowerLevel here
- //This makes staff too powerful
- if ( ent->client->ps.saber[saberNum].twoHanded )
- {
- entPowerLevel++;
- }
- //FIXME: what if dualSabers && both sabers are hitting at same time?
- entPowerLevel += ent->client->ps.saber[saberNum].breakParryBonus;
- }
- else if ( entDefending )
- {//add twoHanded bonus and dualSaber bonus and parryBonus to entPowerLevel here
- if ( ent->client->ps.saber[saberNum].twoHanded
- || (ent->client->ps.dualSabers && ent->client->ps.saber[1].Active()) )
- {
- entPowerLevel++;
- }
- //FIXME: what about second saber if dualSabers?
- entPowerLevel += ent->client->ps.saber[saberNum].parryBonus;
- }
- if ( PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) )
- {
- hitOwnerAttacking = qtrue;
- }
- else if ( hitOwnerPowerLevel > FORCE_LEVEL_2 )
- {//stronger styles count as attacking even if in a transition
- if ( PM_SaberInTransitionAny( hitOwner->client->ps.saberMove ) )
- {
- hitOwnerAttacking = qtrue;
- }
- }
- if ( PM_SaberInParry( hitOwner->client->ps.saberMove )
- || hitOwner->client->ps.saberMove == LS_READY )
- {
- hitOwnerDefending = qtrue;
- }
- if ( hitOwner->client->ps.torsoAnim == BOTH_A1_SPECIAL
- || hitOwner->client->ps.torsoAnim == BOTH_A2_SPECIAL
- || hitOwner->client->ps.torsoAnim == BOTH_A3_SPECIAL )
- {//parry/block/break-parry bonus for single-style kata moves
- hitOwnerPowerLevel++;
- }
- if ( hitOwnerAttacking )
- {//add twoHanded bonus and breakParryBonus to entPowerLevel here
- if ( hitOwner->client->ps.saber[0].twoHanded )
- {
- hitOwnerPowerLevel++;
- }
- hitOwnerPowerLevel += hitOwner->client->ps.saber[0].breakParryBonus;
- if ( hitOwner->client->ps.dualSabers && Q_irand( 0, 1 ) )
- {//FIXME: assumes both sabers are hitting at same time...?
- hitOwnerPowerLevel += 1 + hitOwner->client->ps.saber[1].breakParryBonus;
- }
- }
- else if ( hitOwnerDefending )
- {//add twoHanded bonus and dualSaber bonus and parryBonus to entPowerLevel here
- if ( hitOwner->client->ps.saber[0].twoHanded || (hitOwner->client->ps.dualSabers && hitOwner->client->ps.saber[1].Active()) )
- {
- hitOwnerPowerLevel++;
- }
- hitOwnerPowerLevel += hitOwner->client->ps.saber[0].parryBonus;
- if ( hitOwner->client->ps.dualSabers && Q_irand( 0, 1 ) )
- {//FIXME: assumes both sabers are defending at same time...?
- hitOwnerPowerLevel += 1 + hitOwner->client->ps.saber[1].parryBonus;
- }
- }
- if ( PM_SuperBreakLoseAnim( ent->client->ps.torsoAnim )
- || PM_SuperBreakWinAnim( ent->client->ps.torsoAnim )
- || PM_SuperBreakLoseAnim( hitOwner->client->ps.torsoAnim )
- || PM_SuperBreakWinAnim( hitOwner->client->ps.torsoAnim ) )
- {//don't mess with this
- collisionResolved = qtrue;
- }
- else if ( entAttacking
- && hitOwnerAttacking
- && !Q_irand( 0, g_saberLockRandomNess->integer )
- && ( g_debugSaberLock->integer || forceLock
- || entPowerLevel == hitOwnerPowerLevel
- || (entPowerLevel > FORCE_LEVEL_2 && hitOwnerPowerLevel > FORCE_LEVEL_2 )
- || (entPowerLevel < FORCE_LEVEL_3 && hitOwnerPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_2 && Q_irand( 0, 3 ))
- || (entPowerLevel < FORCE_LEVEL_2 && hitOwnerPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_1 && Q_irand( 0, 2 ))
- || (hitOwnerPowerLevel < FORCE_LEVEL_3 && entPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_2 && !Q_irand( 0, 1 ))
- || (hitOwnerPowerLevel < FORCE_LEVEL_2 && entPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_1 && !Q_irand( 0, 1 )))
- && WP_SabersCheckLock( ent, hitOwner ) )
- {
- collisionResolved = qtrue;
- }
- else if ( hitOwnerAttacking
- && entDefending
- && !Q_irand( 0, g_saberLockRandomNess->integer*3 )
- && (g_debugSaberLock->integer || forceLock ||
- ((ent->client->ps.saberMove != LS_READY || (hitOwnerPowerLevel-ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE]) < Q_irand( -6, 0 ) )
- && ((hitOwnerPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 )||
- (hitOwnerPowerLevel < FORCE_LEVEL_2 && ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_1 )||
- (hitOwnerPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_0 && !Q_irand( 0, (hitOwnerPowerLevel-ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE]+1)*2 ))) ))
- && WP_SabersCheckLock( hitOwner, ent ) )
- {
- collisionResolved = qtrue;
- }
- else if ( entAttacking && hitOwnerDefending )
- {//I'm attacking hit, they're parrying
- qboolean activeDefense = (hitOwner->s.number||g_saberAutoBlocking->integer||hitOwner->client->ps.saberBlockingTime > level.time);
- if ( !Q_irand( 0, g_saberLockRandomNess->integer*3 )
- && activeDefense
- && (g_debugSaberLock->integer || forceLock ||
- ((hitOwner->client->ps.saberMove != LS_READY || (entPowerLevel-hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]) < Q_irand( -6, 0 ) )
- && ( ( entPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 )
- || ( entPowerLevel < FORCE_LEVEL_2 && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_1 )
- || ( entPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_0 && !Q_irand( 0, (entPowerLevel-hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]+1)*2 )) ) ))
- && WP_SabersCheckLock( ent, hitOwner ) )
- {
- collisionResolved = qtrue;
- }
- else if ( saberHitFraction < 1.0f )
- {//an actual collision
- if ( entPowerLevel < FORCE_LEVEL_3 && activeDefense )
- {//strong attacks cannot be deflected
- //based on angle of attack & angle of defensive saber, see if I should deflect off in another dir rather than bounce back
- deflected = WP_GetSaberDeflectionAngle( ent, hitOwner );
- //just so Jedi knows that he was blocked
- ent->client->ps.saberEventFlags |= SEF_BLOCKED;
- }
- //base parry breaks on animation (saber attack level), not FP_SABER_OFFENSE
- if ( entPowerLevel < FORCE_LEVEL_3
- //&& ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] < FORCE_LEVEL_3//if you have high saber offense, you cannot have your attack knocked away, regardless of what style you're using?
- //&& hitOwner->client->ps.saberAnimLevel != FORCE_LEVEL_5
- && activeDefense
- && (hitOwnerPowerLevel > FORCE_LEVEL_2||(hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]>FORCE_LEVEL_2&&Q_irand(0,hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE]))) )
- {//knockaways can make fast-attacker go into a broken parry anim if the ent is using fast or med (but not Tavion)
- //make me parry
- WP_SaberParry( hitOwner, ent );
- //turn the parry into a knockaway
- hitOwner->client->ps.saberBounceMove = PM_KnockawayForParry( hitOwner->client->ps.saberBlocked );
- //make them go into a broken parry
- ent->client->ps.saberBounceMove = PM_BrokenParryForAttack( ent->client->ps.saberMove );
- ent->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
- if ( saberNum == 0 )
- {//FIXME: can only lose right-hand saber for now
- if ( ent->client->ps.saber[saberNum].disarmable
- && ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] < FORCE_LEVEL_2
- //&& (ent->s.number||g_saberRealisticCombat->integer)
- && Q_irand( 0, hitOwner->client->ps.SaberDisarmBonus() ) > 0
- && (hitOwner->s.number || g_saberAutoBlocking->integer || !Q_irand( 0, 2 )) )//if player defending and autoblocking is on, this is less likely to happen, so don't do the random check
- {//knocked the saber right out of his hand! (never happens to player)
- //Get a good velocity to send the saber in based on my parry move
- vec3_t throwDir;
- if ( !PM_VelocityForBlockedMove( &hitOwner->client->ps, throwDir ) )
- {
- PM_VelocityForSaberMove( &ent->client->ps, throwDir );
- }
- WP_SaberLose( ent, throwDir );
- }
- }
- //just so Jedi knows that he was blocked
- ent->client->ps.saberEventFlags |= SEF_BLOCKED;
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer )
- {
- gi.Printf( S_COLOR_RED"%s knockaway %s's attack, new move = %s, anim = %s\n", hitOwner->NPC_type, ent->NPC_type, saberMoveData[ent->client->ps.saberBounceMove].name, animTable[saberMoveData[ent->client->ps.saberBounceMove].animToUse].name );
- }
- #endif
- }
- else if ( !activeDefense//they're not defending
- || (entPowerLevel > FORCE_LEVEL_2 //I hit hard
- && hitOwnerPowerLevel < entPowerLevel)//they are defending, but their defense strength is lower than my attack...
- || (!deflected && Q_irand( 0, PM_PowerLevelForSaberAnim( &ent->client->ps, saberNum ) - hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]/*PM_PowerLevelForSaberAnim( &hitOwner->client->ps )*/ ) > 0 ) )
- {//broke their parry altogether
- if ( entPowerLevel > FORCE_LEVEL_2 || Q_irand( 0, ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] - hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] ) )
- {//chance of continuing with the attack (not bouncing back)
- ent->client->ps.saberEventFlags &= ~SEF_BLOCKED;
- ent->client->ps.saberBounceMove = LS_NONE;
- brokenParry = qtrue;
- }
- //do some time-consuming saber-knocked-aside broken parry anim
- hitOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
- hitOwner->client->ps.saberBounceMove = LS_NONE;
- //FIXME: for now, you always disarm the right-hand saber
- if ( hitOwner->client->ps.saber[0].disarmable
- && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] < FORCE_LEVEL_2
- //&& (ent->s.number||g_saberRealisticCombat->integer)
- && Q_irand( 0, 2-ent->client->ps.SaberDisarmBonus() ) <= 0 )
- {//knocked the saber right out of his hand!
- //get the right velocity for my attack direction
- vec3_t throwDir;
- PM_VelocityForSaberMove( &ent->client->ps, throwDir );
- WP_SaberLose( hitOwner, throwDir );
- if ( (ent->client->ps.saberAnimLevel == SS_STRONG && !Q_irand(0,3) )
- || ( ent->client->ps.saberAnimLevel==SS_DESANN&&!Q_irand(0,1) ) )
- {// a strong attack
- if ( WP_BrokenParryKnockDown( hitOwner ) )
- {
- hitOwner->client->ps.saberBlocked = BLOCKED_NONE;
- hitOwner->client->ps.saberBounceMove = LS_NONE;
- }
- }
- }
- else
- {
- if ( (ent->client->ps.saberAnimLevel == SS_STRONG && !Q_irand(0,5) )
- || ( ent->client->ps.saberAnimLevel==SS_DESANN&&!Q_irand(0,3) ) )
- {// a strong attack
- if ( WP_BrokenParryKnockDown( hitOwner ) )
- {
- hitOwner->client->ps.saberBlocked = BLOCKED_NONE;
- hitOwner->client->ps.saberBounceMove = LS_NONE;
- }
- }
- }
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer )
- {
- if ( ent->client->ps.saberEventFlags&SEF_BLOCKED )
- {
- gi.Printf( S_COLOR_RED"%s parry broken (bounce/deflect)!\n", hitOwner->targetname );
- }
- else
- {
- gi.Printf( S_COLOR_RED"%s parry broken (follow-through)!\n", hitOwner->targetname );
- }
- }
- #endif
- }
- else
- {//just a parry, possibly the hitOwner can knockaway the ent
- WP_SaberParry( hitOwner, ent );
- if ( PM_SaberInBounce( ent->client->ps.saberMove ) //FIXME: saberMove not set until pmove!
- && activeDefense
- && hitOwner->client->ps.saberAnimLevel != SS_FAST //&& hitOwner->client->ps.saberAnimLevel != FORCE_LEVEL_5
- && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 )
- {//attacker bounced off, and defender has ability to do knockaways, so do one unless we're using fast attacks
- //turn the parry into a knockaway
- hitOwner->client->ps.saberBounceMove = PM_KnockawayForParry( hitOwner->client->ps.saberBlocked );
- }
- else if ( (ent->client->ps.saberAnimLevel == SS_STRONG && !Q_irand(0,6) )
- || ( ent->client->ps.saberAnimLevel==SS_DESANN && !Q_irand(0,3) ) )
- {// a strong attack can sometimes do a knockdown
- //HMM... maybe only if they're moving backwards?
- if ( WP_BrokenParryKnockDown( hitOwner ) )
- {
- hitOwner->client->ps.saberBlocked = BLOCKED_NONE;
- hitOwner->client->ps.saberBounceMove = LS_NONE;
- }
- }
- }
- collisionResolved = qtrue;
- }
- }
- /*
- else if ( entDefending && hitOwnerAttacking )
- {//I'm parrying, they're attacking
- if ( hitOwnerPowerLevel < FORCE_LEVEL_3 )
- {//strong attacks cannot be deflected
- //based on angle of attack & angle of defensive saber, see if I should deflect off in another dir rather than bounce back
- deflected = WP_GetSaberDeflectionAngle( hitOwner, ent );
- //just so Jedi knows that he was blocked
- hitOwner->client->ps.saberEventFlags |= SEF_BLOCKED;
- }
- //FIXME: base parry breaks on animation (saber attack level), not FP_SABER_OFFENSE
- if ( hitOwnerPowerLevel > FORCE_LEVEL_2 || (!deflected && Q_irand( 0, PM_PowerLevelForSaberAnim( &hitOwner->client->ps ) - ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] ) > 0 ) )
- {//broke my parry altogether
- if ( hitOwnerPowerLevel > FORCE_LEVEL_2 || Q_irand( 0, hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE] - ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] ) )
- {//chance of continuing with the attack (not bouncing back)
- hitOwner->client->ps.saberEventFlags &= ~SEF_BLOCKED;
- hitOwner->client->ps.saberBounceMove = LS_NONE;
- }
- //do some time-consuming saber-knocked-aside broken parry anim
- ent->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer )
- {
- if ( hitOwner->client->ps.saberEventFlags&SEF_BLOCKED )
- {
- gi.Printf( S_COLOR_RED"%s parry broken (bounce/deflect)!\n", ent->targetname );
- }
- else
- {
- gi.Printf( S_COLOR_RED"%s parry broken (follow-through)!\n", ent->targetname );
- }
- }
- #endif
- }
- else
- {
- WP_SaberParry( ent, hitOwner );
- }
- collisionResolved = qtrue;
- }
- */
- else
- {//some other kind of in-hand saber collision
- }
- }
- }
- else
- {//some kind of in-flight collision
- }
- if ( saberHitFraction < 1.0f )
- {
- if ( !collisionResolved && baseDamage )
- {//some other kind of in-hand saber collision
- //handle my reaction
- if ( !ent->client->ps.saberInFlight
- && ent->client->ps.saberLockTime < level.time )
- {//my saber is in hand
- if ( ent->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
- {
- if ( PM_SaberInAttack( ent->client->ps.saberMove ) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) ||
- (entPowerLevel > FORCE_LEVEL_2&&!PM_SaberInIdle(ent->client->ps.saberMove)&&!PM_SaberInParry(ent->client->ps.saberMove)&&!PM_SaberInReflect(ent->client->ps.saberMove)) )
- {//in the middle of attacking
- if ( entPowerLevel < FORCE_LEVEL_3 && hitOwner->health > 0 )
- {//don't deflect/bounce in strong attack or when enemy is dead
- WP_GetSaberDeflectionAngle( ent, hitOwner );
- ent->client->ps.saberEventFlags |= SEF_BLOCKED;
- //since it was blocked/deflected, take away any damage done
- //FIXME: what if the damage was done before the parry?
- WP_SaberClearDamageForEntNum( hitOwner->s.number );
- }
- }
- else
- {//saber collided when not attacking, parry it
- //since it was blocked/deflected, take away any damage done
- //FIXME: what if the damage was done before the parry?
- WP_SaberClearDamageForEntNum( hitOwner->s.number );
- /*
- if ( ent->s.number || g_saberAutoBlocking->integer || ent->client->ps.saberBlockingTime > level.time )
- {//either an NPC or a player who has blocking
- if ( !PM_SaberInTransitionAny( ent->client->ps.saberMove ) && !PM_SaberInBounce( ent->client->ps.saberMove ) )
- {//I'm not attacking, in transition or in a bounce, so play a parry
- //just so Jedi knows that he parried something
- WP_SaberBlockNonRandom( ent, saberHitLocation, qfalse );
- }
- ent->client->ps.saberEventFlags |= SEF_PARRIED;
- }
- */
- }
- }
- else
- {
- //since it was blocked/deflected, take away any damage done
- //FIXME: what if the damage was done before the parry?
- WP_SaberClearDamageForEntNum( hitOwner->s.number );
- }
- }
- else
- {//nothing happens to *me* when my inFlight saber hits something
- }
- //handle their reaction
- if ( hitOwner
- && hitOwner->health > 0
- && hitOwner->client
- && !hitOwner->client->ps.saberInFlight
- && hitOwner->client->ps.saberLockTime < level.time )
- {//their saber is in hand
- if ( PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) ||
- (hitOwner->client->ps.saberAnimLevel > SS_MEDIUM&&!PM_SaberInIdle(hitOwner->client->ps.saberMove)&&!PM_SaberInParry(hitOwner->client->ps.saberMove)&&!PM_SaberInReflect(hitOwner->client->ps.saberMove)) )
- {//in the middle of attacking
- /*
- if ( hitOwner->client->ps.saberAnimLevel < SS_STRONG )
- {//don't deflect/bounce in strong attack
- WP_GetSaberDeflectionAngle( hitOwner, ent );
- hitOwner->client->ps.saberEventFlags |= SEF_BLOCKED;
- }
- */
- }
- else
- {//saber collided when not attacking, parry it
- if ( !PM_SaberInBrokenParry( hitOwner->client->ps.saberMove ) )
- {//not currently in a broken parry
- if ( !WP_SaberParry( hitOwner, ent ) )
- {//FIXME: hitOwner can't parry, do some time-consuming saber-knocked-aside broken parry anim?
- //hitOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
- }
- }
- }
- }
- else
- {//nothing happens to *hitOwner* when their inFlight saber hits something
- }
- }
- //collision must have been handled by now
- //Set the blocked attack bounce value in saberBlocked so we actually play our saberBounceMove anim
- if ( ent->client->ps.saberEventFlags & SEF_BLOCKED )
- {
- if ( ent->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
- {
- ent->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
- }
- }
- /*
- if ( hitOwner && hitOwner->client->ps.saberEventFlags & SEF_BLOCKED )
- {
- hitOwner->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
- }
- */
- }
- if ( saberHitFraction < 1.0f || collisionResolved )
- {//either actually hit or locked
- if ( ent->client->ps.saberLockTime < level.time )
- {
- if ( inFlightSaberBlocked )
- {//FIXME: never hear this sound
- G_Sound( &g_entities[ent->client->ps.saberEntityNum], G_SoundIndex( va( "sound/weapons/saber/saberbounce%d.wav", Q_irand(1,3) ) ) );
- }
- else
- {
- if ( deflected )
- {
- #ifdef _IMMERSION
- int index = Q_irand(1,3);
- G_Sound( ent, G_SoundIndex( va("sound/weapons/saber/saberbounce%d.wav", index) ) );
- int ff = G_ForceIndex( va("fffx/weapons/saber/saberbounce%d", index), FF_CHANNEL_WEAPON );
- if ( !ent->s.saberInFlight )
- {
- G_Force( ent, ff );
- }
- if ( hitOwner && !hitOwner->s.saberInFlight )
- {
- G_Force( hitOwner, ff );
- }
- #else
- G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberbounce%d.wav", Q_irand(1,3) ) ) );
- #endif // _IMMERSION
- }
- else
- {
- #ifdef _IMMERSION
- int index = Q_irand(1, 9);
- G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", index) ) );
- int ff = G_ForceIndex( va("fffx/weapons/saber/saberblock%d", index), FF_CHANNEL_WEAPON );
- if ( !ent->s.saberInFlight )
- {
- G_Force( ent, ff );
- }
- if ( hitOwner && !hitOwner->s.saberInFlight )
- {
- G_Force( hitOwner, ff );
- }
- #else
- G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
- #endif // _IMMERSION
- }
- }
- if ( !g_saberNoEffects )
- {
- G_PlayEffect( "saber/saber_block", saberHitLocation, saberHitNormal );
- if(ent->client && ent->client->ps.clientNum == 0)
- FF_Play(fffx_Laser1);
- }
- }
- // Set the little screen flash - only when an attack is blocked
- g_saberFlashTime = level.time-50;
- VectorCopy( saberHitLocation, g_saberFlashPos );
- }
- if ( saberHitFraction < 1.0f )
- {
- if ( inFlightSaberBlocked )
- {//we threw a saber and it was blocked, do any effects, etc.
- int knockAway = 5;
- if ( hitEnt
- && hitOwner
- && hitOwner->client
- && (PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) || PM_SpinningSaberAnim( hitOwner->client->ps.torsoAnim )) )
- {//if hit someone who was in an attack or spin anim, more likely to have in-flight saber knocked away
- if ( hitOwnerPowerLevel > FORCE_LEVEL_2 )
- {//strong attacks almost always knock it aside!
- knockAway = 1;
- }
- else
- {//33% chance
- knockAway = 2;
- }
- knockAway -= hitOwner->client->ps.SaberDisarmBonus();
- }
- if ( Q_irand( 0, knockAway ) <= 0 || //random
- ( hitOwner
- && hitOwner->client
- && hitOwner->NPC
- && (hitOwner->NPC->aiFlags&NPCAI_BOSS_CHARACTER)
- ) //or if blocked by a Boss character FIXME: or base on defense level?
- )//FIXME: player should not auto-block a flying saber, let him override the parry with an attack to knock the saber from the air, rather than this random chance
- {//knock it aside and turn it off
- if ( !g_saberNoEffects )
- {
- G_PlayEffect( "saber/saber_cut", saberHitLocation, saberHitNormal );
- if(ent->client && ent->client->ps.clientNum == 0)
- FF_Play(fffx_Laser1);
- }
- if ( hitEnt )
- {
- vec3_t newDir;
- VectorSubtract( g_entities[ent->client->ps.saberEntityNum].currentOrigin, hitEnt->currentOrigin, newDir );
- VectorNormalize( newDir );
- G_ReflectMissile( ent, &g_entities[ent->client->ps.saberEntityNum], newDir );
- }
- Jedi_PlayDeflectSound( hitOwner );
- WP_SaberDrop( ent, &g_entities[ent->client->ps.saberEntityNum] );
- }
- else
- {
- if ( !Q_irand( 0, 2 ) && hitEnt )
- {
- vec3_t newDir;
- VectorSubtract( g_entities[ent->client->ps.saberEntityNum].currentOrigin, hitEnt->currentOrigin, newDir );
- VectorNormalize( newDir );
- G_ReflectMissile( ent, &g_entities[ent->client->ps.saberEntityNum], newDir );
- }
- WP_SaberReturn( ent, &g_entities[ent->client->ps.saberEntityNum] );
- }
- }
- }
- }
- if ( ent->client->ps.saberLockTime > level.time
- && ent->s.number < ent->client->ps.saberLockEnemy
- && !Q_irand( 0, 3 ) )
- {//need to make some kind of effect
- vec3_t hitNorm = {0,0,1};
- if ( WP_SabersIntersection( ent, &g_entities[ent->client->ps.saberLockEnemy], g_saberFlashPos ) )
- {
- if ( Q_irand( 0, 10 ) )
- {
- if ( !g_saberNoEffects )
- {
- G_PlayEffect( "saber/saber_block", g_saberFlashPos, hitNorm );
- if(ent->client && ent->client->ps.clientNum == 0)
- FF_Play(fffx_Laser1);
- }
- }
- else
- {
- g_saberFlashTime = level.time-50;
- if ( !g_saberNoEffects )
- {
- G_PlayEffect( "saber/saber_cut", g_saberFlashPos, hitNorm );
- if(ent->client && ent->client->ps.clientNum == 0)
- FF_Play(fffx_Laser1);
- }
- }
- #ifdef _IMMERSION
- int index = Q_irand(1, 9);
- G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", index ) ) );
- int ff = G_ForceIndex( va("fffx/weapons/saber/saberblock%d", index), FF_CHANNEL_WEAPON );
- if ( !ent->s.saberInFlight )
- {
- G_Force( ent, ff );
- }
- if ( !g_entities[ent->client->ps.saberLockEnemy].s.saberInFlight )
- {
- G_Force( &g_entities[ent->client->ps.saberLockEnemy], ff );
- }
- #else
- G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
- #endif // _IMMERSION
- }
- }
- if ( WP_SaberApplyDamage( ent, baseDamage, baseDFlags, brokenParry, ent->client->ps.saber[saberNum].type, (qboolean)(saberNum==0&&ent->client->ps.saberInFlight) ) )
- {//actually did damage to something
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer )
- {
- gi.Printf( "base damage was %4.2f\n", baseDamage );
- }
- #endif
- WP_SaberHitSound( ent, saberNum );
- }
-
- if ( hit_wall )
- {
- //just so Jedi knows that he hit a wall
- ent->client->ps.saberEventFlags |= SEF_HITWALL;
- if ( ent->s.number == 0 )
- {
- AddSoundEvent( ent, ent->currentOrigin, 128, AEL_DISCOVERED, qfalse, qtrue );//FIXME: is this impact on ground or not?
- AddSightEvent( ent, ent->currentOrigin, 256, AEL_DISCOVERED, 50 );
- }
- }
- }
- void WP_SabersDamageTrace( gentity_t *ent, qboolean noEffects )
- {
- if ( !ent->client )
- {
- return;
- }
- if ( PM_SuperBreakLoseAnim( ent->client->ps.torsoAnim ) )
- {
- return;
- }
- // Saber 1.
- g_saberNoEffects = noEffects;
- for ( int i = 0; i < ent->client->ps.saber[0].numBlades; i++ )
- {
- // If the Blade is not active and the length is 0, don't trace it, try the next blade...
- if ( !ent->client->ps.saber[0].blade[i].active && ent->client->ps.saber[0].blade[i].length == 0 )
- continue;
- if ( i != 0 )
- {//not first blade
- if ( ent->client->ps.saber[0].type == SABER_BROAD ||
- ent->client->ps.saber[0].type == SABER_SAI ||
- ent->client->ps.saber[0].type == SABER_CLAW )
- {
- g_saberNoEffects = qtrue;
- }
- }
- WP_SaberDamageTrace( ent, 0, i );
- }
- // Saber 2.
- g_saberNoEffects = noEffects;
- if ( ent->client->ps.dualSabers )
- {
- for ( int i = 0; i < ent->client->ps.saber[1].numBlades; i++ )
- {
- // If the Blade is not active and the length is 0, don't trace it, try the next blade...
- if ( !ent->client->ps.saber[1].blade[i].active && ent->client->ps.saber[1].blade[i].length == 0 )
- continue;
- if ( i != 0 )
- {//not first blade
- if ( ent->client->ps.saber[1].type == SABER_BROAD ||
- ent->client->ps.saber[1].type == SABER_SAI ||
- ent->client->ps.saber[1].type == SABER_CLAW )
- {
- g_saberNoEffects = qtrue;
- }
- }
- WP_SaberDamageTrace( ent, 1, i );
- }
- }
- g_saberNoEffects = qfalse;
- }
- //SABER THROWING============================================================================
- //SABER THROWING============================================================================
- //SABER THROWING============================================================================
- //SABER THROWING============================================================================
- //SABER THROWING============================================================================
- //SABER THROWING============================================================================
- /*
- ================
- WP_SaberImpact
- ================
- */
- void WP_SaberImpact( gentity_t *owner, gentity_t *saber, trace_t *trace )
- {
- gentity_t *other;
- other = &g_entities[trace->entityNum];
- if ( other->takedamage && (other->svFlags&SVF_BBRUSH) )
- {//a breakable brush? break it!
- if ( (other->spawnflags&1)//INVINCIBLE
- ||(other->flags&FL_DMG_BY_HEAVY_WEAP_ONLY) )//HEAVY weapon damage only
- {//can't actually break it
- //no hit effect (besides regular client-side one)
- }
- else if ( other->NPC_targetname &&
- (!owner||!owner->targetname||Q_stricmp(owner->targetname,other->NPC_targetname)) )
- {//only breakable by an entity who is not the attacker
- //no hit effect (besides regular client-side one)
- }
- else
- {
- vec3_t dir;
- VectorCopy( saber->s.pos.trDelta, dir );
- VectorNormalize( dir );
- int dmg = other->health*2;
- if ( other->health > 50 && dmg > 20 && !(other->svFlags&SVF_GLASS_BRUSH) )
- {
- dmg = 20;
- }
- G_Damage( other, saber, owner, dir, trace->endpos, dmg, 0, MOD_SABER );
- G_PlayEffect( "saber/saber_cut", trace->endpos, dir );
- if(owner->client && owner->client->ps.clientNum == 0)
- FF_Play(fffx_Laser1);
- if ( owner->s.number == 0 )
- {
- AddSoundEvent( owner, trace->endpos, 256, AEL_DISCOVERED );
- AddSightEvent( owner, trace->endpos, 512, AEL_DISCOVERED, 50 );
- }
- return;
- }
- }
- if ( saber->s.pos.trType == TR_LINEAR )
- {
- //hit a wall? send it back
- WP_SaberReturn( saber->owner, saber );
- }
- if ( other && !other->client && (other->contents&CONTENTS_LIGHTSABER) )//&& other->s.weapon == WP_SABER )
- {//2 in-flight sabers collided!
- //Big flash
- //FIXME: bigger effect/sound?
- //FIXME: STILL DOESNT WORK!!!
- G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
- G_PlayEffect( "saber/saber_block", trace->endpos );
- if(owner->client && owner->client->ps.clientNum == 0)
- FF_Play(fffx_Laser1);
- g_saberFlashTime = level.time-50;
- VectorCopy( trace->endpos, g_saberFlashPos );
- }
- if ( owner && owner->s.number == 0 && owner->client )
- {
- //Add the event
- if ( owner->client->ps.SaberLength() > 0 )
- {//saber is on, very suspicious
- if ( (!owner->client->ps.saberInFlight && owner->client->ps.groundEntityNum == ENTITYNUM_WORLD)//holding saber and on ground
- || saber->s.pos.trType == TR_STATIONARY )//saber out there somewhere and on ground
- {//an on-ground alert
- AddSoundEvent( owner, saber->currentOrigin, 128, AEL_DISCOVERED, qfalse, qtrue );
- }
- else
- {//an in-air alert
- AddSoundEvent( owner, saber->currentOrigin, 128, AEL_DISCOVERED );
- }
- AddSightEvent( owner, saber->currentOrigin, 256, AEL_DISCOVERED, 50 );
- }
- else
- {//saber is off, not as suspicious
- AddSoundEvent( owner, saber->currentOrigin, 128, AEL_SUSPICIOUS );
- AddSightEvent( owner, saber->currentOrigin, 256, AEL_SUSPICIOUS );
- }
- }
- // check for bounce
- if ( !other->takedamage && ( saber->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) ) )
- {
- // Check to see if there is a bounce count
- if ( saber->bounceCount ) {
- // decrement number of bounces and then see if it should be done bouncing
- if ( --saber->bounceCount <= 0 ) {
- // He (or she) will bounce no more (after this current bounce, that is).
- saber->s.eFlags &= !( EF_BOUNCE | EF_BOUNCE_HALF );
- if ( saber->s.pos.trType == TR_LINEAR && owner && owner->client && owner->client->ps.saberEntityState == SES_RETURNING )
- {
- WP_SaberDrop( saber->owner, saber );
- }
- return;
- }
- else
- {//bounced and still have bounces left
- if ( saber->s.pos.trType == TR_LINEAR && owner && owner->client && owner->client->ps.saberEntityState == SES_RETURNING )
- {//under telekinetic control
- if ( !gi.inPVS( saber->currentOrigin, owner->client->renderInfo.handRPoint ) )
- {//not in the PVS of my master
- saber->bounceCount -= 25;
- }
- }
- }
- }
- if ( saber->s.pos.trType == TR_LINEAR && owner && owner->client && owner->client->ps.saberEntityState == SES_RETURNING )
- {
- //don't home for a few frames so we can get around this thing
- trace_t bounceTr;
- vec3_t end;
- float owner_dist = Distance( owner->client->renderInfo.handRPoint, saber->currentOrigin );
- VectorMA( saber->currentOrigin, 10, trace->plane.normal, end );
- gi.trace( &bounceTr, saber->currentOrigin, saber->mins, saber->maxs, end, owner->s.number, saber->clipmask );
- VectorCopy( bounceTr.endpos, saber->currentOrigin );
- if ( owner_dist > 0 )
- {
- if ( owner_dist > 50 )
- {
- owner->client->ps.saberEntityDist = owner_dist-50;
- }
- else
- {
- owner->client->ps.saberEntityDist = 0;
- }
- }
- return;
- }
-
- G_BounceMissile( saber, trace );
-
- if ( saber->s.pos.trType == TR_GRAVITY )
- {//bounced
- //play a bounce sound
- if ( owner
- && owner->client
- && owner->client->ps.saber[0].type == SABER_SITH_SWORD )
- {
- G_Sound( saber, G_SoundIndex( va( "sound/weapons/sword/fall%d.wav", Q_irand( 1, 7 ) ) ) );
- }
- else
- {
- G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/bounce%d.wav", Q_irand( 1, 3 ) ) ) );
- }
- //change rotation
- VectorCopy( saber->currentAngles, saber->s.apos.trBase );
- saber->s.apos.trType = TR_LINEAR;
- saber->s.apos.trTime = level.time;
- VectorSet( saber->s.apos.trDelta, Q_irand( -300, 300 ), Q_irand( -300, 300 ), Q_irand( -300, 300 ) );
- }
- //see if we stopped
- else if ( saber->s.pos.trType == TR_STATIONARY )
- {//stopped
- //play a bounce sound
- if ( owner
- && owner->client
- && owner->client->ps.saber[0].type == SABER_SITH_SWORD )
- {
- G_Sound( saber, G_SoundIndex( va( "sound/weapons/sword/fall%d.wav", Q_irand( 1, 7 ) ) ) );
- }
- else
- {
- G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/bounce%d.wav", Q_irand( 1, 3 ) ) ) );
- }
- //stop rotation
- VectorClear( saber->s.apos.trDelta );
- saber->currentAngles[0] = SABER_PITCH_HACK;
- VectorCopy( saber->currentAngles, saber->s.apos.trBase );
- //remember when it fell so it can return automagically
- saber->aimDebounceTime = level.time;
- }
- }
- else if ( other->client && other->health > 0
- && ( (other->NPC && (other->NPC->aiFlags&NPCAI_BOSS_CHARACTER))
- //|| other->client->NPC_class == CLASS_ALORA
- || other->client->NPC_class == CLASS_BOBAFETT
- || ( other->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) ) )
- {//Luke, Desann and Tavion slap thrown sabers aside
- WP_SaberDrop( owner, saber );
- G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
- G_PlayEffect( "saber/saber_block", trace->endpos );
- if(owner->client && owner->client->ps.clientNum == 0)
- FF_Play(fffx_Laser1);
- g_saberFlashTime = level.time-50;
- VectorCopy( trace->endpos, g_saberFlashPos );
- //FIXME: make Luke/Desann/Tavion play an attack anim or some other special anim when this happens
- Jedi_PlayDeflectSound( other );
- }
- }
- extern float G_PointDistFromLineSegment( const vec3_t start, const vec3_t end, const vec3_t from );
- void WP_SaberInFlightReflectCheck( gentity_t *self, usercmd_t *ucmd )
- {
- gentity_t *ent;
- gentity_t *entityList[MAX_GENTITIES];
- gentity_t *missile_list[MAX_GENTITIES];
- int numListedEntities;
- vec3_t mins, maxs;
- int i, e, numSabers;
- int ent_count = 0;
- int radius = 180;
- vec3_t center;
- vec3_t tip;
- vec3_t up = {0,0,1};
- qboolean willHit = qfalse;
- if ( self->NPC && (self->NPC->scriptFlags&SCF_IGNORE_ALERTS) )
- {//don't react to things flying at me...
- return;
- }
- //sanity checks: make sure we actually have a saberent
- if ( self->client->ps.weapon != WP_SABER )
- {
- return;
- }
- if ( !self->client->ps.saberInFlight )
- {
- return;
- }
- if ( !self->client->ps.SaberLength() )
- {
- return;
- }
- if ( self->client->ps.saberEntityNum == ENTITYNUM_NONE )
- {
- return;
- }
- gentity_t *saberent = &g_entities[self->client->ps.saberEntityNum];
- if ( !saberent )
- {
- return;
- }
- //okay, enough damn sanity checks
- VectorCopy( saberent->currentOrigin, center );
- for ( i = 0 ; i < 3 ; i++ )
- {
- mins[i] = center[i] - radius;
- maxs[i] = center[i] + radius;
- }
- numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
- //FIXME: check visibility?
- for ( e = 0 ; e < numListedEntities ; e++ )
- {
- ent = entityList[ e ];
- if (ent == self)
- continue;
- if (ent->owner == self)
- continue;
- if ( !(ent->inuse) )
- continue;
- if ( ent->s.eType != ET_MISSILE )
- {
- if ( ent->client || ent->s.weapon != WP_SABER )
- {//FIXME: wake up bad guys?
- continue;
- }
- if ( ent->s.eFlags & EF_NODRAW )
- {
- continue;
- }
- if ( Q_stricmp( "lightsaber", ent->classname ) != 0 )
- {//not a lightsaber
- continue;
- }
- }
- else
- {//FIXME: make exploding missiles explode?
- if ( ent->s.pos.trType == TR_STATIONARY )
- {//nothing you can do with a stationary missile
- continue;
- }
- if ( ent->splashDamage || ent->splashRadius )
- {//can't deflect exploding missiles
- if ( DistanceSquared( ent->currentOrigin, center ) < 256 )//16 squared
- {
- G_MissileImpacted( ent, saberent, ent->currentOrigin, up );
- }
- continue;
- }
- }
- //don't deflect it if it's not within 16 units of the blade
- //do this for all blades
- willHit = qfalse;
- numSabers = 1;
- if ( self->client->ps.dualSabers )
- {
- numSabers = 2;
- }
- for ( int saberNum = 0; saberNum < numSabers; saberNum++ )
- {
- for ( int bladeNum = 0; bladeNum < self->client->ps.saber[saberNum].numBlades; bladeNum++ )
- {
- VectorMA( self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, self->client->ps.saber[saberNum].blade[bladeNum].length, self->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, tip );
-
- if( G_PointDistFromLineSegment( self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, tip, ent->currentOrigin ) <= 32 )
- {
- willHit = qtrue;
- break;
- }
- }
- if ( willHit )
- {
- break;
- }
- }
- if ( !willHit )
- {
- continue;
- }
- // ok, we are within the radius, add us to the incoming list
- missile_list[ent_count] = ent;
- ent_count++;
- }
- if ( ent_count )
- {
- vec3_t fx_dir;
- // we are done, do we have any to deflect?
- if ( ent_count )
- {
- for ( int x = 0; x < ent_count; x++ )
- {
- if ( missile_list[x]->s.weapon == WP_SABER )
- {//just send it back
- if ( missile_list[x]->owner && missile_list[x]->owner->client && missile_list[x]->owner->client->ps.saber[0].Active() && missile_list[x]->s.pos.trType == TR_LINEAR && missile_list[x]->owner->client->ps.saberEntityState != SES_RETURNING )
- {//it's on and being controlled
- //FIXME: prevent it from damaging me?
- WP_SaberReturn( missile_list[x]->owner, missile_list[x] );
- VectorNormalize2( missile_list[x]->s.pos.trDelta, fx_dir );
- G_PlayEffect( "saber/saber_block", missile_list[x]->currentOrigin, fx_dir );
- if(ent->client && ent->client->ps.clientNum == 0)
- FF_Play(fffx_Laser1);
- if ( missile_list[x]->owner->client->ps.saberInFlight && self->client->ps.saberInFlight )
- {
- G_Sound( missile_list[x], G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
- g_saberFlashTime = level.time-50;
- gentity_t *saber = &g_entities[self->client->ps.saberEntityNum];
- vec3_t org;
- VectorSubtract( missile_list[x]->currentOrigin, saber->currentOrigin, org );
- VectorMA( saber->currentOrigin, 0.5, org, org );
- VectorCopy( org, g_saberFlashPos );
- }
- }
- }
- else
- {//bounce it
- vec3_t reflectAngle, forward;
- if ( self->client && !self->s.number )
- {
- self->client->sess.missionStats.saberBlocksCnt++;
- }
- VectorCopy( saberent->s.apos.trBase, reflectAngle );
- reflectAngle[PITCH] = Q_flrand( -90, 90 );
- AngleVectors( reflectAngle, forward, NULL, NULL );
-
- G_ReflectMissile( self, missile_list[x], forward );
- //do an effect
- VectorNormalize2( missile_list[x]->s.pos.trDelta, fx_dir );
- G_PlayEffect( "blaster/deflect", missile_list[x]->currentOrigin, fx_dir );
- if(ent->client && ent->client->ps.clientNum == 0)
- FF_Play(fffx_Laser2);
- }
- }
- }
- }
- }
- qboolean WP_SaberValidateEnemy( gentity_t *self, gentity_t *enemy )
- {
- if ( !enemy )
- {
- return qfalse;
- }
- if ( !enemy || enemy == self || !enemy->inuse || !enemy->client )
- {//not valid
- return qfalse;
- }
- if ( enemy->health <= 0 )
- {//corpse
- return qfalse;
- }
- /*
- if ( enemy->client->ps.weapon == WP_SABER
- && enemy->client->ps.SaberActive() )
- {//not other saber-users?
- return qfalse;
- }
- */
- if ( enemy->client->ps.forcePowersKnown )
- {//not other jedi?
- return qfalse;
- }
- if ( DistanceSquared( self->client->renderInfo.handRPoint, enemy->currentOrigin ) > saberThrowDistSquared[self->client->ps.forcePowerLevel[FP_SABERTHROW]] )
- {//too far
- return qfalse;
- }
- if ( (!InFront( enemy->currentOrigin, self->currentOrigin, self->client->ps.viewangles, 0.0f) || !G_ClearLOS( self, self->client->renderInfo.eyePoint, enemy ) )
- && ( DistanceHorizontalSquared( enemy->currentOrigin, self->currentOrigin ) > 65536 || fabs(enemy->currentOrigin[2]-self->currentOrigin[2]) > 384 ) )
- {//(not in front or not clear LOS) & greater than 256 away
- return qfalse;
- }
- if ( enemy->client->playerTeam == self->client->playerTeam )
- {//on same team
- return qfalse;
- }
- //LOS?
- return qtrue;
- }
- float WP_SaberRateEnemy( gentity_t *enemy, vec3_t center, vec3_t forward, float radius )
- {
- float rating;
- vec3_t dir;
- VectorSubtract( enemy->currentOrigin, center, dir );
- rating = (1.0f-(VectorNormalize( dir )/radius));
- rating *= DotProduct( forward, dir );
- return rating;
- }
- gentity_t *WP_SaberFindEnemy( gentity_t *self, gentity_t *saber )
- {
- //FIXME: should be a more intelligent way of doing this, like auto aim?
- //closest, most in front... did damage to... took damage from? How do we know who the player is focusing on?
- gentity_t *ent, *bestEnt = NULL;
- gentity_t *entityList[MAX_GENTITIES];
- int numListedEntities;
- vec3_t center, mins, maxs, fwdangles, forward;
- int i, e;
- float radius = 400;
- float rating, bestRating = 0.0f;
- //FIXME: no need to do this in 1st person?
- fwdangles[1] = self->client->ps.viewangles[1];
- AngleVectors( fwdangles, forward, NULL, NULL );
- VectorCopy( saber->currentOrigin, center );
- for ( i = 0 ; i < 3 ; i++ )
- {
- mins[i] = center[i] - radius;
- maxs[i] = center[i] + radius;
- }
- //if the saber has an enemy from the last time it looked, init to that one
- if ( WP_SaberValidateEnemy( self, saber->enemy ) )
- {
- if ( gi.inPVS( self->currentOrigin, saber->enemy->currentOrigin ) )
- {//potentially visible
- if ( G_ClearLOS( self, self->client->renderInfo.eyePoint, saber->enemy ) )
- {//can see him
- bestEnt = saber->enemy;
- bestRating = WP_SaberRateEnemy( bestEnt, center, forward, radius );
- }
- }
- }
- //If I have an enemy, see if that's even better
- if ( WP_SaberValidateEnemy( self, self->enemy ) )
- {
- float myEnemyRating = WP_SaberRateEnemy( self->enemy, center, forward, radius );
- if ( myEnemyRating > bestRating )
- {
- if ( gi.inPVS( self->currentOrigin, self->enemy->currentOrigin ) )
- {//potentially visible
- if ( G_ClearLOS( self, self->client->renderInfo.eyePoint, self->enemy ) )
- {//can see him
- bestEnt = self->enemy;
- bestRating = myEnemyRating;
- }
- }
- }
- }
- numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
-
- if ( !numListedEntities )
- {//should we clear the enemy?
- return bestEnt;
- }
- for ( e = 0 ; e < numListedEntities ; e++ )
- {
- ent = entityList[ e ];
- if ( ent == self || ent == saber || ent == bestEnt )
- {
- continue;
- }
- if ( !WP_SaberValidateEnemy( self, ent ) )
- {//doesn't meet criteria of valid look enemy (don't check current since we would have done that before this func's call
- continue;
- }
- if ( !gi.inPVS( self->currentOrigin, ent->currentOrigin ) )
- {//not even potentially visible
- continue;
- }
- if ( !G_ClearLOS( self, self->client->renderInfo.eyePoint, ent ) )
- {//can't see him
- continue;
- }
- //rate him based on how close & how in front he is
- rating = WP_SaberRateEnemy( ent, center, forward, radius );
- if ( rating > bestRating )
- {
- bestEnt = ent;
- bestRating = rating;
- }
- }
- return bestEnt;
- }
- void WP_RunSaber( gentity_t *self, gentity_t *saber )
- {
- vec3_t origin, oldOrg;
- trace_t tr;
- VectorCopy( saber->currentOrigin, oldOrg );
- // get current position
- EvaluateTrajectory( &saber->s.pos, level.time, origin );
- // get current angles
- EvaluateTrajectory( &saber->s.apos, level.time, saber->currentAngles );
- // trace a line from the previous position to the current position,
- // ignoring interactions with the missile owner
- int clipmask = saber->clipmask;
- if ( !self || !self->client || self->client->ps.SaberLength() <= 0 )
- {//don't keep hitting other sabers when turned off
- clipmask &= ~CONTENTS_LIGHTSABER;
- }
- gi.trace( &tr, saber->currentOrigin, saber->mins, saber->maxs, origin,
- saber->owner ? saber->owner->s.number : ENTITYNUM_NONE, clipmask );
- VectorCopy( tr.endpos, saber->currentOrigin );
- if ( self->client->ps.SaberActive() )
- {
- if ( self->client->ps.saberInFlight || (self->client->ps.weaponTime&&!Q_irand( 0, 100 )) )
- {//make enemies run from a lit saber in flight or from me when I'm attacking
- if ( !Q_irand( 0, 10 ) )
- {//not so often...
- AddSightEvent( self, saber->currentOrigin, self->client->ps.SaberLength()*3, AEL_DANGER, 100 );
- }
- }
- }
- if ( tr.startsolid )
- {
- tr.fraction = 0;
- }
- gi.linkentity( saber );
- //touch push triggers?
- if ( tr.fraction != 1 )
- {
- WP_SaberImpact( self, saber, &tr );
- }
- if ( saber->s.pos.trType == TR_LINEAR )
- {//home
- //figure out where saber should be
- vec3_t forward, saberHome, saberDest, fwdangles = {0};
- VectorCopy( self->client->ps.viewangles, fwdangles );
- if ( self->s.number )
- {
- fwdangles[0] -= 8;
- }
- else if ( cg.renderingThirdPerson )
- {
- fwdangles[0] -= 5;
- }
- if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_1
- || self->client->ps.saberEntityState == SES_RETURNING
- || VectorCompare( saber->s.pos.trDelta, vec3_origin ) )
- {//control if it's returning or just starting
- float saberSpeed = 500;//FIXME: based on force level?
- float dist;
- gentity_t *enemy = NULL;
- AngleVectors( fwdangles, forward, NULL, NULL );
- if ( self->client->ps.saberEntityDist < 100 )
- {//make the saber head to my hand- the bolt it was attached to
- VectorCopy( self->client->renderInfo.handRPoint, saberHome );
- }
- else
- {//aim saber from eyes
- VectorCopy( self->client->renderInfo.eyePoint, saberHome );
- }
- VectorMA( saberHome, self->client->ps.saberEntityDist, forward, saberDest );
- if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2
- && self->client->ps.saberEntityState == SES_LEAVING )
- {//max level
- if ( self->enemy &&
- !WP_SaberValidateEnemy( self, self->enemy ) )
- {//if my enemy isn't valid to auto-aim at, don't autoaim
- }
- else
- {
- //pick an enemy
- enemy = WP_SaberFindEnemy( self, saber );
- if ( enemy )
- {//home in on enemy
- float enemyDist = Distance( self->client->renderInfo.handRPoint, enemy->currentOrigin );
- VectorCopy( enemy->currentOrigin, saberDest );
- saberDest[2] += enemy->maxs[2]/2.0f;//FIXME: when in a knockdown anim, the saber float above them... do we care?
- self->client->ps.saberEntityDist = enemyDist;
- //once you pick an enemy, stay with it!
- saber->enemy = enemy;
- //FIXME: lock onto that enemy for a minimum amount of time (unless they become invalid?)
- }
- }
- }
-
- //Make the saber head there
- VectorSubtract( saberDest, saber->currentOrigin, saber->s.pos.trDelta );
- dist = VectorNormalize( saber->s.pos.trDelta );
- if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 && self->client->ps.saberEntityState == SES_LEAVING && !enemy )
- {
- if ( dist < 200 )
- {
- saberSpeed = 400 - (dist*2);
- }
- }
- else if ( self->client->ps.saberEntityState == SES_LEAVING && dist < 50 )
- {
- saberSpeed = dist * 2 + 30;
- if ( (enemy && dist > enemy->maxs[0]) || (!enemy && dist > 24) )
- {//auto-tracking an enemy and we can't hit him
- if ( saberSpeed < 120 )
- {//clamp to a minimum speed
- saberSpeed = 120;
- }
- }
- }
- /*
- if ( self->client->ps.saberEntityState == SES_RETURNING )
- {//FIXME: if returning, move faster?
- saberSpeed = 800;
- if ( dist < 200 )
- {
- saberSpeed -= 400 - (dist*2);
- }
- }
- */
- VectorScale( saber->s.pos.trDelta, saberSpeed, saber->s.pos.trDelta );
- //SnapVector( saber->s.pos.trDelta ); // save net bandwidth
- VectorCopy( saber->currentOrigin, saber->s.pos.trBase );
- saber->s.pos.trTime = level.time;
- saber->s.pos.trType = TR_LINEAR;
- }
- else
- {
- VectorCopy( saber->currentOrigin, saber->s.pos.trBase );
- saber->s.pos.trTime = level.time;
- saber->s.pos.trType = TR_LINEAR;
- }
- //if it's heading back, point it's base at us
- if ( self->client->ps.saberEntityState == SES_RETURNING
- && self->client->ps.saber[0].returnDamage == qfalse )//type != SABER_STAR )
- {
- fwdangles[0] += SABER_PITCH_HACK;
- VectorCopy( fwdangles, saber->s.apos.trBase );
- saber->s.apos.trTime = level.time;
- saber->s.apos.trType = TR_INTERPOLATE;
- VectorClear( saber->s.apos.trDelta );
- }
- }
- }
- qboolean WP_SaberLaunch( gentity_t *self, gentity_t *saber, qboolean thrown, qboolean noFail = qfalse )
- {//FIXME: probably need a debounce time
- vec3_t saberMins={-3.0f,-3.0f,-3.0f};
- vec3_t saberMaxs={3.0f,3.0f,3.0f};
- trace_t trace;
- if ( self->client->NPC_class == CLASS_SABER_DROID )
- {//saber droids can't drop their saber
- return qfalse;
- }
- if ( !noFail )
- {
- if ( thrown )
- {//this is a regular throw, so see if it's legal
- if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
- {
- if ( !WP_ForcePowerUsable( self, FP_SABERTHROW, 20 ) )
- {
- return qfalse;
- }
- }
- else
- {
- if ( !WP_ForcePowerUsable( self, FP_SABERTHROW, 0 ) )
- {
- return qfalse;
- }
- }
- }
- if ( !self->s.number && (cg.zoomMode || in_camera) )
- {//can't saber throw when zoomed in or in cinematic
- return qfalse;
- }
- //make sure it won't start in solid
- gi.trace( &trace, self->client->renderInfo.handRPoint, saberMins, saberMaxs, self->client->renderInfo.handRPoint, saber->s.number, MASK_SOLID );
- if ( trace.startsolid || trace.allsolid )
- {
- return qfalse;
- }
- //make sure I'm not throwing it on the other side of a door or wall or whatever
- gi.trace( &trace, self->currentOrigin, vec3_origin, vec3_origin, self->client->renderInfo.handRPoint, self->s.number, MASK_SOLID );
- if ( trace.startsolid || trace.allsolid || trace.fraction < 1.0f )
- {
- return qfalse;
- }
- if ( thrown )
- {//this is a regular throw, so take force power
- if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
- {//at max skill, the cost increases as keep it out
- WP_ForcePowerStart( self, FP_SABERTHROW, 10 );
- }
- else
- {
- WP_ForcePowerStart( self, FP_SABERTHROW, 0 );
- }
- }
- }
- //clear the enemy
- saber->enemy = NULL;
- //draw it
- saber->s.eFlags &= ~EF_NODRAW;
- saber->svFlags |= SVF_BROADCAST;
- saber->svFlags &= ~SVF_NOCLIENT;
-
- //place it
- VectorCopy( self->client->renderInfo.handRPoint, saber->currentOrigin );//muzzlePoint
- VectorCopy( saber->currentOrigin, saber->s.pos.trBase );
- saber->s.pos.trTime = level.time;
- saber->s.pos.trType = TR_LINEAR;
- VectorClear( saber->s.pos.trDelta );
- gi.linkentity( saber );
- //spin it
- VectorClear( saber->s.apos.trBase );
- saber->s.apos.trTime = level.time;
- saber->s.apos.trType = TR_LINEAR;
- if ( self->health > 0 && thrown )
- {//throwing it
- saber->s.apos.trBase[1] = self->client->ps.viewangles[1];
- saber->s.apos.trBase[0] = SABER_PITCH_HACK;
- }
- else
- {//dropping it
- vectoangles( self->client->renderInfo.muzzleDir, saber->s.apos.trBase );
- }
- VectorClear( saber->s.apos.trDelta );
- switch ( self->client->ps.forcePowerLevel[FP_SABERTHROW] )
- {//FIXME: make a table?
- default:
- case FORCE_LEVEL_1:
- saber->s.apos.trDelta[1] = 600;
- break;
- case FORCE_LEVEL_2:
- saber->s.apos.trDelta[1] = 800;
- break;
- case FORCE_LEVEL_3:
- saber->s.apos.trDelta[1] = 1200;
- break;
- }
- //Take it out of my hand
- self->client->ps.saberInFlight = qtrue;
- self->client->ps.saberEntityState = SES_LEAVING;
- self->client->ps.saberEntityDist = saberThrowDist[self->client->ps.forcePowerLevel[FP_SABERTHROW]];
- self->client->ps.saberThrowTime = level.time;
- //if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
- {
- self->client->ps.forcePowerDebounce[FP_SABERTHROW] = level.time + 1000;//so we can keep it out for a minimum amount of time
- }
-
- if ( thrown )
- {//this is a regular throw, so turn the saber on
- //turn saber on
- if ( self->client->ps.saber[0].singleBladeThrowable )//SaberStaff() )
- {//only first blade can be on
- if ( !self->client->ps.saber[0].blade[0].active )
- {//turn on first one
- self->client->ps.SaberBladeActivate( 0, 0 );
- }
- for ( int i = 1; i < self->client->ps.saber[0].numBlades; i++ )
- {//turn off all others
- if ( self->client->ps.saber[0].blade[i].active )
- {
- self->client->ps.SaberBladeActivate( 0, i, qfalse );
- }
- }
- }
- else
- {//turn the sabers, all blades...?
- self->client->ps.saber[0].Activate();
- //self->client->ps.SaberActivate();
- }
- //turn on the saber trail
- self->client->ps.saber[0].ActivateTrail( 150 );
- }
- //reset the mins
- VectorCopy( saberMins, saber->mins );
- VectorCopy( saberMaxs, saber->maxs );
- saber->contents = 0;//CONTENTS_LIGHTSABER;
- saber->clipmask = MASK_SOLID | CONTENTS_LIGHTSABER;
- // remove the ghoul2 right-hand saber model on the player
- if ( self->weaponModel[0] > 0 )
- {
- gi.G2API_RemoveGhoul2Model(self->ghoul2, self->weaponModel[0]);
- self->weaponModel[0] = -1;
- }
- return qtrue;
- }
- qboolean WP_SaberLose( gentity_t *self, vec3_t throwDir )
- {
- if ( !self || !self->client || self->client->ps.saberEntityNum <= 0 )
- {//WTF?!! We lost it already?
- return qfalse;
- }
- if ( self->client->NPC_class == CLASS_SABER_DROID )
- {//saber droids can't drop their saber
- return qfalse;
- }
- gentity_t *dropped = &g_entities[self->client->ps.saberEntityNum];
- if ( !self->client->ps.saberInFlight )
- {//not alreay in air
- /*
- qboolean noForceThrow = qfalse;
- //make it so we can throw it
- self->client->ps.forcePowersKnown |= (1<<FP_SABERTHROW);
- if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] < FORCE_LEVEL_1 )
- {
- noForceThrow = qtrue;
- self->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_1;
- }
- */
- //throw it
- if ( !WP_SaberLaunch( self, dropped, qfalse ) )
- {//couldn't throw it
- return qfalse;
- }
- /*
- if ( noForceThrow )
- {
- self->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_0;
- }
- */
- }
- if ( self->client->ps.saber[0].Active() )
- {//on
- //drop it instantly
- WP_SaberDrop( self, dropped );
- }
- //optionally give it some thrown velocity
- if ( throwDir && !VectorCompare( throwDir, vec3_origin ) )
- {
- VectorCopy( throwDir, dropped->s.pos.trDelta );
- }
- //don't pull it back on the next frame
- if ( self->NPC )
- {
- self->NPC->last_ucmd.buttons &= ~BUTTON_ATTACK;
- }
- return qtrue;
- }
- void WP_SetSaberOrigin( gentity_t *self, vec3_t newOrg )
- {
- if ( !self || !self->client )
- {
- return;
- }
- if ( self->client->ps.saberEntityNum <= 0 || self->client->ps.saberEntityNum >= ENTITYNUM_WORLD )
- {//no saber ent to reposition
- return;
- }
- if ( self->client->NPC_class == CLASS_SABER_DROID )
- {//saber droids can't drop their saber
- return;
- }
- gentity_t *dropped = &g_entities[self->client->ps.saberEntityNum];
- if ( !self->client->ps.saberInFlight )
- {//not already in air
- qboolean noForceThrow = qfalse;
- //make it so we can throw it
- self->client->ps.forcePowersKnown |= (1<<FP_SABERTHROW);
- if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] < FORCE_LEVEL_1 )
- {
- noForceThrow = qtrue;
- self->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_1;
- }
- //throw it
- if ( !WP_SaberLaunch( self, dropped, qfalse, qtrue ) )
- {//couldn't throw it
- return;
- }
- if ( noForceThrow )
- {
- self->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_0;
- }
- }
- VectorCopy( newOrg, dropped->s.origin );
- VectorCopy( newOrg, dropped->currentOrigin );
- VectorCopy( newOrg, dropped->s.pos.trBase );
- //drop it instantly
- WP_SaberDrop( self, dropped );
- //don't pull it back on the next frame
- if ( self->NPC )
- {
- self->NPC->last_ucmd.buttons &= ~BUTTON_ATTACK;
- }
- }
- void WP_SaberCatch( gentity_t *self, gentity_t *saber, qboolean switchToSaber )
- {//FIXME: probably need a debounce time
- if ( self->health > 0 && !PM_SaberInBrokenParry( self->client->ps.saberMove ) && self->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
- {
- //clear the enemy
- saber->enemy = NULL;
- //don't draw it
- saber->s.eFlags |= EF_NODRAW;
- saber->svFlags &= SVF_BROADCAST;
- saber->svFlags |= SVF_NOCLIENT;
- //take off any gravity stuff if we'd dropped it
- saber->s.pos.trType = TR_LINEAR;
- saber->s.eFlags &= ~EF_BOUNCE_HALF;
- //Put it in my hand
- self->client->ps.saberInFlight = qfalse;
- self->client->ps.saberEntityState = SES_LEAVING;
- //turn off the saber trail
- self->client->ps.saber[0].DeactivateTrail( 75 );
- //reset its contents/clipmask
- saber->contents = CONTENTS_LIGHTSABER;// | CONTENTS_SHOTCLIP;
- saber->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
- //play catch sound
- G_Sound( saber, G_SoundIndex( "sound/weapons/saber/saber_catch.wav" ) );
- //FIXME: if an NPC, don't turn it back on if no enemy or enemy is dead...
- //if it's not our current weapon, make it our current weapon
- if ( self->client->ps.weapon == WP_SABER )
- {//only do the first saber since we only throw the first one
- WP_SaberAddG2SaberModels( self, qfalse );
- }
- if ( switchToSaber )
- {
- if ( self->client->ps.weapon != WP_SABER )
- {
- CG_ChangeWeapon( WP_SABER );
- }
- else
- {//if it's not active, turn it on
- if ( self->client->ps.saber[0].singleBladeThrowable )//SaberStaff() )
- {//only first blade can be on
- if ( !self->client->ps.saber[0].blade[0].active )
- {//only turn it on if first blade is off, otherwise, leave as-is
- self->client->ps.saber[0].Activate();
- }
- }
- else
- {//turn all blades on
- self->client->ps.saber[0].Activate();
- }
- }
- }
- }
- }
- void WP_SaberReturn( gentity_t *self, gentity_t *saber )
- {
- if ( PM_SaberInBrokenParry( self->client->ps.saberMove ) || self->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN )
- {
- return;
- }
- if ( self && self->client )
- {//still alive and stuff
- //FIXME: when it's returning, flies butt first, but seems to do a lot of damage when going through people... hmm...
- self->client->ps.saberEntityState = SES_RETURNING;
- //turn down the saber trail
- if ( self->client->ps.saber[0].returnDamage == qfalse )//type != SABER_STAR )
- {
- self->client->ps.saber[0].DeactivateTrail( 75 );
- }
- }
- if ( !(saber->s.eFlags&EF_BOUNCE) )
- {
- saber->s.eFlags |= EF_BOUNCE;
- saber->bounceCount = 300;
- }
- }
- void WP_SaberDrop( gentity_t *self, gentity_t *saber )
- {
- //clear the enemy
- saber->enemy = NULL;
- saber->s.eFlags &= ~EF_BOUNCE;
- saber->bounceCount = 0;
- //make it fall
- saber->s.pos.trType = TR_GRAVITY;
- //make it bounce some
- saber->s.eFlags |= EF_BOUNCE_HALF;
- //make it spin
- VectorCopy( saber->currentAngles, saber->s.apos.trBase );
- saber->s.apos.trType = TR_LINEAR;
- saber->s.apos.trTime = level.time;
- VectorSet( saber->s.apos.trDelta, Q_irand( -300, 300 ), saber->s.apos.trDelta[1], Q_irand( -300, 300 ) );
- if ( !saber->s.apos.trDelta[1] )
- {
- saber->s.apos.trDelta[1] = Q_irand( -300, 300 );
- }
- //force it to be ready to return
- self->client->ps.saberEntityDist = 0;
- self->client->ps.saberEntityState = SES_RETURNING;
- //turn it off
- self->client->ps.saber[0].Deactivate();
- //turn off the saber trail
- self->client->ps.saber[0].DeactivateTrail( 75 );
- //play the saber turning off sound
- G_SoundIndexOnEnt( saber, CHAN_AUTO, self->client->ps.saber[0].soundOff );
- #ifdef _IMMERSION
- if ( self->client->playerTeam == TEAM_PLAYER )
- {
- G_Force( self, G_ForceIndex( "fffx/weapons/saber/saberoff", FF_CHANNEL_WEAPON ) );
- }
- else
- {
- G_Force( self, G_ForceIndex( "fffx/weapons/saber/enemy_saber_off", FF_CHANNEL_WEAPON ) );
- }
- #endif // _IMMERSION
- if ( self->health <= 0 )
- {//owner is dead!
- saber->s.time = level.time;//will make us free ourselves after a time
- }
- }
- void WP_SaberPull( gentity_t *self, gentity_t *saber )
- {
- if ( PM_SaberInBrokenParry( self->client->ps.saberMove ) || self->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN )
- {
- return;
- }
- if ( self->health > 0 )
- {
- //take off gravity
- saber->s.pos.trType = TR_LINEAR;
- //take off bounce
- saber->s.eFlags &= EF_BOUNCE_HALF;
- //play sound
- G_Sound( self, G_SoundIndex( "sound/weapons/force/pull.wav" ) );
- #ifdef _IMMERSION
- G_Force( self, G_ForceIndex( "fffx/weapons/force/pull", FF_CHANNEL_WEAPON ) );
- #endif // _IMMERSION
- }
- }
- // Check if we are throwing it, launch it if needed, update position if needed.
- void WP_SaberThrow( gentity_t *self, usercmd_t *ucmd )
- {
- static float MAX_SABER_DIST = 400;
- vec3_t saberDiff;
- trace_t tr;
- //static float SABER_SPEED = 10;
- gentity_t *saberent;
-
- if ( self->client->ps.saberEntityNum <= 0 || self->client->ps.saberEntityNum >= ENTITYNUM_WORLD )
- {//WTF?!! We lost it?
- return;
- }
-
- if ( self->client->ps.torsoAnim == BOTH_LOSE_SABER )
- {//can't catch it while it's being yanked from your hand!
- return;
- }
- if ( !g_saberNewControlScheme->integer )
- {
- if ( PM_SaberInKata( (saberMoveName_t)self->client->ps.saberMove ) )
- {//don't throw saber when in special attack (alt+attack)
- return;
- }
- if ( (ucmd->buttons&BUTTON_ATTACK)
- && (ucmd->buttons&BUTTON_ALT_ATTACK)
- && !self->client->ps.saberInFlight )
- {//trying to do special attack, don't throw it
- return;
- }
- else if ( self->client->ps.torsoAnim == BOTH_A1_SPECIAL
- || self->client->ps.torsoAnim == BOTH_A2_SPECIAL
- || self->client->ps.torsoAnim == BOTH_A3_SPECIAL )
- {//don't throw in these anims!
- return;
- }
- }
- saberent = &g_entities[self->client->ps.saberEntityNum];
- VectorSubtract( self->client->renderInfo.handRPoint, saberent->currentOrigin, saberDiff );
- //is our saber in flight?
- if ( !self->client->ps.saberInFlight )
- {//saber is not in flight right now
- if ( self->client->ps.weapon != WP_SABER )
- {//don't even have it out
- return;
- }
- else if ( (ucmd->buttons&BUTTON_ALT_ATTACK) && !(self->client->ps.pm_flags&PMF_ALT_ATTACK_HELD) )
- {//still holding it, not still holding attack from a previous throw, so throw it.
- if ( !(self->client->ps.saberEventFlags&SEF_INWATER) && WP_SaberLaunch( self, saberent, qtrue ) )
- {
- if ( self->client && !self->s.number )
- {
- self->client->sess.missionStats.saberThrownCnt++;
- }
- //need to recalc this because we just moved it
- VectorSubtract( self->client->renderInfo.handRPoint, saberent->currentOrigin, saberDiff );
- }
- else
- {//couldn't throw it
- return;
- }
- }
- else
- {//holding it, don't want to throw it, go away.
- return;
- }
- }
- else
- {//inflight
- //is our saber currently on it's way back to us?
- if ( self->client->ps.saberEntityState == SES_RETURNING )
- {//see if we're close enough to pick it up
- if ( VectorLengthSquared( saberDiff ) <= 256 )//16 squared//G_BoundsOverlap( self->absmin, self->absmax, saberent->absmin, saberent->absmax ) )//
- {//caught it
- vec3_t axisPoint;
- trace_t trace;
- VectorCopy( self->currentOrigin, axisPoint );
- axisPoint[2] = self->client->renderInfo.handRPoint[2];
- gi.trace( &trace, axisPoint, vec3_origin, vec3_origin, self->client->renderInfo.handRPoint, self->s.number, MASK_SOLID );
- if ( !trace.startsolid && trace.fraction >= 1.0f )
- {//our hand isn't through a wall
- WP_SaberCatch( self, saberent, qtrue );
- //NPC_SetAnim( self, SETANIM_TORSO, TORSO_HANDRETRACT1, SETANIM_FLAG_OVERRIDE );
- }
- return;
- }
- }
- if ( saberent->s.pos.trType != TR_STATIONARY )
- {//saber is in flight, lerp it
- WP_RunSaber( self, saberent );
- }
- else
- {//it fell on the ground
- if ( self->health <= 0 && level.time > saberent->s.time + 5000 )
- {//make us free ourselves after a time
- G_FreeEntity( saberent );
- self->client->ps.saberEntityNum = ENTITYNUM_NONE;
- return;
- }
- if ( (!self->s.number && level.time - saberent->aimDebounceTime > 15000)
- || (self->s.number && level.time - saberent->aimDebounceTime > 5000) )
- {//(only for player) been missing for 15 seconds, automagicially return
- WP_SaberCatch( self, saberent, qfalse );
- return;
- }
- }
- }
- //are we still trying to use the saber?
- if ( self->client->ps.weapon != WP_SABER )
- {//switched away
- if ( !self->client->ps.saberInFlight )
- {//wasn't throwing saber
- return;
- }
- else if ( saberent->s.pos.trType == TR_LINEAR )
- {//switched away while controlling it, just drop the saber
- WP_SaberDrop( self, saberent );
- return;
- }
- else
- {//it's on the ground, see if it's inside us (touching)
- if ( G_PointInBounds( saberent->currentOrigin, self->absmin, self->absmax ) )
- {//it's in us, pick it up automatically
- WP_SaberPull( self, saberent );
- }
- }
- }
- else if ( saberent->s.pos.trType != TR_LINEAR )
- {//weapon is saber and not flying
- if ( self->client->ps.saberInFlight )
- {//we dropped it
- if ( ucmd->buttons & BUTTON_ATTACK )//|| self->client->ps.weaponstate == WEAPON_RAISING )//ucmd->buttons & BUTTON_ALT_ATTACK ||
- {//we actively want to pick it up or we just switched to it, so pull it back
- gi.trace( &tr, saberent->currentOrigin, saberent->mins, saberent->maxs, self->client->renderInfo.handRPoint, self->s.number, MASK_SOLID );
- if ( tr.allsolid || tr.startsolid || tr.fraction < 1.0f )
- {//can't pick it up yet, no LOS
- return;
- }
- //clear LOS, pick it up
- WP_SaberPull( self, saberent );
- }
- else
- {//see if it's inside us (touching)
- if ( G_PointInBounds( saberent->currentOrigin, self->absmin, self->absmax ) )
- {//it's in us, pick it up automatically
- WP_SaberPull( self, saberent );
- }
- }
- }
- }
- else if ( self->health <= 0 && self->client->ps.saberInFlight )
- {//we died, drop it
- WP_SaberDrop( self, saberent );
- return;
- }
- else if ( !self->client->ps.saber[0].Active() && self->client->ps.saberEntityState != SES_RETURNING )
- {//we turned it off, drop it
- WP_SaberDrop( self, saberent );
- return;
- }
-
- //TODO: if deactivate saber in flight, should it drop?
-
- if ( saberent->s.pos.trType != TR_LINEAR )
- {//don't home
- return;
- }
- float saberDist = VectorLength( saberDiff );
- if ( self->client->ps.saberEntityState == SES_LEAVING )
- {//saber still flying forward
- if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
- {//still holding it out
- if ( !(ucmd->buttons&BUTTON_ALT_ATTACK) && self->client->ps.forcePowerDebounce[FP_SABERTHROW] < level.time )
- {//done throwing, return to me
- if ( self->client->ps.saber[0].Active() )
- {//still on
- WP_SaberReturn( self, saberent );
- }
- }
- else if ( level.time - self->client->ps.saberThrowTime >= 100 )
- {
- if ( WP_ForcePowerAvailable( self, FP_SABERTHROW, 1 ) )
- {
- WP_ForcePowerDrain( self, FP_SABERTHROW, 1 );
- self->client->ps.saberThrowTime = level.time;
- }
- else
- {//out of force power, return to me
- WP_SaberReturn( self, saberent );
- }
- }
- }
- else
- {
- if ( !(ucmd->buttons&BUTTON_ALT_ATTACK) && self->client->ps.forcePowerDebounce[FP_SABERTHROW] < level.time )
- {//not holding button and has been out at least 1 second, return to me
- if ( self->client->ps.saber[0].Active() )
- {//still on
- WP_SaberReturn( self, saberent );
- }
- }
- else if ( level.time - self->client->ps.saberThrowTime > 3000
- || (self->client->ps.forcePowerLevel[FP_SABERTHROW]==FORCE_LEVEL_1&&saberDist>=self->client->ps.saberEntityDist) )
- {//been out too long, or saber throw 1 went too far, return to me
- if ( self->client->ps.saber[0].Active() )
- {//still on
- WP_SaberReturn( self, saberent );
- }
- }
- }
- }
- if ( self->client->ps.saberEntityState == SES_RETURNING )
- {
- if ( self->client->ps.saberEntityDist > 0 )
- {
- self->client->ps.saberEntityDist -= 25;
- }
- if ( self->client->ps.saberEntityDist < 0 )
- {
- self->client->ps.saberEntityDist = 0;
- }
- else if ( saberDist < self->client->ps.saberEntityDist )
- {//if it's coming back to me, never push it away
- self->client->ps.saberEntityDist = saberDist;
- }
- }
- }
- //SABER BLOCKING============================================================================
- //SABER BLOCKING============================================================================
- //SABER BLOCKING============================================================================
- //SABER BLOCKING============================================================================
- //SABER BLOCKING============================================================================
- int WP_MissileBlockForBlock( int saberBlock )
- {
- switch( saberBlock )
- {
- case BLOCKED_UPPER_RIGHT:
- return BLOCKED_UPPER_RIGHT_PROJ;
- break;
- case BLOCKED_UPPER_LEFT:
- return BLOCKED_UPPER_LEFT_PROJ;
- break;
- case BLOCKED_LOWER_RIGHT:
- return BLOCKED_LOWER_RIGHT_PROJ;
- break;
- case BLOCKED_LOWER_LEFT:
- return BLOCKED_LOWER_LEFT_PROJ;
- break;
- case BLOCKED_TOP:
- return BLOCKED_TOP_PROJ;
- break;
- }
- return saberBlock;
- }
- void WP_SaberBlockNonRandom( gentity_t *self, vec3_t hitloc, qboolean missileBlock )
- {
- vec3_t diff, fwdangles={0,0,0}, right;
- float rightdot;
- float zdiff;
- if ( self->client->ps.weaponstate == WEAPON_DROPPING ||
- self->client->ps.weaponstate == WEAPON_RAISING )
- {//don't block while changing weapons
- return;
- }
- if ( PM_SuperBreakLoseAnim( self->client->ps.torsoAnim )
- || PM_SuperBreakWinAnim( self->client->ps.torsoAnim ) )
- {
- return;
- }
- //NPCs don't auto-block
- if ( !missileBlock && self->s.number != 0 && self->client->ps.saberBlocked != BLOCKED_NONE )
- {
- return;
- }
- VectorSubtract( hitloc, self->client->renderInfo.eyePoint, diff );
- diff[2] = 0;
- VectorNormalize( diff );
- fwdangles[1] = self->client->ps.viewangles[1];
- // Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine.
- AngleVectors( fwdangles, NULL, right, NULL );
- rightdot = DotProduct(right, diff);
- zdiff = hitloc[2] - self->client->renderInfo.eyePoint[2];
-
- //FIXME: take torsoAngles into account?
- if ( zdiff > -5 )//0 )//40 )
- {
- if ( rightdot > 0.3 )
- {
- self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
- }
- else if ( rightdot < -0.3 )
- {
- self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
- }
- else
- {
- self->client->ps.saberBlocked = BLOCKED_TOP;
- }
- }
- else if ( zdiff > -22 )//-20 )//20 )
- {
- if ( zdiff < -10 )//30 )
- {//hmm, pretty low, but not low enough to use the low block, so we need to duck
- //NPC should duck, but NPC should never get here
- }
- if ( rightdot > 0.1 )
- {
- self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
- }
- else if ( rightdot < -0.1 )
- {
- self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
- }
- else
- {//FIXME: this looks really weird if the shot is too low!
- self->client->ps.saberBlocked = BLOCKED_TOP;
- }
- }
- else
- {
- if ( rightdot >= 0 )
- {
- self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
- }
- else
- {
- self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
- }
- }
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer )
- {
- if ( !self->s.number )
- {
- gi.Printf( "EyeZ: %4.2f HitZ: %4.2f zdiff: %4.2f rdot: %4.2f\n", self->client->renderInfo.eyePoint[2], hitloc[2], zdiff, rightdot );
- switch ( self->client->ps.saberBlocked )
- {
- case BLOCKED_TOP:
- gi.Printf( "BLOCKED_TOP\n" );
- break;
- case BLOCKED_UPPER_RIGHT:
- gi.Printf( "BLOCKED_UPPER_RIGHT\n" );
- break;
- case BLOCKED_UPPER_LEFT:
- gi.Printf( "BLOCKED_UPPER_LEFT\n" );
- break;
- case BLOCKED_LOWER_RIGHT:
- gi.Printf( "BLOCKED_LOWER_RIGHT\n" );
- break;
- case BLOCKED_LOWER_LEFT:
- gi.Printf( "BLOCKED_LOWER_LEFT\n" );
- break;
- default:
- break;
- }
- }
- }
- #endif
- if ( missileBlock )
- {
- self->client->ps.saberBlocked = WP_MissileBlockForBlock( self->client->ps.saberBlocked );
- }
- if ( self->client->ps.saberBlocked != BLOCKED_NONE )
- {
- int parryReCalcTime = Jedi_ReCalcParryTime( self, EVASION_PARRY );
- if ( self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] < level.time + parryReCalcTime )
- {
- self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + parryReCalcTime;
- }
- }
- }
- void WP_SaberBlock( gentity_t *saber, vec3_t hitloc, qboolean missileBlock )
- {
- gentity_t *playerent;
- vec3_t diff, fwdangles={0,0,0}, right;
- float rightdot;
- float zdiff;
- if (saber && saber->owner)
- {
- playerent = saber->owner;
- if (!playerent->client)
- {
- return;
- }
- if ( playerent->client->ps.weaponstate == WEAPON_DROPPING ||
- playerent->client->ps.weaponstate == WEAPON_RAISING )
- {//don't block while changing weapons
- return;
- }
- }
- else
- { // Bad entity passed.
- return;
- }
-
- //temporarily disabling auto-blocking for NPCs...
- if ( !missileBlock && playerent->s.number != 0 && playerent->client->ps.saberBlocked != BLOCKED_NONE )
- {
- return;
- }
- if ( PM_SuperBreakLoseAnim( playerent->client->ps.torsoAnim ) )
- {
- return;
- }
- VectorSubtract(hitloc, playerent->currentOrigin, diff);
- VectorNormalize(diff);
- fwdangles[1] = playerent->client->ps.viewangles[1];
- // Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine.
- AngleVectors( fwdangles, NULL, right, NULL );
- rightdot = DotProduct(right, diff) + Q_flrand(-0.2f,0.2f);
- zdiff = hitloc[2] - playerent->currentOrigin[2] + Q_irand(-8,8);
-
- // Figure out what quadrant the block was in.
- if (zdiff > 24)
- { // Attack from above
- if (Q_irand(0,1))
- {
- playerent->client->ps.saberBlocked = BLOCKED_TOP;
- }
- else
- {
- playerent->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
- }
- }
- else if (zdiff > 13)
- { // The upper half has three viable blocks...
- if (rightdot > 0.25)
- { // In the right quadrant...
- if (Q_irand(0,1))
- {
- playerent->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
- }
- else
- {
- playerent->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
- }
- }
- else
- {
- switch(Q_irand(0,3))
- {
- case 0:
- playerent->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
- break;
- case 1:
- case 2:
- playerent->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
- break;
- case 3:
- playerent->client->ps.saberBlocked = BLOCKED_TOP;
- break;
- }
- }
- }
- else
- { // The lower half is a bit iffy as far as block coverage. Pick one of the "low" ones at random.
- if (Q_irand(0,1))
- {
- playerent->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
- }
- else
- {
- playerent->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
- }
- }
- if ( missileBlock )
- {
- playerent->client->ps.saberBlocked = WP_MissileBlockForBlock( playerent->client->ps.saberBlocked );
- }
- }
- void WP_SaberStartMissileBlockCheck( gentity_t *self, usercmd_t *ucmd )
- {
- float dist;
- gentity_t *ent, *incoming = NULL;
- gentity_t *entityList[MAX_GENTITIES];
- int numListedEntities;
- vec3_t mins, maxs;
- int i, e;
- float closestDist, radius = 256;
- vec3_t forward, dir, missile_dir, fwdangles = {0};
- trace_t trace;
- vec3_t traceTo, entDir;
- qboolean dodgeOnlySabers = qfalse;
- if ( self->NPC && (self->NPC->scriptFlags&SCF_IGNORE_ALERTS) )
- {//don't react to things flying at me...
- return;
- }
- if ( self->health <= 0 )
- {//dead don't try to block (NOTE: actual deflection happens in missile code)
- return;
- }
- if ( PM_InKnockDown( &self->client->ps ) )
- {//can't block when knocked down
- return;
- }
- if ( PM_SuperBreakLoseAnim( self->client->ps.torsoAnim )
- || PM_SuperBreakWinAnim( self->client->ps.torsoAnim ) )
- {//can't block while in break anim
- return;
- }
- if ( Rosh_BeingHealed( self ) )
- {
- return;
- }
- if ( self->client->NPC_class == CLASS_ROCKETTROOPER )
- {//rockettrooper
- if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
- {//must be in air
- return;
- }
- if ( Q_irand( 0, 4-(g_spskill->integer*2) ) )
- {//easier level guys do this less
- return;
- }
- if ( Q_irand( 0, 3 ) )
- {//base level: 25% chance of looking for something to dodge
- if ( Q_irand( 0, 1 ) )
- {//dodge sabers twice as frequently as other projectiles
- dodgeOnlySabers = qtrue;
- }
- else
- {
- return;
- }
- }
- }
- if ( self->client->NPC_class == CLASS_BOBAFETT )
- {//Boba doesn't dodge quite as much
- if ( Q_irand( 0, 2-g_spskill->integer) )
- {//easier level guys do this less
- return;
- }
- }
- if ( self->client->NPC_class != CLASS_BOBAFETT
- && (self->client->NPC_class != CLASS_REBORN || self->s.weapon == WP_SABER)
- && (self->client->NPC_class != CLASS_ROCKETTROOPER||!self->NPC||self->NPC->rank<RANK_LT)//if a rockettrooper, but not an officer, do these normal checks
- )
- {
- if ( g_debugMelee->integer
- && (ucmd->buttons & BUTTON_USE)
- && cg.renderingThirdPerson
- && G_OkayToLean( &self->client->ps, ucmd, qfalse )
- && (self->client->ps.forcePowersActive&(1<<FP_SPEED)) )
- {
- }
- else
- {
- if ( self->client->ps.weapon != WP_SABER )
- {
- return;
- }
- if ( self->client->ps.saberInFlight )
- {
- return;
- }
- if ( self->s.number < MAX_CLIENTS )
- {
- if ( !self->client->ps.SaberLength() )
- {//player doesn't auto-activate
- return;
- }
- if ( !g_saberAutoBlocking->integer && self->client->ps.saberBlockingTime<level.time )
- {
- return;
- }
- }
- if ( !self->client->ps.saber[0].activeBlocking )
- {//can't actively block with this saber type
- return;
- }
- }
- if ( !self->s.number )
- {//don't do this if already attacking!
- if ( ucmd->buttons & BUTTON_ATTACK
- || PM_SaberInAttack( self->client->ps.saberMove )
- || PM_SaberInSpecialAttack( self->client->ps.torsoAnim )
- || PM_SaberInTransitionAny( self->client->ps.saberMove ))
- {
- return;
- }
- }
- if ( self->client->ps.forcePowerLevel[FP_SABER_DEFENSE] < FORCE_LEVEL_1 )
- {//you have not the SKILLZ
- return;
- }
- if ( self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] > level.time )
- {//can't block while already blocking
- return;
- }
- if ( self->client->ps.forcePowersActive&(1<<FP_LIGHTNING) )
- {//can't block while zapping
- return;
- }
- if ( self->client->ps.forcePowersActive&(1<<FP_DRAIN) )
- {//can't block while draining
- return;
- }
- if ( self->client->ps.forcePowersActive&(1<<FP_PUSH) )
- {//can't block while shoving
- return;
- }
- if ( self->client->ps.forcePowersActive&(1<<FP_GRIP) )
- {//can't block while gripping (FIXME: or should it break the grip? Pain should break the grip, I think...)
- return;
- }
- }
- fwdangles[1] = self->client->ps.viewangles[1];
- AngleVectors( fwdangles, forward, NULL, NULL );
- for ( i = 0 ; i < 3 ; i++ )
- {
- mins[i] = self->currentOrigin[i] - radius;
- maxs[i] = self->currentOrigin[i] + radius;
- }
- numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
- closestDist = radius;
- for ( e = 0 ; e < numListedEntities ; e++ )
- {
- ent = entityList[ e ];
- if (ent == self)
- continue;
- if (ent->owner == self)
- continue;
- if ( !(ent->inuse) )
- continue;
- if ( dodgeOnlySabers )
- {//only care about thrown sabers
- if ( ent->client
- || ent->s.weapon != WP_SABER
- || !ent->classname
- || !ent->classname[0]
- || Q_stricmp( "lightsaber", ent->classname ) )
- {//not a lightsaber, ignore it
- continue;
- }
- }
- if ( ent->s.eType != ET_MISSILE && !(ent->s.eFlags&EF_MISSILE_STICK) )
- {//not a normal projectile
- if ( ent->client || ent->s.weapon != WP_SABER )
- {//FIXME: wake up bad guys?
- continue;
- }
- if ( ent->s.eFlags & EF_NODRAW )
- {
- continue;
- }
- if ( Q_stricmp( "lightsaber", ent->classname ) != 0 )
- {//not a lightsaber
- //FIXME: what about general objects that are small in size- like rocks, etc...
- continue;
- }
- //a lightsaber.. make sure it's on and inFlight
- if ( !ent->owner || !ent->owner->client )
- {
- continue;
- }
- if ( !ent->owner->client->ps.saberInFlight )
- {//not in flight
- continue;
- }
- if ( ent->owner->client->ps.SaberLength() <= 0 )
- {//not on
- continue;
- }
- if ( ent->owner->health <= 0 && g_saberRealisticCombat->integer < 2 )
- {//it's not doing damage, so ignore it
- continue;
- }
- }
- else
- {
- if ( ent->s.pos.trType == TR_STATIONARY && !self->s.number )
- {//nothing you can do with a stationary missile if you're the player
- continue;
- }
- }
- float dot1, dot2;
- //see if they're in front of me
- VectorSubtract( ent->currentOrigin, self->currentOrigin, dir );
- dist = VectorNormalize( dir );
- //FIXME: handle detpacks, proximity mines and tripmines
- if ( ent->s.weapon == WP_THERMAL )
- {//thermal detonator!
- if ( self->NPC && dist < ent->splashRadius )
- {
- if ( dist < ent->splashRadius &&
- ent->nextthink < level.time + 600 &&
- ent->count &&
- self->client->ps.groundEntityNum != ENTITYNUM_NONE &&
- (ent->s.pos.trType == TR_STATIONARY||
- ent->s.pos.trType == TR_INTERPOLATE||
- (dot1 = DotProduct( dir, forward )) < SABER_REFLECT_MISSILE_CONE||
- !WP_ForcePowerUsable( self, FP_PUSH, 0 )) )
- {//TD is close enough to hurt me, I'm on the ground and the thing is at rest or behind me and about to blow up, or I don't have force-push so force-jump!
- //FIXME: sometimes this might make me just jump into it...?
- self->client->ps.forceJumpCharge = 480;
- }
- else if ( self->client->NPC_class != CLASS_BOBAFETT
- && (self->client->NPC_class != CLASS_REBORN || self->s.weapon == WP_SABER)
- && self->client->NPC_class != CLASS_ROCKETTROOPER )
- {//FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]]
- if ( !ent->owner || !OnSameTeam( self, ent->owner ) )
- {
- ForceThrow( self, qfalse );
- }
- }
- }
- continue;
- }
- else if ( ent->splashDamage && ent->splashRadius )
- {//exploding missile
- //FIXME: handle tripmines and detpacks somehow...
- // maybe do a force-gesture that makes them explode?
- // But what if we're within it's splashradius?
- if ( !self->s.number )
- {//players don't auto-handle these at all
- continue;
- }
- else
- {
- if ( self->client->NPC_class == CLASS_BOBAFETT
- || self->client->NPC_class == CLASS_ROCKETTROOPER )
- {
- /*
- if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) )
- {//sorry, you're scrooged here
- //FIXME: maybe jump or go up if on ground?
- continue;
- }
- //else it's a rocket, try to evade it
- */
- //HMM... let's see what happens if these guys try to avoid tripmines and detpacks, too...?
- }
- else
- {//normal Jedi
- if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK)
- && (self->client->NPC_class != CLASS_REBORN || self->s.weapon == WP_SABER) )
- {//a placed explosive like a tripmine or detpack
- if ( InFOV( ent->currentOrigin, self->client->renderInfo.eyePoint, self->client->ps.viewangles, 90, 90 ) )
- {//in front of me
- if ( G_ClearLOS( self, ent ) )
- {//can see it
- vec3_t throwDir;
- //make the gesture
- ForceThrow( self, qfalse );
- //take it off the wall and toss it
- ent->s.pos.trType = TR_GRAVITY;
- ent->s.eType = ET_MISSILE;
- ent->s.eFlags &= ~EF_MISSILE_STICK;
- ent->s.eFlags |= EF_BOUNCE_HALF;
- AngleVectors( ent->currentAngles, throwDir, NULL, NULL );
- VectorMA( ent->currentOrigin, ent->maxs[0]+4, throwDir, ent->currentOrigin );
- VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
- VectorScale( throwDir, 300, ent->s.pos.trDelta );
- ent->s.pos.trDelta[2] += 150;
- VectorMA( ent->s.pos.trDelta, 800, dir, ent->s.pos.trDelta );
- ent->s.pos.trTime = level.time; // move a bit on the very first frame
- VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
- ent->owner = self;
- // make it explode, but with less damage
- ent->splashDamage /= 3;
- ent->splashRadius /= 3;
- ent->e_ThinkFunc = thinkF_WP_Explode;
- ent->nextthink = level.time + Q_irand( 500, 3000 );
- }
- }
- }
- else if ( dist < ent->splashRadius
- && self->client->ps.groundEntityNum != ENTITYNUM_NONE
- && ( DotProduct( dir, forward ) < SABER_REFLECT_MISSILE_CONE
- || !WP_ForcePowerUsable( self, FP_PUSH, 0 ) ) )
- {//NPCs try to evade it
- self->client->ps.forceJumpCharge = 480;
- }
- else if ( (self->client->NPC_class != CLASS_REBORN || self->s.weapon == WP_SABER) )
- {//else, try to force-throw it away
- if ( !ent->owner || !OnSameTeam( self, ent->owner ) )
- {
- //FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]]
- ForceThrow( self, qfalse );
- }
- }
- //otherwise, can't block it, so we're screwed
- continue;
- }
- }
- }
- if ( ent->s.weapon != WP_SABER )
- {//only block shots coming from behind
- if ( (dot1 = DotProduct( dir, forward )) < SABER_REFLECT_MISSILE_CONE )
- continue;
- }
- else if ( !self->s.number )
- {//player never auto-blocks thrown sabers
- continue;
- }//NPCs always try to block sabers coming from behind!
- //see if they're heading towards me
- VectorCopy( ent->s.pos.trDelta, missile_dir );
- VectorNormalize( missile_dir );
- if ( (dot2 = DotProduct( dir, missile_dir )) > 0 )
- continue;
- //FIXME: must have a clear trace to me, too...
- if ( dist < closestDist )
- {
- VectorCopy( self->currentOrigin, traceTo );
- traceTo[2] = self->absmax[2] - 4;
- gi.trace( &trace, ent->currentOrigin, ent->mins, ent->maxs, traceTo, ent->s.number, ent->clipmask );
- if ( trace.allsolid || trace.startsolid || (trace.fraction < 1.0f && trace.entityNum != self->s.number && trace.entityNum != self->client->ps.saberEntityNum) )
- {//okay, try one more check
- VectorNormalize2( ent->s.pos.trDelta, entDir );
- VectorMA( ent->currentOrigin, radius, entDir, traceTo );
- gi.trace( &trace, ent->currentOrigin, ent->mins, ent->maxs, traceTo, ent->s.number, ent->clipmask );
- if ( trace.allsolid || trace.startsolid || (trace.fraction < 1.0f && trace.entityNum != self->s.number && trace.entityNum != self->client->ps.saberEntityNum) )
- {//can't hit me, ignore it
- continue;
- }
- }
- if ( self->s.number != 0 )
- {//An NPC
- if ( self->NPC && !self->enemy && ent->owner )
- {
- if ( ent->owner->health >= 0 && (!ent->owner->client || ent->owner->client->playerTeam != self->client->playerTeam) )
- {
- G_SetEnemy( self, ent->owner );
- }
- }
- }
- //FIXME: if NPC, predict the intersection between my current velocity/path and the missile's, see if it intersects my bounding box (+/-saberLength?), don't try to deflect unless it does?
- closestDist = dist;
- incoming = ent;
- }
- }
- if ( incoming )
- {
- if ( self->NPC && !G_ControlledByPlayer( self ) )
- {
- if ( Jedi_WaitingAmbush( self ) )
- {
- Jedi_Ambush( self );
- }
- if ( ( self->client->NPC_class == CLASS_BOBAFETT || self->client->NPC_class == CLASS_ROCKETTROOPER )
- && self->client->moveType == MT_FLYSWIM
- && incoming->methodOfDeath != MOD_ROCKET_ALT )
- {//a hovering Boba Fett, not a tracking rocket
- if ( !Q_irand( 0, 1 ) )
- {//strafe
- self->NPC->standTime = 0;
- self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + Q_irand( 1000, 2000 );
- }
- if ( !Q_irand( 0, 1 ) )
- {//go up/down
- TIMER_Set( self, "heightChange", Q_irand( 1000, 3000 ) );
- self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + Q_irand( 1000, 2000 );
- }
- }
- else if ( self->client->NPC_class != CLASS_ROCKETTROOPER
- && Jedi_SaberBlockGo( self, &self->NPC->last_ucmd, NULL, NULL, incoming ) != EVASION_NONE )
- {//make sure to turn on your saber if it's not on
- if ( self->client->NPC_class != CLASS_BOBAFETT
- && (self->client->NPC_class != CLASS_REBORN || self->s.weapon == WP_SABER) )
- {
- self->client->ps.SaberActivate();
- }
- }
- }
- else//player
- {
- if ( !(ucmd->buttons & BUTTON_USE) )//self->s.weapon == WP_SABER && self->client->ps.SaberActive() )
- {
- WP_SaberBlockNonRandom( self, incoming->currentOrigin, qtrue );
- }
- else
- {
- vec3_t diff, start, end;
- float dist;
- VectorSubtract( incoming->currentOrigin, self->currentOrigin, diff );
- dist = VectorLength( diff );
- VectorNormalize2( incoming->s.pos.trDelta, entDir );
- VectorMA( incoming->currentOrigin, dist, entDir, start );
- VectorCopy( self->currentOrigin, end );
- end[2] += self->maxs[2]*0.75f;
- gi.trace( &trace, start, incoming->mins, incoming->maxs, end, incoming->s.number, MASK_SHOT, G2_COLLIDE, 10 );
- Jedi_DodgeEvasion( self, incoming->owner, &trace, HL_NONE );
- }
- if ( incoming->owner && incoming->owner->client && (!self->enemy || self->enemy->s.weapon != WP_SABER) )//keep enemy jedi over shooters
- {
- self->enemy = incoming->owner;
- NPC_SetLookTarget( self, incoming->owner->s.number, level.time+1000 );
- }
- }
- }
- }
- //GENERAL SABER============================================================================
- //GENERAL SABER============================================================================
- //GENERAL SABER============================================================================
- //GENERAL SABER============================================================================
- //GENERAL SABER============================================================================
- void WP_SetSaberMove(gentity_t *self, short blocked)
- {
- self->client->ps.saberBlocked = blocked;
- }
- extern void CG_CubeOutline( vec3_t mins, vec3_t maxs, int time, unsigned int color, float alpha );
- void WP_SaberUpdate( gentity_t *self, usercmd_t *ucmd )
- {
- //float swap;
- float minsize = 16;
- if(0)// if ( self->s.number != 0 )
- {//for now only the player can do this // not anymore
- return;
- }
- if ( !self->client )
- {
- return;
- }
-
- if ( self->client->ps.saberEntityNum < 0 || self->client->ps.saberEntityNum >= ENTITYNUM_WORLD )
- {//never got one
- return;
- }
- // Check if we are throwing it, launch it if needed, update position if needed.
- WP_SaberThrow(self, ucmd);
- //vec3_t saberloc;
- //vec3_t sabermins={-8,-8,-8}, sabermaxs={8,8,8};
- gentity_t *saberent;
-
- if (self->client->ps.saberEntityNum <= 0)
- {//WTF?!! We lost it?
- return;
- }
- saberent = &g_entities[self->client->ps.saberEntityNum];
- //FIXME: Based on difficulty level/jedi saber combat skill, make this bounding box fatter/smaller
- if ( self->client->ps.saberBlocked != BLOCKED_NONE )
- {//we're blocking, increase min size
- minsize = 32;
- }
- if ( G_InCinematicSaberAnim( self ) )
- {//fake some blocking
- self->client->ps.saberBlocking = BLK_TIGHT;
- if ( self->client->ps.saber[0].Active() )
- {
- self->client->ps.saber[0].ActivateTrail( 150 );
- }
- if ( self->client->ps.saber[1].Active() )
- {
- self->client->ps.saber[1].ActivateTrail( 150 );
- }
- }
-
- //is our saber in flight?
- if ( !self->client->ps.saberInFlight )
- { // It isn't, which means we can update its position as we will.
- Vehicle_t *pVeh = G_IsRidingVehicle( self );
- if ( !self->client->ps.SaberActive()
- || PM_InKnockDown( &self->client->ps )
- || PM_SuperBreakLoseAnim( self->client->ps.torsoAnim )
- || (pVeh && pVeh->m_pVehicleInfo && pVeh->m_pVehicleInfo->type != VH_ANIMAL && pVeh->m_pVehicleInfo->type != VH_FLIER) )//riding a vehicle that you cannot block shots on
- {//can't block if saber isn't on
- VectorClear(saberent->mins);
- VectorClear(saberent->maxs);
- G_SetOrigin(saberent, self->currentOrigin);
- }
- else if ( self->client->ps.saberBlocking == BLK_TIGHT || self->client->ps.saberBlocking == BLK_WIDE )
- {//FIXME: keep bbox in front of player, even when wide?
- vec3_t saberOrg;
- if ( ( (self->s.number&&!Jedi_SaberBusy(self)&&!g_saberRealisticCombat->integer) || (self->s.number == 0 && self->client->ps.saberBlocking == BLK_WIDE && (g_saberAutoBlocking->integer||self->client->ps.saberBlockingTime>level.time)) )
- && self->client->ps.weaponTime <= 0
- && !G_InCinematicSaberAnim( self ) )
- {//full-size blocking for non-attacking player with g_saberAutoBlocking on
- vec3_t saberang={0,0,0}, fwd, sabermins={-8,-8,-8}, sabermaxs={8,8,8};
- saberang[YAW] = self->client->ps.viewangles[YAW];
- AngleVectors( saberang, fwd, NULL, NULL );
- VectorMA( self->currentOrigin, 12, fwd, saberOrg );
-
- VectorAdd( self->mins, sabermins, saberent->mins );
- VectorAdd( self->maxs, sabermaxs, saberent->maxs );
- saberent->contents = CONTENTS_LIGHTSABER;
- G_SetOrigin( saberent, saberOrg );
- }
- else
- {
- vec3_t saberBase, saberTip;
- int numSabers = 1;
- if ( self->client->ps.dualSabers )
- {
- numSabers = 2;
- }
- for ( int saberNum = 0; saberNum < numSabers; saberNum++ )
- {
- for ( int bladeNum = 0; bladeNum < self->client->ps.saber[saberNum].numBlades; bladeNum++ )
- {
- VectorCopy( self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, saberBase );
- VectorMA( saberBase, self->client->ps.saber[saberNum].blade[bladeNum].length, self->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, saberTip );
- VectorMA( saberBase, self->client->ps.saber[saberNum].blade[bladeNum].length*0.5, self->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, saberOrg );
- for ( int i = 0; i < 3; i++ )
- {
- /*
- if ( saberTip[i] > self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint[i] )
- {
- saberent->maxs[i] = saberTip[i] - saberOrg[i] + 8;//self->client->renderInfo.muzzlePoint[i];
- saberent->mins[i] = self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint[i] - saberOrg[i] - 8;
- }
- else //if ( saberTip[i] < self->client->renderInfo.muzzlePoint[i] )
- {
- saberent->maxs[i] = self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint[i] - saberOrg[i] + 8;
- saberent->mins[i] = saberTip[i] - saberOrg[i] - 8;//self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint[i];
- }
- */
- float newSizeTip = (saberTip[i] - saberOrg[i]);
- newSizeTip += (newSizeTip>=0)?8:-8;
- float newSizeBase = (saberBase[i] - saberOrg[i]);
- newSizeBase += (newSizeBase>=0)?8:-8;
- if ( newSizeTip > saberent->maxs[i] )
- {
- saberent->maxs[i] = newSizeTip;
- }
- if ( newSizeBase > saberent->maxs[i] )
- {
- saberent->maxs[i] = newSizeBase;
- }
- if ( newSizeTip < saberent->mins[i] )
- {
- saberent->mins[i] = newSizeTip;
- }
- if ( newSizeBase < saberent->mins[i] )
- {
- saberent->mins[i] = newSizeBase;
- }
- }
- }
- }
- if ( self->client->ps.weaponTime > 0
- || self->s.number
- || g_saberAutoBlocking->integer
- || self->client->ps.saberBlockingTime > level.time )
- {//if attacking or blocking (or an NPC), inflate to a minimum size
- for ( int i = 0; i < 3; i++ )
- {
- if ( saberent->maxs[i] < minsize )
- {
- saberent->maxs[i] = minsize;
- }
- if ( saberent->mins[i] > -minsize )
- {
- saberent->mins[i] = -minsize;
- }
- }
- }
- saberent->contents = CONTENTS_LIGHTSABER;
- G_SetOrigin( saberent, saberOrg );
- }
- }
- /*
- else if (self->client->ps.saberBlocking == BLK_WIDE)
- { // Assuming that we are not swinging, the saber's bounding box should be around the player.
- vec3_t saberang={0,0,0}, fwd;
- saberang[YAW] = self->client->ps.viewangles[YAW];
- AngleVectors( saberang, fwd, NULL, NULL );
- VectorMA(self->currentOrigin, 12, fwd, saberloc);
-
- VectorAdd(self->mins, sabermins, saberent->mins);
- VectorAdd(self->maxs, sabermaxs, saberent->maxs);
- saberent->contents = CONTENTS_LIGHTSABER;
- G_SetOrigin( saberent, saberloc);
- }
- else if (self->client->ps.saberBlocking == BLK_TIGHT)
- { // If the player is swinging, the bbox is around just the saber
- VectorCopy(self->client->renderInfo.muzzlePoint, sabermins);
- // Put the limits of the bbox around the saber size.
- VectorMA(sabermins, self->client->ps.saberLength, self->client->renderInfo.muzzleDir, sabermaxs);
- // Now make the mins into mins and the maxs into maxs
- if (sabermins[0] > sabermaxs[0])
- {
- swap = sabermins[0];
- sabermins[0] = sabermaxs[0];
- sabermaxs[0] = swap;
- }
- if (sabermins[1] > sabermaxs[1])
- {
- swap = sabermins[1];
- sabermins[1] = sabermaxs[1];
- sabermaxs[1] = swap;
- }
- if (sabermins[2] > sabermaxs[2])
- {
- swap = sabermins[2];
- sabermins[2] = sabermaxs[2];
- sabermaxs[2] = swap;
- }
- // Now the loc is halfway between the (absolute) mins and maxs
- VectorAdd(sabermins, sabermaxs, saberloc);
- VectorScale(saberloc, 0.5, saberloc);
- // Finally, turn the mins and maxs, which are absolute, into relative mins and maxs.
- VectorSubtract(sabermins, saberloc, saberent->mins);
- VectorSubtract(sabermaxs, saberloc, saberent->maxs);
- saberent->contents = CONTENTS_LIGHTSABER;// | CONTENTS_SHOTCLIP;
- G_SetOrigin( saberent, saberloc);
- }
- */
- else
- { // Otherwise there is no blocking possible.
- VectorClear(saberent->mins);
- VectorClear(saberent->maxs);
- G_SetOrigin(saberent, self->currentOrigin);
- }
- saberent->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
- gi.linkentity(saberent);
- }
- else
- {
- WP_SaberInFlightReflectCheck( self, ucmd );
- }
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer > 2 )
- {
- CG_CubeOutline( saberent->absmin, saberent->absmax, 50, WPDEBUG_SaberColor( self->client->ps.saber[0].blade[0].color ), 1 );
- }
- #endif
- }
- #define MAX_RADIUS_ENTS 256 //NOTE: This can cause entities to be lost
- qboolean G_CheckEnemyPresence( gentity_t *ent, int dir, float radius, float tolerance )
- {
- gentity_t *radiusEnts[ MAX_RADIUS_ENTS ];
- vec3_t mins, maxs;
- int numEnts;
- vec3_t checkDir, dir2checkEnt;
- float dist;
- switch( dir )
- {
- case DIR_RIGHT:
- AngleVectors( ent->currentAngles, NULL, checkDir, NULL );
- break;
- case DIR_LEFT:
- AngleVectors( ent->currentAngles, NULL, checkDir, NULL );
- VectorScale( checkDir, -1, checkDir );
- break;
- case DIR_FRONT:
- AngleVectors( ent->currentAngles, checkDir, NULL, NULL );
- break;
- case DIR_BACK:
- AngleVectors( ent->currentAngles, checkDir, NULL, NULL );
- VectorScale( checkDir, -1, checkDir );
- break;
- }
- //Get all ents in range, see if they're living clients and enemies, then check dot to them...
- //Setup the bbox to search in
- for ( int i = 0; i < 3; i++ )
- {
- mins[i] = ent->currentOrigin[i] - radius;
- maxs[i] = ent->currentOrigin[i] + radius;
- }
- //Get a number of entities in a given space
- numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, MAX_RADIUS_ENTS );
- for ( i = 0; i < numEnts; i++ )
- {
- //Don't consider self
- if ( radiusEnts[i] == ent )
- continue;
- //Must be valid
- if ( G_ValidEnemy( ent, radiusEnts[i] ) == qfalse )
- continue;
- VectorSubtract( radiusEnts[i]->currentOrigin, ent->currentOrigin, dir2checkEnt );
- dist = VectorNormalize( dir2checkEnt );
- if ( dist <= radius
- && DotProduct( dir2checkEnt, checkDir ) >= tolerance )
- {
- //stop on the first one
- return qtrue;
- }
- }
- return qfalse;
- }
- //OTHER JEDI POWERS=========================================================================
- //OTHER JEDI POWERS=========================================================================
- //OTHER JEDI POWERS=========================================================================
- //OTHER JEDI POWERS=========================================================================
- //OTHER JEDI POWERS=========================================================================
- extern gentity_t *TossClientItems( gentity_t *self );
- extern void ChangeWeapon( gentity_t *ent, int newWeapon );
- void WP_DropWeapon( gentity_t *dropper, vec3_t velocity )
- {
- if ( !dropper || !dropper->client )
- {
- return;
- }
- int replaceWeap = WP_NONE;
- int oldWeap = dropper->s.weapon;
- gentity_t *weapon = TossClientItems( dropper );
- if ( oldWeap == WP_THERMAL && dropper->NPC )
- {//Hmm, maybe all NPCs should go into melee? Not too many, though, or they mob you and look silly
- replaceWeap = WP_MELEE;
- }
- if ( dropper->ghoul2.IsValid() )
- {
- if ( dropper->weaponModel[0] > 0 )
- {//NOTE: guess you never drop the left-hand weapon, eh?
- gi.G2API_RemoveGhoul2Model( dropper->ghoul2, dropper->weaponModel[0] );
- dropper->weaponModel[0] = -1;
- }
- }
- //FIXME: does this work on the player?
- dropper->client->ps.stats[STAT_WEAPONS] |= ( 1 << replaceWeap );
- if ( !dropper->s.number )
- {
- if ( oldWeap == WP_THERMAL )
- {
- dropper->client->ps.ammo[weaponData[oldWeap].ammoIndex] -= weaponData[oldWeap].energyPerShot;
- }
- else
- {
- dropper->client->ps.stats[STAT_WEAPONS] &= ~( 1 << oldWeap );
- }
- CG_ChangeWeapon( replaceWeap );
- }
- else
- {
- dropper->client->ps.stats[STAT_WEAPONS] &= ~( 1 << oldWeap );
- }
- ChangeWeapon( dropper, replaceWeap );
- dropper->s.weapon = replaceWeap;
- if ( dropper->NPC )
- {
- dropper->NPC->last_ucmd.weapon = replaceWeap;
- }
- if ( weapon != NULL && velocity && !VectorCompare( velocity, vec3_origin ) )
- {//weapon should have a direction to it's throw
- VectorScale( velocity, 3, weapon->s.pos.trDelta );//NOTE: Presumes it is moving already...?
- if ( weapon->s.pos.trDelta[2] < 150 )
- {//this is presuming you don't want them to drop the weapon down on you...
- weapon->s.pos.trDelta[2] = 150;
- }
- //FIXME: gets stuck inside it's former owner...
- weapon->forcePushTime = level.time + 600; // let the push effect last for 600 ms
- }
- }
- void WP_KnockdownTurret( gentity_t *self, gentity_t *pas )
- {
- //knock it over
- VectorCopy( pas->currentOrigin, pas->s.pos.trBase );
- pas->s.pos.trType = TR_LINEAR_STOP;
- pas->s.pos.trDuration = 250;
- pas->s.pos.trTime = level.time;
- pas->s.pos.trDelta[2] = ( 12.0f / ( pas->s.pos.trDuration * 0.001f ) );
- VectorCopy( pas->currentAngles, pas->s.apos.trBase );
- pas->s.apos.trType = TR_LINEAR_STOP;
- pas->s.apos.trDuration = 250;
- pas->s.apos.trTime = level.time;
- //FIXME: pick pitch/roll that always tilts it directly away from pusher
- pas->s.apos.trDelta[PITCH] = ( 100.0f / ( pas->s.apos.trDuration * 0.001f ) );
- //kill it
- pas->count = 0;
- pas->nextthink = -1;
- G_Sound( pas, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
- //push effect?
- pas->forcePushTime = level.time + 600; // let the push effect last for 600 ms
- }
- void WP_ForceThrowHazardTrooper( gentity_t *self, gentity_t *trooper, qboolean pull )
- {
- if ( !self || !self->client )
- {
- return;
- }
- if ( !trooper || !trooper->client )
- {
- return;
- }
- //all levels: see effect on them, they notice us
- trooper->forcePushTime = level.time + 600; // let the push effect last for 600 ms
- if ( (pull&&self->client->ps.forcePowerLevel[FP_PULL]>FORCE_LEVEL_1)
- || (!pull&&self->client->ps.forcePowerLevel[FP_PUSH]>FORCE_LEVEL_1) )
- {//level 2: they stop for a couple seconds and make a sound
- trooper->painDebounceTime = level.time + Q_irand( 1500, 2500 );
- G_AddVoiceEvent( trooper, Q_irand(EV_PUSHED1, EV_PUSHED3), Q_irand( 1000, 3000 ) );
- GEntity_PainFunc( trooper, self, self, trooper->currentOrigin, 0, MOD_MELEE );
- if ( (pull&&self->client->ps.forcePowerLevel[FP_PULL]>FORCE_LEVEL_2)
- || (!pull&&self->client->ps.forcePowerLevel[FP_PUSH]>FORCE_LEVEL_2) )
- {//level 3: they actually play a pushed anim and stumble a bit
- vec3_t hazAngles = {0,trooper->currentAngles[YAW],0};
- int anim = -1;
- if ( InFront( self->currentOrigin, trooper->currentOrigin, hazAngles ) )
- {//I'm on front of him
- if ( pull )
- {
- anim = BOTH_PAIN4;
- }
- else
- {
- anim = BOTH_PAIN1;
- }
- }
- else
- {//I'm behind him
- if ( pull )
- {
- anim = BOTH_PAIN1;
- }
- else
- {
- anim = BOTH_PAIN4;
- }
- }
- if ( anim != -1 )
- {
- if ( anim == BOTH_PAIN1 )
- {//make them take a couple steps back
- AngleVectors( hazAngles, trooper->client->ps.velocity, NULL, NULL );
- VectorScale( trooper->client->ps.velocity, -40.0f, trooper->client->ps.velocity );
- trooper->client->ps.pm_flags |= PMF_TIME_NOFRICTION;
- }
- else if ( anim == BOTH_PAIN4 )
- {//make them stumble forward
- AngleVectors( hazAngles, trooper->client->ps.velocity, NULL, NULL );
- VectorScale( trooper->client->ps.velocity, 80.0f, trooper->client->ps.velocity );
- trooper->client->ps.pm_flags |= PMF_TIME_NOFRICTION;
- }
- NPC_SetAnim( trooper, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- trooper->painDebounceTime += trooper->client->ps.torsoAnimTimer;
- trooper->client->ps.pm_time = trooper->client->ps.torsoAnimTimer;
- }
- }
- if ( trooper->NPC )
- {
- if ( trooper->NPC->shotTime < trooper->painDebounceTime )
- {
- trooper->NPC->shotTime = trooper->painDebounceTime;
- }
- }
- trooper->client->ps.weaponTime = trooper->painDebounceTime-level.time;
- }
- else
- {//level 1: no pain reaction, but they should still notice
- if ( trooper->enemy == NULL//not mad at anyone
- && trooper->client->playerTeam != self->client->playerTeam//not on our team
- && !(trooper->svFlags&SVF_LOCKEDENEMY)//not locked on an enemy
- && !(trooper->svFlags&SVF_IGNORE_ENEMIES)//not ignoring enemie
- && !(self->flags&FL_NOTARGET) )//I'm not in notarget
- {//not already mad at them and can get mad at them, do so
- G_SetEnemy( trooper, self );
- }
- }
- }
- void WP_ResistForcePush( gentity_t *self, gentity_t *pusher, qboolean noPenalty )
- {
- int parts;
- qboolean runningResist = qfalse;
- if ( !self || self->health <= 0 || !self->client || !pusher || !pusher->client )
- {
- return;
- }
- if ( (!self->s.number
- ||( self->NPC && (self->NPC->aiFlags&NPCAI_BOSS_CHARACTER) )
- ||( self->client && self->client->NPC_class == CLASS_SHADOWTROOPER )
- /*
- || self->client->NPC_class == CLASS_DESANN
- || !Q_stricmp("Yoda",self->NPC_type)
- || self->client->NPC_class == CLASS_LUKE*/
- )
- && (VectorLengthSquared( self->client->ps.velocity ) > 10000 || self->client->ps.forcePowerLevel[FP_PUSH] >= FORCE_LEVEL_3 || self->client->ps.forcePowerLevel[FP_PULL] >= FORCE_LEVEL_3 ) )
- {
- runningResist = qtrue;
- }
- if ( !runningResist
- && self->client->ps.groundEntityNum != ENTITYNUM_NONE
- && !PM_SpinningSaberAnim( self->client->ps.legsAnim )
- && !PM_FlippingAnim( self->client->ps.legsAnim )
- && !PM_RollingAnim( self->client->ps.legsAnim )
- && !PM_InKnockDown( &self->client->ps )
- && !PM_CrouchAnim( self->client->ps.legsAnim ))
- {//if on a surface and not in a spin or flip, play full body resist
- parts = SETANIM_BOTH;
- }
- else
- {//play resist just in torso
- parts = SETANIM_TORSO;
- }
- //FIXME: don't interrupt big anims with this!
- NPC_SetAnim( self, parts, BOTH_RESISTPUSH, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- if ( !noPenalty )
- {
- if ( !runningResist )
- {
- VectorClear( self->client->ps.velocity );
- //still stop them from attacking or moving for a bit, though
- //FIXME: maybe push just a little (like, slide)?
- self->client->ps.weaponTime = 1000;
- if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
- {
- self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
- }
- self->client->ps.pm_time = self->client->ps.weaponTime;
- self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
- //play the full body push effect on me
- self->forcePushTime = level.time + 600; // let the push effect last for 600 ms
- }
- else
- {
- self->client->ps.weaponTime = 600;
- if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
- {
- self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
- }
- }
- }
- //play my force push effect on my hand
- //self->client->ps.powerups[PW_FORCE_PUSH] = level.time + self->client->ps.torsoAnimTimer + 500;
- //reset to 0 in case it's still > 0 from a previous push
- //self->client->pushEffectFadeTime = 0;
- if ( !pusher //???
- || pusher == self->enemy//my enemy tried to push me
- || (pusher->client && pusher->client->playerTeam != self->client->playerTeam) )//someone not on my team tried to push me
- {
- Jedi_PlayBlockedPushSound( self );
- }
- }
- extern qboolean Boba_StopKnockdown( gentity_t *self, gentity_t *pusher, const vec3_t pushDir, qboolean forceKnockdown );
- extern qboolean Jedi_StopKnockdown( gentity_t *self, gentity_t *pusher, const vec3_t pushDir );
- void WP_ForceKnockdown( gentity_t *self, gentity_t *pusher, qboolean pull, qboolean strongKnockdown, qboolean breakSaberLock )
- {
- if ( !self || !self->client || !pusher || !pusher->client )
- {
- return;
- }
- if ( self->client->NPC_class == CLASS_ROCKETTROOPER )
- {
- return;
- }
- else if ( PM_LockedAnim( self->client->ps.legsAnim ) )
- {//stuck doing something else
- return;
- }
- else if ( Rosh_BeingHealed( self ) )
- {
- return;
- }
- //break out of a saberLock?
- if ( self->client->ps.saberLockTime > level.time )
- {
- if ( breakSaberLock
- || (pusher && self->client->ps.saberLockEnemy == pusher->s.number) )
- {
- self->client->ps.saberLockTime = 0;
- self->client->ps.saberLockEnemy = ENTITYNUM_NONE;
- }
- else
- {
- return;
- }
- }
- if ( self->health > 0 )
- {
- if ( !self->s.number )
- {
- NPC_SetPainEvent( self );
- }
- else
- {
- GEntity_PainFunc( self, pusher, pusher, self->currentOrigin, 0, MOD_MELEE );
- }
- vec3_t pushDir;
- if ( pull )
- {
- VectorSubtract( pusher->currentOrigin, self->currentOrigin, pushDir );
- }
- else
- {
- VectorSubtract( self->currentOrigin, pusher->currentOrigin, pushDir );
- }
- //FIXME: sometimes do this for some NPC force-users, too!
- if ( Boba_StopKnockdown( self, pusher, pushDir, qtrue ) )
- {//He can backflip instead of be knocked down
- return;
- }
- else if ( Jedi_StopKnockdown( self, pusher, pushDir ) )
- {//They can backflip instead of be knocked down
- return;
- }
-
- G_CheckLedgeDive( self, 72, pushDir, qfalse, qfalse );
- if ( !PM_SpinningSaberAnim( self->client->ps.legsAnim )
- && !PM_FlippingAnim( self->client->ps.legsAnim )
- && !PM_RollingAnim( self->client->ps.legsAnim )
- && !PM_InKnockDown( &self->client->ps ) )
- {
- int knockAnim = BOTH_KNOCKDOWN1;//default knockdown
- if ( pusher->client->NPC_class == CLASS_DESANN && self->client->NPC_class != CLASS_LUKE )
- {//desann always knocks down, unless you're Luke
- strongKnockdown = qtrue;
- }
- if ( !self->s.number
- && !strongKnockdown
- && ( (!pull&&(self->client->ps.forcePowerLevel[FP_PUSH]>FORCE_LEVEL_1||!g_spskill->integer)) || (pull&&(self->client->ps.forcePowerLevel[FP_PULL]>FORCE_LEVEL_1||!g_spskill->integer)) ) )
- {//player only knocked down if pushed *hard*
- if ( self->s.weapon == WP_SABER )
- {//temp HACK: these are the only 2 pain anims that look good when holding a saber
- knockAnim = PM_PickAnim( self, BOTH_PAIN2, BOTH_PAIN3 );
- }
- else
- {
- knockAnim = PM_PickAnim( self, BOTH_PAIN1, BOTH_PAIN18 );
- }
- }
- else if ( PM_CrouchAnim( self->client->ps.legsAnim ) )
- {//crouched knockdown
- knockAnim = BOTH_KNOCKDOWN4;
- }
- else
- {//plain old knockdown
- vec3_t pLFwd, pLAngles = {0,self->client->ps.viewangles[YAW],0};
- vec3_t sFwd, sAngles = {0,pusher->client->ps.viewangles[YAW],0};
- AngleVectors( pLAngles, pLFwd, NULL, NULL );
- AngleVectors( sAngles, sFwd, NULL, NULL );
- if ( DotProduct( sFwd, pLFwd ) > 0.2f )
- {//pushing him from behind
- //FIXME: check to see if we're aiming below or above the waist?
- if ( pull )
- {
- knockAnim = BOTH_KNOCKDOWN1;
- }
- else
- {
- knockAnim = BOTH_KNOCKDOWN3;
- }
- }
- else
- {//pushing him from front
- if ( pull )
- {
- knockAnim = BOTH_KNOCKDOWN3;
- }
- else
- {
- knockAnim = BOTH_KNOCKDOWN1;
- }
- }
- }
- if ( knockAnim == BOTH_KNOCKDOWN1 && strongKnockdown )
- {//push *hard*
- knockAnim = BOTH_KNOCKDOWN2;
- }
- NPC_SetAnim( self, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- if ( self->s.number >= MAX_CLIENTS )
- {//randomize getup times - but not for boba
- int addTime;
- if ( self->client->NPC_class == CLASS_BOBAFETT )
- {
- addTime = Q_irand( -500, 0 );
- }
- else
- {
- addTime = Q_irand( -300, 300 );
- }
- self->client->ps.legsAnimTimer += addTime;
- self->client->ps.torsoAnimTimer += addTime;
- }
- else
- {//player holds extra long so you have more time to decide to do the quick getup
- if ( PM_KnockDownAnim( self->client->ps.legsAnim ) )
- {
- self->client->ps.legsAnimTimer += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME;
- self->client->ps.torsoAnimTimer += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME;
- }
- }
- //
- if ( pusher->NPC && pusher->enemy == self )
- {//pushed pushed down his enemy
- G_AddVoiceEvent( pusher, Q_irand( EV_GLOAT1, EV_GLOAT3 ), 3000 );
- pusher->NPC->blockedSpeechDebounceTime = level.time + 3000;
- }
- }
- }
- self->forcePushTime = level.time + 600; // let the push effect last for 600 ms
- }
- qboolean WP_ForceThrowable( gentity_t *ent, gentity_t *forwardEnt, gentity_t *self, qboolean pull, float cone, float radius, vec3_t forward )
- {
- if (ent == self)
- return qfalse;
- if ( ent->owner == self && ent->s.weapon != WP_THERMAL )//can push your own thermals
- return qfalse;
- if ( !(ent->inuse) )
- return qfalse;
- if ( ent->NPC && ent->NPC->scriptFlags & SCF_NO_FORCE )
- {
- if ( ent->s.weapon == WP_SABER )
- {//Hmm, should jedi do the resist behavior? If this is on, perhaps it's because of a cinematic?
- WP_ResistForcePush( ent, self, qtrue );
- }
- return qfalse;
- }
- if ( (ent->flags&FL_FORCE_PULLABLE_ONLY) && !pull )
- {//simple HACK: cannot force-push ammo rack items (because they may start in solid)
- return qfalse;
- }
- //FIXME: don't push it if I already pushed it a little while ago
- if ( ent->s.eType != ET_MISSILE )
- {
- if ( ent->client )
- {
- if ( ent->client->ps.pullAttackTime > level.time )
- {
- return qfalse;
- }
- }
- if ( cone >= 1.0f )
- {//must be pointing right at them
- if ( ent != forwardEnt )
- {//must be the person I'm looking right at
- if ( ent->client && !pull
- && ent->client->ps.forceGripEntityNum == self->s.number
- && (self->s.eFlags&EF_FORCE_GRIPPED) )
- {//this is the guy that's force-gripping me, use a wider cone regardless of force power level
- }
- else
- {
- if ( ent->client && !pull
- && ent->client->ps.forceDrainEntityNum == self->s.number
- && (self->s.eFlags&EF_FORCE_DRAINED) )
- {//this is the guy that's force-draining me, use a wider cone regardless of force power level
- }
- else
- {
- return qfalse;
- }
- }
- }
- }
- if ( ent->s.eType != ET_ITEM && ent->e_ThinkFunc != thinkF_G_RunObject )//|| !(ent->flags&FL_DROPPED_ITEM) )//was only dropped items
- {
- //FIXME: need pushable objects
- if ( ent->s.eFlags & EF_NODRAW )
- {
- return qfalse;
- }
- if ( !ent->client )
- {
- if ( Q_stricmp( "lightsaber", ent->classname ) != 0 )
- {//not a lightsaber
- if ( !(ent->svFlags&SVF_GLASS_BRUSH) )
- {//and not glass
- if ( Q_stricmp( "func_door", ent->classname ) != 0 || !(ent->spawnflags & 2/*MOVER_FORCE_ACTIVATE*/) )
- {//not a force-usable door
- if ( Q_stricmp( "func_static", ent->classname ) != 0 || (!(ent->spawnflags&1/*F_PUSH*/)&&!(ent->spawnflags&2/*F_PULL*/)) || (ent->spawnflags&32/*SOLITARY*/) )
- {//not a force-usable func_static or, it is one, but it's solitary, so you only press it when looking right at it
- if ( Q_stricmp( "limb", ent->classname ) )
- {//not a limb
- if ( ent->s.weapon == WP_TURRET && !Q_stricmp( "PAS", ent->classname ) && ent->s.apos.trType == TR_STATIONARY )
- {//can knock over placed turrets
- if ( !self->s.number || self->enemy != ent )
- {//only NPCs who are actively mad at this turret can push it over
- return qfalse;
- }
- }
- else
- {
- return qfalse;
- }
- }
- }
- }
- else if ( ent->moverState != MOVER_POS1 && ent->moverState != MOVER_POS2 )
- {//not at rest
- return qfalse;
- }
- }
- }
- //return qfalse;
- }
- else if ( ent->client->NPC_class == CLASS_MARK1 )
- {//can't push Mark1 unless push 3
- if ( pull || self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 )
- {
- return qfalse;
- }
- }
- else if ( ent->client->NPC_class == CLASS_GALAKMECH
- || ent->client->NPC_class == CLASS_ATST
- || ent->client->NPC_class == CLASS_RANCOR
- || ent->client->NPC_class == CLASS_WAMPA
- || ent->client->NPC_class == CLASS_SAND_CREATURE )
- {//can't push ATST or Galak or Rancor or Wampa
- return qfalse;
- }
- else if ( ent->s.weapon == WP_EMPLACED_GUN )
- {//FIXME: maybe can pull them out?
- return qfalse;
- }
- else if ( ent->client->playerTeam == self->client->playerTeam && self->enemy && self->enemy != ent )
- {//can't accidently push a teammate while in combat
- return qfalse;
- }
- else if ( G_IsRidingVehicle( ent )
- && (ent->s.eFlags&EF_NODRAW) )
- {//can't push/pull anyone riding *inside* vehicle
- return qfalse;
- }
- }
- else if ( ent->s.eType == ET_ITEM
- && ent->item
- && ent->item->giType == IT_HOLDABLE
- && ent->item->giTag == INV_SECURITY_KEY )
- //&& (ent->flags&FL_DROPPED_ITEM) ???
- {//dropped security keys can't be pushed? But placed ones can...? does this make any sense?
- if ( !pull || self->s.number )
- {//can't push, NPC's can't do anything to it
- return qfalse;
- }
- else
- {
- if ( g_crosshairEntNum != ent->s.number )
- {//player can pull it if looking *right* at it
- if ( cone >= 1.0f )
- {//we did a forwardEnt trace
- if ( forwardEnt != ent )
- {//must be pointing right at them
- return qfalse;
- }
- }
- else if ( forward )
- {//do a forwardEnt trace
- trace_t tr;
- vec3_t end;
- VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
- gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_OPAQUE|CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_ITEM|CONTENTS_CORPSE );//was MASK_SHOT, changed to match crosshair trace
- if ( tr.entityNum != ent->s.number )
- {//last chance
- return qfalse;
- }
- }
- }
- }
- }
- }
- else
- {
- switch ( ent->s.weapon )
- {//only missiles with mass are force-pushable
- case WP_SABER:
- case WP_FLECHETTE:
- case WP_ROCKET_LAUNCHER:
- case WP_CONCUSSION:
- case WP_THERMAL:
- case WP_TRIP_MINE:
- case WP_DET_PACK:
- break;
- //only alt-fire of this weapon is force-pushable
- case WP_REPEATER:
- if ( ent->methodOfDeath != MOD_REPEATER_ALT )
- {//not an alt-fire missile
- return qfalse;
- }
- break;
- //everything else cannot be pushed
- case WP_ATST_SIDE:
- if ( ent->methodOfDeath != MOD_EXPLOSIVE )
- {//not a rocket
- return qfalse;
- }
- break;
- default:
- return qfalse;
- break;
- }
- if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) )
- {//can't force-push/pull stuck missiles (detpacks, tripmines)
- return qfalse;
- }
- if ( ent->s.pos.trType == TR_STATIONARY && ent->s.weapon != WP_THERMAL )
- {//only thermal detonators can be pushed once stopped
- return qfalse;
- }
- }
- return qtrue;
- }
- bool dontPillarPush = false;
- void ForceThrow( gentity_t *self, qboolean pull, qboolean fake )
- {//FIXME: pass in a target ent so we (an NPC) can push/pull just one targeted ent.
- //shove things in front of you away
- float dist;
- gentity_t *ent, *forwardEnt = NULL;
- gentity_t *entityList[MAX_GENTITIES];
- gentity_t *push_list[MAX_GENTITIES];
- int numListedEntities = 0;
- vec3_t mins, maxs;
- vec3_t v;
- int i, e;
- int ent_count = 0;
- int radius;
- vec3_t center, ent_org, size, forward, right, end, dir, fwdangles = {0};
- float dot1, cone;
- trace_t tr;
- int anim, hold, soundIndex, cost, actualCost;
- qboolean noResist = qfalse;
- #ifdef _IMMERSION
- int forceIndex;
- #endif // _IMMERSION
- if ( self->health <= 0 )
- {
- return;
- }
- if ( self->client->ps.leanofs )
- {//can't force-throw while leaning
- return;
- }
- if ( self->client->ps.forcePowerDebounce[FP_PUSH] > level.time )
- {//already pushing- now you can't haul someone across the room, sorry
- return;
- }
- if ( self->client->ps.forcePowerDebounce[FP_PULL] > level.time )
- {//already pulling- now you can't haul someone across the room, sorry
- return;
- }
- if ( self->client->ps.pullAttackTime > level.time )
- {//already pull-attacking
- return;
- }
- if ( !self->s.number && (cg.zoomMode || in_camera) )
- {//can't force throw/pull when zoomed in or in cinematic
- return;
- }
- if ( self->client->ps.saberLockTime > level.time )
- {
- if ( pull || self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 )
- {//this can be a way to break out
- return;
- }
- //else, I'm breaking my half of the saberlock
- self->client->ps.saberLockTime = 0;
- self->client->ps.saberLockEnemy = ENTITYNUM_NONE;
- }
- if ( self->client->ps.legsAnim == BOTH_KNOCKDOWN3
- || (self->client->ps.torsoAnim == BOTH_FORCE_GETUP_F1 && self->client->ps.torsoAnimTimer > 400)
- || (self->client->ps.torsoAnim == BOTH_FORCE_GETUP_F2 && self->client->ps.torsoAnimTimer > 900)
- || (self->client->ps.torsoAnim == BOTH_GETUP3 && self->client->ps.torsoAnimTimer > 500)
- || (self->client->ps.torsoAnim == BOTH_GETUP4 && self->client->ps.torsoAnimTimer > 300)
- || (self->client->ps.torsoAnim == BOTH_GETUP5 && self->client->ps.torsoAnimTimer > 500) )
- {//we're face-down, so we'd only be force-push/pulling the floor
- return;
- }
- if ( pull )
- {
- radius = forcePushPullRadius[self->client->ps.forcePowerLevel[FP_PULL]];
- }
- else
- {
- radius = forcePushPullRadius[self->client->ps.forcePowerLevel[FP_PUSH]];
- }
- if ( !radius )
- {//no ability to do this yet
- return;
- }
- if ( pull )
- {
- cost = forcePowerNeeded[FP_PULL];
- if ( !WP_ForcePowerUsable( self, FP_PULL, cost ) )
- {
- return;
- }
- //make sure this plays and that you cannot press fire for about 200ms after this
- anim = BOTH_FORCEPULL;
- soundIndex = G_SoundIndex( "sound/weapons/force/pull.wav" );
- #ifdef _IMMERSION
- forceIndex = G_ForceIndex( "fffx/weapons/force/pull", FF_CHANNEL_WEAPON );
- #endif // _IMMERSION
- hold = 200;
- }
- else
- {
- cost = forcePowerNeeded[FP_PUSH];
- if ( !WP_ForcePowerUsable( self, FP_PUSH, cost ) )
- {
- return;
- }
- //make sure this plays and that you cannot press fire for about 1 second after this
- anim = BOTH_FORCEPUSH;
- soundIndex = G_SoundIndex( "sound/weapons/force/push.wav" );
- #ifdef _IMMERSION
- forceIndex = G_ForceIndex( "fffx/weapons/force/push", FF_CHANNEL_WEAPON );
- #endif // _IMMERSION
- hold = 650;
- }
- int parts = SETANIM_TORSO;
- if ( !PM_InKnockDown( &self->client->ps ) )
- {
- if ( self->client->ps.saberLockTime > level.time )
- {
- self->client->ps.saberLockTime = 0;
- self->painDebounceTime = level.time + 2000;
- hold += 1000;
- parts = SETANIM_BOTH;
- }
- else if ( !VectorLengthSquared( self->client->ps.velocity ) && !(self->client->ps.pm_flags&PMF_DUCKED))
- {
- parts = SETANIM_BOTH;
- }
- }
- NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART );
- self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
- self->client->ps.saberBlocked = BLOCKED_NONE;
- if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
- {
- hold = floor( hold*g_timescale->value );
- }
- self->client->ps.weaponTime = hold;//was 1000, but want to swing sooner
- //do effect... FIXME: build-up or delay this until in proper part of anim
- self->client->ps.powerups[PW_FORCE_PUSH] = level.time + self->client->ps.torsoAnimTimer + 500;
- //reset to 0 in case it's still > 0 from a previous push
- self->client->pushEffectFadeTime = 0;
- G_Sound( self, soundIndex );
- #ifdef _IMMERSION
- G_Force( self, forceIndex );
- #endif // _IMMERSION
- if ( (!pull && self->client->ps.forcePowersForced&(1<<FP_PUSH))
- || (pull && self->client->ps.forcePowersForced&(1<<FP_PULL))
- || (pull&&self->client->NPC_class==CLASS_KYLE&&(self->spawnflags&1)&&TIMER_Done( self, "kyleTakesSaber" )) )
- {
- noResist = qtrue;
- }
- VectorCopy( self->client->ps.viewangles, fwdangles );
- //fwdangles[1] = self->client->ps.viewangles[1];
- AngleVectors( fwdangles, forward, right, NULL );
- VectorCopy( self->currentOrigin, center );
- if ( pull )
- {
- cone = forcePullCone[self->client->ps.forcePowerLevel[FP_PULL]];
- }
- else
- {
- cone = forcePushCone[self->client->ps.forcePowerLevel[FP_PUSH]];
- }
- // if ( cone >= 1.0f )
- {//must be pointing right at them
- VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
- gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_OPAQUE|CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_ITEM|CONTENTS_CORPSE );//was MASK_SHOT, changed to match crosshair trace
- if ( tr.entityNum < ENTITYNUM_WORLD )
- {//found something right in front of self,
- forwardEnt = &g_entities[tr.entityNum];
- if ( !forwardEnt->client && !Q_stricmp( "func_static", forwardEnt->classname ) )
- {
- if ( (forwardEnt->spawnflags&1/*F_PUSH*/)||(forwardEnt->spawnflags&2/*F_PULL*/) )
- {//push/pullable
- if ( (forwardEnt->spawnflags&32/*SOLITARY*/) )
- {//can only push/pull ME, ignore all others
- if ( forwardEnt->NPC_targetname == NULL
- || (self->targetname&&Q_stricmp( forwardEnt->NPC_targetname, self->targetname ) == 0) )
- {//anyone can push it or only 1 person can push it and it's me
- push_list[0] = forwardEnt;
- ent_count = numListedEntities = 1;
- }
- }
- }
- }
- }
- }
- if ( forwardEnt )
- {
- if ( G_TryingPullAttack( self, &self->client->usercmd, qtrue ) )
- {//we're going to try to do a pull attack on our forwardEnt
- if ( WP_ForceThrowable( forwardEnt, forwardEnt, self, pull, cone, radius, forward ) )
- {//we will actually pull-attack him, so don't pull him or anything else here
- //activate the power, here, though, so the later check that actually does the pull attack knows we tried to pull
- self->client->ps.forcePowersActive |= (1<<FP_PULL);
- self->client->ps.forcePowerDebounce[FP_PULL] = level.time + 100; //force-pulling
- return;
- }
- }
- }
- if ( !numListedEntities )
- {
- for ( i = 0 ; i < 3 ; i++ )
- {
- mins[i] = center[i] - radius;
- maxs[i] = center[i] + radius;
- }
- numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
- for ( e = 0 ; e < numListedEntities ; e++ )
- {
- ent = entityList[ e ];
- if ( !WP_ForceThrowable( ent, forwardEnt, self, pull, cone, radius, forward ) )
- {
- continue;
- }
- //this is all to see if we need to start a saber attack, if it's in flight, this doesn't matter
- // find the distance from the edge of the bounding box
- for ( i = 0 ; i < 3 ; i++ )
- {
- if ( center[i] < ent->absmin[i] )
- {
- v[i] = ent->absmin[i] - center[i];
- } else if ( center[i] > ent->absmax[i] )
- {
- v[i] = center[i] - ent->absmax[i];
- } else
- {
- v[i] = 0;
- }
- }
- VectorSubtract( ent->absmax, ent->absmin, size );
- VectorMA( ent->absmin, 0.5, size, ent_org );
- //see if they're in front of me
- VectorSubtract( ent_org, center, dir );
- VectorNormalize( dir );
- if ( cone < 1.0f )
- {//must be within the forward cone
- if ( ent->client && !pull
- && ent->client->ps.forceGripEntityNum == self->s.number
- && (self->s.eFlags&EF_FORCE_GRIPPED) )
- {//this is the guy that's force-gripping me, use a wider cone regardless of force power level
- if ( (dot1 = DotProduct( dir, forward )) < cone-0.3f )
- continue;
- }
- else if ( ent->client && !pull
- && ent->client->ps.forceDrainEntityNum == self->s.number
- && (self->s.eFlags&EF_FORCE_DRAINED) )
- {//this is the guy that's force-draining me, use a wider cone regardless of force power level
- if ( (dot1 = DotProduct( dir, forward )) < cone-0.3f )
- continue;
- }
- else if ( ent->s.eType == ET_MISSILE )//&& ent->s.eType != ET_ITEM && ent->e_ThinkFunc != thinkF_G_RunObject )
- {//missiles are easier to force-push, never require direct trace (FIXME: maybe also items and general physics objects)
- if ( (dot1 = DotProduct( dir, forward )) < cone-0.3f )
- continue;
- }
- else if ( (dot1 = DotProduct( dir, forward )) < cone )
- {
- continue;
- }
- }
- else if ( ent->s.eType == ET_MISSILE )
- {//a missile and we're at force level 1... just use a small cone, but not ridiculously small
- if ( (dot1 = DotProduct( dir, forward )) < 0.75f )
- {
- continue;
- }
- }//else is an NPC or brush entity that our forward trace would have to hit
- dist = VectorLength( v );
- //Now check and see if we can actually deflect it
- //method1
- //if within a certain range, deflect it
- if ( ent->s.eType == ET_MISSILE && cone >= 1.0f )
- {//smaller radius on missile checks at force push 1
- if ( dist >= 192 )
- {
- continue;
- }
- }
- else if ( dist >= radius )
- {
- continue;
- }
-
- //in PVS?
- if ( !ent->bmodel && !gi.inPVS( ent_org, self->client->renderInfo.eyePoint ) )
- {//must be in PVS
- continue;
- }
- if ( ent != forwardEnt )
- {//don't need to trace against forwardEnt again
- //really should have a clear LOS to this thing...
- gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_FORCE_PUSH );//was MASK_SHOT, but changed to match above trace and crosshair trace
- if ( tr.fraction < 1.0f && tr.entityNum != ent->s.number )
- {//must have clear LOS
- continue;
- }
- }
- // ok, we are within the radius, add us to the incoming list
- push_list[ent_count] = ent;
- ent_count++;
- }
- }
- if ( ent_count )
- {
- for ( int x = 0; x < ent_count; x++ )
- {
- if ( push_list[x]->client )
- {
- vec3_t pushDir;
- float knockback = pull?0:200;
-
- //SIGH band-aid...
- if ( push_list[x]->s.number >= MAX_CLIENTS
- && self->s.number < MAX_CLIENTS )
- {
- if ( (push_list[x]->client->ps.forcePowersActive&(1<<FP_GRIP))
- //&& push_list[x]->client->ps.forcePowerDebounce[FP_GRIP] < level.time
- && push_list[x]->client->ps.forceGripEntityNum == self->s.number )
- {
- WP_ForcePowerStop( push_list[x], FP_GRIP );
- }
- if ( (push_list[x]->client->ps.forcePowersActive&(1<<FP_DRAIN))
- //&& push_list[x]->client->ps.forcePowerDebounce[FP_DRAIN] < level.time
- && push_list[x]->client->ps.forceDrainEntityNum == self->s.number )
- {
- WP_ForcePowerStop( push_list[x], FP_DRAIN );
- }
- }
- if ( Rosh_BeingHealed( push_list[x] ) )
- {
- continue;
- }
- if ( push_list[x]->client->NPC_class == CLASS_HAZARD_TROOPER
- && push_list[x]->health > 0 )
- {//living hazard troopers resist push/pull
- WP_ForceThrowHazardTrooper( self, push_list[x], pull );
- continue;
- }
- if ( fake )
- {//always resist
- WP_ResistForcePush( push_list[x], self, qfalse );
- continue;
- }
- //FIXMEFIXMEFIXMEFIXMEFIXME: extern a lot of this common code when I have the time!!!
- int powerLevel, powerUse;
- if (pull)
- {
- powerLevel = self->client->ps.forcePowerLevel[FP_PULL];
- powerUse = FP_PULL;
- }
- else
- {
- powerLevel = self->client->ps.forcePowerLevel[FP_PUSH];
- powerUse = FP_PUSH;
- }
- int modPowerLevel = WP_AbsorbConversion( push_list[x], push_list[x]->client->ps.forcePowerLevel[FP_ABSORB], self, powerUse, powerLevel, forcePowerNeeded[self->client->ps.forcePowerLevel[powerUse]] );
- if (push_list[x]->client->NPC_class==CLASS_ASSASSIN_DROID ||
- push_list[x]->client->NPC_class==CLASS_HAZARD_TROOPER)
- {
- modPowerLevel = 0; // devides throw by 10
- }
- //First, if this is the player we're push/pulling, see if he can counter it
- if ( modPowerLevel != -1
- && !noResist
- && InFront( self->currentOrigin, push_list[x]->client->renderInfo.eyePoint, push_list[x]->client->ps.viewangles, 0.3f ) )
- {//absorbed and I'm in front of them
- //counter it
- if ( push_list[x]->client->ps.forcePowerLevel[FP_ABSORB] > FORCE_LEVEL_2 )
- {//no reaction at all
- }
- else
- {
- WP_ResistForcePush( push_list[x], self, qfalse );
- push_list[x]->client->ps.saberMove = push_list[x]->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
- push_list[x]->client->ps.saberBlocked = BLOCKED_NONE;
- }
- continue;
- }
- else if ( !push_list[x]->s.number )
- {//player
- if ( !noResist
- && push_list[x]->health > 0 //alive
- && push_list[x]->client //client
- && push_list[x]->client->ps.forceRageRecoveryTime < level.time //not recobering from rage
- && push_list[x]->client->ps.torsoAnim != BOTH_FORCEGRIP_HOLD// BOTH_FORCEGRIP1//wasn't trying to grip anyone
- //&& push_list[x]->client->ps.torsoAnim != BOTH_HUGGER1// wasn't trying to grip-drain anyone
- && push_list[x]->client->ps.torsoAnim != BOTH_FORCE_DRAIN_GRAB_START// wasn't trying to grip-drain anyone
- && push_list[x]->client->ps.torsoAnim != BOTH_FORCE_DRAIN_GRAB_HOLD// wasn't trying to grip-drain anyone
- && ((self->client->NPC_class != CLASS_DESANN&&Q_stricmp("Yoda",self->NPC_type)) || !Q_irand( 0, 2 ) )//only 30% chance of resisting a Desann push
- && push_list[x]->client->ps.groundEntityNum != ENTITYNUM_NONE//on the ground
- && !PM_InKnockDown( &push_list[x]->client->ps )//not knocked down already
- && push_list[x]->client->ps.saberLockTime < level.time//not involved in a saberLock
- && push_list[x]->client->ps.weaponTime < level.time//not attacking or otherwise busy
- && (push_list[x]->client->ps.weapon == WP_SABER||push_list[x]->client->ps.weapon == WP_MELEE) )//using saber or fists
- {//trying to push or pull the player!
- if ( push_list[x]->client->ps.powerups[PW_FORCE_PUSH] > level.time//player was pushing/pulling too
- ||( pull && Q_irand( 0, (push_list[x]->client->ps.forcePowerLevel[FP_PULL] - self->client->ps.forcePowerLevel[FP_PULL])*2+1 ) > 0 )//player's pull is high enough
- ||( !pull && Q_irand( 0, (push_list[x]->client->ps.forcePowerLevel[FP_PUSH] - self->client->ps.forcePowerLevel[FP_PUSH])*2+1 ) > 0 ) )//player's push is high enough
- {//player's force push/pull is high enough to try to stop me
- if ( InFront( self->currentOrigin, push_list[x]->client->renderInfo.eyePoint, push_list[x]->client->ps.viewangles, 0.3f ) )
- {//I'm in front of player
- WP_ResistForcePush( push_list[x], self, qfalse );
- push_list[x]->client->ps.saberMove = push_list[x]->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
- push_list[x]->client->ps.saberBlocked = BLOCKED_NONE;
- continue;
- }
- }
- }
- }
- else if ( push_list[x]->client && Jedi_WaitingAmbush( push_list[x] ) )
- {
- WP_ForceKnockdown( push_list[x], self, pull, qtrue, qfalse );
- continue;
- }
- G_KnockOffVehicle( push_list[x], self, pull );
-
- if ( !pull
- && push_list[x]->client->ps.forceDrainEntityNum == self->s.number
- && (self->s.eFlags&EF_FORCE_DRAINED) )
- {//stop them from draining me now, dammit!
- WP_ForcePowerStop( push_list[x], FP_DRAIN );
- }
- //okay, everyone else (or player who couldn't resist it)...
- if ( ((self->s.number == 0 && Q_irand( 0, 2 ) ) || Q_irand( 0, 2 ) ) && push_list[x]->client && push_list[x]->health > 0 //a living client
- && push_list[x]->client->ps.weapon == WP_SABER //Jedi
- && push_list[x]->health > 0 //alive
- && push_list[x]->client->ps.forceRageRecoveryTime < level.time //not recobering from rage
- && ((self->client->NPC_class != CLASS_DESANN&&Q_stricmp("Yoda",self->NPC_type)) || !Q_irand( 0, 2 ) )//only 30% chance of resisting a Desann push
- && push_list[x]->client->ps.groundEntityNum != ENTITYNUM_NONE //on the ground
- && InFront( self->currentOrigin, push_list[x]->currentOrigin, push_list[x]->client->ps.viewangles, 0.3f ) //I'm in front of him
- && ( push_list[x]->client->ps.powerups[PW_FORCE_PUSH] > level.time ||//he's pushing too
- (push_list[x]->s.number != 0 && push_list[x]->client->ps.weaponTime < level.time)//not the player and not attacking (NPC jedi auto-defend against pushes)
- )
- )
- {//Jedi don't get pushed, they resist as long as they aren't already attacking and are on the ground
- if ( push_list[x]->client->ps.saberLockTime > level.time )
- {//they're in a lock
- if ( push_list[x]->client->ps.saberLockEnemy != self->s.number )
- {//they're not in a lock with me
- continue;
- }
- else if ( pull || self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 ||
- push_list[x]->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 )
- {//they're in a lock with me, but my push is too weak
- continue;
- }
- else
- {//we will knock them down
- self->painDebounceTime = 0;
- self->client->ps.weaponTime = 500;
- if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
- {
- self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
- }
- }
- }
- int resistChance = Q_irand(0, 2);
- if ( push_list[x]->s.number >= MAX_CLIENTS )
- {//NPC
- if ( g_spskill->integer == 1 )
- {//stupid tweak for graham
- resistChance = Q_irand(0, 3);
- }
- }
- if ( noResist ||
- ( !pull
- && modPowerLevel == -1
- && self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2
- && !resistChance
- && push_list[x]->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 )
- )
- {//a level 3 push can even knock down a jedi
- if ( PM_InKnockDown( &push_list[x]->client->ps ) )
- {//can't knock them down again
- continue;
- }
- WP_ForceKnockdown( push_list[x], self, pull, qfalse, qtrue );
- }
- else
- {
- WP_ResistForcePush( push_list[x], self, qfalse );
- }
- }
- else
- {
- //UGH: FIXME: for enemy jedi, they should probably always do force pull 3, and not your weapon (if player?)!
- //shove them
- if ( push_list[x]->NPC
- && push_list[x]->NPC->jumpState == JS_JUMPING )
- {//don't interrupt a scripted jump
- //WP_ResistForcePush( push_list[x], self, qfalse );
- push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms
- continue;
- }
- if ( push_list[x]->s.number
- && (push_list[x]->message || (push_list[x]->flags&FL_NO_KNOCKBACK)) )
- {//an NPC who has a key
- //don't push me... FIXME: maybe can pull the key off me?
- WP_ForceKnockdown( push_list[x], self, pull, qfalse, qfalse );
- continue;
- }
- if ( pull )
- {
- VectorSubtract( self->currentOrigin, push_list[x]->currentOrigin, pushDir );
- if ( self->client->ps.forcePowerLevel[FP_PULL] >= FORCE_LEVEL_3
- && self->client->NPC_class == CLASS_KYLE
- && (self->spawnflags&1)
- && TIMER_Done( self, "kyleTakesSaber" )
- && push_list[x]->client
- && push_list[x]->client->ps.weapon == WP_SABER
- && !push_list[x]->client->ps.saberInFlight
- && push_list[x]->client->ps.saberEntityNum < ENTITYNUM_WORLD
- && !PM_InOnGroundAnim( &push_list[x]->client->ps ) )
- {
- vec3_t throwVec;
- VectorScale( pushDir, 10.0f, throwVec );
- WP_SaberLose( push_list[x], throwVec );
- NPC_SetAnim( push_list[x], SETANIM_BOTH, BOTH_LOSE_SABER, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- push_list[x]->client->ps.torsoAnimTimer += 500;
- push_list[x]->client->ps.pm_time = push_list[x]->client->ps.weaponTime = push_list[x]->client->ps.torsoAnimTimer;
- push_list[x]->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
- push_list[x]->client->ps.saberMove = LS_NONE;
- push_list[x]->aimDebounceTime = level.time + push_list[x]->client->ps.torsoAnimTimer;
- VectorClear( push_list[x]->client->ps.velocity );
- VectorClear( push_list[x]->client->ps.moveDir );
- //Kyle will stand around for a bit, too...
- self->client->ps.pm_time = self->client->ps.weaponTime = 2000;
- self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
- self->painDebounceTime = level.time + self->client->ps.weaponTime;
- TIMER_Set( self, "kyleTakesSaber", Q_irand( 60000, 180000 ) );//don't do this again for a while
- G_AddVoiceEvent( self, Q_irand(EV_TAUNT1,EV_TAUNT3), Q_irand( 4000, 6000 ) );
- VectorClear( self->client->ps.velocity );
- VectorClear( self->client->ps.moveDir );
- continue;
- }
- else if ( push_list[x]->NPC
- && (push_list[x]->NPC->scriptFlags&SCF_DONT_FLEE) )
- {//*SIGH*... if an NPC can't flee, they can't run after and pick up their weapon, do don't drop it
- }
- else if ( self->client->ps.forcePowerLevel[FP_PULL] > FORCE_LEVEL_1
- && push_list[x]->client->NPC_class != CLASS_ROCKETTROOPER//rockettroopers never drop their weapon
- && push_list[x]->client->NPC_class != CLASS_VEHICLE
- && push_list[x]->client->NPC_class != CLASS_BOBAFETT
- && push_list[x]->client->NPC_class != CLASS_TUSKEN
- && push_list[x]->client->NPC_class != CLASS_HAZARD_TROOPER
- && push_list[x]->client->NPC_class != CLASS_ASSASSIN_DROID
- && push_list[x]->s.weapon != WP_SABER
- && push_list[x]->s.weapon != WP_MELEE
- && push_list[x]->s.weapon != WP_THERMAL
- && push_list[x]->s.weapon != WP_CONCUSSION // so rax can't drop his
- )
- {//yank the weapon - NOTE: level 1 just knocks them down, not take weapon
- //FIXME: weapon yank anim if not a knockdown?
- if ( InFront( self->currentOrigin, push_list[x]->currentOrigin, push_list[x]->client->ps.viewangles, 0.0f ) )
- {//enemy has to be facing me, too...
- WP_DropWeapon( push_list[x], pushDir );
- }
- }
- knockback += VectorNormalize( pushDir );
- if ( knockback > 200 )
- {
- knockback = 200;
- }
- if ( self->client->ps.forcePowerLevel[FP_PULL] < FORCE_LEVEL_3 )
- {//maybe just knock them down
- knockback /= 3;
- }
- }
- else
- {
- VectorSubtract( push_list[x]->currentOrigin, self->currentOrigin, pushDir );
- knockback -= VectorNormalize( pushDir );
- if ( knockback < 100 )
- {
- knockback = 100;
- }
- //scale for push level
- if ( self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_2 )
- {//maybe just knock them down
- knockback /= 3;
- }
- else if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 )
- {//super-hard push
- //Hmm, maybe in this case can even nudge/knockdown a jedi? Especially if close?
- //knockback *= 5;
- }
- }
- if ( modPowerLevel != -1 )
- {
- if ( !modPowerLevel )
- {
- knockback /= 10.0f;
- }
- else if ( modPowerLevel == 1 )
- {
- knockback /= 6.0f;
- }
- else// if ( modPowerLevel == 2 )
- {
- knockback /= 2.0f;
- }
- }
- //actually push/pull the enemy
- G_Throw( push_list[x], pushDir, knockback );
- //make it so they don't actually hurt me when pulled at me...
- push_list[x]->forcePuller = self->s.number;
- if ( push_list[x]->client->ps.groundEntityNum != ENTITYNUM_NONE )
- {//if on the ground, make sure they get shoved up some
- if ( push_list[x]->client->ps.velocity[2] < knockback )
- {
- push_list[x]->client->ps.velocity[2] = knockback;
- }
- }
- if ( push_list[x]->health > 0 )
- {//target is still alive
- if ( (push_list[x]->s.number||(cg.renderingThirdPerson&&!cg.zoomMode)) //NPC or 3rd person player
- && ((!pull&&self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_2 && push_list[x]->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_1) //level 1 push
- || (pull && self->client->ps.forcePowerLevel[FP_PULL] < FORCE_LEVEL_2 && push_list[x]->client->ps.forcePowerLevel[FP_PULL] < FORCE_LEVEL_1)) )//level 1 pull
- {//NPC or third person player (without force push/pull skill), and force push/pull level is at 1
- WP_ForceKnockdown( push_list[x], self, pull, (!pull&&knockback>150), qfalse );
- }
- else if ( !push_list[x]->s.number )
- {//player, have to force an anim on him
- WP_ForceKnockdown( push_list[x], self, pull, (!pull&&knockback>150), qfalse );
- }
- else
- {//NPC and force-push/pull at level 2 or higher
- WP_ForceKnockdown( push_list[x], self, pull, (!pull&&knockback>100), qfalse );
- }
- }
- push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms
- }
- }
- else if ( !fake )
- {//not a fake push/pull
- if ( push_list[x]->s.weapon == WP_SABER && (push_list[x]->contents&CONTENTS_LIGHTSABER) )
- {//a thrown saber, just send it back
- /*
- if ( pull )
- {//steal it?
- }
- else */if ( push_list[x]->owner && push_list[x]->owner->client && push_list[x]->owner->client->ps.SaberActive() && push_list[x]->s.pos.trType == TR_LINEAR && push_list[x]->owner->client->ps.saberEntityState != SES_RETURNING )
- {//it's on and being controlled
- //FIXME: prevent it from damaging me?
- if ( self->s.number == 0 || Q_irand( 0, 2 ) )
- {//certain chance of throwing it aside and turning it off?
- //give it some velocity away from me
- //FIXME: maybe actually push or pull it?
- if ( Q_irand( 0, 1 ) )
- {
- VectorScale( right, -1, right );
- }
- G_ReflectMissile( self, push_list[x], right );
- //FIXME: isn't turning off!!!
- WP_SaberDrop( push_list[x]->owner, push_list[x] );
- }
- else
- {
- WP_SaberReturn( push_list[x]->owner, push_list[x] );
- }
- //different effect?
- }
- }
- else if ( push_list[x]->s.eType == ET_MISSILE
- && push_list[x]->s.pos.trType != TR_STATIONARY
- && (push_list[x]->s.pos.trType != TR_INTERPOLATE||push_list[x]->s.weapon != WP_THERMAL) )//rolling and stationary thermal detonators are dealt with below
- {
- vec3_t dir2Me;
- VectorSubtract( self->currentOrigin, push_list[x]->currentOrigin, dir2Me );
- float dot = DotProduct( push_list[x]->s.pos.trDelta, dir2Me );
- if ( pull )
- {//deflect rather than reflect?
- }
- else
- {
- if ( push_list[x]->s.eFlags&EF_MISSILE_STICK )
- {//caught a sticky in-air
- push_list[x]->s.eType = ET_MISSILE;
- push_list[x]->s.eFlags &= ~EF_MISSILE_STICK;
- push_list[x]->s.eFlags |= EF_BOUNCE_HALF;
- push_list[x]->splashDamage /= 3;
- push_list[x]->splashRadius /= 3;
- push_list[x]->e_ThinkFunc = thinkF_WP_Explode;
- push_list[x]->nextthink = level.time + Q_irand( 500, 3000 );
- }
- if ( dot >= 0 )
- {//it's heading towards me
- G_ReflectMissile( self, push_list[x], forward );
- }
- else
- {
- VectorScale( push_list[x]->s.pos.trDelta, 1.25f, push_list[x]->s.pos.trDelta );
- }
- //deflect sound
- //G_Sound( push_list[x], G_SoundIndex( va("sound/weapons/blaster/reflect%d.wav", Q_irand( 1, 3 ) ) ) );
- //push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms
- }
- if ( push_list[x]->s.eType == ET_MISSILE
- && push_list[x]->s.weapon == WP_ROCKET_LAUNCHER
- && push_list[x]->damage < 60 )
- {//pushing away a rocket raises it's damage to the max for NPCs
- push_list[x]->damage = 60;
- }
- }
- else if ( push_list[x]->svFlags & SVF_GLASS_BRUSH )
- {//break the glass
- trace_t tr;
- vec3_t pushDir;
- float damage = 800;
- AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
- VectorNormalize( forward );
- VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
- gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT );
- if ( tr.entityNum != push_list[x]->s.number || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
- {//must be pointing right at it
- continue;
- }
- if ( pull )
- {
- VectorSubtract( self->client->renderInfo.eyePoint, tr.endpos, pushDir );
- }
- else
- {
- VectorSubtract( tr.endpos, self->client->renderInfo.eyePoint, pushDir );
- }
- /*
- VectorSubtract( push_list[x]->absmax, push_list[x]->absmin, size );
- VectorMA( push_list[x]->absmin, 0.5, size, center );
- if ( pull )
- {
- VectorSubtract( self->client->renderInfo.eyePoint, center, pushDir );
- }
- else
- {
- VectorSubtract( center, self->client->renderInfo.eyePoint, pushDir );
- }
- */
- damage -= VectorNormalize( pushDir );
- if ( damage < 200 )
- {
- damage = 200;
- }
- VectorScale( pushDir, damage, pushDir );
- G_Damage( push_list[x], self, self, pushDir, tr.endpos, damage, 0, MOD_UNKNOWN );
- }
- else if ( !Q_stricmp( "func_static", push_list[x]->classname ) )
- {//force-usable func_static
- if ( !pull && (push_list[x]->spawnflags&1/*F_PUSH*/) )
- {
- if ( push_list[x]->NPC_targetname == NULL
- || (self->targetname&&Q_stricmp( push_list[x]->NPC_targetname, self->targetname ) == 0) )
- {//anyone can pull it or only 1 person can push it and it's me
- if(push_list[x]->behaviorSet[BSET_USE] &&
- !Q_stricmp(push_list[x]->behaviorSet[BSET_USE],
- "kor2/pillar_push")) {
- if(!dontPillarPush) {
- GEntity_UseFunc( push_list[x], self, self );
- }
- dontPillarPush = true;
- } else {
- GEntity_UseFunc( push_list[x], self, self );
- }
- }
- }
- else if ( pull && (push_list[x]->spawnflags&2/*F_PULL*/) )
- {
- if ( push_list[x]->NPC_targetname == NULL
- || (self->targetname&&Q_stricmp( push_list[x]->NPC_targetname, self->NPC_targetname ) == 0) )
- {//anyone can push it or only 1 person can push it and it's me
- GEntity_UseFunc( push_list[x], self, self );
- }
- }
- }
- else if ( !Q_stricmp( "func_door", push_list[x]->classname ) && (push_list[x]->spawnflags&2/*MOVER_FORCE_ACTIVATE*/) )
- {//push/pull the door
- vec3_t pos1, pos2;
- AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
- VectorNormalize( forward );
- VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
- gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT );
- if ( tr.entityNum != push_list[x]->s.number || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
- {//must be pointing right at it
- continue;
- }
- if ( VectorCompare( vec3_origin, push_list[x]->s.origin ) )
- {//does not have an origin brush, so pos1 & pos2 are relative to world origin, need to calc center
- VectorSubtract( push_list[x]->absmax, push_list[x]->absmin, size );
- VectorMA( push_list[x]->absmin, 0.5, size, center );
- if ( (push_list[x]->spawnflags&1) && push_list[x]->moverState == MOVER_POS1 )
- {//if at pos1 and started open, make sure we get the center where it *started* because we're going to add back in the relative values pos1 and pos2
- VectorSubtract( center, push_list[x]->pos1, center );
- }
- else if ( !(push_list[x]->spawnflags&1) && push_list[x]->moverState == MOVER_POS2 )
- {//if at pos2, make sure we get the center where it *started* because we're going to add back in the relative values pos1 and pos2
- VectorSubtract( center, push_list[x]->pos2, center );
- }
- VectorAdd( center, push_list[x]->pos1, pos1 );
- VectorAdd( center, push_list[x]->pos2, pos2 );
- }
- else
- {//actually has an origin, pos1 and pos2 are absolute
- VectorCopy( push_list[x]->currentOrigin, center );
- VectorCopy( push_list[x]->pos1, pos1 );
- VectorCopy( push_list[x]->pos2, pos2 );
- }
- if ( Distance( pos1, self->client->renderInfo.eyePoint ) < Distance( pos2, self->client->renderInfo.eyePoint ) )
- {//pos1 is closer
- if ( push_list[x]->moverState == MOVER_POS1 )
- {//at the closest pos
- if ( pull )
- {//trying to pull, but already at closest point, so screw it
- continue;
- }
- }
- else if ( push_list[x]->moverState == MOVER_POS2 )
- {//at farthest pos
- if ( !pull )
- {//trying to push, but already at farthest point, so screw it
- continue;
- }
- }
- }
- else
- {//pos2 is closer
- if ( push_list[x]->moverState == MOVER_POS1 )
- {//at the farthest pos
- if ( !pull )
- {//trying to push, but already at farthest point, so screw it
- continue;
- }
- }
- else if ( push_list[x]->moverState == MOVER_POS2 )
- {//at closest pos
- if ( pull )
- {//trying to pull, but already at closest point, so screw it
- continue;
- }
- }
- }
- GEntity_UseFunc( push_list[x], self, self );
- }
- else if ( push_list[x]->s.eType == ET_MISSILE/*thermal resting on ground*/
- || push_list[x]->s.eType == ET_ITEM
- || push_list[x]->e_ThinkFunc == thinkF_G_RunObject || Q_stricmp( "limb", push_list[x]->classname ) == 0 )
- {//general object, toss it
- vec3_t pushDir, kvel;
- float knockback = pull?0:200;
- float mass = 200;
- if ( pull )
- {
- if ( push_list[x]->s.eType == ET_ITEM )
- {//pull it to a little higher point
- vec3_t adjustedOrg;
- VectorCopy( self->currentOrigin, adjustedOrg );
- adjustedOrg[2] += self->maxs[2]/3;
- VectorSubtract( adjustedOrg, push_list[x]->currentOrigin, pushDir );
- }
- else if ( self->enemy //I have an enemy
- //&& push_list[x]->s.eType != ET_ITEM //not an item
- && self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 //have push 3 or greater
- && InFront(push_list[x]->currentOrigin, self->currentOrigin, self->currentAngles, 0.25f)//object is generally in front of me
- && InFront(self->enemy->currentOrigin, self->currentOrigin, self->currentAngles, 0.75f)//enemy is pretty much right in front of me
- && !InFront(push_list[x]->currentOrigin, self->enemy->currentOrigin, self->enemy->currentAngles, -0.25f)//object is generally behind enemy
- //FIXME: check dist to enemy and clear LOS to enemy and clear Path between object and enemy?
- && ( (self->NPC&&(noResist||Q_irand(0,RANK_CAPTAIN)<self->NPC->rank) )//NPC with enough skill
- ||( self->s.number<MAX_CLIENTS ) )
- )
- {//if I have an auto-enemy & he's in front of me, push it toward him!
- /*
- if ( targetedObjectMassTotal + push_list[x]->mass > TARGETED_OBJECT_PUSH_MASS_MAX )
- {//already pushed too many things
- //FIXME: pick closest?
- continue;
- }
- targetedObjectMassTotal += push_list[x]->mass;
- */
- VectorSubtract( self->enemy->currentOrigin, push_list[x]->currentOrigin, pushDir );
- }
- else
- {
- VectorSubtract( self->currentOrigin, push_list[x]->currentOrigin, pushDir );
- }
- knockback += VectorNormalize( pushDir );
- if ( knockback > 200 )
- {
- knockback = 200;
- }
- if ( push_list[x]->s.eType == ET_ITEM
- && push_list[x]->item
- && push_list[x]->item->giType == IT_HOLDABLE
- && push_list[x]->item->giTag == INV_SECURITY_KEY )
- {//security keys are pulled with less enthusiasm
- if ( knockback > 100 )
- {
- knockback = 100;
- }
- }
- else if ( knockback > 200 )
- {
- knockback = 200;
- }
- }
- else
- {
- if ( self->enemy //I have an enemy
- && push_list[x]->s.eType != ET_ITEM //not an item
- && self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 //have push 3 or greater
- && InFront(push_list[x]->currentOrigin, self->currentOrigin, self->currentAngles, 0.25f)//object is generally in front of me
- && InFront(self->enemy->currentOrigin, self->currentOrigin, self->currentAngles, 0.75f)//enemy is pretty much right in front of me
- && InFront(push_list[x]->currentOrigin, self->enemy->currentOrigin, self->enemy->currentAngles, 0.25f)//object is generally in front of enemy
- //FIXME: check dist to enemy and clear LOS to enemy and clear Path between object and enemy?
- && ( (self->NPC&&(noResist||Q_irand(0,RANK_CAPTAIN)<self->NPC->rank) )//NPC with enough skill
- ||( self->s.number<MAX_CLIENTS ) )
- )
- {//if I have an auto-enemy & he's in front of me, push it toward him!
- /*
- if ( targetedObjectMassTotal + push_list[x]->mass > TARGETED_OBJECT_PUSH_MASS_MAX )
- {//already pushed too many things
- //FIXME: pick closest?
- continue;
- }
- targetedObjectMassTotal += push_list[x]->mass;
- */
- VectorSubtract( self->enemy->currentOrigin, push_list[x]->currentOrigin, pushDir );
- }
- else
- {
- VectorSubtract( push_list[x]->currentOrigin, self->currentOrigin, pushDir );
- }
- knockback -= VectorNormalize( pushDir );
- if ( knockback < 100 )
- {
- knockback = 100;
- }
- }
- //FIXME: if pull a FL_FORCE_PULLABLE_ONLY, clear the flag, assuming it's no longer in solid? or check?
- VectorCopy( push_list[x]->currentOrigin, push_list[x]->s.pos.trBase );
- push_list[x]->s.pos.trTime = level.time; // move a bit on the very first frame
- if ( push_list[x]->s.pos.trType != TR_INTERPOLATE )
- {//don't do this to rolling missiles
- push_list[x]->s.pos.trType = TR_GRAVITY;
- }
- if ( push_list[x]->e_ThinkFunc == thinkF_G_RunObject && push_list[x]->physicsBounce )
- {//it's a pushable misc_model_breakable, use it's mass instead of our one-size-fits-all mass
- mass = push_list[x]->physicsBounce;//same as push_list[x]->mass, right?
- }
- if ( mass < 50 )
- {//???
- mass = 50;
- }
- if ( g_gravity->value > 0 )
- {
- VectorScale( pushDir, g_knockback->value * knockback / mass * 0.8, kvel );
- kvel[2] = pushDir[2] * g_knockback->value * knockback / mass * 1.5;
- }
- else
- {
- VectorScale( pushDir, g_knockback->value * knockback / mass, kvel );
- }
- VectorAdd( push_list[x]->s.pos.trDelta, kvel, push_list[x]->s.pos.trDelta );
- if ( g_gravity->value > 0 )
- {
- if ( push_list[x]->s.pos.trDelta[2] < knockback )
- {
- push_list[x]->s.pos.trDelta[2] = knockback;
- }
- }
- //no trDuration?
- if ( push_list[x]->e_ThinkFunc != thinkF_G_RunObject )
- {//objects spin themselves?
- //spin it
- //FIXME: messing with roll ruins the rotational center???
- push_list[x]->s.apos.trTime = level.time;
- push_list[x]->s.apos.trType = TR_LINEAR;
- VectorClear( push_list[x]->s.apos.trDelta );
- push_list[x]->s.apos.trDelta[1] = Q_irand( -800, 800 );
- }
- if ( Q_stricmp( "limb", push_list[x]->classname ) == 0 )
- {//make sure it runs it's physics
- push_list[x]->e_ThinkFunc = thinkF_LimbThink;
- push_list[x]->nextthink = level.time + FRAMETIME;
- }
- push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms
- push_list[x]->forcePuller = self->s.number;//remember this regardless
- if ( push_list[x]->item && push_list[x]->item->giTag == INV_SECURITY_KEY )
- {
- AddSightEvent( player, push_list[x]->currentOrigin, 128, AEL_DISCOVERED );//security keys are more important
- }
- else
- {
- AddSightEvent( player, push_list[x]->currentOrigin, 128, AEL_SUSPICIOUS );//hmm... or should this always be discovered?
- }
- }
- else if ( push_list[x]->s.weapon == WP_TURRET
- && !Q_stricmp( "PAS", push_list[x]->classname )
- && push_list[x]->s.apos.trType == TR_STATIONARY )
- {//a portable turret
- WP_KnockdownTurret( self, push_list[x] );
- }
- }
- }
- if ( pull )
- {
- if ( self->client->ps.forcePowerLevel[FP_PULL] > FORCE_LEVEL_2 )
- {//at level 3, can pull multiple, so it costs more
- actualCost = forcePowerNeeded[FP_PULL]*ent_count;
- if ( actualCost > 50 )
- {
- actualCost = 50;
- }
- else if ( actualCost < cost )
- {
- actualCost = cost;
- }
- }
- else
- {
- actualCost = cost;
- }
- WP_ForcePowerStart( self, FP_PULL, actualCost );
- }
- else
- {
- if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 )
- {//at level 3, can push multiple, so costs more
- actualCost = forcePowerNeeded[FP_PUSH]*ent_count;
- if ( actualCost > 50 )
- {
- actualCost = 50;
- }
- else if ( actualCost < cost )
- {
- actualCost = cost;
- }
- }
- else if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_1 )
- {//at level 2, can push multiple, so costs more
- actualCost = floor(forcePowerNeeded[FP_PUSH]*ent_count/1.5f);
- if ( actualCost > 50 )
- {
- actualCost = 50;
- }
- else if ( actualCost < cost )
- {
- actualCost = cost;
- }
- }
- else
- {
- actualCost = cost;
- }
- WP_ForcePowerStart( self, FP_PUSH, actualCost );
- }
- }
- else
- {//didn't push or pull anything? don't penalize them too much
- if ( pull )
- {
- WP_ForcePowerStart( self, FP_PULL, 5 );
- }
- else
- {
- WP_ForcePowerStart( self, FP_PUSH, 5 );
- }
- }
- if ( pull )
- {
- if ( self->NPC )
- {//NPCs can push more often
- //FIXME: vary by rank and game skill?
- self->client->ps.forcePowerDebounce[FP_PULL] = level.time + 200;
- }
- else
- {
- self->client->ps.forcePowerDebounce[FP_PULL] = level.time + self->client->ps.torsoAnimTimer + 500;
- }
- }
- else
- {
- if ( self->NPC )
- {//NPCs can push more often
- //FIXME: vary by rank and game skill?
- self->client->ps.forcePowerDebounce[FP_PUSH] = level.time + 200;
- }
- else
- {
- self->client->ps.forcePowerDebounce[FP_PUSH] = level.time + self->client->ps.torsoAnimTimer + 500;
- }
- }
- }
- void WP_DebounceForceDeactivateTime( gentity_t *self )
- {
- //FIXME: if these are interruptable, should they also drain power at a constant rate
- // rather than just taking one lump sum of force power upfront?
- if ( self && self->client )
- {
- if ( self->client->ps.forcePowersActive&(1<<FP_SPEED)
- || self->client->ps.forcePowersActive&(1<<FP_PROTECT)
- || self->client->ps.forcePowersActive&(1<<FP_ABSORB)
- || self->client->ps.forcePowersActive&(1<<FP_RAGE)
- || self->client->ps.forcePowersActive&(1<<FP_SEE) )
- {//already running another power that can be manually, stopped don't debounce so long
- self->client->ps.forceAllowDeactivateTime = level.time + 500;
- }
- else
- {//not running one of the interruptable powers
- //FIXME: this should be shorter for force speed and rage (because of timescaling)
- self->client->ps.forceAllowDeactivateTime = level.time + 1500;
- }
- }
- }
- void ForceSpeed( gentity_t *self, int duration )
- {
- if ( self->health <= 0 )
- {
- return;
- }
- if (self->client->ps.forceAllowDeactivateTime < level.time &&
- (self->client->ps.forcePowersActive & (1 << FP_SPEED)) )
- {//stop using it
- WP_ForcePowerStop( self, FP_SPEED );
- return;
- }
- if ( !WP_ForcePowerUsable( self, FP_SPEED, 0 ) )
- {
- return;
- }
- if ( self->client->ps.saberLockTime > level.time )
- {//FIXME: can this be a way to break out?
- return;
- }
-
- WP_DebounceForceDeactivateTime( self );
- WP_ForcePowerStart( self, FP_SPEED, 0 );
- if ( duration )
- {
- self->client->ps.forcePowerDuration[FP_SPEED] = level.time + duration;
- }
- G_Sound( self, G_SoundIndex( "sound/weapons/force/speed.wav" ) );
- #ifdef _IMMERSION
- G_Force( self, G_ForceIndex( "fffx/weapons/force/speed", FF_CHANNEL_WEAPON ) );
- #endif // _IMMERSION
- }
- void WP_StartForceHealEffects( gentity_t *self )
- {
- if ( self->ghoul2.size() )
- {
- if ( self->chestBolt != -1 )
- {
- G_PlayEffect( G_EffectIndex( "force/heal2" ), self->playerModel, self->chestBolt, self->s.number, self->currentOrigin, 3000, qtrue );
- }
- /*
- if ( self->headBolt != -1 )
- {
- G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->headBolt, self->s.number, self->currentOrigin, 3000, qtrue );
- }
- if ( self->cervicalBolt != -1 )
- {
- G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->cervicalBolt, self->s.number, self->currentOrigin, 3000, qtrue );
- }
- if ( self->chestBolt != -1 )
- {
- G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->chestBolt, self->s.number, self->currentOrigin, 3000, qtrue );
- }
- if ( self->gutBolt != -1 )
- {
- G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->gutBolt, self->s.number, self->currentOrigin, 3000, qtrue );
- }
- if ( self->kneeLBolt != -1 )
- {
- G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->kneeLBolt, self->s.number, self->currentOrigin, 3000, qtrue );
- }
- if ( self->kneeRBolt != -1 )
- {
- G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->kneeRBolt, self->s.number, self->currentOrigin, 3000, qtrue );
- }
- if ( self->elbowLBolt != -1 )
- {
- G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->elbowLBolt, self->s.number, self->currentOrigin, 3000, qtrue );
- }
- if ( self->elbowRBolt != -1 )
- {
- G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->elbowRBolt, self->s.number, self->currentOrigin, 3000, qtrue );
- }
- */
- }
- }
- void WP_StopForceHealEffects( gentity_t *self )
- {
- if ( self->ghoul2.size() )
- {
- if ( self->chestBolt != -1 )
- {
- G_StopEffect( G_EffectIndex( "force/heal2" ), self->playerModel, self->chestBolt, self->s.number );
- }
- /*
- if ( self->headBolt != -1 )
- {
- G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->headBolt, self->s.number );
- }
- if ( self->cervicalBolt != -1 )
- {
- G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->cervicalBolt, self->s.number );
- }
- if ( self->chestBolt != -1 )
- {
- G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->chestBolt, self->s.number );
- }
- if ( self->gutBolt != -1 )
- {
- G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->gutBolt, self->s.number );
- }
- if ( self->kneeLBolt != -1 )
- {
- G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->kneeLBolt, self->s.number );
- }
- if ( self->kneeRBolt != -1 )
- {
- G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->kneeRBolt, self->s.number );
- }
- if ( self->elbowLBolt != -1 )
- {
- G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->elbowLBolt, self->s.number );
- }
- if ( self->elbowRBolt != -1 )
- {
- G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->elbowRBolt, self->s.number );
- }
- */
- }
- }
- int FP_MaxForceHeal( gentity_t *self )
- {
- if ( self->s.number >= MAX_CLIENTS )
- {
- return MAX_FORCE_HEAL_HARD;
- }
- switch ( g_spskill->integer )
- {
- case 0://easy
- return MAX_FORCE_HEAL_EASY;
- break;
- case 1://medium
- return MAX_FORCE_HEAL_MEDIUM;
- break;
- case 2://hard
- default:
- return MAX_FORCE_HEAL_HARD;
- break;
- }
- }
- int FP_ForceHealInterval( gentity_t *self )
- {
- return (self->client->ps.forcePowerLevel[FP_HEAL]>FORCE_LEVEL_2)?50:FORCE_HEAL_INTERVAL;
- }
- void ForceHeal( gentity_t *self )
- {
- if ( self->health <= 0 || self->client->ps.stats[STAT_MAX_HEALTH] <= self->health )
- {
- return;
- }
- if ( !WP_ForcePowerUsable( self, FP_HEAL, 20 ) )
- {//must have enough force power for at least 5 points of health
- return;
- }
- if ( self->painDebounceTime > level.time || (self->client->ps.weaponTime&&self->client->ps.weapon!=WP_NONE) )
- {//can't initiate a heal while taking pain or attacking
- return;
- }
- if ( self->client->ps.saberLockTime > level.time )
- {//FIXME: can this be a way to break out?
- return;
- }
- /*
- if ( self->client->ps.forcePowerLevel[FP_HEAL] > FORCE_LEVEL_2 )
- {//instant heal
- //no more than available force power
- int max = self->client->ps.forcePower;
- if ( max > MAX_FORCE_HEAL )
- {//no more than max allowed
- max = MAX_FORCE_HEAL;
- }
- if ( max > self->client->ps.stats[STAT_MAX_HEALTH] - self->health )
- {//no more than what's missing
- max = self->client->ps.stats[STAT_MAX_HEALTH] - self->health;
- }
- self->health += max;
- WP_ForcePowerDrain( self, FP_HEAL, max );
- G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d.mp3", Q_irand( 1, 4 ) ) );
- }
- else
- */
- {
- //start health going up
- //NPC_SetAnim( self, SETANIM_TORSO, ?, SETANIM_FLAG_OVERRIDE );
- WP_ForcePowerStart( self, FP_HEAL, 0 );
- if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_2 )
- {//must meditate
- //FIXME: holster weapon (select WP_NONE?)
- //FIXME: BOTH_FORCEHEAL_START
- NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCEHEAL_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
- self->client->ps.saberBlocked = BLOCKED_NONE;
- self->client->ps.torsoAnimTimer = self->client->ps.legsAnimTimer = FP_ForceHealInterval(self)*FP_MaxForceHeal(self) + 2000;//???
- WP_DeactivateSaber( self );//turn off saber when meditating
- }
- else
- {//just a quick gesture
- /*
- //Can't get an anim that looks good...
- NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEHEAL_QUICK, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
- self->client->ps.saberBlocked = BLOCKED_NONE;
- */
- }
- }
-
- //FIXME: always play healing effect
- G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/heal.mp3" );
- #ifdef _IMMERSION
- G_Force( self, G_ForceIndex( "fffx/weapons/force/heal", FF_CHANNEL_WEAPON ) );
- #endif // _IMMERSION
- }
- extern void NPC_PlayConfusionSound( gentity_t *self );
- extern void NPC_Jedi_PlayConfusionSound( gentity_t *self );
- qboolean WP_CheckBreakControl( gentity_t *self )
- {
- if ( !self )
- {
- return qfalse;
- }
- if ( !self->s.number )
- {//player
- if ( self->client && self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 )
- {//control-level
- if ( self->client->ps.viewEntity > 0 && self->client->ps.viewEntity < ENTITYNUM_WORLD )
- {//we are in a viewentity
- gentity_t *controlled = &g_entities[self->client->ps.viewEntity];
- if ( controlled->NPC && controlled->NPC->controlledTime > level.time )
- {//it is an NPC we controlled
- //clear it and return
- G_ClearViewEntity( self );
- return qtrue;
- }
- }
- }
- }
- else
- {//NPC
- if ( self->NPC && self->NPC->controlledTime > level.time )
- {//being controlled
- gentity_t *controller = &g_entities[0];
- if ( controller->client && controller->client->ps.viewEntity == self->s.number )
- {//we are being controlled by player
- if ( controller->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 )
- {//control-level mind trick
- //clear the control and return
- G_ClearViewEntity( controller );
- return qtrue;
- }
- }
- }
- }
- return qfalse;
- }
- extern bool Pilot_AnyVehiclesRegistered();
- void ForceTelepathy( gentity_t *self )
- {
- trace_t tr;
- vec3_t end, forward;
- gentity_t *traceEnt;
- qboolean targetLive = qfalse;
- if ( WP_CheckBreakControl( self ) )
- {
- return;
- }
- if ( self->health <= 0 )
- {
- return;
- }
- //FIXME: if mind trick 3 and aiming at an enemy need more force power
- if ( !WP_ForcePowerUsable( self, FP_TELEPATHY, 0 ) )
- {
- return;
- }
- if ( self->client->ps.weaponTime >= 800 )
- {//just did one!
- return;
- }
- if ( self->client->ps.saberLockTime > level.time )
- {//FIXME: can this be a way to break out?
- return;
- }
- AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
- VectorNormalize( forward );
- VectorMA( self->client->renderInfo.eyePoint, 2048, forward, end );
-
- //Cause a distraction if enemy is not fighting
- gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_OPAQUE|CONTENTS_BODY );
- if ( tr.entityNum == ENTITYNUM_NONE || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
- {
- return;
- }
-
- traceEnt = &g_entities[tr.entityNum];
-
- if( traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE )
- {
- return;
- }
- if ( traceEnt && traceEnt->client )
- {
- switch ( traceEnt->client->NPC_class )
- {
- case CLASS_GALAKMECH://cant grip him, he's in armor
- case CLASS_ATST://much too big to grip!
- //no droids either
- case CLASS_PROBE:
- case CLASS_GONK:
- case CLASS_R2D2:
- case CLASS_R5D2:
- case CLASS_MARK1:
- case CLASS_MARK2:
- case CLASS_MOUSE:
- case CLASS_SEEKER:
- case CLASS_REMOTE:
- case CLASS_PROTOCOL:
- case CLASS_ASSASSIN_DROID:
- case CLASS_SABER_DROID:
- case CLASS_BOBAFETT:
- break;
- case CLASS_RANCOR:
- if ( !(traceEnt->spawnflags&1) )
- {
- targetLive = qtrue;
- }
- break;
- default:
- targetLive = qtrue;
- break;
- }
- }
- if ( targetLive
- && traceEnt->NPC
- && traceEnt->health > 0 )
- {//hit an organic non-player
- if ( G_ActivateBehavior( traceEnt, BSET_MINDTRICK ) )
- {//activated a script on him
- //FIXME: do the visual sparkles effect on their heads, still?
- WP_ForcePowerStart( self, FP_TELEPATHY, 0 );
- }
- else if ( traceEnt->client->playerTeam != self->client->playerTeam )
- {//an enemy
- int override = 0;
- if ( (traceEnt->NPC->scriptFlags&SCF_NO_MIND_TRICK) )
- {
- if ( traceEnt->client->NPC_class == CLASS_GALAKMECH )
- {
- G_AddVoiceEvent( traceEnt, Q_irand( EV_CONFUSE1, EV_CONFUSE3 ), Q_irand( 3000, 5000 ) );
- }
- }
- else if ( self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 )
- {//control them, even jedi
- G_SetViewEntity( self, traceEnt );
- traceEnt->NPC->controlledTime = level.time + 30000;
- }
- else if ( traceEnt->s.weapon != WP_SABER
- && traceEnt->client->NPC_class != CLASS_REBORN )
- {//haha! Jedi aren't easily confused!
- if ( self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_2
- && traceEnt->s.weapon != WP_NONE //don't charm people who aren't capable of fighting... like ugnaughts and droids, just confuse them
- && traceEnt->client->NPC_class != CLASS_TUSKEN//don't charm them, just confuse them
- && traceEnt->client->NPC_class != CLASS_NOGHRI//don't charm them, just confuse them
- && !Pilot_AnyVehiclesRegistered() //also, don't charm guys when bikes are near
- )
- {//turn them to our side
- //if mind trick 3 and aiming at an enemy need more force power
- override = 50;
- if ( self->client->ps.forcePower < 50 )
- {
- return;
- }
- if ( traceEnt->enemy )
- {
- G_ClearEnemy( traceEnt );
- }
- if ( traceEnt->NPC )
- {
- //traceEnt->NPC->tempBehavior = BS_FOLLOW_LEADER;
- traceEnt->client->leader = self;
- }
- //FIXME: maybe pick an enemy right here?
- //FIXME: does nothing to TEAM_FREE and TEAM_NEUTRALs!!!
- team_t saveTeam = traceEnt->client->enemyTeam;
- traceEnt->client->enemyTeam = traceEnt->client->playerTeam;
- traceEnt->client->playerTeam = saveTeam;
- //FIXME: need a *charmed* timer on this...? Or do TEAM_PLAYERS assume that "confusion" means they should switch to team_enemy when done?
- traceEnt->NPC->charmedTime = level.time + mindTrickTime[self->client->ps.forcePowerLevel[FP_TELEPATHY]];
- if ( traceEnt->ghoul2.size() && traceEnt->headBolt != -1 )
- {//FIXME: what if already playing effect?
- G_PlayEffect( G_EffectIndex( "force/confusion" ), traceEnt->playerModel, traceEnt->headBolt, traceEnt->s.number, traceEnt->currentOrigin, mindTrickTime[self->client->ps.forcePowerLevel[FP_TELEPATHY]], qtrue );
- }
- }
- else
- {//just confuse them
- //somehow confuse them? Set don't fire to true for a while? Drop their aggression? Maybe just take their enemy away and don't let them pick one up for a while unless shot?
- traceEnt->NPC->confusionTime = level.time + mindTrickTime[self->client->ps.forcePowerLevel[FP_TELEPATHY]];//confused for about 10 seconds
- if ( traceEnt->ghoul2.size() && traceEnt->headBolt != -1 )
- {//FIXME: what if already playing effect?
- G_PlayEffect( G_EffectIndex( "force/confusion" ), traceEnt->playerModel, traceEnt->headBolt, traceEnt->s.number, traceEnt->currentOrigin, mindTrickTime[self->client->ps.forcePowerLevel[FP_TELEPATHY]], qtrue );
- }
- NPC_PlayConfusionSound( traceEnt );
- if ( traceEnt->enemy )
- {
- G_ClearEnemy( traceEnt );
- }
- }
- }
- else
- {
- NPC_Jedi_PlayConfusionSound( traceEnt );
- }
- WP_ForcePowerStart( self, FP_TELEPATHY, override );
- }
- else if ( traceEnt->client->playerTeam == self->client->playerTeam )
- {//an ally
- //maybe just have him look at you? Respond? Take your enemy?
- if ( traceEnt->client->ps.pm_type < PM_DEAD && traceEnt->NPC!=NULL && !(traceEnt->NPC->scriptFlags&SCF_NO_RESPONSE) )
- {
- NPC_UseResponse( traceEnt, self, qfalse );
- WP_ForcePowerStart( self, FP_TELEPATHY, 1 );
- }
- }//NOTE: no effect on TEAM_NEUTRAL?
- vec3_t eyeDir;
- AngleVectors( traceEnt->client->renderInfo.eyeAngles, eyeDir, NULL, NULL );
- VectorNormalize( eyeDir );
- G_PlayEffect( "force/force_touch", traceEnt->client->renderInfo.eyePoint, eyeDir );
- //make sure this plays and that you cannot press fire for about 1 second after this
- //FIXME: BOTH_FORCEMINDTRICK or BOTH_FORCEDISTRACT
- NPC_SetAnim( self, SETANIM_TORSO, BOTH_MINDTRICK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD );
- //FIXME: build-up or delay this until in proper part of anim
- }
- else
- {
- if ( self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_1 && tr.fraction * 2048 > 64 )
- {//don't create a diversion less than 64 from you of if at power level 1
- //use distraction anim instead
- G_PlayEffect( G_EffectIndex( "force/force_touch" ), tr.endpos, tr.plane.normal );
- //FIXME: these events don't seem to always be picked up...?
- AddSoundEvent( self, tr.endpos, 512, AEL_SUSPICIOUS, qtrue, qtrue );
- AddSightEvent( self, tr.endpos, 512, AEL_SUSPICIOUS, 50 );
- WP_ForcePowerStart( self, FP_TELEPATHY, 0 );
- }
- NPC_SetAnim( self, SETANIM_TORSO, BOTH_MINDTRICK2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD );
- }
- self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
- self->client->ps.saberBlocked = BLOCKED_NONE;
- self->client->ps.weaponTime = 1000;
- if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
- {
- self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
- }
- }
- //rww - RAGDOLL_BEGIN
- //#define JK2_RAGDOLL_GRIPNOHEALTH
- //rww - RAGDOLL_END
- void ForceGrip( gentity_t *self )
- {//FIXME: make enemy Jedi able to use this
- trace_t tr;
- vec3_t end, forward;
- gentity_t *traceEnt = NULL;
- if ( self->health <= 0 )
- {
- return;
- }
- if ( !self->s.number && (cg.zoomMode || in_camera) )
- {//can't force grip when zoomed in or in cinematic
- return;
- }
- if ( self->client->ps.leanofs )
- {//can't force-grip while leaning
- return;
- }
- if ( self->client->ps.forceGripEntityNum <= ENTITYNUM_WORLD )
- {//already gripping
- if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
- {
- self->client->ps.forcePowerDuration[FP_GRIP] = level.time + 100;
- self->client->ps.weaponTime = 1000;
- if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
- {
- self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
- }
- }
- return;
- }
- if ( !WP_ForcePowerUsable( self, FP_GRIP, 0 ) )
- {//can't use it right now
- return;
- }
- if ( self->client->ps.forcePower < 26 )
- {//need 20 to start, 6 to hold it for any decent amount of time...
- return;
- }
- if ( self->client->ps.weaponTime )
- {//busy
- return;
- }
- if ( self->client->ps.saberLockTime > level.time )
- {//FIXME: can this be a way to break out?
- return;
- }
- //Cause choking anim + health drain in ent in front of me
- NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEGRIP_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
- self->client->ps.saberBlocked = BLOCKED_NONE;
- self->client->ps.weaponTime = 1000;
- if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
- {
- self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
- }
- AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
- VectorNormalize( forward );
- VectorMA( self->client->renderInfo.handLPoint, FORCE_GRIP_DIST, forward, end );
-
- if ( self->enemy )
- {//I have an enemy
- if ( !self->enemy->message
- && !(self->flags&FL_NO_KNOCKBACK) )
- {//don't auto-pickup guys with keys
- if ( DistanceSquared( self->enemy->currentOrigin, self->currentOrigin ) < FORCE_GRIP_DIST_SQUARED )
- {//close enough to grab
- float minDot = 0.5f;
- if ( self->s.number < MAX_CLIENTS )
- {//player needs to be facing more directly
- minDot = 0.2f;
- }
- if ( InFront( self->enemy->currentOrigin, self->client->renderInfo.eyePoint, self->client->ps.viewangles, minDot ) ) //self->s.number || //NPCs can always lift enemy since we assume they're looking at them...?
- {//need to be facing the enemy
- if ( gi.inPVS( self->enemy->currentOrigin, self->client->renderInfo.eyePoint ) )
- {//must be in PVS
- gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, self->enemy->currentOrigin, self->s.number, MASK_SHOT );
- if ( tr.fraction == 1.0f || tr.entityNum == self->enemy->s.number )
- {//must have clear LOS
- traceEnt = self->enemy;
- }
- }
- }
- }
- }
- }
- if ( !traceEnt )
- {//okay, trace straight ahead and see what's there
- gi.trace( &tr, self->client->renderInfo.handLPoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT );
- if ( tr.entityNum >= ENTITYNUM_WORLD || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
- {
- return;
- }
- traceEnt = &g_entities[tr.entityNum];
- }
- //rww - RAGDOLL_BEGIN
- #ifdef JK2_RAGDOLL_GRIPNOHEALTH
- if ( !traceEnt || traceEnt == self/*???*/ || traceEnt->bmodel || (traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE) )
- {
- return;
- }
- #else
- //rww - RAGDOLL_END
- if ( !traceEnt || traceEnt == self/*???*/ || traceEnt->bmodel || (traceEnt->health <= 0 && traceEnt->takedamage) || (traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE) )
- {
- return;
- }
- //rww - RAGDOLL_BEGIN
- #endif
- //rww - RAGDOLL_END
- if ( traceEnt->m_pVehicle != NULL )
- {//is it a vehicle
- //grab pilot if there is one
- if ( traceEnt->m_pVehicle->m_pPilot != NULL
- && traceEnt->m_pVehicle->m_pPilot->client != NULL )
- {//grip the pilot
- traceEnt = traceEnt->m_pVehicle->m_pPilot;
- }
- else
- {//can't grip a vehicle
- return;
- }
- }
- if ( traceEnt->client )
- {
- if ( traceEnt->client->ps.forceJumpZStart )
- {//can't catch them in mid force jump - FIXME: maybe base it on velocity?
- return;
- }
- if ( traceEnt->client->ps.pullAttackTime > level.time )
- {//can't grip someone who is being pull-attacked or is pull-attacking
- return;
- }
- if ( !Q_stricmp("Yoda",traceEnt->NPC_type) )
- {
- Jedi_PlayDeflectSound( traceEnt );
- ForceThrow( traceEnt, qfalse );
- return;
- }
- if ( G_IsRidingVehicle( traceEnt )
- && (traceEnt->s.eFlags&EF_NODRAW) )
- {//riding *inside* vehicle
- return;
- }
- switch ( traceEnt->client->NPC_class )
- {
- case CLASS_GALAKMECH://cant grip him, he's in armor
- G_AddVoiceEvent( traceEnt, Q_irand(EV_PUSHED1, EV_PUSHED3), Q_irand( 3000, 5000 ) );
- return;
- break;
- case CLASS_HAZARD_TROOPER://cant grip him, he's in armor
- return;
- break;
- case CLASS_ATST://much too big to grip!
- case CLASS_RANCOR://much too big to grip!
- case CLASS_WAMPA://much too big to grip!
- case CLASS_SAND_CREATURE://much too big to grip!
- return;
- break;
- //no droids either...?
- case CLASS_GONK:
- case CLASS_R2D2:
- case CLASS_R5D2:
- case CLASS_MARK1:
- case CLASS_MARK2:
- case CLASS_MOUSE://?
- case CLASS_PROTOCOL:
- //*sigh*... in JK3, you'll be able to grab and move *anything*...
- return;
- break;
- //not even combat droids? (No animation for being gripped...)
- case CLASS_SABER_DROID:
- case CLASS_ASSASSIN_DROID:
- //*sigh*... in JK3, you'll be able to grab and move *anything*...
- return;
- break;
- case CLASS_PROBE:
- case CLASS_SEEKER:
- case CLASS_REMOTE:
- case CLASS_SENTRY:
- case CLASS_INTERROGATOR:
- //*sigh*... in JK3, you'll be able to grab and move *anything*...
- return;
- break;
- case CLASS_DESANN://Desann cannot be gripped, he just pushes you back instantly
- case CLASS_KYLE:
- case CLASS_TAVION:
- case CLASS_LUKE:
- Jedi_PlayDeflectSound( traceEnt );
- ForceThrow( traceEnt, qfalse );
- return;
- break;
- case CLASS_REBORN:
- case CLASS_SHADOWTROOPER:
- case CLASS_ALORA:
- case CLASS_JEDI:
- if ( traceEnt->NPC && traceEnt->NPC->rank > RANK_CIVILIAN && self->client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 )
- {
- Jedi_PlayDeflectSound( traceEnt );
- ForceThrow( traceEnt, qfalse );
- return;
- }
- break;
- }
- if ( traceEnt->s.weapon == WP_EMPLACED_GUN )
- {//FIXME: maybe can pull them out?
- return;
- }
- if ( self->enemy && traceEnt != self->enemy && traceEnt->client->playerTeam == self->client->playerTeam )
- {//can't accidently grip your teammate in combat
- return;
- }
- //=CHECKABSORB===
- if ( -1 != WP_AbsorbConversion( traceEnt, traceEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_GRIP, self->client->ps.forcePowerLevel[FP_GRIP], forcePowerNeeded[self->client->ps.forcePowerLevel[FP_GRIP]]) )
- {
- //WP_ForcePowerStop( self, FP_GRIP );
- return;
- }
- //===============
- }
- else
- {//can't grip non-clients... right?
- //FIXME: Make it so objects flagged as "grabbable" are let through
- //if ( Q_stricmp( "misc_model_breakable", traceEnt->classname ) || !(traceEnt->s.eFlags&EF_BOUNCE_HALF) || !traceEnt->physicsBounce )
- {
- return;
- }
- }
- // Make sure to turn off Force Protection and Force Absorb.
- if (self->client->ps.forcePowersActive & (1 << FP_PROTECT) )
- {
- WP_ForcePowerStop( self, FP_PROTECT );
- }
- if (self->client->ps.forcePowersActive & (1 << FP_ABSORB) )
- {
- WP_ForcePowerStop( self, FP_ABSORB );
- }
- WP_ForcePowerStart( self, FP_GRIP, 20 );
- //FIXME: rule out other things?
- //FIXME: Jedi resist, like the push and pull?
- self->client->ps.forceGripEntityNum = traceEnt->s.number;
- if ( traceEnt->client )
- {
- Vehicle_t *pVeh;
- if ( ( pVeh = G_IsRidingVehicle( traceEnt ) ) != NULL )
- {//riding vehicle? pull him off!
- //FIXME: if in an AT-ST or X-Wing, shouldn't do this... :)
- //pull him off of it
- //((CVehicleNPC *)traceEnt->NPC)->Eject( traceEnt );
- pVeh->m_pVehicleInfo->Eject( pVeh, traceEnt, qtrue );
- //G_DriveVehicle( traceEnt, NULL, NULL );
- }
- G_AddVoiceEvent( traceEnt, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
- if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 || traceEnt->s.weapon == WP_SABER )
- {//if we pick up & carry, drop their weap
- if ( traceEnt->s.weapon
- && traceEnt->client->NPC_class != CLASS_ROCKETTROOPER
- && traceEnt->client->NPC_class != CLASS_VEHICLE
- && traceEnt->client->NPC_class != CLASS_HAZARD_TROOPER
- && traceEnt->client->NPC_class != CLASS_TUSKEN
- && traceEnt->client->NPC_class != CLASS_BOBAFETT
- && traceEnt->client->NPC_class != CLASS_ASSASSIN_DROID
- && traceEnt->s.weapon != WP_CONCUSSION // so rax can't drop his
- )
- {
- if ( traceEnt->client->NPC_class == CLASS_BOBAFETT )
- {//he doesn't drop them, just puts it away
- ChangeWeapon( traceEnt, WP_MELEE );
- }
- else if ( traceEnt->s.weapon == WP_MELEE )
- {//they can't take that away from me, oh no...
- }
- else if ( traceEnt->NPC
- && (traceEnt->NPC->scriptFlags&SCF_DONT_FLEE) )
- {//*SIGH*... if an NPC can't flee, they can't run after and pick up their weapon, do don't drop it
- }
- else if ( traceEnt->s.weapon != WP_SABER )
- {
- WP_DropWeapon( traceEnt, NULL );
- }
- else
- {
- //turn it off?
- traceEnt->client->ps.SaberDeactivate();
- G_SoundOnEnt( traceEnt, CHAN_WEAPON, "sound/weapons/saber/saberoffquick.wav" );
- #ifdef _IMMERSION
- G_Force( traceEnt, G_ForceIndex( "fffx/weapons/saber/saberoffquick", FF_CHANNEL_WEAPON ) );
- #endif // _IMMERSION
- }
- }
- }
- //else FIXME: need a one-armed choke if we're not on a high enough level to make them drop their gun
- VectorCopy( traceEnt->client->renderInfo.headPoint, self->client->ps.forceGripOrg );
- }
- else
- {
- VectorCopy( traceEnt->currentOrigin, self->client->ps.forceGripOrg );
- }
- self->client->ps.forceGripOrg[2] += 48;//FIXME: define?
- if ( self->client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 )
- {//just a duration
- self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + 250;
- self->client->ps.forcePowerDuration[FP_GRIP] = level.time + 5000;
-
- if ( self->m_pVehicle && self->m_pVehicle->m_pVehicleInfo->Inhabited( self->m_pVehicle ) )
- {//empty vehicles don't make gripped noise
- traceEnt->s.loopSound = G_SoundIndex( "sound/weapons/force/grip.mp3" );
- }
- #ifdef _IMMERSION
- G_Force( self, G_ForceIndex( "fffx/weapons/force/gripcast", FF_CHANNEL_FORCE ) );
- //G_Force( traceEnt, G_ForceIndex( "fffx/weapons/force/grip", FF_CHANNEL_DAMAGE ) );
- #endif // _IMMERSION
- }
- else
- {
- if ( self->client->ps.forcePowerLevel[FP_GRIP] == FORCE_LEVEL_2 )
- {//lifting sound? or always?
- }
- //if ( traceEnt->s.number )
- {//picks them up for a second first
- self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + 1000;
- }
- /*
- else
- {//player should take damage right away
- self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + 250;
- }
- */
- // force grip sound should only play when the target is alive?
- // if (traceEnt->health>0)
- // {
- self->s.loopSound = G_SoundIndex( "sound/weapons/force/grip.mp3" );
- // }
- }
- }
- qboolean ForceLightningCheck2Handed( gentity_t *self )
- {
- if ( self && self->client )
- {
- if ( self->s.weapon == WP_NONE
- || self->s.weapon == WP_MELEE
- || (self->s.weapon == WP_SABER && !self->client->ps.SaberActive()) )
- {
- return qtrue;
- }
- }
- return qfalse;
- }
- void ForceLightningAnim( gentity_t *self )
- {
- if ( !self || !self->client )
- {
- return;
- }
- //one-handed lightning 2 and above
- int startAnim = BOTH_FORCELIGHTNING_START;
- int holdAnim = BOTH_FORCELIGHTNING_HOLD;
- if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] >= FORCE_LEVEL_3
- && ForceLightningCheck2Handed( self ) )
- {//empty handed lightning 3
- startAnim = BOTH_FORCE_2HANDEDLIGHTNING_START;
- holdAnim = BOTH_FORCE_2HANDEDLIGHTNING_HOLD;
- }
- //FIXME: if standing still, play on whole body? Especially 2-handed version
- if ( self->client->ps.torsoAnim == startAnim )
- {
- if ( !self->client->ps.torsoAnimTimer )
- {
- NPC_SetAnim( self, SETANIM_TORSO, holdAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- else
- {
- NPC_SetAnim( self, SETANIM_TORSO, startAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- }
- else
- {
- NPC_SetAnim( self, SETANIM_TORSO, holdAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- }
- void ForceLightning( gentity_t *self )
- {
- if ( self->health <= 0 )
- {
- return;
- }
- if ( !self->s.number && (cg.zoomMode || in_camera) )
- {//can't force lightning when zoomed in or in cinematic
- return;
- }
- if ( self->client->ps.leanofs )
- {//can't force-lightning while leaning
- return;
- }
- if ( self->client->ps.forcePower < 25 || !WP_ForcePowerUsable( self, FP_LIGHTNING, 0 ) )
- {
- return;
- }
- if ( self->client->ps.forcePowerDebounce[FP_LIGHTNING] > level.time )
- {//stops it while using it and also after using it, up to 3 second delay
- return;
- }
- if ( self->client->ps.saberLockTime > level.time )
- {//FIXME: can this be a way to break out?
- return;
- }
- // Make sure to turn off Force Protection and Force Absorb.
- if (self->client->ps.forcePowersActive & (1 << FP_PROTECT) )
- {
- WP_ForcePowerStop( self, FP_PROTECT );
- }
- if (self->client->ps.forcePowersActive & (1 << FP_ABSORB) )
- {
- WP_ForcePowerStop( self, FP_ABSORB );
- }
- //Shoot lightning from hand
- //make sure this plays and that you cannot press fire for about 1 second after this
- if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] < FORCE_LEVEL_2 )
- {
- NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- else
- {
- ForceLightningAnim( self );
- /*
- if ( ForceLightningCheck2Handed( self ) )
- {//empty handed lightning 3
- NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_2HANDEDLIGHTNING_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- else
- {//one-handed lightning 3
- NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- */
- }
- self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
- self->client->ps.saberBlocked = BLOCKED_NONE;
- G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/lightning.wav" );
- #ifdef _IMMERSION
- G_Force( self, G_ForceIndex( "fffx/weapons/force/lightning", FF_CHANNEL_WEAPON ) );
- #endif // _IMMERSION
- if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] < FORCE_LEVEL_2 )
- {//short burst
- //G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/lightning.wav" );
- }
- else
- {//holding it
- self->s.loopSound = G_SoundIndex( "sound/weapons/force/lightning2.wav" );
- }
-
- //FIXME: build-up or delay this until in proper part of anim
- self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
- WP_ForcePowerStart( self, FP_LIGHTNING, self->client->ps.torsoAnimTimer );
- }
- void ForceLightningDamage( gentity_t *self, gentity_t *traceEnt, vec3_t dir, float dist, float dot, vec3_t impactPoint )
- {
- if( traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE )
- {
- return;
- }
- if ( traceEnt && traceEnt->takedamage )
- {
- if ( !traceEnt->client || traceEnt->client->playerTeam != self->client->playerTeam || self->enemy == traceEnt || traceEnt->enemy == self )
- {//an enemy or object
- int dmg;
- //FIXME: check for client using FP_ABSORB
- if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_2 )
- {//more damage if closer and more in front
- dmg = 1;
- if ( self->client->NPC_class == CLASS_REBORN
- && self->client->ps.weapon == WP_NONE )
- {//Cultist: looks fancy, but does less damage
- }
- else
- {
- if ( dist < 100 )
- {
- dmg += 2;
- }
- else if ( dist < 200 )
- {
- dmg += 1;
- }
- if ( dot > 0.9f )
- {
- dmg += 2;
- }
- else if ( dot > 0.7f )
- {
- dmg += 1;
- }
- }
- if ( self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING
- || self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_START
- || self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_HOLD
- || self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_RELEASE )
- {//jackin' 'em up, Palpatine-style
- dmg *= 2;
- }
- }
- else
- {
- dmg = Q_irand( 1, 3 );//*self->client->ps.forcePowerLevel[FP_LIGHTNING];
- }
- if ( traceEnt->client
- && traceEnt->health > 0
- && traceEnt->NPC
- && (traceEnt->NPC->aiFlags&NPCAI_BOSS_CHARACTER) )
- {//Luke, Desann Tavion and Kyle can shield themselves from the attack
- //FIXME: shield effect or something?
- int parts;
- if ( traceEnt->client->ps.groundEntityNum != ENTITYNUM_NONE && !PM_SpinningSaberAnim( traceEnt->client->ps.legsAnim ) && !PM_FlippingAnim( traceEnt->client->ps.legsAnim ) && !PM_RollingAnim( traceEnt->client->ps.legsAnim ) )
- {//if on a surface and not in a spin or flip, play full body resist
- parts = SETANIM_BOTH;
- }
- else
- {//play resist just in torso
- parts = SETANIM_TORSO;
- }
- //FIXME: don't interrupt big anims with this!
- NPC_SetAnim( traceEnt, parts, BOTH_RESISTPUSH, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- Jedi_PlayDeflectSound( traceEnt );
- dmg = Q_irand(0,1);
- }
- else if ( traceEnt->s.weapon == WP_SABER )
- {//saber can block lightning
- if ( traceEnt->client //a client
- && !traceEnt->client->ps.saberInFlight//saber in hand
- && ( traceEnt->client->ps.saberMove == LS_READY || PM_SaberInParry( traceEnt->client->ps.saberMove ) || PM_SaberInReturn( traceEnt->client->ps.saberMove ) )//not attacking with saber
- && InFOV( self->currentOrigin, traceEnt->currentOrigin, traceEnt->client->ps.viewangles, 20, 35 ) //I'm in front of them
- && !PM_InKnockDown( &traceEnt->client->ps ) //they're not in a knockdown
- && !PM_SuperBreakLoseAnim( traceEnt->client->ps.torsoAnim )
- && !PM_SuperBreakWinAnim( traceEnt->client->ps.torsoAnim )
- && !PM_SaberInSpecialAttack( traceEnt->client->ps.torsoAnim )
- && !PM_InSpecialJump( traceEnt->client->ps.torsoAnim )
- && (!traceEnt->s.number||(traceEnt->NPC&&traceEnt->NPC->rank>=RANK_LT_COMM)) )//the player or a tough jedi/reborn
- {
- if ( Q_irand( 0, traceEnt->client->ps.forcePowerLevel[FP_SABER_DEFENSE]*3 ) > 0 )//more of a chance of defending if saber defense is high
- {
- dmg = 0;
- }
- if ( (traceEnt->client->ps.forcePowersActive&(1<<FP_ABSORB))
- && traceEnt->client->ps.forcePowerLevel[FP_ABSORB] > FORCE_LEVEL_2 )
- {//no parry, just absorb
- }
- else
- {
- //make them do a parry
- traceEnt->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
- int parryReCalcTime = Jedi_ReCalcParryTime( traceEnt, EVASION_PARRY );
- if ( traceEnt->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] < level.time + parryReCalcTime )
- {
- traceEnt->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + parryReCalcTime;
- }
- traceEnt->client->ps.weaponTime = Q_irand( 100, 300 );//hold this move - can't attack! - FIXME: unless dual sabers?
- }
- }
- else if ( Q_irand( 0, 1 ) )
- {//jedi less likely to be damaged
- dmg = 0;
- }
- else
- {
- dmg = 1;
- }
- }
- if ( traceEnt && traceEnt->client && traceEnt->client->ps.powerups[PW_GALAK_SHIELD] )
- {
- //has shield up
- dmg = 0;
- }
- int modPowerLevel = -1;
-
- if (traceEnt->client)
- {
- modPowerLevel = WP_AbsorbConversion(traceEnt, traceEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_LIGHTNING, self->client->ps.forcePowerLevel[FP_LIGHTNING], 1);
- }
- if (modPowerLevel != -1)
- {
- if ( !modPowerLevel )
- {
- dmg = 0;
- }
- else if ( modPowerLevel == 1 )
- {
- dmg = floor((float)dmg/4.0f);
- }
- else if ( modPowerLevel == 2 )
- {
- dmg = floor((float)dmg/2.0f);
- }
- }
- //FIXME: if ForceDrain, sap force power and add health to self, use different sound & effects
- if ( dmg )
- {
- G_Damage( traceEnt, self, self, dir, impactPoint, dmg, 0, MOD_FORCE_LIGHTNING );
- }
- if ( traceEnt->client )
- {
- if ( !Q_irand( 0, 2 ) )
- {
- G_Sound( traceEnt, G_SoundIndex( va( "sound/weapons/force/lightninghit%d.wav", Q_irand( 1, 3 ) ) ) );
- }
- traceEnt->s.powerups |= ( 1 << PW_SHOCKED );
- // If we are dead or we are a bot, we can do the full version
- class_t npc_class = traceEnt->client->NPC_class;
- if ( traceEnt->health <= 0 || ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE ||
- npc_class == CLASS_MOUSE || npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_REMOTE ||
- npc_class == CLASS_R5D2 || npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 ||
- npc_class == CLASS_MARK2 || npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST ) ||
- npc_class == CLASS_SENTRY )
- {
- traceEnt->client->ps.powerups[PW_SHOCKED] = level.time + 4000;
- }
- else //short version
- {
- traceEnt->client->ps.powerups[PW_SHOCKED] = level.time + 500;
- }
- }
- }
- }
- }
- void ForceShootLightning( gentity_t *self )
- {
- trace_t tr;
- vec3_t end, forward;
- gentity_t *traceEnt;
- if ( self->health <= 0 )
- {
- return;
- }
- if ( !self->s.number && cg.zoomMode )
- {//can't force lightning when zoomed in
- return;
- }
- AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
- VectorNormalize( forward );
- //FIXME: if lightning hits water, do water-only-flagged radius damage from that point
- if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_2 )
- {//arc
- vec3_t center, mins, maxs, dir, ent_org, size, v;
- float radius = 512, dot, dist;
- gentity_t *entityList[MAX_GENTITIES];
- int e, numListedEntities, i;
- VectorCopy( self->currentOrigin, center );
- for ( i = 0 ; i < 3 ; i++ )
- {
- mins[i] = center[i] - radius;
- maxs[i] = center[i] + radius;
- }
- numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
- for ( e = 0 ; e < numListedEntities ; e++ )
- {
- traceEnt = entityList[e];
- if ( !traceEnt )
- continue;
- if ( traceEnt == self )
- continue;
- if ( traceEnt->owner == self && traceEnt->s.weapon != WP_THERMAL )//can push your own thermals
- continue;
- if ( !traceEnt->inuse )
- continue;
- if ( !traceEnt->takedamage )
- continue;
- /*
- if ( traceEnt->health <= 0 )//no torturing corpses
- continue;
- */
- //this is all to see if we need to start a saber attack, if it's in flight, this doesn't matter
- // find the distance from the edge of the bounding box
- for ( i = 0 ; i < 3 ; i++ )
- {
- if ( center[i] < traceEnt->absmin[i] )
- {
- v[i] = traceEnt->absmin[i] - center[i];
- } else if ( center[i] > traceEnt->absmax[i] )
- {
- v[i] = center[i] - traceEnt->absmax[i];
- } else
- {
- v[i] = 0;
- }
- }
- VectorSubtract( traceEnt->absmax, traceEnt->absmin, size );
- VectorMA( traceEnt->absmin, 0.5, size, ent_org );
- //see if they're in front of me
- //must be within the forward cone
- VectorSubtract( ent_org, center, dir );
- VectorNormalize( dir );
- if ( (dot = DotProduct( dir, forward )) < 0.5 )
- continue;
- //must be close enough
- dist = VectorLength( v );
- if ( dist >= radius )
- {
- continue;
- }
-
- //in PVS?
- if ( !traceEnt->bmodel && !gi.inPVS( ent_org, self->client->renderInfo.handLPoint ) )
- {//must be in PVS
- continue;
- }
- //Now check and see if we can actually hit it
- gi.trace( &tr, self->client->renderInfo.handLPoint, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_SHOT );
- if ( tr.fraction < 1.0f && tr.entityNum != traceEnt->s.number )
- {//must have clear LOS
- continue;
- }
- // ok, we are within the radius, add us to the incoming list
- //FIXME: maybe add up the ents and do more damage the less ents there are
- // as if we're spreading out the damage?
- ForceLightningDamage( self, traceEnt, dir, dist, dot, ent_org );
- }
- }
- else
- {//trace-line
- int ignore = self->s.number;
- int traces = 0;
- vec3_t start;
- VectorCopy( self->client->renderInfo.handLPoint, start );
- VectorMA( self->client->renderInfo.handLPoint, 2048, forward, end );
-
- while ( traces < 10 )
- {//need to loop this in case we hit a Jedi who dodges the shot
- gi.trace( &tr, start, vec3_origin, vec3_origin, end, ignore, MASK_SHOT, G2_RETURNONHIT, 10 );
- if ( tr.entityNum == ENTITYNUM_NONE || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
- {
- return;
- }
- traceEnt = &g_entities[tr.entityNum];
- //NOTE: only NPCs do this auto-dodge
- if ( traceEnt
- && traceEnt->s.number >= MAX_CLIENTS
- && traceEnt->client
- && traceEnt->client->ps.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 )//&& traceEnt->NPC
- {//FIXME: need a more reliable way to know we hit a jedi?
- if ( !Jedi_DodgeEvasion( traceEnt, self, &tr, HL_NONE ) )
- {//act like we didn't even hit him
- VectorCopy( tr.endpos, start );
- ignore = tr.entityNum;
- traces++;
- continue;
- }
- }
- //a Jedi is not dodging this shot
- break;
- }
-
- traceEnt = &g_entities[tr.entityNum];
- ForceLightningDamage( self, traceEnt, forward, 0, 0, tr.endpos );
- }
- }
- void WP_DeactivateSaber( gentity_t *self, qboolean clearLength )
- {
- if ( !self || !self->client )
- {
- return;
- }
- //keep my saber off!
- if ( self->client->ps.SaberActive() )
- {
- self->client->ps.SaberDeactivate();
- if ( clearLength )
- {
- self->client->ps.SetSaberLength( 0 );
- }
- G_SoundIndexOnEnt( self, CHAN_WEAPON, self->client->ps.saber[0].soundOff );
- #ifdef _IMMERSION
- if ( !self->s.number )
- {//this is kind of silly to try to do on an NPC
- if ( self->client->playerTeam == TEAM_PLAYER )
- {
- G_Force( self, G_ForceIndex( "fffx/weapons/saber/saberoff", FF_CHANNEL_WEAPON ) );
- }
- else
- {
- G_Force( self, G_ForceIndex( "fffx/weapons/saber/enemy_saber_off", FF_CHANNEL_WEAPON ) );
- }
- }
- #endif // _IMMERSION
- }
- }
- static void ForceShootDrain( gentity_t *self );
- void ForceDrainGrabStart( gentity_t *self )
- {
- NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCE_DRAIN_GRAB_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
- self->client->ps.saberBlocked = BLOCKED_NONE;
- self->client->ps.weaponTime = 1000;
- if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
- {
- self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
- }
- //actually grabbing someone, so turn off the saber!
- WP_DeactivateSaber( self, qtrue );
- }
- qboolean ForceDrain2( gentity_t *self )
- {//FIXME: make enemy Jedi able to use this
- trace_t tr;
- vec3_t end, forward;
- gentity_t *traceEnt = NULL;
- if ( self->health <= 0 )
- {
- return qtrue;
- }
- if ( !self->s.number && (cg.zoomMode || in_camera) )
- {//can't force grip when zoomed in or in cinematic
- return qtrue;
- }
- if ( self->client->ps.leanofs )
- {//can't force-drain while leaning
- return qtrue;
- }
- /*
- if ( self->client->ps.SaberLength() > 0 )
- {//can't do this if saber is on!
- return qfalse;
- }
- */
- if ( self->client->ps.forceDrainEntityNum <= ENTITYNUM_WORLD )
- {//already draining
- //keep my saber off!
- WP_DeactivateSaber( self, qtrue );
- if ( self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_1 )
- {
- self->client->ps.forcePowerDuration[FP_DRAIN] = level.time + 100;
- self->client->ps.weaponTime = 1000;
- if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
- {
- self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
- }
- }
- return qtrue;
- }
- if ( self->client->ps.forcePowerDebounce[FP_DRAIN] > level.time )
- {//stops it while using it and also after using it, up to 3 second delay
- return qtrue;
- }
- if ( self->client->ps.weaponTime > 0 )
- {//busy
- return qtrue;
- }
- if ( self->client->ps.forcePower < 25 || !WP_ForcePowerUsable( self, FP_DRAIN, 0 ) )
- {
- return qtrue;
- }
- if ( self->client->ps.saberLockTime > level.time )
- {//in saberlock
- return qtrue;
- }
- //NOTE: from here on, if it fails, it's okay to try a normal drain, so return qfalse
- if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE )
- {//in air
- return qfalse;
- }
- //Cause choking anim + health drain in ent in front of me
- AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
- VectorNormalize( forward );
- VectorMA( self->client->renderInfo.eyePoint, FORCE_DRAIN_DIST, forward, end );
-
- //okay, trace straight ahead and see what's there
- gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT );
- if ( tr.entityNum >= ENTITYNUM_WORLD || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
- {
- return qfalse;
- }
- traceEnt = &g_entities[tr.entityNum];
- if ( !traceEnt || traceEnt == self/*???*/ || traceEnt->bmodel || (traceEnt->health <= 0 && traceEnt->takedamage) || (traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE) )
- {
- return qfalse;
- }
- if ( traceEnt->client )
- {
- if ( traceEnt->client->ps.forceJumpZStart )
- {//can't catch them in mid force jump - FIXME: maybe base it on velocity?
- return qfalse;
- }
- if ( traceEnt->client->ps.groundEntityNum == ENTITYNUM_NONE )
- {//can't catch them in mid air
- return qfalse;
- }
- if ( !Q_stricmp("Yoda",traceEnt->NPC_type) )
- {
- Jedi_PlayDeflectSound( traceEnt );
- ForceThrow( traceEnt, qfalse );
- return qtrue;
- }
- switch ( traceEnt->client->NPC_class )
- {
- case CLASS_GALAKMECH://cant grab him, he's in armor
- G_AddVoiceEvent( traceEnt, Q_irand(EV_PUSHED1, EV_PUSHED3), Q_irand( 3000, 5000 ) );
- return qfalse;
- break;
- case CLASS_ROCKETTROOPER://cant grab him, he's in armor
- case CLASS_HAZARD_TROOPER://cant grab him, he's in armor
- return qfalse;
- break;
- case CLASS_ATST://much too big to grab!
- return qfalse;
- break;
- //no droids either
- case CLASS_GONK:
- case CLASS_R2D2:
- case CLASS_R5D2:
- case CLASS_MARK1:
- case CLASS_MARK2:
- case CLASS_MOUSE:
- case CLASS_PROTOCOL:
- case CLASS_SABER_DROID:
- case CLASS_ASSASSIN_DROID:
- return qfalse;
- break;
- case CLASS_PROBE:
- case CLASS_SEEKER:
- case CLASS_REMOTE:
- case CLASS_SENTRY:
- case CLASS_INTERROGATOR:
- return qfalse;
- break;
- case CLASS_DESANN://Desann cannot be gripped, he just pushes you back instantly
- case CLASS_KYLE:
- case CLASS_TAVION:
- case CLASS_LUKE:
- Jedi_PlayDeflectSound( traceEnt );
- ForceThrow( traceEnt, qfalse );
- return qtrue;
- break;
- case CLASS_REBORN:
- case CLASS_SHADOWTROOPER:
- //case CLASS_ALORA:
- case CLASS_JEDI:
- if ( traceEnt->NPC
- && traceEnt->NPC->rank > RANK_CIVILIAN
- && self->client->ps.forcePowerLevel[FP_DRAIN] < FORCE_LEVEL_2
- && traceEnt->client->ps.weaponTime <= 0 )
- {
- ForceDrainGrabStart( self );
- Jedi_PlayDeflectSound( traceEnt );
- ForceThrow( traceEnt, qfalse );
- return qtrue;
- }
- break;
- }
- if ( traceEnt->s.weapon == WP_EMPLACED_GUN )
- {//FIXME: maybe can pull them out?
- return qfalse;
- }
- if ( traceEnt != self->enemy && OnSameTeam(self, traceEnt) )
- {//can't accidently grip-drain your teammate
- return qfalse;
- }
- //=CHECKABSORB===
- /*
- if ( -1 != WP_AbsorbConversion( traceEnt, traceEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_DRAIN, self->client->ps.forcePowerLevel[FP_DRAIN], forcePowerNeeded[self->client->ps.forcePowerLevel[FP_DRAIN]]) )
- {
- //WP_ForcePowerStop( self, FP_DRAIN );
- return;
- }
- */
- //===============
- if ( !FP_ForceDrainGrippableEnt( traceEnt ) )
- {
- return qfalse;
- }
- }
- else
- {//can't drain non-clients
- return qfalse;
- }
- ForceDrainGrabStart( self );
- WP_ForcePowerStart( self, FP_DRAIN, 10 );
- self->client->ps.forceDrainEntityNum = traceEnt->s.number;
- // G_AddVoiceEvent( traceEnt, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
- G_AddVoiceEvent( traceEnt, Q_irand(EV_CHOKE1, EV_CHOKE3), 2000 );
- if ( /*self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_2 ||*/ traceEnt->s.weapon == WP_SABER )
- {//if we pick up, turn off their weapon
- WP_DeactivateSaber( traceEnt, qtrue );
- }
- /*
- if ( self->client->ps.forcePowerLevel[FP_DRAIN] < FORCE_LEVEL_2 )
- {//just a duration
- self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 250;
- self->client->ps.forcePowerDuration[FP_DRAIN] = level.time + 5000;
- }
- */
- G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/drain.mp3" );
- // NPC_SetAnim( traceEnt, SETANIM_BOTH, BOTH_HUGGEE1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- NPC_SetAnim( traceEnt, SETANIM_BOTH, BOTH_FORCE_DRAIN_GRABBED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
-
- WP_SabersCheckLock2( self, traceEnt, LOCK_FORCE_DRAIN );
- return qtrue;
- }
- void ForceDrain( gentity_t *self, qboolean triedDrain2 )
- {
- if ( self->health <= 0 )
- {
- return;
- }
- if ( !triedDrain2 && self->client->ps.weaponTime > 0 )
- {
- return;
- }
- if ( self->client->ps.forcePower < 25 || !WP_ForcePowerUsable( self, FP_DRAIN, 0 ) )
- {
- return;
- }
- if ( self->client->ps.forcePowerDebounce[FP_DRAIN] > level.time )
- {//stops it while using it and also after using it, up to 3 second delay
- return;
- }
- if ( self->client->ps.saberLockTime > level.time )
- {//FIXME: can this be a way to break out?
- return;
- }
- // Make sure to turn off Force Protection and Force Absorb.
- if ( self->client->ps.forcePowersActive & (1 << FP_PROTECT) )
- {
- WP_ForcePowerStop( self, FP_PROTECT );
- }
- if ( self->client->ps.forcePowersActive & (1 << FP_ABSORB) )
- {
- WP_ForcePowerStop( self, FP_ABSORB );
- }
- G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/drain.mp3" );
-
- WP_ForcePowerStart( self, FP_DRAIN, 0 );
- }
- qboolean FP_ForceDrainableEnt( gentity_t *victim )
- {
- if ( !victim || !victim->client )
- {
- return qfalse;
- }
- switch ( victim->client->NPC_class )
- {
- case CLASS_SAND_CREATURE://??
- case CLASS_ATST: // technically droid...
- case CLASS_GONK: // droid
- case CLASS_INTERROGATOR: // droid
- case CLASS_MARK1: // droid
- case CLASS_MARK2: // droid
- case CLASS_GALAKMECH: // droid
- case CLASS_MINEMONSTER:
- case CLASS_MOUSE: // droid
- case CLASS_PROBE: // droid
- case CLASS_PROTOCOL: // droid
- case CLASS_R2D2: // droid
- case CLASS_R5D2: // droid
- case CLASS_REMOTE:
- case CLASS_SEEKER: // droid
- case CLASS_SENTRY:
- case CLASS_SABER_DROID:
- case CLASS_ASSASSIN_DROID:
- case CLASS_VEHICLE:
- return qfalse;
- break;
- }
- return qtrue;
- }
- qboolean FP_ForceDrainGrippableEnt( gentity_t *victim )
- {
- if ( !victim || !victim->client )
- {
- return qfalse;
- }
- if ( !FP_ForceDrainableEnt( victim ) )
- {
- return qfalse;
- }
- switch ( victim->client->NPC_class )
- {
- case CLASS_RANCOR:
- case CLASS_SAND_CREATURE:
- case CLASS_WAMPA:
- case CLASS_LIZARD:
- case CLASS_MINEMONSTER:
- case CLASS_MURJJ:
- case CLASS_SWAMP:
- case CLASS_ROCKETTROOPER:
- case CLASS_HAZARD_TROOPER:
- return qfalse;
- }
- return qtrue;
- }
- void ForceDrainDamage( gentity_t *self, gentity_t *traceEnt, vec3_t dir, vec3_t impactPoint )
- {
- if ( traceEnt
- && traceEnt->health > 0
- && traceEnt->takedamage
- && FP_ForceDrainableEnt( traceEnt ) )
- {
- if ( traceEnt->client
- && (!OnSameTeam(self, traceEnt)||self->enemy==traceEnt)//don't drain an ally unless that is actually my current enemy
- && self->client->ps.forceDrainTime < level.time )
- {//an enemy or object
- int modPowerLevel = -1;
- int dmg = self->client->ps.forcePowerLevel[FP_DRAIN] + 1;
- int dflags = (DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC);//|DAMAGE_NO_KILL);
- if ( traceEnt->s.number == self->client->ps.forceDrainEntityNum )
- {//grabbing hold of them does more damage/drains more, and can actually kill them
- dmg += 3;
- dflags |= DAMAGE_IGNORE_TEAM;
- //dflags &= ~DAMAGE_NO_KILL;
- }
-
- if (traceEnt->client)
- {
- //check for client using FP_ABSORB
- modPowerLevel = WP_AbsorbConversion(traceEnt, traceEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_DRAIN, self->client->ps.forcePowerLevel[FP_DRAIN], 0);
- //Since this is drain, don't absorb any power, but nullify the affect it has
- }
- if ( modPowerLevel != -1 )
- {
- if ( !modPowerLevel )
- {
- dmg = 0;
- }
- else if ( modPowerLevel == 1 )
- {
- dmg = 1;
- }
- else if ( modPowerLevel == 2 )
- {
- dmg = 2;
- }
- }
- if ( dmg )
- {
- int drain = 0;
- if ( traceEnt->client->ps.forcePower )
- {
- if ( dmg > traceEnt->client->ps.forcePower )
- {
- drain = traceEnt->client->ps.forcePower;
- dmg -= drain;
- traceEnt->client->ps.forcePower = 0;
- }
- else
- {
- drain = dmg;
- traceEnt->client->ps.forcePower -= (dmg);
- dmg = 0;
- }
- }
- /*
- if ( (dflags&DAMAGE_NO_KILL) )
- {//must cap damage
- if ( traceEnt->health <= 1 )
- {//can't drain more than they have
- dmg = 0;
- }
- else if ( dmg >= traceEnt->health )
- {//no more than they have, leaving one for them
- dmg = traceEnt->health-1;
- }
- }
- */
- int maxHealth = self->client->ps.stats[STAT_MAX_HEALTH];
- if ( self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_2 )
- {//overcharge health
- maxHealth = floor( (float)self->client->ps.stats[STAT_MAX_HEALTH] * 1.25f );
- }
- if (self->client->ps.stats[STAT_HEALTH] < maxHealth &&
- self->health > 0 && self->client->ps.stats[STAT_HEALTH] > 0)
- {
- self->health += (drain+dmg);
- if (self->health > maxHealth )
- {
- self->health = maxHealth;
- }
- self->client->ps.stats[STAT_HEALTH] = self->health;
- if ( self->health > self->client->ps.stats[STAT_MAX_HEALTH] )
- {
- self->flags |= FL_OVERCHARGED_HEALTH;
- }
- }
- if ( dmg )
- {//do damage, too
- G_Damage( traceEnt, self, self, dir, impactPoint, dmg, dflags, MOD_FORCE_DRAIN );
- }
- else if ( drain )
- {
- /*
- if ( traceEnt->s.number == self->client->ps.forceDrainEntityNum
- || traceEnt->s.number < MAX_CLIENTS )
- {//grip-draining (or player - only does sound)
- */
- NPC_SetPainEvent( traceEnt );
- /*
- }
- else
- {
- GEntity_PainFunc( traceEnt, self, self, impactPoint, 0, MOD_FORCE_DRAIN );
- }
- */
- }
- if ( !Q_irand( 0, 2 ) )
- {
- G_Sound( traceEnt, G_SoundIndex( "sound/weapons/force/drained.mp3" ) );
- }
- traceEnt->client->ps.forcePowerRegenDebounceTime = level.time + 800; //don't let the client being drained get force power back right away
- }
- }
- }
- }
- qboolean WP_CheckForceDraineeStopMe( gentity_t *self, gentity_t *drainee )
- {
- if ( drainee->NPC
- && drainee->client
- && (drainee->client->ps.forcePowersKnown&(1<<FP_PUSH))
- && level.time-(self->client->ps.forcePowerDebounce[FP_DRAIN]>self->client->ps.forcePowerLevel[FP_DRAIN]*500)//at level 1, I always get at least 500ms of drain, at level 3 I get 1500ms
- && !Q_irand( 0, 100-(drainee->NPC->stats.evasion*10)-(g_spskill->integer*12) ) )
- {//a jedi who broke free
- ForceThrow( drainee, qfalse );
- //FIXME: I need to go into some pushed back anim...
- WP_ForcePowerStop( self, FP_DRAIN );
- //can't drain again for 2 seconds
- self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 4000;
- return qtrue;
- }
- return qfalse;
- }
- void ForceShootDrain( gentity_t *self )
- {
- trace_t tr;
- vec3_t end, forward;
- gentity_t *traceEnt;
- int numDrained = 0;
- if ( self->health <= 0 )
- {
- return;
- }
-
- if ( self->client->ps.forcePowerDebounce[FP_DRAIN] <= level.time )
- {
- AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
- VectorNormalize( forward );
- if ( self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_2 )
- {//arc
- vec3_t center, mins, maxs, dir, ent_org, size, v;
- float radius = MAX_DRAIN_DISTANCE, dot, dist;
- gentity_t *entityList[MAX_GENTITIES];
- int e, numListedEntities, i;
- VectorCopy( self->client->ps.origin, center );
- for ( i = 0 ; i < 3 ; i++ )
- {
- mins[i] = center[i] - radius;
- maxs[i] = center[i] + radius;
- }
- numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
- for ( e = 0 ; e < numListedEntities ; e++ )
- {
- traceEnt = entityList[e];
- if ( !traceEnt )
- continue;
- if ( traceEnt == self )
- continue;
- if ( !traceEnt->inuse )
- continue;
- if ( !traceEnt->takedamage )
- continue;
- if ( traceEnt->health <= 0 )//no torturing corpses
- continue;
- if ( !traceEnt->client )
- continue;
- /*
- if ( !traceEnt->client->ps.forcePower )
- continue;
- */
- // if (traceEnt->client->ps.forceSide == FORCE_DARKSIDE) // We no longer care if the victim is dark or light
- // continue;
- if (self->enemy != traceEnt//not my enemy
- && OnSameTeam(self, traceEnt))//on my team
- continue;
- //this is all to see if we need to start a saber attack, if it's in flight, this doesn't matter
- // find the distance from the edge of the bounding box
- for ( i = 0 ; i < 3 ; i++ )
- {
- if ( center[i] < traceEnt->absmin[i] )
- {
- v[i] = traceEnt->absmin[i] - center[i];
- } else if ( center[i] > traceEnt->absmax[i] )
- {
- v[i] = center[i] - traceEnt->absmax[i];
- } else
- {
- v[i] = 0;
- }
- }
- VectorSubtract( traceEnt->absmax, traceEnt->absmin, size );
- VectorMA( traceEnt->absmin, 0.5, size, ent_org );
- //see if they're in front of me
- //must be within the forward cone
- VectorSubtract( ent_org, center, dir );
- VectorNormalize( dir );
- if ( (dot = DotProduct( dir, forward )) < 0.5 )
- continue;
- //must be close enough
- dist = VectorLength( v );
- if ( dist >= radius )
- {
- continue;
- }
-
- //in PVS?
- if ( !traceEnt->bmodel && !gi.inPVS( ent_org, self->client->renderInfo.handLPoint ) )
- {//must be in PVS
- continue;
- }
- //Now check and see if we can actually hit it
- gi.trace( &tr, self->client->ps.origin, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_SHOT, G2_RETURNONHIT, 10 );
- if ( tr.fraction < 1.0f && tr.entityNum != traceEnt->s.number )
- {//must have clear LOS
- continue;
- }
- if ( traceEnt
- && traceEnt->s.number >= MAX_CLIENTS
- && traceEnt->client
- && traceEnt->client->ps.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 )//&& traceEnt->NPC
- {
- if ( !Q_irand( 0, 4 ) && !Jedi_DodgeEvasion( traceEnt, self, &tr, HL_NONE ) )
- {//act like we didn't even hit him
- continue;
- }
- }
- // ok, we are within the radius, add us to the incoming list
- if ( WP_CheckForceDraineeStopMe( self, traceEnt ) )
- {
- continue;
- }
- ForceDrainDamage( self, traceEnt, dir, ent_org );
- numDrained++;
- }
- }
- else
- {//trace-line
- int ignore = self->s.number;
- int traces = 0;
- vec3_t start;
- VectorCopy( self->client->renderInfo.handLPoint, start );
- VectorMA( start, MAX_DRAIN_DISTANCE, forward, end );
-
- while ( traces < 10 )
- {//need to loop this in case we hit a Jedi who dodges the shot
- gi.trace( &tr, start, vec3_origin, vec3_origin, end, ignore, MASK_SHOT, G2_RETURNONHIT, 10 );
- if ( tr.entityNum == ENTITYNUM_NONE || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
- {
- //always take 1 force point per frame that we're shooting this
- WP_ForcePowerDrain( self, FP_DRAIN, 1 );
- return;
- }
- traceEnt = &g_entities[tr.entityNum];
- //NOTE: only NPCs do this auto-dodge
- if ( traceEnt
- && traceEnt->s.number >= MAX_CLIENTS
- && traceEnt->client
- && traceEnt->client->ps.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 )//&& traceEnt->NPC
- {
- if ( !Q_irand( 0, 2 ) && !Jedi_DodgeEvasion( traceEnt, self, &tr, HL_NONE ) )
- {//act like we didn't even hit him
- VectorCopy( tr.endpos, start );
- ignore = tr.entityNum;
- traces++;
- continue;
- }
- }
- //a Jedi is not dodging this shot
- break;
- }
- traceEnt = &g_entities[tr.entityNum];
- if ( !WP_CheckForceDraineeStopMe( self, traceEnt ) )
- {
- ForceDrainDamage( self, traceEnt, forward, tr.endpos );
- }
- numDrained = 1;
- }
- self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 200;//so we don't drain so damn fast!
- }
- self->client->ps.forcePowerRegenDebounceTime = level.time + 500;
-
- if ( !numDrained )
- {//always take 1 force point per frame that we're shooting this
- WP_ForcePowerDrain( self, FP_DRAIN, 1 );
- }
- else
- {
- WP_ForcePowerDrain( self, FP_DRAIN, numDrained );//was 2, but...
- }
- return;
- }
- void ForceDrainEnt( gentity_t *self, gentity_t *drainEnt )
- {
- if ( self->health <= 0 )
- {
- return;
- }
-
- if ( self->client->ps.forcePowerDebounce[FP_DRAIN] <= level.time )
- {
- if ( !drainEnt )
- return;
- if ( drainEnt == self )
- return;
- if ( !drainEnt->inuse )
- return;
- if ( !drainEnt->takedamage )
- return;
- if ( drainEnt->health <= 0 )//no torturing corpses
- return;
- if ( !drainEnt->client )
- return;
- if (OnSameTeam(self, drainEnt))
- return;
- vec3_t fwd;
- AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL );
- drainEnt->painDebounceTime = 0;
- ForceDrainDamage( self, drainEnt, fwd, drainEnt->currentOrigin );
- drainEnt->painDebounceTime = level.time + 2000;
- if ( drainEnt->s.number )
- {
- if ( self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_2 )
- {//do damage faster at level 3
- self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + Q_irand( 100, 500 );
- }
- else
- {
- self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + Q_irand( 200, 800 );
- }
- }
- else
- {//player takes damage faster
- self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + Q_irand( 100, 500 );
- }
- }
- self->client->ps.forcePowerRegenDebounceTime = level.time + 500;
- }
- void ForceSeeing( gentity_t *self )
- {
- if ( self->health <= 0 )
- {
- return;
- }
- if (self->client->ps.forceAllowDeactivateTime < level.time &&
- (self->client->ps.forcePowersActive & (1 << FP_SEE)) )
- {
- WP_ForcePowerStop( self, FP_SEE );
- return;
- }
- if ( !WP_ForcePowerUsable( self, FP_SEE, 0 ) )
- {
- return;
- }
- WP_DebounceForceDeactivateTime( self );
- WP_ForcePowerStart( self, FP_SEE, 0 );
- G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/see.wav" );
- }
- void ForceProtect( gentity_t *self )
- {
- if ( self->health <= 0 )
- {
- return;
- }
- if (self->client->ps.forceAllowDeactivateTime < level.time &&
- (self->client->ps.forcePowersActive & (1 << FP_PROTECT)) )
- {
- WP_ForcePowerStop( self, FP_PROTECT );
- return;
- }
- if ( !WP_ForcePowerUsable( self, FP_PROTECT, 0 ) )
- {
- return;
- }
- // Make sure to turn off Force Rage and Force Absorb.
- if (self->client->ps.forcePowersActive & (1 << FP_RAGE) )
- {
- WP_ForcePowerStop( self, FP_RAGE );
- }
- WP_DebounceForceDeactivateTime( self );
- WP_ForcePowerStart( self, FP_PROTECT, 0 );
- if ( self->client->ps.saberLockTime < level.time )
- {
- if ( self->client->ps.forcePowerLevel[FP_PROTECT] < FORCE_LEVEL_3 )
- {//animate
- int parts = SETANIM_BOTH;
- int anim = BOTH_FORCE_PROTECT;
- if ( self->client->ps.forcePowerLevel[FP_PROTECT] > FORCE_LEVEL_1 )
- {//level 2 only does it on torso (can keep running)
- parts = SETANIM_TORSO;
- anim = BOTH_FORCE_PROTECT_FAST;
- }
- else
- {
- if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
- {
- VectorClear( self->client->ps.velocity );
- }
- if ( self->NPC )
- {
- VectorClear( self->client->ps.moveDir );
- self->client->ps.speed = 0;
- }
- //FIXME: what if in air?
- }
- NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- //don't move or attack during this anim
- if ( self->client->ps.forcePowerLevel[FP_PROTECT] < FORCE_LEVEL_2 )
- {
- self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
- self->client->ps.pm_time = self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
- if ( self->s.number )
- {//NPC
- self->painDebounceTime = level.time + self->client->ps.torsoAnimTimer;
- }
- else
- {//player
- self->aimDebounceTime = level.time + self->client->ps.torsoAnimTimer;
- }
- }
- else
- {
- self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
- }
- }
- }
- }
- void ForceAbsorb( gentity_t *self )
- {
- if ( self->health <= 0 )
- {
- return;
- }
- if (self->client->ps.forceAllowDeactivateTime < level.time &&
- (self->client->ps.forcePowersActive & (1 << FP_ABSORB)) )
- {
- WP_ForcePowerStop( self, FP_ABSORB );
- return;
- }
- if ( !WP_ForcePowerUsable( self, FP_ABSORB, 0 ) )
- {
- return;
- }
- // Make sure to turn off Force Rage and Force Protection.
- if (self->client->ps.forcePowersActive & (1 << FP_RAGE) )
- {
- WP_ForcePowerStop( self, FP_RAGE );
- }
- WP_DebounceForceDeactivateTime( self );
- WP_ForcePowerStart( self, FP_ABSORB, 0 );
- if ( self->client->ps.saberLockTime < level.time )
- {
- if ( self->client->ps.forcePowerLevel[FP_ABSORB] < FORCE_LEVEL_3 )
- {//must animate
- int parts = SETANIM_BOTH;
- if ( self->client->ps.forcePowerLevel[FP_ABSORB] > FORCE_LEVEL_1 )
- {//level 2 only does it on torso (can keep running)
- parts = SETANIM_TORSO;
- }
- else
- {
- if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
- {
- VectorClear( self->client->ps.velocity );
- }
- if ( self->NPC )
- {
- VectorClear( self->client->ps.moveDir );
- self->client->ps.speed = 0;
- }
- //FIXME: what if in air?
- }
- /*
- //if in air, only do on torso - NOTE: or moving?
- if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE )//|| !VectorCompare( self->client->ps.velocity, vec3_origin ) )
- {
- parts = SETANIM_TORSO;
- }
- */
- NPC_SetAnim( self, parts, BOTH_FORCE_ABSORB, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
- if ( parts == SETANIM_BOTH )
- {//can't move
- self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
- self->client->ps.pm_time = self->client->ps.legsAnimTimer = self->client->ps.torsoAnimTimer;// = self->client->ps.forcePowerDuration[FP_ABSORB];
- if ( self->s.number )
- {//NPC
- self->painDebounceTime = level.time + self->client->ps.pm_time;//self->client->ps.forcePowerDuration[FP_ABSORB];
- }
- else
- {//player
- self->aimDebounceTime = level.time + self->client->ps.pm_time;//self->client->ps.forcePowerDuration[FP_ABSORB];
- }
- }
- //stop saber
- //WP_DeactivateSaber( self );//turn off saber when meditating
- self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
- self->client->ps.saberBlocked = BLOCKED_NONE;
- }
- }
- }
- void ForceRage( gentity_t *self )
- {
- if ( self->health <= 0 )
- {
- return;
- }
- //FIXME: prevent them from using any other force powers when raging?
- if (self->client->ps.forceAllowDeactivateTime < level.time &&
- (self->client->ps.forcePowersActive & (1 << FP_RAGE)) )
- {
- WP_ForcePowerStop( self, FP_RAGE );
- return;
- }
- if ( !WP_ForcePowerUsable( self, FP_RAGE, 0 ) )
- {
- return;
- }
- if (self->client->ps.forceRageRecoveryTime >= level.time)
- {
- return;
- }
- if ( self->s.number < MAX_CLIENTS
- && self->health < 25 )
- {//have to have at least 25 health to start it
- return;
- }
- if (self->health < 10)
- {
- return;
- }
- // Make sure to turn off Force Protection and Force Absorb.
- if (self->client->ps.forcePowersActive & (1 << FP_PROTECT) )
- {
- WP_ForcePowerStop( self, FP_PROTECT );
- }
- if (self->client->ps.forcePowersActive & (1 << FP_ABSORB) )
- {
- WP_ForcePowerStop( self, FP_ABSORB );
- }
- WP_DebounceForceDeactivateTime( self );
- WP_ForcePowerStart( self, FP_RAGE, 0 );
- if ( self->client->ps.saberLockTime < level.time )
- {
- if ( self->client->ps.forcePowerLevel[FP_RAGE] < FORCE_LEVEL_3 )
- {//must animate
- if ( self->client->ps.forcePowerLevel[FP_RAGE] < FORCE_LEVEL_2 )
- {//have to stand still for whole length of anim
- //FIXME: if in air, only do on torso?
- NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCE_RAGE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- //don't attack during this anim
- self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
- self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
- self->client->ps.pm_time = self->client->ps.torsoAnimTimer;
- if ( self->s.number )
- {//NPC
- self->painDebounceTime = level.time + self->client->ps.torsoAnimTimer;
- }
- else
- {//player
- self->aimDebounceTime = level.time + self->client->ps.torsoAnimTimer;
- }
- }
- else
- {
- NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_RAGE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- //don't attack during this anim
- self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
- }
- //stop saber
- self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
- self->client->ps.saberBlocked = BLOCKED_NONE;
- }
- }
- }
- void ForceJumpCharge( gentity_t *self, usercmd_t *ucmd )
- {
- float forceJumpChargeInterval = forceJumpStrength[0] / (FORCE_JUMP_CHARGE_TIME/FRAMETIME);
- if ( self->health <= 0 )
- {
- return;
- }
- if ( !self->s.number && cg.zoomMode )
- {//can't force jump when zoomed in
- return;
- }
- //need to play sound
- if ( !self->client->ps.forceJumpCharge )
- {//FIXME: this should last only as long as the actual charge-up
- G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jumpbuild.wav" );
- #ifdef _IMMERSION
- G_Force( self, G_ForceIndex( "fffx/weapons/force/jumpbuild", FF_CHANNEL_WEAPON ) );
- #endif // _IMMERSION
- }
- //Increment
- self->client->ps.forceJumpCharge += forceJumpChargeInterval;
- //clamp to max strength for current level
- if ( self->client->ps.forceJumpCharge > forceJumpStrength[self->client->ps.forcePowerLevel[FP_LEVITATION]] )
- {
- self->client->ps.forceJumpCharge = forceJumpStrength[self->client->ps.forcePowerLevel[FP_LEVITATION]];
- }
- //clamp to max available force power
- if ( self->client->ps.forceJumpCharge/forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME)*forcePowerNeeded[FP_LEVITATION] > self->client->ps.forcePower )
- {//can't use more than you have
- self->client->ps.forceJumpCharge = self->client->ps.forcePower*forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME);
- }
- //FIXME: a simple tap should always do at least a normal height's jump?
- }
- int WP_GetVelocityForForceJump( gentity_t *self, vec3_t jumpVel, usercmd_t *ucmd )
- {
- float pushFwd = 0, pushRt = 0;
- vec3_t view, forward, right;
- VectorCopy( self->client->ps.viewangles, view );
- view[0] = 0;
- AngleVectors( view, forward, right, NULL );
- if ( ucmd->forwardmove && ucmd->rightmove )
- {
- if ( ucmd->forwardmove > 0 )
- {
- pushFwd = 50;
- }
- else
- {
- pushFwd = -50;
- }
- if ( ucmd->rightmove > 0 )
- {
- pushRt = 50;
- }
- else
- {
- pushRt = -50;
- }
- }
- else if ( ucmd->forwardmove || ucmd->rightmove )
- {
- if ( ucmd->forwardmove > 0 )
- {
- pushFwd = 100;
- }
- else if ( ucmd->forwardmove < 0 )
- {
- pushFwd = -100;
- }
- else if ( ucmd->rightmove > 0 )
- {
- pushRt = 100;
- }
- else if ( ucmd->rightmove < 0 )
- {
- pushRt = -100;
- }
- }
- VectorMA( self->client->ps.velocity, pushFwd, forward, jumpVel );
- VectorMA( self->client->ps.velocity, pushRt, right, jumpVel );
- jumpVel[2] += self->client->ps.forceJumpCharge;//forceJumpStrength;
- if ( pushFwd > 0 && self->client->ps.forceJumpCharge > 200 )
- {
- return FJ_FORWARD;
- }
- else if ( pushFwd < 0 && self->client->ps.forceJumpCharge > 200 )
- {
- return FJ_BACKWARD;
- }
- else if ( pushRt > 0 && self->client->ps.forceJumpCharge > 200 )
- {
- return FJ_RIGHT;
- }
- else if ( pushRt < 0 && self->client->ps.forceJumpCharge > 200 )
- {
- return FJ_LEFT;
- }
- else
- {//FIXME: jump straight up anim
- return FJ_UP;
- }
- }
- void ForceJump( gentity_t *self, usercmd_t *ucmd )
- {
- if ( self->client->ps.forcePowerDuration[FP_LEVITATION] > level.time )
- {
- return;
- }
- if ( !WP_ForcePowerUsable( self, FP_LEVITATION, 0 ) )
- {
- return;
- }
- if ( self->s.groundEntityNum == ENTITYNUM_NONE )
- {
- return;
- }
- if ( self->client->ps.pm_flags&PMF_JUMP_HELD )
- {
- return;
- }
- if ( self->health <= 0 )
- {
- return;
- }
- if ( !self->s.number && (cg.zoomMode || in_camera) )
- {//can't force jump when zoomed in or in cinematic
- return;
- }
- if ( self->client->ps.saberLockTime > level.time )
- {//FIXME: can this be a way to break out?
- return;
- }
- if ( self->client->NPC_class == CLASS_BOBAFETT
- || self->client->NPC_class == CLASS_ROCKETTROOPER )
- {
- if ( self->client->ps.forceJumpCharge > 300 )
- {
- JET_FlyStart(NPC);
- }
- else
- {
- G_AddEvent( self, EV_JUMP, 0 );
- }
- }
- else
- {
- G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" );
- }
- #ifdef _IMMERSION
- G_Force( self, G_ForceIndex( "fffx/weapons/force/jump", FF_CHANNEL_WEAPON ) );
- #endif // _IMMERSION
- float forceJumpChargeInterval = forceJumpStrength[self->client->ps.forcePowerLevel[FP_LEVITATION]]/(FORCE_JUMP_CHARGE_TIME/FRAMETIME);
- int anim;
- vec3_t jumpVel;
- switch( WP_GetVelocityForForceJump( self, jumpVel, ucmd ) )
- {
- case FJ_FORWARD:
- if ( ((self->client->NPC_class == CLASS_BOBAFETT||self->client->NPC_class == CLASS_ROCKETTROOPER) && self->client->ps.forceJumpCharge > 300 )
- || ( self->NPC &&
- self->NPC->rank != RANK_CREWMAN &&
- self->NPC->rank <= RANK_LT_JG ) )
- {//can't do acrobatics
- anim = BOTH_FORCEJUMP1;
- }
- else
- {
- if ( self->client->NPC_class == CLASS_ALORA && Q_irand( 0, 3 ) )
- {
- anim = Q_irand( BOTH_ALORA_FLIP_1, BOTH_ALORA_FLIP_3 );
- }
- else
- {
- anim = BOTH_FLIP_F;
- }
- }
- break;
- case FJ_BACKWARD:
- if ( ((self->client->NPC_class == CLASS_BOBAFETT||self->client->NPC_class == CLASS_ROCKETTROOPER) && self->client->ps.forceJumpCharge > 300 )
- || ( self->NPC &&
- self->NPC->rank != RANK_CREWMAN &&
- self->NPC->rank <= RANK_LT_JG ) )
- {//can't do acrobatics
- anim = BOTH_FORCEJUMPBACK1;
- }
- else
- {
- anim = BOTH_FLIP_B;
- }
- break;
- case FJ_RIGHT:
- if ( ((self->client->NPC_class == CLASS_BOBAFETT||self->client->NPC_class == CLASS_ROCKETTROOPER) && self->client->ps.forceJumpCharge > 300 )
- || ( self->NPC &&
- self->NPC->rank != RANK_CREWMAN &&
- self->NPC->rank <= RANK_LT_JG ) )
- {//can't do acrobatics
- anim = BOTH_FORCEJUMPRIGHT1;
- }
- else
- {
- anim = BOTH_FLIP_R;
- }
- break;
- case FJ_LEFT:
- if ( ((self->client->NPC_class == CLASS_BOBAFETT||self->client->NPC_class == CLASS_ROCKETTROOPER) && self->client->ps.forceJumpCharge > 300 )
- || ( self->NPC &&
- self->NPC->rank != RANK_CREWMAN &&
- self->NPC->rank <= RANK_LT_JG ) )
- {//can't do acrobatics
- anim = BOTH_FORCEJUMPLEFT1;
- }
- else
- {
- anim = BOTH_FLIP_L;
- }
- break;
- default:
- case FJ_UP:
- anim = BOTH_JUMP1;
- break;
- }
- int parts = SETANIM_BOTH;
- if ( self->client->ps.weaponTime )
- {//FIXME: really only care if we're in a saber attack anim.. maybe trail length?
- parts = SETANIM_LEGS;
- }
- NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- //FIXME: sound effect
- self->client->ps.forceJumpZStart = self->currentOrigin[2];//remember this for when we land
- VectorCopy( jumpVel, self->client->ps.velocity );
- //wasn't allowing them to attack when jumping, but that was annoying
- //self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
- WP_ForcePowerStart( self, FP_LEVITATION, self->client->ps.forceJumpCharge/forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME)*forcePowerNeeded[FP_LEVITATION] );
- //self->client->ps.forcePowerDuration[FP_LEVITATION] = level.time + self->client->ps.weaponTime;
- self->client->ps.forceJumpCharge = 0;
- }
- int WP_AbsorbConversion(gentity_t *attacked, int atdAbsLevel, gentity_t *attacker, int atPower, int atPowerLevel, int atForceSpent)
- {
- int getLevel = 0;
- int addTot = 0;
- if (atPower != FP_LIGHTNING &&
- atPower != FP_DRAIN &&
- atPower != FP_GRIP &&
- atPower != FP_PUSH &&
- atPower != FP_PULL)
- { //Only these powers can be absorbed
- return -1;
- }
- if (!atdAbsLevel)
- { //looks like attacker doesn't have any absorb power
- return -1;
- }
- if (!(attacked->client->ps.forcePowersActive & (1 << FP_ABSORB)))
- { //absorb is not active
- return -1;
- }
- //Subtract absorb power level from the offensive force power
- getLevel = atPowerLevel;
- getLevel -= atdAbsLevel;
- if (getLevel < 0)
- {
- getLevel = 0;
- }
- //let the attacker absorb an amount of force used in this attack based on his level of absorb
- addTot = (atForceSpent/3)*attacked->client->ps.forcePowerLevel[FP_ABSORB];
- if (addTot < 1 && atForceSpent >= 1)
- {
- addTot = 1;
- }
- attacked->client->ps.forcePower += addTot;
- if (attacked->client->ps.forcePower > 100)
- {
- attacked->client->ps.forcePower = 100;
- }
- G_SoundOnEnt( attacked, CHAN_ITEM, "sound/weapons/force/absorbhit.wav" );
- return getLevel;
- }
- void WP_ForcePowerRegenerate( gentity_t *self, int overrideAmt )
- {
- if ( !self->client )
- {
- return;
- }
- if ( self->client->ps.forcePower < self->client->ps.forcePowerMax )
- {
- if ( overrideAmt )
- {
- self->client->ps.forcePower += overrideAmt;
- }
- else
- {
- self->client->ps.forcePower++;
- }
- if ( self->client->ps.forcePower > self->client->ps.forcePowerMax )
- {
- self->client->ps.forcePower = self->client->ps.forcePowerMax;
- }
- }
- }
- static bool infiniteForce = false;
- bool Cheat_InfiniteForce( void )
- {
- infiniteForce = !infiniteForce;
- return true;
- }
- void WP_ForcePowerDrain( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
- {
- // PCs have infinite force power when the cheat is turned on
- if ( self->NPC || infiniteForce )
- {//For now, NPCs have infinite force power
- return;
- }
- //take away the power
- int drain = overrideAmt;
- if ( !drain )
- {
- drain = forcePowerNeeded[forcePower];
- }
- if ( !drain )
- {
- return;
- }
- self->client->ps.forcePower -= drain;
- if ( self->client->ps.forcePower < 0 )
- {
- self->client->ps.forcePower = 0;
- }
- }
- void WP_ForcePowerStart( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
- {
- int duration = 0;
- //FIXME: debounce some of these?
- self->client->ps.forcePowerDebounce[forcePower] = 0;
- //and it in
- //set up duration time
- switch( (int)forcePower )
- {
- case FP_HEAL:
- self->client->ps.forcePowersActive |= ( 1 << forcePower );
- self->client->ps.forceHealCount = 0;
- WP_StartForceHealEffects( self );
- break;
- case FP_LEVITATION:
- self->client->ps.forcePowersActive |= ( 1 << forcePower );
- break;
- case FP_SPEED:
- //duration is always 5 seconds, player time
- duration = ceil(FORCE_SPEED_DURATION*forceSpeedValue[self->client->ps.forcePowerLevel[FP_SPEED]]);//FIXME: because the timescale scales down (not instant), this doesn't end up being exactly right...
- self->client->ps.forcePowersActive |= ( 1 << forcePower );
- self->s.loopSound = G_SoundIndex( "sound/weapons/force/speedloop.wav" );
- if ( self->client->ps.forcePowerLevel[FP_SPEED] > FORCE_LEVEL_2 )
- {//HACK: just using this as a timestamp for when the power started, setting debounce to current time shouldn't adversely affect anything else
- self->client->ps.forcePowerDebounce[FP_SPEED] = level.time;
- }
- break;
- case FP_PUSH:
- break;
- case FP_PULL:
- self->client->ps.forcePowersActive |= ( 1 << forcePower );
- break;
- case FP_TELEPATHY:
- break;
- case FP_GRIP:
- duration = 1000;
- self->client->ps.forcePowersActive |= ( 1 << forcePower );
- //HACK: just using this as a timestamp for when the power started, setting debounce to current time shouldn't adversely affect anything else
- //self->client->ps.forcePowerDebounce[forcePower] = level.time;
- break;
- case FP_LIGHTNING:
- duration = overrideAmt;
- overrideAmt = 0;
- self->client->ps.forcePowersActive |= ( 1 << forcePower );
- break;
- //new Jedi Academy force powers
- case FP_RAGE:
- //duration is always 5 seconds, player time
- duration = ceil(FORCE_RAGE_DURATION*forceSpeedValue[self->client->ps.forcePowerLevel[FP_RAGE]-1]);//FIXME: because the timescale scales down (not instant), this doesn't end up being exactly right...
- self->client->ps.forcePowersActive |= ( 1 << forcePower );
- G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/rage.mp3" );
- self->s.loopSound = G_SoundIndex( "sound/weapons/force/rageloop.wav" );
- if ( self->chestBolt != -1 )
- {
- G_PlayEffect( G_EffectIndex( "force/rage2" ), self->playerModel, self->chestBolt, self->s.number, self->currentOrigin, duration, qtrue );
- }
- break;
- case FP_DRAIN:
- if ( self->client->ps.forcePowerLevel[forcePower] > FORCE_LEVEL_1
- && self->client->ps.forceDrainEntityNum >= ENTITYNUM_WORLD )
- {
- duration = overrideAmt;
- overrideAmt = 0;
- //HACK: just using this as a timestamp for when the power started, setting debounce to current time shouldn't adversely affect anything else
- self->client->ps.forcePowerDebounce[forcePower] = level.time;
- }
- else
- {
- duration = 1000;
- }
- self->client->ps.forcePowersActive |= ( 1 << forcePower );
- break;
- case FP_PROTECT:
- switch ( self->client->ps.forcePowerLevel[FP_PROTECT] )
- {
- case FORCE_LEVEL_3:
- duration = 20000;
- break;
- case FORCE_LEVEL_2:
- duration = 15000;
- break;
- case FORCE_LEVEL_1:
- default:
- duration = 10000;
- break;
- }
- self->client->ps.forcePowersActive |= ( 1 << forcePower );
- G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/protect.mp3" );
- self->s.loopSound = G_SoundIndex( "sound/weapons/force/protectloop.wav" );
- break;
- case FP_ABSORB:
- duration = 20000;
- self->client->ps.forcePowersActive |= ( 1 << forcePower );
- G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/absorb.mp3" );
- self->s.loopSound = G_SoundIndex( "sound/weapons/force/absorbloop.wav" );
- break;
- case FP_SEE:
- if ( self->client->ps.forcePowerLevel[FP_SEE] == FORCE_LEVEL_1 )
- {
- duration = 5000;
- }
- else if ( self->client->ps.forcePowerLevel[FP_SEE] == FORCE_LEVEL_2 )
- {
- duration = 10000;
- }
- else// if ( self->client->ps.forcePowerLevel[FP_SEE] == FORCE_LEVEL_3 )
- {
- duration = 20000;
- }
- self->client->ps.forcePowersActive |= ( 1 << forcePower );
- G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/see.mp3" );
- self->s.loopSound = G_SoundIndex( "sound/weapons/force/seeloop.wav" );
- break;
- default:
- break;
- }
- if ( duration )
- {
- self->client->ps.forcePowerDuration[forcePower] = level.time + duration;
- }
- else
- {
- self->client->ps.forcePowerDuration[forcePower] = 0;
- }
-
- WP_ForcePowerDrain( self, forcePower, overrideAmt );
- if ( !self->s.number )
- {
- self->client->sess.missionStats.forceUsed[(int)forcePower]++;
- }
- }
- qboolean WP_ForcePowerAvailable( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
- {
- if ( forcePower == FP_LEVITATION )
- {
- return qtrue;
- }
- int drain = overrideAmt?overrideAmt:forcePowerNeeded[forcePower];
- if ( !drain )
- {
- return qtrue;
- }
- if ( self->client->ps.forcePower < drain )
- {
- //G_AddEvent( self, EV_NOAMMO, 0 );
- return qfalse;
- }
- return qtrue;
- }
- extern void CG_PlayerLockedWeaponSpeech( int jumping );
- extern qboolean Rosh_TwinNearBy( gentity_t *self );
- qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
- {
- if ( !(self->client->ps.forcePowersKnown & ( 1 << forcePower )) )
- {//don't know this power
- return qfalse;
- }
- else if ( self->NPC && (self->NPC->aiFlags&NPCAI_ROSH) )
- {
- if ( ((1<<forcePower)&FORCE_POWERS_ROSH_FROM_TWINS) )
- {//this is a force power we can only use when a twin is near us
- if ( !Rosh_TwinNearBy( self ) )
- {
- return qfalse;
- }
- }
- }
-
- if ( self->client->ps.forcePowerLevel[forcePower] <= 0 )
- {//can't use this power
- return qfalse;
- }
- if( (self->flags&FL_LOCK_PLAYER_WEAPONS) ) // yes this locked weapons check also includes force powers, if we need a separate check later I'll make one
- {
- if ( self->s.number < MAX_CLIENTS )
- {
- CG_PlayerLockedWeaponSpeech( qfalse );
- }
- return qfalse;
- }
- if ( in_camera && self->s.number < MAX_CLIENTS )
- {//player can't turn on force powers duing cinematics
- return qfalse;
- }
- if ( PM_LockedAnim( self->client->ps.torsoAnim ) && self->client->ps.torsoAnimTimer )
- {//no force powers during these special anims
- return qfalse;
- }
- if ( PM_SuperBreakLoseAnim( self->client->ps.torsoAnim )
- || PM_SuperBreakWinAnim( self->client->ps.torsoAnim ) )
- {
- return qfalse;
- }
- if ( (self->client->ps.forcePowersActive & ( 1 << forcePower )) )
- {//already using this power
- return qfalse;
- }
- /*
- if ( !self->client->ps.forcePowerLevel[(int)(forcePower)] )
- {
- return qfalse;
- }
- */
- if ( self->client->NPC_class == CLASS_ATST )
- {//Doh! No force powers in an AT-ST!
- return qfalse;
- }
- Vehicle_t *pVeh = NULL;
- if ( (pVeh = G_IsRidingVehicle( self )) != NULL )
- {//Doh! No force powers when flying a vehicle!
- if ( pVeh->m_pVehicleInfo->numHands > 1 )
- {//if in a two-handed vehicle
- return qfalse;
- }
- }
- if ( self->client->ps.viewEntity > 0 && self->client->ps.viewEntity < ENTITYNUM_WORLD )
- {//Doh! No force powers when controlling an NPC
- return qfalse;
- }
- if ( self->client->ps.eFlags & EF_LOCKED_TO_WEAPON )
- {//Doh! No force powers when in an emplaced gun!
- return qfalse;
- }
-
- if ( self->client->ps.saber[0].singleBladeThrowable//SaberStaff() //using staff
- && !self->client->ps.dualSabers //only 1, in right hand
- && !self->client->ps.saber[0].blade[1].active )//only first blade is on
- {//allow power
- //FIXME: externalize this condition seperately?
- }
- else
- {
- if ( forcePower == FP_SABERTHROW && !self->client->ps.saber[0].throwable )
- {//cannot throw this kind of saber
- return qfalse;
- }
- if ( self->client->ps.saber[0].Active() )
- {
- if ( self->client->ps.saber[0].twoHanded )
- {
- if ( g_saberRestrictForce->integer )
- {
- switch ( forcePower )
- {
- case FP_PUSH:
- case FP_PULL:
- case FP_TELEPATHY:
- case FP_GRIP:
- case FP_LIGHTNING:
- case FP_DRAIN:
- return qfalse;
- break;
- }
- }
- }
- if ( self->client->ps.saber[0].twoHanded || (self->client->ps.dualSabers && self->client->ps.saber[1].Active()) )
- {//this saber requires the use of two hands OR our other hand is using an active saber too
- if ( (self->client->ps.saber[0].forceRestrictions&(1<<forcePower)) )
- {//this power is verboten when using this saber
- return qfalse;
- }
- }
- }
- if ( self->client->ps.dualSabers && self->client->ps.saber[1].Active() )
- {
- if ( g_saberRestrictForce->integer )
- {
- switch ( forcePower )
- {
- case FP_PUSH:
- case FP_PULL:
- case FP_TELEPATHY:
- case FP_GRIP:
- case FP_LIGHTNING:
- case FP_DRAIN:
- return qfalse;
- break;
- }
- }
- if ( (self->client->ps.saber[1].forceRestrictions&(1<<forcePower)) )
- {//this power is verboten when using this saber
- return qfalse;
- }
- }
- }
- return WP_ForcePowerAvailable( self, forcePower, overrideAmt );
- }
- void WP_ForcePowerStop( gentity_t *self, forcePowers_t forcePower )
- {
- gentity_t *gripEnt;
- gentity_t *drainEnt;
- if ( !(self->client->ps.forcePowersActive&(1<<forcePower)) )
- {//umm, wasn't doing it, so...
- return;
- }
- self->client->ps.forcePowersActive &= ~( 1 << forcePower );
- switch( (int)forcePower )
- {
- case FP_HEAL:
- //if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_3 )
- {//wasn't an instant heal and heal is now done
- if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_2 )
- {//if in meditation pose, must come out of it
- //FIXME: BOTH_FORCEHEAL_STOP
- if ( self->client->ps.legsAnim == BOTH_FORCEHEAL_START )
- {
- NPC_SetAnim( self, SETANIM_LEGS, BOTH_FORCEHEAL_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- if ( self->client->ps.torsoAnim == BOTH_FORCEHEAL_START )
- {
- NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEHEAL_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
- self->client->ps.saberBlocked = BLOCKED_NONE;
- }
- }
- WP_StopForceHealEffects( self );
- if (self->health >= self->client->ps.stats[STAT_MAX_HEALTH]/3)
- {
- gi.G2API_ClearSkinGore(self->ghoul2);
- }
- break;
- case FP_LEVITATION:
- self->client->ps.forcePowerDebounce[FP_LEVITATION] = 0;
- break;
- case FP_SPEED:
- if ( !self->s.number )
- {//player using force speed
- if ( g_timescale->value != 1.0 )
- {
- if ( !(self->client->ps.forcePowersActive&(1<<FP_RAGE))||self->client->ps.forcePowerLevel[FP_RAGE] < FORCE_LEVEL_2 )
- {//not slowed down because of force rage
- gi.cvar_set("timescale", "1");
- }
- }
- }
- //FIXME: reset my current anim, keeping current frame, but with proper anim speed
- // otherwise, the anim will continue playing at high speed
- self->s.loopSound = 0;
- break;
- case FP_PUSH:
- break;
- case FP_PULL:
- break;
- case FP_TELEPATHY:
- break;
- case FP_GRIP:
- if ( self->NPC )
- {
- TIMER_Set( self, "gripping", -level.time );
- }
- if ( self->client->ps.forceGripEntityNum < ENTITYNUM_WORLD )
- {
- gripEnt = &g_entities[self->client->ps.forceGripEntityNum];
- if ( gripEnt )
- {
- gripEnt->s.loopSound = 0;
- if ( gripEnt->client )
- {
- gripEnt->client->ps.eFlags &= ~EF_FORCE_GRIPPED;
- if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
- {//sanity-cap the velocity
- float gripVel = VectorNormalize( gripEnt->client->ps.velocity );
- if ( gripVel > 500.0f )
- {
- gripVel = 500.0f;
- }
- VectorScale( gripEnt->client->ps.velocity, gripVel, gripEnt->client->ps.velocity );
- }
- //FIXME: they probably dropped their weapon, should we make them flee? Or should AI handle no-weapon behavior?
- //rww - RAGDOLL_BEGIN
- #ifndef JK2_RAGDOLL_GRIPNOHEALTH
- //rww - RAGDOLL_END
- if ( gripEnt->health > 0 )
- //rww - RAGDOLL_BEGIN
- #endif
- //rww - RAGDOLL_END
- {
- int holdTime = 500;
- if ( gripEnt->health > 0 )
- {
- G_AddEvent( gripEnt, EV_WATER_CLEAR, 0 );
- }
- if ( gripEnt->client->ps.forcePowerDebounce[FP_PUSH] > level.time )
- {//they probably pushed out of it
- holdTime = 0;
- }
- else if ( gripEnt->s.weapon == WP_SABER )
- {//jedi recover faster
- holdTime = self->client->ps.forcePowerLevel[FP_GRIP]*200;
- }
- else
- {
- holdTime = self->client->ps.forcePowerLevel[FP_GRIP]*500;
- }
- //stop the anims soon, keep them locked in place for a bit
- if ( gripEnt->client->ps.torsoAnim == BOTH_CHOKE1 || gripEnt->client->ps.torsoAnim == BOTH_CHOKE3 )
- {//stop choking anim on torso
- if ( gripEnt->client->ps.torsoAnimTimer > holdTime )
- {
- gripEnt->client->ps.torsoAnimTimer = holdTime;
- }
- }
- if ( gripEnt->client->ps.legsAnim == BOTH_CHOKE1 || gripEnt->client->ps.legsAnim == BOTH_CHOKE3 )
- {//stop choking anim on legs
- gripEnt->client->ps.legsAnimTimer = 0;
- if ( holdTime )
- {
- //lock them in place for a bit
- gripEnt->client->ps.pm_time = gripEnt->client->ps.torsoAnimTimer;
- gripEnt->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
- if ( gripEnt->s.number )
- {//NPC
- gripEnt->painDebounceTime = level.time + gripEnt->client->ps.torsoAnimTimer;
- }
- else
- {//player
- gripEnt->aimDebounceTime = level.time + gripEnt->client->ps.torsoAnimTimer;
- }
- }
- }
- if ( gripEnt->NPC )
- {
- if ( !(gripEnt->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
- {//not falling to their death
- gripEnt->NPC->nextBStateThink = level.time + holdTime;
- }
- //if still alive after stopped gripping, let them wake others up
- if ( gripEnt->health > 0 )
- {
- G_AngerAlert( gripEnt );
- }
- }
- }
- }
- else
- {
- gripEnt->s.eFlags &= ~EF_FORCE_GRIPPED;
- if ( gripEnt->s.eType == ET_MISSILE )
- {//continue normal movement
- if ( gripEnt->s.weapon == WP_THERMAL )
- {
- gripEnt->s.pos.trType = TR_INTERPOLATE;
- }
- else
- {
- gripEnt->s.pos.trType = TR_LINEAR;//FIXME: what about gravity-effected projectiles?
- }
- VectorCopy( gripEnt->currentOrigin, gripEnt->s.pos.trBase );
- gripEnt->s.pos.trTime = level.time;
- }
- else
- {//drop it
- gripEnt->e_ThinkFunc = thinkF_G_RunObject;
- gripEnt->nextthink = level.time + FRAMETIME;
- gripEnt->s.pos.trType = TR_GRAVITY;
- VectorCopy( gripEnt->currentOrigin, gripEnt->s.pos.trBase );
- gripEnt->s.pos.trTime = level.time;
- }
- }
- }
- self->s.loopSound = 0;
- self->client->ps.forceGripEntityNum = ENTITYNUM_NONE;
- }
- if ( self->client->ps.torsoAnim == BOTH_FORCEGRIP_HOLD )
- {
- NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCEGRIP_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- break;
- case FP_LIGHTNING:
- if ( self->NPC )
- {
- TIMER_Set( self, "holdLightning", -level.time );
- }
- if ( self->client->ps.torsoAnim == BOTH_FORCELIGHTNING_HOLD
- || self->client->ps.torsoAnim == BOTH_FORCELIGHTNING_START )
- {
- NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- else if ( self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_HOLD
- || self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_START )
- {
- NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_2HANDEDLIGHTNING_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] < FORCE_LEVEL_2 )
- {//don't do it again for 3 seconds, minimum... FIXME: this should be automatic once regeneration is slower (normal)
- self->client->ps.forcePowerDebounce[FP_LIGHTNING] = level.time + 3000;//FIXME: define?
- }
- else
- {//stop the looping sound
- self->client->ps.forcePowerDebounce[FP_LIGHTNING] = level.time + 1000;//FIXME: define?
- self->s.loopSound = 0;
- }
- break;
- case FP_RAGE:
- self->client->ps.forceRageRecoveryTime = level.time + 10000;//recover for 10 seconds
- if ( self->client->ps.forcePowerDuration[FP_RAGE] > level.time )
- {//still had time left, we cut it short
- self->client->ps.forceRageRecoveryTime -= (self->client->ps.forcePowerDuration[FP_RAGE] - level.time);//minus however much time you had left when you cut it short
- }
- if ( !self->s.number )
- {//player using force speed
- if ( g_timescale->value != 1.0 )
- {
- if ( !(self->client->ps.forcePowersActive&(1<<FP_SPEED)) )
- {//not slowed down because of force speed
- gi.cvar_set("timescale", "1");
- }
- }
- }
- //FIXME: reset my current anim, keeping current frame, but with proper anim speed
- // otherwise, the anim will continue playing at high speed
- self->s.loopSound = 0;
- if ( self->NPC )
- {
- Jedi_RageStop( self );
- }
- if ( self->chestBolt != -1 )
- {
- G_StopEffect("force/rage2", self->playerModel, self->chestBolt, self->s.number );
- }
- break;
- case FP_DRAIN:
- if ( self->NPC )
- {
- TIMER_Set( self, "draining", -level.time );
- }
- if ( self->client->ps.forcePowerLevel[FP_DRAIN] < FORCE_LEVEL_2 )
- {//don't do it again for 3 seconds, minimum... FIXME: this should be automatic once regeneration is slower (normal)
- self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 3000;//FIXME: define?
- }
- else
- {//stop the looping sound
- self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 1000;//FIXME: define?
- self->s.loopSound = 0;
- }
- //drop them
- if ( self->client->ps.forceDrainEntityNum < ENTITYNUM_WORLD )
- {
- drainEnt = &g_entities[self->client->ps.forceDrainEntityNum];
- if ( drainEnt )
- {
- if ( drainEnt->client )
- {
- drainEnt->client->ps.eFlags &= ~EF_FORCE_DRAINED;
- //VectorClear( drainEnt->client->ps.velocity );
- if ( drainEnt->health > 0 )
- {
- if ( drainEnt->client->ps.forcePowerDebounce[FP_PUSH] > level.time )
- {//they probably pushed out of it
- }
- else
- {
- //NPC_SetAnim( drainEnt, SETANIM_BOTH, BOTH_HUGGEESTOP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- if ( drainEnt->client->ps.torsoAnim != BOTH_FORCEPUSH )
- {//don't stop the push
- drainEnt->client->ps.torsoAnimTimer = 0;
- }
- drainEnt->client->ps.legsAnimTimer = 0;
- }
- if ( drainEnt->NPC )
- {//if still alive after stopped draining, let them wake others up
- G_AngerAlert( drainEnt );
- }
- }
- else
- {//leave the effect playing on them for a few seconds
- //drainEnt->client->ps.eFlags |= EF_FORCE_DRAINED;
- drainEnt->s.powerups |= ( 1 << PW_DRAINED );
- drainEnt->client->ps.powerups[PW_DRAINED] = level.time + Q_irand( 1000, 4000 );
- }
- }
- }
- self->client->ps.forceDrainEntityNum = ENTITYNUM_NONE;
- }
- if ( self->client->ps.torsoAnim == BOTH_HUGGER1 )
- {//old anim
- NPC_SetAnim( self, SETANIM_BOTH, BOTH_HUGGERSTOP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- else if ( self->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_START
- || self->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_HOLD )
- {//new anim
- NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCE_DRAIN_GRAB_END, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- else if ( self->client->ps.torsoAnim == BOTH_FORCE_DRAIN_HOLD
- || self->client->ps.torsoAnim == BOTH_FORCE_DRAIN_START )
- {
- NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_DRAIN_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- break;
- case FP_PROTECT:
- self->s.loopSound = 0;
- break;
- case FP_ABSORB:
- self->s.loopSound = 0;
- if ( self->client->ps.legsAnim == BOTH_FORCE_ABSORB_START )
- {
- NPC_SetAnim( self, SETANIM_LEGS, BOTH_FORCE_ABSORB_END, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- if ( self->client->ps.torsoAnim == BOTH_FORCE_ABSORB_START )
- {
- NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_ABSORB_END, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- if ( self->client->ps.forcePowerLevel[FP_ABSORB] < FORCE_LEVEL_2 )
- {//was stuck, free us in case we interrupted it or something
- self->client->ps.weaponTime = 0;
- self->client->ps.pm_flags &= ~PMF_TIME_KNOCKBACK;
- self->client->ps.pm_time = 0;
- if ( self->s.number )
- {//NPC
- self->painDebounceTime = 0;
- }
- else
- {//player
- self->aimDebounceTime = 0;
- }
- }
- break;
- case FP_SEE:
- self->s.loopSound = 0;
- break;
- default:
- break;
- }
- }
- void WP_ForceForceThrow( gentity_t *thrower )
- {
- if ( !thrower || !thrower->client )
- {
- return;
- }
- qboolean removePush = qfalse;
- qboolean relock = qfalse;
- if ( !(thrower->client->ps.forcePowersKnown&(1<<FP_PUSH)) )
- {//give them push just for this specific purpose
- thrower->client->ps.forcePowersKnown |= (1<<FP_PUSH);
- thrower->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_1;
- removePush = qtrue;
- }
- if ( thrower->NPC
- && (thrower->NPC->aiFlags&NPCAI_HEAL_ROSH)
- && (thrower->flags&FL_LOCK_PLAYER_WEAPONS) )
- {
- thrower->flags &= ~FL_LOCK_PLAYER_WEAPONS;
- relock = qtrue;
- }
-
- ForceThrow( thrower, qfalse );
- if ( relock )
- {
- thrower->flags |= FL_LOCK_PLAYER_WEAPONS;
- }
- if ( thrower )
- {//take it back off
- thrower->client->ps.forcePowersKnown &= ~(1<<FP_PUSH);
- thrower->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_0;
- }
- }
- extern qboolean PM_ForceJumpingUp( gentity_t *gent );
- static void WP_ForcePowerRun( gentity_t *self, forcePowers_t forcePower, usercmd_t *cmd )
- {
- float speed, newSpeed;
- gentity_t *gripEnt, *drainEnt;
- vec3_t angles, dir, gripOrg, gripEntOrg;
- float dist;
- extern usercmd_t ucmd;
- switch( (int)forcePower )
- {
- case FP_HEAL:
- if ( self->client->ps.forceHealCount >= FP_MaxForceHeal(self) || self->health >= self->client->ps.stats[STAT_MAX_HEALTH] )
- {//fully healed or used up all 25
- if ( !Q3_TaskIDPending( self, TID_CHAN_VOICE ) )
- {
- int index = Q_irand( 1, 4 );
- if ( self->s.number < MAX_CLIENTS )
- {
- G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d_%c.mp3", index, g_sex->string[0] ) );
- }
- else if ( self->NPC )
- {
- if ( self->NPC->blockedSpeechDebounceTime <= level.time )
- {//enough time has passed since our last speech
- if ( Q3_TaskIDPending( self, TID_CHAN_VOICE ) )
- {//not playing a scripted line
- //say "Ahhh...."
- if ( self->NPC->stats.sex == SEX_MALE
- || self->NPC->stats.sex == SEX_NEUTRAL )
- {
- G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d_m.mp3", index ) );
- }
- else//all other sexes use female sounds
- {
- G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d_f.mp3", index ) );
- }
- }
- }
- }
- #ifdef _IMMERSION
- G_Force( self, G_ForceIndex( va( "fffx/weapons/force/heal%d", index ), FF_CHANNEL_WEAPON ) );
- #endif // _IMMERSION
- }
- WP_ForcePowerStop( self, forcePower );
- }
- else if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_3 && ( (cmd->buttons&BUTTON_ATTACK) || (cmd->buttons&BUTTON_ALT_ATTACK) || self->painDebounceTime > level.time || (self->client->ps.weaponTime&&self->client->ps.weapon!=WP_NONE) ) )
- {//attacked or was hit while healing...
- //stop healing
- WP_ForcePowerStop( self, forcePower );
- }
- else if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_2 && ( cmd->rightmove || cmd->forwardmove || cmd->upmove > 0 ) )
- {//moved while healing... FIXME: also, in WP_ForcePowerStart, stop healing if any other force power is used
- //stop healing
- WP_ForcePowerStop( self, forcePower );
- }
- else if ( self->client->ps.forcePowerDebounce[FP_HEAL] < level.time )
- {//time to heal again
- if ( WP_ForcePowerAvailable( self, forcePower, 4 ) )
- {//have available power
- int healInterval = FP_ForceHealInterval( self );
- int healAmount = 1;//hard, normal healing rate
- if ( self->s.number < MAX_CLIENTS )
- {
- if ( g_spskill->integer == 1 )
- {//medium, heal twice as fast
- healAmount *= 2;
- }
- else if ( g_spskill->integer == 0 )
- {//easy, heal 3 times as fast...
- healAmount *= 3;
- }
- }
- if ( self->health + healAmount > self->client->ps.stats[STAT_MAX_HEALTH] )
- {
- healAmount = self->client->ps.stats[STAT_MAX_HEALTH] - self->health;
- }
- self->health += healAmount;
- self->client->ps.forceHealCount += healAmount;
- self->client->ps.forcePowerDebounce[FP_HEAL] = level.time + healInterval;
- WP_ForcePowerDrain( self, forcePower, 4 );
- }
- else
- {//stop
- WP_ForcePowerStop( self, forcePower );
- }
- }
- break;
- case FP_LEVITATION:
- if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE && !self->client->ps.forceJumpZStart )
- {//done with jump
- WP_ForcePowerStop( self, forcePower );
- }
- else
- {
- if ( PM_ForceJumpingUp( self ) )
- {//holding jump in air
- if ( cmd->upmove > 10 )
- {//still trying to go up
- if ( WP_ForcePowerAvailable( self, FP_LEVITATION, 1 ) )
- {
- if ( self->client->ps.forcePowerDebounce[FP_LEVITATION] < level.time )
- {
- WP_ForcePowerDrain( self, FP_LEVITATION, 5 );
- self->client->ps.forcePowerDebounce[FP_LEVITATION] = level.time + 100;
- }
- self->client->ps.forcePowersActive |= ( 1 << FP_LEVITATION );
- self->client->ps.forceJumpCharge = 1;//just used as a flag for the player, cleared when he lands
- }
- else
- {//cut the jump short
- WP_ForcePowerStop( self, forcePower );
- }
- }
- else
- {//cut the jump short
- WP_ForcePowerStop( self, forcePower );
- }
- }
- else
- {
- WP_ForcePowerStop( self, forcePower );
- }
- }
- break;
- case FP_SPEED:
- speed = forceSpeedValue[self->client->ps.forcePowerLevel[FP_SPEED]];
- if ( !self->s.number )
- {//player using force speed
- if ( !(self->client->ps.forcePowersActive&(1<<FP_RAGE))
- || self->client->ps.forcePowerLevel[FP_SPEED] >= self->client->ps.forcePowerLevel[FP_RAGE] )
- {//either not using rage or rage is at a lower level than speed
- gi.cvar_set("timescale", va("%4.2f", speed));
- if ( g_timescale->value > speed )
- {
- newSpeed = g_timescale->value - 0.05;
- if ( newSpeed < speed )
- {
- newSpeed = speed;
- }
- gi.cvar_set("timescale", va("%4.2f", newSpeed));
- }
- }
- }
- break;
- case FP_PUSH:
- break;
- case FP_PULL:
- break;
- case FP_TELEPATHY:
- break;
- case FP_GRIP:
- if ( !WP_ForcePowerAvailable( self, FP_GRIP, 0 )
- || (self->client->ps.forcePowerLevel[FP_GRIP]>FORCE_LEVEL_1&&!self->s.number&&!(cmd->buttons&BUTTON_FORCEGRIP)) )
- {
- WP_ForcePowerStop( self, FP_GRIP );
- return;
- }
- else if ( self->client->ps.forceGripEntityNum >= 0 && self->client->ps.forceGripEntityNum < ENTITYNUM_WORLD )
- {
- gripEnt = &g_entities[self->client->ps.forceGripEntityNum];
- if ( !gripEnt || !gripEnt->inuse )
- {//invalid or freed ent
- WP_ForcePowerStop( self, FP_GRIP );
- return;
- }
- else
- //rww - RAGDOLL_BEGIN
- #ifndef JK2_RAGDOLL_GRIPNOHEALTH
- //rww - RAGDOLL_END
- if ( gripEnt->health <= 0 && gripEnt->takedamage )//FIXME: what about things that never had health or lose takedamage when they die?
- {//either invalid ent, or dead ent
- WP_ForcePowerStop( self, FP_GRIP );
- return;
- }
- else
- //rww - RAGDOLL_BEGIN
- #endif
- //rww - RAGDOLL_END
- if ( self->client->ps.forcePowerLevel[FP_GRIP] == FORCE_LEVEL_1
- && gripEnt->client
- && gripEnt->client->ps.groundEntityNum == ENTITYNUM_NONE
- && gripEnt->client->moveType != MT_FLYSWIM )
- {
- WP_ForcePowerStop( self, FP_GRIP );
- return;
- }
- else if ( gripEnt->client && gripEnt->client->moveType == MT_FLYSWIM && VectorLengthSquared( gripEnt->client->ps.velocity ) > (300*300) )
- {//flying creature broke free
- WP_ForcePowerStop( self, FP_GRIP );
- return;
- }
- else if ( gripEnt->client
- && gripEnt->health>0 //dead dudes don't fly
- && (gripEnt->client->NPC_class == CLASS_BOBAFETT || gripEnt->client->NPC_class == CLASS_ROCKETTROOPER)
- && self->client->ps.forcePowerDebounce[FP_GRIP] < level.time
- && !Q_irand( 0, 3 )
- )
- {//boba fett - fly away!
- gripEnt->client->ps.forceJumpCharge = 0;//so we don't play the force flip anim
- gripEnt->client->ps.velocity[2] = 250;
- gripEnt->client->ps.forceJumpZStart = gripEnt->currentOrigin[2];//so we don't take damage if we land at same height
- gripEnt->client->ps.pm_flags |= PMF_JUMPING;
- G_AddEvent( gripEnt, EV_JUMP, 0 );
- JET_FlyStart( gripEnt );
- WP_ForcePowerStop( self, FP_GRIP );
- return;
- }
- else if ( gripEnt->NPC
- && gripEnt->client
- && gripEnt->client->ps.forcePowersKnown
- && (gripEnt->client->NPC_class==CLASS_REBORN||gripEnt->client->ps.weapon==WP_SABER)
- && !Jedi_CultistDestroyer(gripEnt)
- && !Q_irand( 0, 100-(gripEnt->NPC->stats.evasion*8)-(g_spskill->integer*20) ) )
- {//a jedi who broke free FIXME: maybe have some minimum grip length- a reaction time?
- WP_ForceForceThrow( gripEnt );
- //FIXME: I need to go into some pushed back anim...
- WP_ForcePowerStop( self, FP_GRIP );
- return;
- }
- else if ( PM_SaberInAttack( self->client->ps.saberMove )
- || PM_SaberInStart( self->client->ps.saberMove ) )
- {//started an attack
- WP_ForcePowerStop( self, FP_GRIP );
- return;
- }
- else
- {
- int gripLevel = self->client->ps.forcePowerLevel[FP_GRIP];
- if ( gripEnt->client )
- {
- gripLevel = WP_AbsorbConversion( gripEnt, gripEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_GRIP, self->client->ps.forcePowerLevel[FP_GRIP], forcePowerNeeded[gripLevel] );
- }
- if ( !gripLevel )
- {
- WP_ForcePowerStop( self, forcePower );
- return;
- }
- if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
- {//holding it
- NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEGRIP_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- if ( self->client->ps.torsoAnimTimer < 100 ){//we were already playing this anim, we didn't want to restart it, but we want to hold it for at least 100ms, sooo....
-
- self->client->ps.torsoAnimTimer = 100;
- }
- }
- //get their org
- VectorCopy( self->client->ps.viewangles, angles );
- angles[0] -= 10;
- AngleVectors( angles, dir, NULL, NULL );
- if ( gripEnt->client )
- {//move
- VectorCopy( gripEnt->client->renderInfo.headPoint, gripEntOrg );
- }
- else
- {
- VectorCopy( gripEnt->currentOrigin, gripEntOrg );
- }
- //how far are they
- dist = Distance( self->client->renderInfo.handLPoint, gripEntOrg );
- if ( self->client->ps.forcePowerLevel[FP_GRIP] == FORCE_LEVEL_2 &&
- (!InFront( gripEntOrg, self->client->renderInfo.handLPoint, self->client->ps.viewangles, 0.3f ) ||
- DistanceSquared( gripEntOrg, self->client->renderInfo.handLPoint ) > FORCE_GRIP_DIST_SQUARED))
- {//must face them
- WP_ForcePowerStop( self, FP_GRIP );
- return;
- }
- //check for lift or carry
- if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2
- && (!gripEnt->client || (!gripEnt->message&&!(gripEnt->flags&FL_NO_KNOCKBACK))) )
- {//carry
- //cap dist
- if ( dist > FORCE_GRIP_3_MAX_DIST )
- {
- dist = FORCE_GRIP_3_MAX_DIST;
- }
- else if ( dist < FORCE_GRIP_3_MIN_DIST )
- {
- dist = FORCE_GRIP_3_MIN_DIST;
- }
- VectorMA( self->client->renderInfo.handLPoint, dist, dir, gripOrg );
- }
- else if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
- {//just lift
- VectorCopy( self->client->ps.forceGripOrg, gripOrg );
- }
- else
- {
- VectorCopy( gripEnt->currentOrigin, gripOrg );
- }
- if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
- {//if holding him, make sure there's a clear LOS between my hand and him
- trace_t gripTrace;
- gi.trace( &gripTrace, self->client->renderInfo.handLPoint, NULL, NULL, gripEntOrg, ENTITYNUM_NONE, MASK_FORCE_PUSH );
- if ( gripTrace.startsolid
- || gripTrace.startsolid
- || gripTrace.fraction < 1.0f )
- {//no clear trace, drop them
- WP_ForcePowerStop( self, FP_GRIP );
- return;
- }
- }
- //now move them
- if ( gripEnt->client )
- {
- if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
- {//level 1 just holds them
- VectorSubtract( gripOrg, gripEntOrg, gripEnt->client->ps.velocity );
- if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2
- && (!gripEnt->client || (!gripEnt->message&&!(gripEnt->flags&FL_NO_KNOCKBACK)) ) )
- {//level 2 just lifts them
- float gripDist = VectorNormalize( gripEnt->client->ps.velocity )/3.0f;
- if ( gripDist < 20.0f )
- {
- if (gripDist<2.0f)
- {
- VectorClear(gripEnt->client->ps.velocity);
- }
- else
- {
- VectorScale( gripEnt->client->ps.velocity, (gripDist*gripDist), gripEnt->client->ps.velocity );
- }
- }
- else
- {
- VectorScale( gripEnt->client->ps.velocity, (gripDist*gripDist), gripEnt->client->ps.velocity );
- }
- }
- }
- //stop them from thinking
- gripEnt->client->ps.pm_time = 2000;
- gripEnt->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
- if ( gripEnt->NPC )
- {
- if ( !(gripEnt->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
- {//not falling to their death
- gripEnt->NPC->nextBStateThink = level.time + 2000;
- }
- if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
- {//level 1 just holds them
- vectoangles( dir, angles );
- gripEnt->NPC->desiredYaw = AngleNormalize180(angles[YAW]+180);
- gripEnt->NPC->desiredPitch = -angles[PITCH];
- SaveNPCGlobals();
- SetNPCGlobals( gripEnt );
- NPC_UpdateAngles( qtrue, qtrue );
- gripEnt->NPC->last_ucmd.angles[0] = ucmd.angles[0];
- gripEnt->NPC->last_ucmd.angles[1] = ucmd.angles[1];
- gripEnt->NPC->last_ucmd.angles[2] = ucmd.angles[2];
- RestoreNPCGlobals();
- //FIXME: why does he turn back to his original angles once he dies or is let go?
- }
- }
- else if ( !gripEnt->s.number )
- {
- //vectoangles( dir, angles );
- //gripEnt->client->ps.viewangles[0] = -angles[0];
- //gripEnt->client->ps.viewangles[1] = AngleNormalize180(angles[YAW]+180);
- gripEnt->enemy = self;
- NPC_SetLookTarget( gripEnt, self->s.number, level.time+1000 );
- }
- gripEnt->client->ps.eFlags |= EF_FORCE_GRIPPED;
- //dammit! Make sure that saber stays off!
- WP_DeactivateSaber( gripEnt );
- }
- else
- {//move
- if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
- {//level 1 just holds them
- VectorCopy( gripEnt->currentOrigin, gripEnt->s.pos.trBase );
- VectorSubtract( gripOrg, gripEntOrg, gripEnt->s.pos.trDelta );
- if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2
- && (!gripEnt->client || (!gripEnt->message&&!(gripEnt->flags&FL_NO_KNOCKBACK))) )
- {//level 2 just lifts them
- VectorScale( gripEnt->s.pos.trDelta, 10, gripEnt->s.pos.trDelta );
- }
- gripEnt->s.pos.trType = TR_LINEAR;
- gripEnt->s.pos.trTime = level.time;
- }
- gripEnt->s.eFlags |= EF_FORCE_GRIPPED;
- }
-
- //Shouldn't this be discovered?
- //AddSightEvent( self, gripOrg, 128, AEL_DANGER, 20 );
- AddSightEvent( self, gripOrg, 128, AEL_DISCOVERED, 20 );
- if ( self->client->ps.forcePowerDebounce[FP_GRIP] < level.time )
- {
- //GEntity_PainFunc( gripEnt, self, self, gripOrg, 0, MOD_CRUSH );
- if ( !gripEnt->client
- || gripEnt->client->NPC_class != CLASS_VEHICLE
- || (gripEnt->m_pVehicle
- && gripEnt->m_pVehicle->m_pVehicleInfo
- && gripEnt->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL) )
- {//we don't damage the empty vehicle
- gripEnt->painDebounceTime = 0;
- int gripDmg = forceGripDamage[self->client->ps.forcePowerLevel[FP_GRIP]];
- if ( gripLevel != -1 )
- {
- if ( gripLevel == 1 )
- {
- gripDmg = floor((float)gripDmg/3.0f);
- }
- else //if ( gripLevel == 2 )
- {
- gripDmg = floor((float)gripDmg/1.5f);
- }
- }
- G_Damage( gripEnt, self, self, dir, gripOrg, gripDmg, DAMAGE_NO_ARMOR, MOD_CRUSH );//MOD_???
- }
- if ( gripEnt->s.number )
- {
- if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 )
- {//do damage faster at level 3
- self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + Q_irand( 150, 750 );
- }
- else
- {
- self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + Q_irand( 250, 1000 );
- }
- }
- else
- {//player takes damage faster
- self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + Q_irand( 100, 600 );
- }
- if ( forceGripDamage[self->client->ps.forcePowerLevel[FP_GRIP]] > 0 )
- {//no damage at level 1
- WP_ForcePowerDrain( self, FP_GRIP, 3 );
- }
- if ( self->client->NPC_class == CLASS_KYLE
- && (self->spawnflags&1) )
- {//"Boss" Kyle
- if ( gripEnt->client )
- {
- if ( !Q_irand( 0, 2 ) )
- {//toss him aside!
- vec3_t vRt;
- AngleVectors( self->currentAngles, NULL, vRt, NULL );
- //stop gripping
- TIMER_Set( self, "gripping", -level.time );
- WP_ForcePowerStop( self, FP_GRIP );
- //now toss him
- if ( Q_irand( 0, 1 ) )
- {//throw him to my left
- NPC_SetAnim( self, SETANIM_BOTH, BOTH_TOSS1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- VectorScale( vRt, -1500.0f, gripEnt->client->ps.velocity );
- G_Knockdown( gripEnt, self, vRt, 500, qfalse );
- }
- else
- {//throw him to my right
- NPC_SetAnim( self, SETANIM_BOTH, BOTH_TOSS2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- VectorScale( vRt, 1500.0f, gripEnt->client->ps.velocity );
- G_Knockdown( gripEnt, self, vRt, 500, qfalse );
- }
- //don't do anything for a couple seconds
- self->client->ps.weaponTime = self->client->ps.torsoAnimTimer + 2000;
- self->painDebounceTime = level.time + self->client->ps.weaponTime;
- //stop moving
- VectorClear( self->client->ps.velocity );
- VectorClear( self->client->ps.moveDir );
- return;
- }
- }
- }
- }
- else
- {
- //WP_ForcePowerDrain( self, FP_GRIP, 0 );
- if ( !gripEnt->enemy )
- {
- if ( gripEnt->client
- && gripEnt->client->playerTeam == TEAM_PLAYER
- && self->s.number < MAX_CLIENTS
- && self->client
- && self->client->playerTeam == TEAM_PLAYER )
- {//this shouldn't make allies instantly turn on you, let the damage->pain routine determine how allies should react to this
- }
- else
- {
- G_SetEnemy( gripEnt, self );
- }
- }
- }
- if ( gripEnt->client && gripEnt->health > 0 )
- {
- int anim = BOTH_CHOKE3; //left-handed choke
- if ( gripEnt->client->ps.weapon == WP_NONE || gripEnt->client->ps.weapon == WP_MELEE )
- {
- anim = BOTH_CHOKE1; //two-handed choke
- }
- if ( self->client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 )
- {//still on ground, only set anim on torso
- NPC_SetAnim( gripEnt, SETANIM_TORSO, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- else
- {//in air, set on whole body
- NPC_SetAnim( gripEnt, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- gripEnt->painDebounceTime = level.time + 2000;
- }
- }
- }
- break;
- case FP_LIGHTNING:
- if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_1 )
- {//higher than level 1
- if ( cmd->buttons & BUTTON_FORCE_LIGHTNING )
- {//holding it keeps it going
- self->client->ps.forcePowerDuration[FP_LIGHTNING] = level.time + 500;
- ForceLightningAnim( self );
- }
- }
- if ( !WP_ForcePowerAvailable( self, forcePower, 0 ) )
- {
- WP_ForcePowerStop( self, forcePower );
- }
- else
- {
- ForceShootLightning( self );
- if ( self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING
- || self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_START
- || self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_HOLD
- || self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_RELEASE )
- {//jackin' 'em up, Palpatine-style
- //extra cost
- WP_ForcePowerDrain( self, forcePower, 0 );
- }
- WP_ForcePowerDrain( self, forcePower, 0 );
- }
- break;
- //new Jedi Academy force powers
- case FP_RAGE:
- if (self->health < 1)
- {
- WP_ForcePowerStop(self, forcePower);
- break;
- }
- if (self->client->ps.forceRageDrainTime < level.time)
- {
- int addTime = 400;
- self->health -= 2;
- if (self->client->ps.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_1)
- {
- addTime = 100;
- }
- else if (self->client->ps.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_2)
- {
- addTime = 250;
- }
- else if (self->client->ps.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_3)
- {
- addTime = 500;
- }
- self->client->ps.forceRageDrainTime = level.time + addTime;
- }
- if ( self->health < 1 )
- {
- self->health = 1;
- //WP_ForcePowerStop( self, forcePower );
- }
- else
- {
- self->client->ps.stats[STAT_HEALTH] = self->health;
- speed = forceSpeedValue[self->client->ps.forcePowerLevel[FP_RAGE]-1];
- if ( !self->s.number )
- {//player using force rage
- if ( !(self->client->ps.forcePowersActive&(1<<FP_SPEED))
- || self->client->ps.forcePowerLevel[FP_RAGE] > self->client->ps.forcePowerLevel[FP_SPEED]+1 )
- {//either not using speed or speed is at a lower level than rage
- gi.cvar_set("timescale", va("%4.2f", speed));
- if ( g_timescale->value > speed )
- {
- newSpeed = g_timescale->value - 0.05;
- if ( newSpeed < speed )
- {
- newSpeed = speed;
- }
- gi.cvar_set("timescale", va("%4.2f", newSpeed));
- }
- }
- }
- }
- break;
- case FP_DRAIN:
- if ( cmd->buttons & BUTTON_FORCE_DRAIN )
- {//holding it keeps it going
- self->client->ps.forcePowerDuration[FP_DRAIN] = level.time + 500;
- }
- if ( !WP_ForcePowerAvailable( self, forcePower, 0 ) )
- {//no more force power, stop
- WP_ForcePowerStop( self, forcePower );
- }
- else if ( self->client->ps.forceDrainEntityNum >= 0 && self->client->ps.forceDrainEntityNum < ENTITYNUM_WORLD )
- {//holding someone
- if ( !WP_ForcePowerAvailable( self, FP_DRAIN, 0 )
- || (self->client->ps.forcePowerLevel[FP_DRAIN]>FORCE_LEVEL_1
- && !self->s.number
- && !(cmd->buttons&BUTTON_FORCE_DRAIN)
- && self->client->ps.forcePowerDuration[FP_DRAIN]<level.time) )
- {
- WP_ForcePowerStop( self, FP_DRAIN );
- return;
- }
- else
- {
- drainEnt = &g_entities[self->client->ps.forceDrainEntityNum];
- if ( !drainEnt )
- {//invalid ent
- WP_ForcePowerStop( self, FP_DRAIN );
- return;
- }
- else if ( (drainEnt->health <= 0&&drainEnt->takedamage) )//FIXME: what about things that never had health or lose takedamage when they die?
- {//dead ent
- WP_ForcePowerStop( self, FP_DRAIN );
- return;
- }
- else if ( drainEnt->client && drainEnt->client->moveType == MT_FLYSWIM && VectorLengthSquared( NPC->client->ps.velocity ) > (300*300) )
- {//flying creature broke free
- WP_ForcePowerStop( self, FP_DRAIN );
- return;
- }
- else if ( drainEnt->client
- && drainEnt->health>0 //dead dudes don't fly
- && (drainEnt->client->NPC_class == CLASS_BOBAFETT || drainEnt->client->NPC_class == CLASS_ROCKETTROOPER)
- && self->client->ps.forcePowerDebounce[FP_DRAIN] < level.time
- && !Q_irand( 0, 10 ) )
- {//boba fett - fly away!
- drainEnt->client->ps.forceJumpCharge = 0;//so we don't play the force flip anim
- drainEnt->client->ps.velocity[2] = 250;
- drainEnt->client->ps.forceJumpZStart = drainEnt->currentOrigin[2];//so we don't take damage if we land at same height
- drainEnt->client->ps.pm_flags |= PMF_JUMPING;
- G_AddEvent( drainEnt, EV_JUMP, 0 );
- JET_FlyStart( drainEnt );
- WP_ForcePowerStop( self, FP_DRAIN );
- return;
- }
- else if ( drainEnt->NPC
- && drainEnt->client
- && drainEnt->client->ps.forcePowersKnown
- && (drainEnt->client->NPC_class==CLASS_REBORN||drainEnt->client->ps.weapon==WP_SABER)
- && !Jedi_CultistDestroyer(drainEnt)
- && level.time-(self->client->ps.forcePowerDebounce[FP_DRAIN]>self->client->ps.forcePowerLevel[FP_DRAIN]*500)//at level 1, I always get at least 500ms of drain, at level 3 I get 1500ms
- && !Q_irand( 0, 100-(drainEnt->NPC->stats.evasion*8)-(g_spskill->integer*15) ) )
- {//a jedi who broke free FIXME: maybe have some minimum grip length- a reaction time?
- WP_ForceForceThrow( drainEnt );
- //FIXME: I need to go into some pushed back anim...
- WP_ForcePowerStop( self, FP_DRAIN );
- //can't drain again for 2 seconds
- self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 4000;
- return;
- }
- else
- {
- /*
- int drainLevel = WP_AbsorbConversion( drainEnt, drainEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_DRAIN, self->client->ps.forcePowerLevel[FP_DRAIN], forcePowerNeeded[self->client->ps.forcePowerLevel[FP_DRAIN]] );
- if ( !drainLevel )
- {
- WP_ForcePowerStop( self, forcePower );
- return;
- }
- */
- //NPC_SetAnim( self, SETANIM_BOTH, BOTH_HUGGER1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- if ( self->client->ps.torsoAnim != BOTH_FORCE_DRAIN_GRAB_START
- || !self->client->ps.torsoAnimTimer )
- {
- NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCE_DRAIN_GRAB_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- if ( self->handLBolt != -1 )
- {
- G_PlayEffect( G_EffectIndex( "force/drain_hand" ), self->playerModel, self->handLBolt, self->s.number, self->currentOrigin, 200, qtrue );
- }
- if ( self->handRBolt != -1 )
- {
- G_PlayEffect( G_EffectIndex( "force/drain_hand" ), self->playerModel, self->handRBolt, self->s.number, self->currentOrigin, 200, qtrue );
- }
-
- //how far are they
- dist = Distance( self->client->renderInfo.eyePoint, drainEnt->currentOrigin );
- if ( DistanceSquared( drainEnt->currentOrigin, self->currentOrigin ) > FORCE_DRAIN_DIST_SQUARED )
- {//must be close, got away somehow!
- WP_ForcePowerStop( self, FP_DRAIN );
- return;
- }
- //keep my saber off!
- WP_DeactivateSaber( self, qtrue );
- if ( drainEnt->client )
- {
- //now move them
- VectorCopy( self->client->ps.viewangles, angles );
- angles[0] = 0;
- AngleVectors( angles, dir, NULL, NULL );
- /*
- VectorMA( self->currentOrigin, self->maxs[0], dir, drainEnt->client->ps.forceDrainOrg );
- trace_t trace;
- gi.trace( &trace, drainEnt->currentOrigin, drainEnt->mins, drainEnt->maxs, drainEnt->client->ps.forceDrainOrg, drainEnt->s.number, drainEnt->clipmask );
- if ( !trace.startsolid && !trace.allsolid )
- {
- G_SetOrigin( drainEnt, trace.endpos );
- gi.linkentity( drainEnt );
- VectorClear( drainEnt->client->ps.velocity );
- }
- VectorMA( self->currentOrigin, self->maxs[0]*0.5f, dir, drainEnt->client->ps.forceDrainOrg );
- */
- //stop them from thinking
- drainEnt->client->ps.pm_time = 2000;
- drainEnt->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
- if ( drainEnt->NPC )
- {
- if ( !(drainEnt->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
- {//not falling to their death
- drainEnt->NPC->nextBStateThink = level.time + 2000;
- }
- vectoangles( dir, angles );
- drainEnt->NPC->desiredYaw = AngleNormalize180(angles[YAW]+180);
- drainEnt->NPC->desiredPitch = -angles[PITCH];
- SaveNPCGlobals();
- SetNPCGlobals( drainEnt );
- NPC_UpdateAngles( qtrue, qtrue );
- drainEnt->NPC->last_ucmd.angles[0] = ucmd.angles[0];
- drainEnt->NPC->last_ucmd.angles[1] = ucmd.angles[1];
- drainEnt->NPC->last_ucmd.angles[2] = ucmd.angles[2];
- RestoreNPCGlobals();
- //FIXME: why does he turn back to his original angles once he dies or is let go?
- }
- else if ( !drainEnt->s.number )
- {
- drainEnt->enemy = self;
- NPC_SetLookTarget( drainEnt, self->s.number, level.time+1000 );
- }
- drainEnt->client->ps.eFlags |= EF_FORCE_DRAINED;
- //dammit! Make sure that saber stays off!
- WP_DeactivateSaber( drainEnt, qtrue );
- }
- //Shouldn't this be discovered?
- AddSightEvent( self, drainEnt->currentOrigin, 128, AEL_DISCOVERED, 20 );
- if ( self->client->ps.forcePowerDebounce[FP_DRAIN] < level.time )
- {
- int drainLevel = WP_AbsorbConversion( drainEnt, drainEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_DRAIN, self->client->ps.forcePowerLevel[FP_DRAIN], forcePowerNeeded[self->client->ps.forcePowerLevel[FP_DRAIN]] );
- if ( (drainLevel && drainLevel == -1)
- || Q_irand( drainLevel, 3 ) < 3 )
- {//the drain is being absorbed
- ForceDrainEnt( self, drainEnt );
- }
- WP_ForcePowerDrain( self, FP_DRAIN, 3 );
- }
- else
- {
- if ( !Q_irand( 0, 4 ) )
- {
- WP_ForcePowerDrain( self, FP_DRAIN, 1 );
- }
- if ( !drainEnt->enemy )
- {
- G_SetEnemy( drainEnt, self );
- }
- }
- if ( drainEnt->health > 0 )
- {//still alive
- //NPC_SetAnim( drainEnt, SETANIM_BOTH, BOTH_HUGGEE1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- NPC_SetAnim( drainEnt, SETANIM_BOTH, BOTH_FORCE_DRAIN_GRABBED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- }
- }
- }
- else if ( self->client->ps.forcePowerLevel[forcePower] > FORCE_LEVEL_1 )
- {//regular distance-drain
- if ( cmd->buttons & BUTTON_FORCE_DRAIN )
- {//holding it keeps it going
- self->client->ps.forcePowerDuration[FP_DRAIN] = level.time + 500;
- if ( self->client->ps.torsoAnim == BOTH_FORCE_DRAIN_START )
- {
- if ( !self->client->ps.torsoAnimTimer )
- {
- NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_DRAIN_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- else
- {
- NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_DRAIN_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- }
- else
- {
- NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_DRAIN_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- }
- if ( !WP_ForcePowerAvailable( self, forcePower, 0 ) )
- {
- WP_ForcePowerStop( self, forcePower );
- }
- else
- {
- ForceShootDrain( self );
- }
- }
- break;
- case FP_PROTECT:
- break;
- case FP_ABSORB:
- break;
- case FP_SEE:
- break;
- default:
- break;
- }
- }
- void WP_CheckForcedPowers( gentity_t *self, usercmd_t *ucmd )
- {
- for ( int forcePower = FP_FIRST; forcePower < NUM_FORCE_POWERS; forcePower++ )
- {
- if ( (self->client->ps.forcePowersForced&(1<<forcePower)) )
- {
- switch ( forcePower )
- {
- case FP_HEAL:
- ForceHeal( self );
- //do only once
- self->client->ps.forcePowersForced &= ~(1<<forcePower);
- break;
- case FP_LEVITATION:
- //nothing
- break;
- case FP_SPEED:
- ForceSpeed( self );
- //do only once
- self->client->ps.forcePowersForced &= ~(1<<forcePower);
- break;
- case FP_PUSH:
- ForceThrow( self, qfalse );
- //do only once
- self->client->ps.forcePowersForced &= ~(1<<forcePower);
- break;
- case FP_PULL:
- ForceThrow( self, qtrue );
- //do only once
- self->client->ps.forcePowersForced &= ~(1<<forcePower);
- break;
- case FP_TELEPATHY:
- //FIXME: target at enemy?
- ForceTelepathy( self );
- //do only once
- self->client->ps.forcePowersForced &= ~(1<<forcePower);
- break;
- case FP_GRIP:
- ucmd->buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK|BUTTON_FORCE_FOCUS|BUTTON_FORCE_DRAIN|BUTTON_FORCE_LIGHTNING);
- ucmd->buttons |= BUTTON_FORCEGRIP;
- //holds until cleared
- break;
- case FP_LIGHTNING:
- ucmd->buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK|BUTTON_FORCE_FOCUS|BUTTON_FORCEGRIP|BUTTON_FORCE_DRAIN);
- ucmd->buttons |= BUTTON_FORCE_LIGHTNING;
- //holds until cleared
- break;
- case FP_SABERTHROW:
- ucmd->buttons &= ~(BUTTON_ATTACK|BUTTON_FORCE_FOCUS|BUTTON_FORCEGRIP|BUTTON_FORCE_DRAIN|BUTTON_FORCE_LIGHTNING);
- ucmd->buttons |= BUTTON_ALT_ATTACK;
- //holds until cleared?
- break;
- case FP_SABER_DEFENSE:
- //nothing
- break;
- case FP_SABER_OFFENSE:
- //nothing
- break;
- case FP_RAGE:
- ForceRage( self );
- //do only once
- self->client->ps.forcePowersForced &= ~(1<<forcePower);
- break;
- case FP_PROTECT:
- ForceProtect( self );
- //do only once
- self->client->ps.forcePowersForced &= ~(1<<forcePower);
- break;
- case FP_ABSORB:
- ForceAbsorb( self );
- //do only once
- self->client->ps.forcePowersForced &= ~(1<<forcePower);
- break;
- case FP_DRAIN:
- ucmd->buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK|BUTTON_FORCE_FOCUS|BUTTON_FORCEGRIP|BUTTON_FORCE_LIGHTNING);
- ucmd->buttons |= BUTTON_FORCE_DRAIN;
- //holds until cleared
- break;
- case FP_SEE:
- //nothing
- break;
- }
- }
- }
- }
- void WP_ForcePowersUpdate( gentity_t *self, usercmd_t *ucmd )
- {
- qboolean usingForce = qfalse;
- int i;
- //see if any force powers are running
- if ( !self )
- {
- return;
- }
- if ( !self->client )
- {
- return;
- }
- if ( self->health <= 0 )
- {//if dead, deactivate any active force powers
- for ( i = 0; i < NUM_FORCE_POWERS; i++ )
- {
- if ( self->client->ps.forcePowerDuration[i] || (self->client->ps.forcePowersActive&( 1 << i )) )
- {
- WP_ForcePowerStop( self, (forcePowers_t)i );
- self->client->ps.forcePowerDuration[i] = 0;
- }
- }
- return;
- }
- WP_CheckForcedPowers( self, ucmd );
- if ( !self->s.number )
- {//player uses different kind of force-jump
- }
- else
- {
- /*
- if ( ucmd->buttons & BUTTON_FORCEJUMP )
- {//just charging up
- ForceJumpCharge( self, ucmd );
- }
- else */
- if ( self->client->ps.forceJumpCharge )
- {//let go of charge button, have charge
- //if leave the ground by some other means, cancel the force jump so we don't suddenly jump when we land.
- if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE
- && !PM_SwimmingAnim( self->client->ps.legsAnim ) )
- {//FIXME: stop sound?
- //self->client->ps.forceJumpCharge = 0;
- //FIXME: actually, we want this to still be cleared... don't clear it if the button isn't being pressed, but clear it if not holding button and not on ground.
- }
- else
- {//still on ground, so jump
- ForceJump( self, ucmd );
- return;
- }
- }
- }
- if ( ucmd->buttons & BUTTON_FORCEGRIP )
- {
- ForceGrip( self );
- }
- if ( ucmd->buttons & BUTTON_FORCE_LIGHTNING )
- {
- ForceLightning( self );
- }
- if ( ucmd->buttons & BUTTON_FORCE_DRAIN )
- {
- if ( !ForceDrain2( self ) )
- {//can't drain-grip someone right in front
- if ( self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_1 )
- {//try ranged
- ForceDrain( self, qtrue );
- }
- }
- }
- for ( i = 0; i < NUM_FORCE_POWERS; i++ )
- {
- if ( self->client->ps.forcePowerDuration[i] )
- {
- if ( self->client->ps.forcePowerDuration[i] < level.time )
- {
- if ( (self->client->ps.forcePowersActive&( 1 << i )) )
- {//turn it off
- WP_ForcePowerStop( self, (forcePowers_t)i );
- }
- self->client->ps.forcePowerDuration[i] = 0;
- }
- }
- if ( (self->client->ps.forcePowersActive&( 1 << i )) )
- {
- usingForce = qtrue;
- WP_ForcePowerRun( self, (forcePowers_t)i, ucmd );
- }
- }
- if ( self->client->ps.saberInFlight )
- {//don't regen force power while throwing saber
- if ( self->client->ps.saberEntityNum < ENTITYNUM_NONE && self->client->ps.saberEntityNum > 0 )//player is 0
- {//
- if ( &g_entities[self->client->ps.saberEntityNum] != NULL && g_entities[self->client->ps.saberEntityNum].s.pos.trType == TR_LINEAR )
- {//fell to the ground and we're trying to pull it back
- usingForce = qtrue;
- }
- }
- }
- if ( PM_ForceUsingSaberAnim( self->client->ps.torsoAnim ) )
- {
- usingForce = qtrue;
- }
- if ( !usingForce )
- {//when not using the force, regenerate at 10 points per second
- if ( self->client->ps.forcePowerRegenDebounceTime < level.time )
- {
- WP_ForcePowerRegenerate( self, self->client->ps.forcePowerRegenAmount );
- self->client->ps.forcePowerRegenDebounceTime = level.time + self->client->ps.forcePowerRegenRate;
- if ( self->client->ps.forceRageRecoveryTime >= level.time )
- {//regen half as fast
- self->client->ps.forcePowerRegenDebounceTime += self->client->ps.forcePowerRegenRate;
- }
- }
- }
- }
- void WP_InitForcePowers( gentity_t *ent )
- {
- if ( !ent || !ent->client )
- {
- return;
- }
- if ( !ent->client->ps.forcePowerMax )
- {
- ent->client->ps.forcePowerMax = FORCE_POWER_MAX;
- }
- if ( !ent->client->ps.forcePowerRegenRate )
- {
- ent->client->ps.forcePowerRegenRate = 100;
- }
- ent->client->ps.forcePower = ent->client->ps.forcePowerMax;
- ent->client->ps.forcePowerRegenDebounceTime = 0;
- ent->client->ps.forceGripEntityNum = ent->client->ps.forceDrainEntityNum = ent->client->ps.pullAttackEntNum = ENTITYNUM_NONE;
- ent->client->ps.forceRageRecoveryTime = 0;
- ent->client->ps.forceDrainTime = 0;
- ent->client->ps.pullAttackTime = 0;
- if ( ent->s.number < MAX_CLIENTS )
- {//player
- if ( !g_cheats->integer )//devmaps give you all the FP
- {
- ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_1;
- ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_1;
- }
- else
- {
- ent->client->ps.forcePowersKnown = ( 1 << FP_HEAL )|( 1 << FP_LEVITATION )|( 1 << FP_SPEED )|( 1 << FP_PUSH )|( 1 << FP_PULL )|( 1 << FP_TELEPATHY )|( 1 << FP_GRIP )|( 1 << FP_LIGHTNING)|( 1 << FP_SABERTHROW)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE )|( 1<< FP_RAGE )|( 1<< FP_DRAIN )|( 1<< FP_PROTECT )|( 1<< FP_ABSORB )|( 1<< FP_SEE );
- ent->client->ps.forcePowerLevel[FP_HEAL] = FORCE_LEVEL_2;
- ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_2;
- ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_1;
- ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_1;
- ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_2;
- ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_2;
- ent->client->ps.forcePowerLevel[FP_LIGHTNING] = FORCE_LEVEL_1;
- ent->client->ps.forcePowerLevel[FP_TELEPATHY] = FORCE_LEVEL_2;
- ent->client->ps.forcePowerLevel[FP_RAGE] = FORCE_LEVEL_1;
- ent->client->ps.forcePowerLevel[FP_PROTECT] = FORCE_LEVEL_1;
- ent->client->ps.forcePowerLevel[FP_ABSORB] = FORCE_LEVEL_1;
- ent->client->ps.forcePowerLevel[FP_DRAIN] = FORCE_LEVEL_1;
- ent->client->ps.forcePowerLevel[FP_SEE] = FORCE_LEVEL_1;
- ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
- ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_3;
- ent->client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_2;
- }
- }
- }
- // Xbox Saber cycling cheat:
- bool Cheat_ChangeSaber( void )
- {
- // Need to extern these. We can't #include qcommon.h because some fuckwit
- // used LS_NONE to mean both a lightstyle and a sabermove.
- extern char *Cvar_VariableString( const char *var_name );
- extern void Cbuf_ExecuteText( int exec_when, const char *text );
- // First: If we have NO saber, don't do the cheat! (t2_dpred) Also, if
- // client is invalid, we're probably in the UI, so don't do anything:
- if( !(g_entities[0].client) ||
- !(g_entities[0].client->ps.stats[STAT_WEAPONS] & (1 << WP_SABER)) )
- return false;
- // Saved copies of saber vars, so we use the same model when we cycle
- static char singleType1[32] = { 0 };
- static char singleType2[32] = { 0 };
- static char staffType[32] = { 0 };
- // Get current saber information
- char *s1 = Cvar_VariableString( "g_saber" );
- char *s2 = Cvar_VariableString( "g_saber2" );
- if( g_entities[0].client->ps.saber[0].type == SABER_STAFF )
- {
- // We're using a staff right now. Copy type out:
- Q_strncpyz( staffType, s1, sizeof(staffType), qtrue );
- // If we don't have a remembered single type, put in default
- if( !singleType1[0] )
- Q_strncpyz( singleType1, "single_1", sizeof(singleType1), qtrue );
- // Switch to single saber, all three styles known:
- g_entities[0].client->ps.saberStylesKnown = (1 << SS_FAST) | (1 << SS_MEDIUM) | (1 << SS_STRONG);
- Cbuf_ExecuteText( EXEC_NOW, va("saber %s\n", singleType1) );
- }
- else if( g_entities[0].client->ps.dualSabers )
- {
- // We're using two sabers right now. Copy types:
- Q_strncpyz( singleType1, s1, sizeof(singleType1), qtrue );
- Q_strncpyz( singleType2, s2, sizeof(singleType2), qtrue );
- // If we don't have a remembered staff type, put in default:
- if( !staffType[0] )
- Q_strncpyz( staffType, "dual_1", sizeof(staffType), qtrue );
- // Switch to staff:
- g_entities[0].client->ps.saberStylesKnown = (1 << SS_STAFF);
- Cbuf_ExecuteText( EXEC_NOW, va("saber %s\n", staffType) );
- }
- else
- {
- // We're using one saber right now. Copy type:
- Q_strncpyz( singleType1, s1, sizeof(singleType1), qtrue );
- // If we don't have a remembered second type, copy first as default:
- if( !singleType2[0] )
- Q_strncpyz( singleType2, singleType1, sizeof(singleType2), qtrue );
- // Switch to dual:
- g_entities[0].client->ps.saberStylesKnown = (1 << SS_DUAL);
- Cbuf_ExecuteText( EXEC_NOW, va("saber %s %s\n", singleType1, singleType2) );
- }
- return true;
- }
|