@infernus/map-loader
Version:
A tool for obj conversion, parsing, and loading
9 lines (8 loc) • 10.6 kB
JavaScript
import{ObjectMaterialAlignmentEnum as F,ObjectMaterialTextSizeEnum as p,PlayerEvent as te,DynamicObject as $,Player as re,StreamerDistances as B,Streamer as X,StreamerItemTypes as k,E_STREAMER as H,GameMode as ne}from"@infernus/core";import{createRequire as V}from"node:module";import A from"node:assert";import h from"node:fs";import Y from"node:path";import oe from"node:readline";const E={removedBuilding:[],loadedMaps:new Map},U=-1,R="putafuckingspacehere",q="_",G="~N~",P={OBJECT_MATERIAL_SIZE_32x32:p._32x32,OBJECT_MATERIAL_SIZE_64x32:p._64x32,OBJECT_MATERIAL_SIZE_64x64:p._64x64,OBJECT_MATERIAL_SIZE_128x32:p._128x32,OBJECT_MATERIAL_SIZE_128x64:p._128x64,OBJECT_MATERIAL_SIZE_128x128:p._128x128,OBJECT_MATERIAL_SIZE_256x32:p._256x32,OBJECT_MATERIAL_SIZE_256x64:p._256x64,OBJECT_MATERIAL_SIZE_256x128:p._256x128,OBJECT_MATERIAL_SIZE_256x256:p._256x256,OBJECT_MATERIAL_SIZE_512x64:p._512x64,OBJECT_MATERIAL_SIZE_512x128:p._512x128,OBJECT_MATERIAL_SIZE_512x256:p._512x256,OBJECT_MATERIAL_SIZE_512x512:p._512x512,OBJECT_MATERIAL_TEXT_ALIGN_LEFT:F.LEFT,OBJECT_MATERIAL_TEXT_ALIGN_CENTER:F.CENTER,OBJECT_MATERIAL_TEXT_ALIGN_RIGHT:F.RIGHT};class T extends Error{constructor(e){super(`[MapLoader] ${JSON.stringify(e)}`),this.context=e}}function w(t,e,o,a){if(a<o)throw new T({type:t,msg:`Expected ${o} elements, got ${a}`,details:JSON.stringify(e)})}te.onConnect(({player:t,next:e})=>(E.removedBuilding.flat().forEach(o=>{t.removeBuilding(...o)}),e()));function ae(t){return w("removeBuilding",t,6,t.length),t.slice(1).map(e=>{const o=+e;return A(!Number.isNaN(o)),o})}function ie(t,e){w("objParser",t,8,t.length);const o=t.map(j=>{const J=Number(j);if(Number.isNaN(J))throw new T({msg:`invalid value ${j}`});return J}),[a,l,i,c,s,n,d,u,m,x,r,_,I,b]=o,{xOffset:C=0,yOffset:O=0,zOffset:N=0,playerId:z,worldId:f,interiorId:y,areaId:L,priority:M,streamDistance:g,drawDistance:D,overwrite:S}=e;return new $({modelId:a,x:l+C,y:i+O,z:c+N,rx:s,ry:n,rz:d,worldId:S?f:u??f,areaId:S?L:I??L,interiorId:S?y:m??y,streamDistance:S?g:r??g,drawDistance:S?D:_??D,playerId:z,priority:S?M:b??M}).create()}function le(t,e,o){w("materialTextParser",e,10,e.length);let[a,l,i,c,s,n,d,u]=e.slice(1);A(!Number.isNaN(+a)),A(!Number.isNaN(+l)),A(!Number.isNaN(+s)),A(!Number.isNaN(+n)),A(!Number.isNaN(+u)),i=i.replaceAll(q," ");const m=e.slice(9).join(" ").replaceAll(G,`
`).replaceAll(R," ");return t.setMaterialText(o.charset,+a,m,+l,i,+c,+s,+n,+d,+u)}function ce(t,e){w("materialParser",e,6,e.length);let[o,a,l,i,c]=e.slice(1);return A(!Number.isNaN(+o)),A(!Number.isNaN(+a)),A(!Number.isNaN(+c)),i.includes(R)&&(i=i.replaceAll(R," ")),t.setMaterial(+o,+a,l,i,+c)}function W(t){const e=h.createReadStream(t);return oe.createInterface({input:e,crlfDelay:1/0})}function se(){let t=-1;for(;t===-1;){const e=Math.floor(Math.random()*16777215);E.loadedMaps.has(e)||(t=e)}return t}function K(t,e,o){return t==="16644"&&e==="a51_detailstuff"&&o==="roucghstonebrtb"?{modelId:18888,textureLib:"forcefields",textureName:"white"}:{modelId:+t,textureLib:e,textureName:o}}function v(t,e=2){return"0x"+(t>>>0).toString(16).padStart(e,"0")}function Z(t){return t.split(/,(?=(?:[^"]*"[^"]*")*[^"]*$)/).map(e=>e.trim().replace(/^"|"$/g,""))}function de(t,e){const o=Y.resolve(t),a=Y.resolve(e);return process.platform==="win32"?o.toLowerCase()===a.toLowerCase():o===a}async function me(t){let e=0,o=null;const a=[],l=[];function i(s){if(!s.length)return;e++;const n=s.split(" "),d=n[0];if(d==="rmv")a.push(ae(n));else if(d==="mat"){if(!o)throw new T({msg:"parseLine: no object target, cannot material"});ce(o,n)}else if(d==="txt"){if(!o)throw new T({msg:"parseLine: no object target, cannot materialText"});le(o,n,t)}else o=ie(n,t),l.push(o)}let c=null;try{const{source:s}=t;if(typeof s=="string"){c=W(s);for await(const d of c)i(d.trim())}else{let d=!1;for(;!d;){const u=await s(()=>{d=!0});if(!u.length){d=!0;break}for(const m of u)i(m.trim())}}let n=-1;return a.length&&(n=E.removedBuilding.push(a)-1),re.getInstances().forEach(d=>{if(a.forEach(u=>{d.removeBuilding(...u)}),samp.defined&&samp.defined._colandreas_included)try{const u=(typeof global.require<"u"?global.require:V(import.meta.url))("@infernus/colandreas");a.forEach(m=>{u.removeBuilding(...m)})}catch{}}),{objects:l,removedBuilding:a,removedBuildingIdx:n}}catch(s){throw l.forEach(n=>{n.isValid()&&n.destroy()}),l.length=0,a.length=0,s instanceof T?(s.context.msg=`lineNo: ${e} ${s.context.msg}`,new T(s.context)):s}finally{c&&c.close()}}function ue(t,e){let o="CreateObject";t[2]?o="CreateDynamicObjectEx":t[1]&&(o="CreateDynamicObject");const a=Z(e.replace(/^.*Create(Dynamic)?Object(Ex)?\(|\);\s*\/?\*?\w*.*$/g,""));if(w("objConverter",a,7,a.length),o==="CreateObject"){const[l,i,c,s,n,d,u,m]=a;let x=+m;return(!m||m==="STREAMER_OBJECT_DD")&&(x=B.OBJECT_DD),{modelId:+l,x:+i,y:+c,z:+s,rX:+n,rY:+d,rZ:+u,worldId:-1,interiorId:-1,playerId:-1,streamerDistance:B.OBJECT_SD,drawDistance:x,areaId:-1,priority:0}}else if(o==="CreateDynamicObject"){const[l,i,c,s,n,d,u,m=-1,x=-1,r=-1,_=B.OBJECT_SD,I,b=-1,C=0]=a;let O=+I;(!I||I==="STREAMER_OBJECT_DD")&&(O=B.OBJECT_DD);let N=+_;return(!_||_==="STREAMER_OBJECT_SD")&&(N=B.OBJECT_DD),{modelId:+l,x:+i,y:+c,z:+s,rX:+n,rY:+d,rZ:+u,worldId:+m,interiorId:+x,playerId:+r,streamerDistance:N,drawDistance:O,areaId:+b,priority:+C}}else{const[l,i,c,s,n,d,u,m,x,r,_,I,b,C=0]=a;let O=+x;(!x||x==="STREAMER_OBJECT_DD")&&(O=B.OBJECT_DD);let N=+m;return(!m||m==="STREAMER_OBJECT_SD")&&(N=B.OBJECT_DD),{modelId:+l,x:+i,y:+c,z:+s,rX:+n,rY:+d,rZ:+u,worldId:-1,interiorId:-1,playerId:-1,streamerDistance:N,drawDistance:O,areaId:-1,priority:+C}}}function fe(t){const e=Z(t.replace(/^.*Set(Dynamic)?ObjectMaterial\(|\);\s*\/?\*?\w*.*$/g,""));w("materialConverter",e,6,e.length);let[o,a,l,i,c,s]=e;const n=K(l,i,c);let d=n.modelId;return i=n.textureLib,c=n.textureName,c.includes(" ")&&(c=c.replaceAll(" ",R)),{index:+a,modelId:d,textureLib:i,textureName:c,color:+s}}function xe(t,e){let o="SetObjectMaterialText";t[1]&&(o="SetDynamicObjectMaterialText");const a=Z(e.replace(/^.*Set(Dynamic)?ObjectMaterialText\(|\);\s*\/?\*?\w*.*$/g,""));w("materialTextConverter",a,9,a.length);let[l,i,c,s,n,d,u,m,x,r=F.LEFT]=a;o==="SetObjectMaterialText"&&([i,c]=[c,i]),c=c.replace(/\n|\\n/g,G),n=n.replaceAll(" ",q);let _=+s;s in P&&(_=P[s]);let I=+r;return r in P&&(I=P[r]),{materialIndex:+i,text:c,materialSize:_,fontFace:n,fontSize:+d,bold:+!!u,fontColor:m,backColor:x,textAlignment:I}}function Ee(t){const e=Z(t.replace(/^.*RemoveBuildingForPlayer\(|\);\s*\/?\*?\w*.*$/g,""));w("removeBuildingConverter",e,6,e.length);const[o,a,l,i,c,s]=e;return{modelId:+a,centerX:+l,centerY:+i,centerZ:+c,radius:+s}}async function pe(t){const{input:e,output:o,removeOutput:a}=t;A(!de(e,o));const l=W(e);a!==!1&&h.existsSync(o)&&h.unlinkSync(o);const i=h.createWriteStream(o,{flags:"a"}),c=[/^.*Create(Dynamic)?Object(Ex)?\(/,/^.*Set(Dynamic)?ObjectMaterial\(/,/^.*Set(Dynamic)?ObjectMaterialText\(/,/^.*RemoveBuildingForPlayer\(/];let s=0;try{for await(const n of l){s++;let d=null;const u=c.findIndex(r=>(d=n.match(r),d));if(u===-1)continue;let m=[];if(u===0){const r=ue(d,n);m=[r.modelId,r.x.toFixed(6),r.y.toFixed(6),r.z.toFixed(6),r.rX.toFixed(6),r.rY.toFixed(6),r.rZ.toFixed(6),r.worldId,r.interiorId,r.playerId,r.streamerDistance.toFixed(6),r.drawDistance.toFixed(6),r.areaId,r.priority]}else if(u===1){const r=fe(n);m=["mat",r.index,r.modelId,r.textureLib,r.textureName,v(r.color,8)]}else if(u===2){const r=xe(d,n);m=["txt",r.materialIndex,r.materialSize,r.fontFace,r.fontSize,+r.bold,v(+r.fontColor),v(+r.backColor,8),r.textAlignment,r.text]}else if(u===3){const r=Ee(n);m=["rmv",r.modelId,r.centerX.toFixed(6),r.centerY.toFixed(6),r.centerZ.toFixed(6),r.radius.toFixed(2)]}if(m.some(r=>Number.isNaN(r)||r==="NaN"))throw new T({msg:"Invalid Value Converted: NaN",details:n});const x=m.join(" ")+`
`;i.write(x)||await new Promise(r=>i.once("drain",r))}}catch(n){throw n instanceof T?(n.context.msg=`lineNo: ${s} ${n.context.msg}`,new T(n.context)):n}finally{await new Promise((n,d)=>{i.once("error",d),i.once("finish",n),i.once("close",n),i.end()})}}async function _e(t){const{output:e,removeOutput:o}=t;o!==!1&&h.existsSync(e)&&h.unlinkSync(e);const a=h.createWriteStream(e,{flags:"a"});try{const l=X.getUpperBound(k.OBJECT);for(let i=1;i<=l;i++){if(!$.isValid(i))continue;const c=X.getIntData(k.OBJECT,i,H.MODEL_ID),s=X.getIntData(k.OBJECT,i,H.WORLD_ID),n=new $(i),{x:d,y:u,z:m}=n.getPos(),{rx:x,ry:r,rz:_}=n.getRot(),I=[c,d.toFixed(6),u.toFixed(6),m.toFixed(6),x.toFixed(6),r.toFixed(6),_.toFixed(6),s].join(" ")+`
`,b=[],C=[];for(let f=0;f<15;f++)if(n.isMaterialUsed(f)){let{modelId:y,materialColor:L,txdName:M,textureName:g}=n.getMaterial(f);const D=K(y,M,g);y=D.modelId,M=D.textureLib,g=D.textureName,g.includes(" ")&&(g=g.replaceAll(" ",R)),b.push(["mat",f,y,M,g,v(L,8)].join(" "))}for(let f=0;f<15;f++)if(n.isMaterialTextUsed(f)){let{text:y,materialSize:L,fontFace:M,fontSize:g,bold:D,fontColor:S,backColor:j,textAlignment:J}=n.getMaterialText(f);y=y.replace(/\n|\\n/g,G),M=M.replaceAll(" ",q),C.push(["txt",f,L,M,g,D,v(S),v(j,8),J,y].join(" "))}const O=b.join(`
`),N=C.join(`
`),z=I+O+(b.length?`
`:"")+N+(C.length?`
`:"");a.write(z)||await new Promise(f=>a.once("drain",f))}}finally{await new Promise((l,i)=>{a.once("error",i),a.once("finish",l),a.once("close",l),a.end()})}}ne.onExit(({next:t})=>(E.loadedMaps.clear(),t()));async function Q(t){const{objects:e,removedBuilding:o,removedBuildingIdx:a}=await me(t),l=se();E.loadedMaps.set(l,{options:t,objects:e,_rmvBuildingIdx:a});const{onLoaded:i}=t;return i&&i(e,o),l}function Ie(t){if(!E.loadedMaps.has(t))throw new T({msg:`invalid mapId ${t}`});const e=E.loadedMaps.get(t),{onUnloaded:o}=e.options;e.objects.forEach(l=>{l.isValid()&&l.destroy()});const a=e._rmvBuildingIdx>-1?E.removedBuilding[e._rmvBuildingIdx]:[];if(e._rmvBuildingIdx>-1&&E.removedBuilding.splice(e._rmvBuildingIdx,1),E.loadedMaps.delete(t),samp.defined&&samp.defined._colandreas_included)try{const l=(typeof global.require<"u"?global.require:V(import.meta.url))("@infernus/colandreas");a.forEach(i=>{l.restoreBuilding(...i)})}catch{}return o&&o(a),t}function ee(t){const e=E.loadedMaps.get(t);if(!e)throw new T({msg:`invalid mapId ${t}`});Q(e.options)}function ge(){E.loadedMaps.keys().forEach(t=>{ee(t)})}function Te(){return E.loadedMaps.size}function ye(t){const e=E.loadedMaps.entries().find(([,o])=>o.objects.find(a=>a===t));return e?e[0]:U}function Me(t){return E.loadedMaps.get(t)}export{U as INVALID_MAP_ID,Te as getMapCount,ye as getMapIDFromObject,Me as getMapInfoFromID,Q as loadMap,pe as mapConverter,_e as mapExporter,ee as reloadMap,ge as reloadMaps,Ie as unloadMap};