Primitive scenary editor
Posted: Mon Jul 28, 2025 9:34 pm
I was about to go crazy trying to make a scenario with code, I came up with the idea of making a very primitive but useful editor.
This is very green, the editor is version 0.001
, you can modify for your own use.
Was tested over PB6.21 and work. For PB6.02 change line 629 with this CameraUserControl(0,0.001)
The system is very simple. There are three main commands. With this you include a mesh or texture. You can then copy and position it as many times as you want from the editor.
This is a module, then to run any of procedures you need call the procedures in this way: deco::ini()
ini()
Start the scenary mode. You need call this at before use the other procedures.
addTextura(...)
addTextura(fileTt3d.s, repeX.f=1,repeZ.f=1, fileNormalMap.s="")
Add one texture to the memory to use with addPrimitive
addPrimitive(...)
addPrimitive(name.s="Obj", tipo.s="cube", tt3d.s="", x.f=0,y.f=0,z.f=0, colitype.i=0, scale.f=1, ang.f=0, csx.f=0,csy.f=0,csz.f=0, NoShadow.b=#False)
Add to scene an PB object Plane or cube only. tt3d is the texture to use.
add(...)
add( fileMesh.s, x.f=0,y.f=0,z.f=0, colitype.i=0, scale.f=1, angX.f=0,angY.f=0,angZ.f=0, csx.f=0,csy.f=0,csz.f=0, NoShadow.b=#False)
Add an entity to the scene.
deco::carga() is load and deco::graba() is save, you can rename
I think it serves as a starting point for a scenario editor. For now, it only works with the keyboard for editing and the mouse to select the objects.
MOUSE
Clik over any object to select.
KEYBOARD:
F Copy selected entity.
A,W,S,D move the selected object in X,Z axis. (Hold SHIFT for small step, Hold LCTRL for one step only)
Z,X move the selected object in Y axis. (Hold SHIFT for small step, Hold LCTRL for one step only)
R,T,Y Rotate selected object in axis Y,Z,X (Hold SHIFT for reverse, Hold LCTRL for one step only)
DEL Delete object from scene
N New scene, delete all.
U Set angle 0,0,0 to selected object.
P,O (todo) change object one by one, by name.
G Save the scene.
F1 Show mode normal
F2 Show entitys boxes
F3 Show collision mesh
F4 Wireframe mode
0 (cero) Shadows on Off, only 6.02. For 6.21 use pf_shadoko shaders
MODE OF USE:
1) Include all the elements you go to use in your scene with the comands add, addPrimitive, addTexture.
comment this line, you need later changing with the name of your scenary file to load it. deco::carga("nivel1newB.tscn")
In line 561 you have this 3 examples of how add your own elements. Modify with your textures and meshes.
; deco::addTextura("adobemuro.png",0.0625,0.0625)
; deco::addPrimitive("sueloDesert1","plane","adobemuro.png",0,0,0, #PB_Entity_StaticBody, 50, 0,0,0,0,#True)
; deco::add("meta_obj.mesh", 0,0,0, #PB_Entity_StaticBody, 0.0022, 0,180,0, 0,0,0, #True)
2) move, copy, and put all the entitys where you want. Make any beauty thing please!
3) Press key G to save your scene the extension is tscn, but you can change.
4) After create your own scene, to load the scene in the code of your game use includeFile("scenary.pbi").
Now include in your code this line: deco::carga("mylevel.tscn"), Where mylevel.tscn is the name of your file scenary.
IMPORTANT:
When reload your scene in the editor, comment the lines add, addPrimitive, etc. because no need anymore.
If no comment then this elements are created again.
Save this file as scenary.pbi
Can add pf_shadoko shaders for better results.
This is an scenary maked with this code:

For the next version...
If I can... It will be better. This was a draft to get an idea because I never take notes or plan how to make the programs, everything is in the coconut
I hope it will help someone, that they make a game, that they sell it, that they become rich, that they set up an aerospace company... hmm. Wait!, that's already happened...
This is very green, the editor is version 0.001

Was tested over PB6.21 and work. For PB6.02 change line 629 with this CameraUserControl(0,0.001)
The system is very simple. There are three main commands. With this you include a mesh or texture. You can then copy and position it as many times as you want from the editor.
This is a module, then to run any of procedures you need call the procedures in this way: deco::ini()
ini()
Start the scenary mode. You need call this at before use the other procedures.
addTextura(...)
addTextura(fileTt3d.s, repeX.f=1,repeZ.f=1, fileNormalMap.s="")
Add one texture to the memory to use with addPrimitive
addPrimitive(...)
addPrimitive(name.s="Obj", tipo.s="cube", tt3d.s="", x.f=0,y.f=0,z.f=0, colitype.i=0, scale.f=1, ang.f=0, csx.f=0,csy.f=0,csz.f=0, NoShadow.b=#False)
Add to scene an PB object Plane or cube only. tt3d is the texture to use.
add(...)
add( fileMesh.s, x.f=0,y.f=0,z.f=0, colitype.i=0, scale.f=1, angX.f=0,angY.f=0,angZ.f=0, csx.f=0,csy.f=0,csz.f=0, NoShadow.b=#False)
Add an entity to the scene.
deco::carga() is load and deco::graba() is save, you can rename
I think it serves as a starting point for a scenario editor. For now, it only works with the keyboard for editing and the mouse to select the objects.
MOUSE
Clik over any object to select.
KEYBOARD:
F Copy selected entity.
A,W,S,D move the selected object in X,Z axis. (Hold SHIFT for small step, Hold LCTRL for one step only)
Z,X move the selected object in Y axis. (Hold SHIFT for small step, Hold LCTRL for one step only)
R,T,Y Rotate selected object in axis Y,Z,X (Hold SHIFT for reverse, Hold LCTRL for one step only)
DEL Delete object from scene
N New scene, delete all.
U Set angle 0,0,0 to selected object.
P,O (todo) change object one by one, by name.
G Save the scene.
F1 Show mode normal
F2 Show entitys boxes
F3 Show collision mesh
F4 Wireframe mode
0 (cero) Shadows on Off, only 6.02. For 6.21 use pf_shadoko shaders
MODE OF USE:
1) Include all the elements you go to use in your scene with the comands add, addPrimitive, addTexture.
comment this line, you need later changing with the name of your scenary file to load it. deco::carga("nivel1newB.tscn")
In line 561 you have this 3 examples of how add your own elements. Modify with your textures and meshes.
; deco::addTextura("adobemuro.png",0.0625,0.0625)
; deco::addPrimitive("sueloDesert1","plane","adobemuro.png",0,0,0, #PB_Entity_StaticBody, 50, 0,0,0,0,#True)
; deco::add("meta_obj.mesh", 0,0,0, #PB_Entity_StaticBody, 0.0022, 0,180,0, 0,0,0, #True)
2) move, copy, and put all the entitys where you want. Make any beauty thing please!

3) Press key G to save your scene the extension is tscn, but you can change.
4) After create your own scene, to load the scene in the code of your game use includeFile("scenary.pbi").
Now include in your code this line: deco::carga("mylevel.tscn"), Where mylevel.tscn is the name of your file scenary.
IMPORTANT:
When reload your scene in the editor, comment the lines add, addPrimitive, etc. because no need anymore.
If no comment then this elements are created again.
Save this file as scenary.pbi
Code: Select all
DeclareModule deco
;objetos 3D
Structure deco_items_stru ;elementos del decorado creados
enti.i
scale.f
entiColision.i
visible.i
pos.Vector3
ang.Vector3
EndStructure
Structure deco_original
name.s
mesh.i
enti.i
tipo.s
textura.s
scale.f
entiColision.i
colitype.i ;tipo de colisión (box, sphere...)
coliSize.Vector3
visible.b
noShadow.b
pos.Vector3
ang.Vector3
List item.deco_items_stru()
EndStructure
Global NewList deco.deco_original()
Global meshCube,meshPlane,mateColision ;dummys para las colisiones
;billboards
Structure bill_stru
grup.i
tt3dfile.s
tt3d.i
mate.i
EndStructure
;texturas
Structure tt3d_stru
name.s
tt3d.i
tt3dnorm.i
normname.s
mate.i
repeX.f
repeZ.f
EndStructure
Global NewList textura.tt3d_stru()
Declare ini()
Declare freeEscena()
Declare graba()
Declare carga(file.s, borra.a=0)
Declare addTextura(fileTt3d.s, repeX.f=1,repeZ.f=1, fileNormalMap.s="")
Declare add( fileMesh.s, x.f=0,y.f=0,z.f=0, colitype.i=0, scale.f=1, angX.f=0,angY.f=0,angZ.f=0, csx.f=0,csy.f=0,csz.f=0, NoShadow.b=#False)
Declare addPrimitive(name.s="Obj", tipo.s="cube", tt3d.s="", x.f=0,y.f=0,z.f=0, colitype.i=0, scale.f=1, ang.f=0, csx.f=0,csy.f=0,csz.f=0, NoShadow.b=#False)
EndDeclareModule
Module deco
Procedure ini()
;inicializa el sistema de decorados
;malla usada para las colisiones
meshPlane= CreatePlane(#PB_Any,1,1,1,1,1,1)
meshCube= CreateCube(#PB_Any,1)
mateColision= CreateMaterial(#PB_Any,#Null,$ff0000)
EndProcedure
Procedure freeEscena()
ForEach deco()
ForEach deco()\item()
If IsEntity(deco()\item()\enti):FreeEntity(deco()\item()\enti):EndIf
If IsEntity(deco()\item()\entiColision):FreeEntity(deco()\item()\entiColision):EndIf
Next
If IsEntity(deco()\enti):FreeEntity(deco()\enti):EndIf
If IsEntity(deco()\entiColision):FreeEntity(deco()\entiColision):EndIf
If IsMesh(deco()\mesh):FreeMesh(deco()\mesh):EndIf
Next
ForEach textura()
If IsMaterial(textura()\mate):FreeMaterial(textura()\mate):EndIf
If IsTexture(textura()\tt3d):FreeTexture(textura()\tt3d):EndIf
Next
ClearList(textura())
ClearList(deco())
EndProcedure
Procedure add( fileMesh.s, x.f=0,y.f=0,z.f=0, colitype.i=0, scale.f=1, angX.f=0,angY.f=0,angZ.f=0, csx.f=0,csy.f=0,csz.f=0, NoShadow.b=#False)
;añade entidades al escenario
;#PB_Entity_StaticBody, #PB_Entity_BoxBody, #PB_Entity_SphereBody, #PB_Entity_CylinderBody, #PB_Entity_ConeBody, #PB_Entity_ConvexHullBody
Protected enti
Protected.b existe
ForEach deco()
If fileMesh= deco()\name
; Debug "EXISTE: "+Str(existe)
existe= #True
Break
EndIf
Next
If existe ;si se ha creado el objeto original
enti= deco()\enti
If IsEntity(enti)
AddElement(deco()\item())
deco()\item()\pos\x= x
deco()\item()\pos\y= y
deco()\item()\pos\z= z
deco()\item()\ang\x= angX
deco()\item()\ang\y= angY
deco()\item()\ang\z= angZ
deco()\item()\scale= scale
deco()\item()\enti= CopyEntity(deco()\enti, #PB_Any)
RotateEntity(deco()\item()\enti, angX,angY,angZ, #PB_Absolute)
MoveEntity(deco()\item()\enti,x,y,z,#PB_Absolute)
If deco()\noShadow
EntityRenderMode(deco()\item()\enti,#PB_Shadow_None)
EndIf
If deco()\colitype
If deco()\coliSize\x+deco()\coliSize\y+deco()\coliSize\z > 0
deco()\item()\entiColision= CreateEntity(#PB_Any,MeshID(meshCube),MaterialID(mateColision),x,y,z)
RotateEntity(deco()\item()\entiColision, angX,angY,angZ, #PB_Absolute)
ScaleEntity(deco()\item()\entiColision, deco()\coliSize\x,deco()\coliSize\y,deco()\coliSize\z,#PB_Absolute)
EntityRenderMode(deco()\item()\entiColision,#PB_Shadow_None)
CreateEntityBody(deco()\item()\entiColision, #PB_Entity_StaticBody,0,0.1,0.7);,1,0,0,0, deco()\coliSize\x,deco()\coliSize\y,deco()\coliSize\z)
SetEntityCollisionFilter(deco()\item()\entiColision,1<<1,1<<1)
HideEntity(deco()\item()\entiColision,1)
Else
CreateEntityBody(deco()\item()\enti, deco()\colitype,0,0.1,0.7)
EndIf
SetEntityCollisionFilter(deco()\item()\enti,1<<1,1<<1)
EndIf
EndIf
Else ;si no existe el objeto original, crea uno
AddElement(deco())
deco()\pos\x= x
deco()\pos\y= y
deco()\pos\z= z
deco()\ang\x= angX
deco()\ang\y= angY
deco()\ang\z= angZ
deco()\scale= scale
deco()\name= fileMesh
deco()\mesh= LoadMesh(#PB_Any,deco()\name)
deco()\enti= CreateEntity(#PB_Any,MeshID(deco()\mesh),#PB_Material_None)
ScaleEntity(deco()\enti, scale,scale,scale,#PB_Absolute)
RotateEntity(deco()\enti, angX,angY,angZ, #PB_Absolute)
MoveEntity(deco()\enti,x,y,z,#PB_Absolute)
deco()\colitype= colitype
deco()\coliSize\x= csx
deco()\coliSize\y= csy
deco()\coliSize\z= csz
deco()\noShadow= NoShadow
If deco()\noShadow
EntityRenderMode(deco()\enti,#PB_Shadow_None)
EndIf
If deco()\colitype
If deco()\coliSize\x+deco()\coliSize\y+deco()\coliSize\z > 0 ;colisión extra con nueva entidad
deco()\entiColision= CreateEntity(#PB_Any,MeshID(meshCube),MaterialID(mateColision),x,y,z)
RotateEntity(deco()\entiColision, angX,angY,angZ, #PB_Absolute)
ScaleEntity(deco()\entiColision, deco()\coliSize\x,deco()\coliSize\y,deco()\coliSize\z,#PB_Absolute)
CreateEntityBody(deco()\entiColision, #PB_Entity_StaticBody,0,0.1,0.7);,1,0,0,0, deco()\coliSize\x,deco()\coliSize\y,deco()\coliSize\z)
SetEntityCollisionFilter(deco()\entiColision,1<<1,1<<1)
HideEntity(deco()\entiColision,1)
Else
CreateEntityBody(deco()\enti, deco()\colitype,0,0.1,0.7)
SetEntityCollisionFilter(deco()\enti,1<<1,1<<1)
EndIf
EndIf
EndIf
EndProcedure
Procedure addPrimitive(name.s="Obj", tipo.s="cube", tt3d.s="", x.f=0,y.f=0,z.f=0, colitype.i=0, scale.f=1, ang.f=0, csx.f=0,csy.f=0,csz.f=0, NoShadow.b=#False)
;añade entidades tipo (plane,cube) al escenario
;#PB_Entity_StaticBody, #PB_Entity_BoxBody, #PB_Entity_SphereBody, #PB_Entity_CylinderBody, #PB_Entity_ConeBody, #PB_Entity_ConvexHullBody
Protected enti
Protected.b existe
Protected.b textu
ForEach textura()
If textura()\name= tt3d
textu= #True
Break
EndIf
Next
If Not textu
AddElement(textura())
textura()\name= tt3d
textura()\tt3d= LoadTexture(#PB_Any,textura()\name)
textura()\mate= CreateMaterial(#PB_Any,TextureID(textura()\tt3d))
EndIf
;crea objetos
ForEach deco()
If name= deco()\name
existe= #True
Break
EndIf
Next
If existe ;si se ha creado el objeto original
If IsEntity(deco()\enti)
;{ copia
AddElement(deco()\item())
deco()\item()\pos\x= x
deco()\item()\pos\x= y
deco()\item()\pos\x= z
deco()\item()\ang\y= ang
deco()\item()\scale= scale
Select deco()\tipo
Case "plane"
deco()\item()\enti= CreateEntity(#PB_Any,MeshID(meshPlane),MaterialID(textura()\mate),x,y,z)
Case "cube"
deco()\item()\enti= CreateEntity(#PB_Any,MeshID(meshCube),MaterialID(textura()\mate),x,y,z)
EndSelect
RotateEntity(deco()\item()\enti, 0,ang, 0, #PB_Absolute)
MoveEntity(deco()\item()\enti,x,y,z,#PB_Absolute)
ScaleEntity(deco()\item()\enti, deco()\item()\scale,deco()\item()\scale,deco()\item()\scale,#PB_Absolute)
If deco()\noShadow
EntityRenderMode(deco()\item()\enti,#PB_Shadow_None)
EndIf
If deco()\colitype
If deco()\coliSize\x+deco()\coliSize\y+deco()\coliSize\z > 0
deco()\item()\entiColision= CreateEntity(#PB_Any,MeshID(meshCube),MaterialID(mateColision),x,y,z)
RotateEntity(deco()\item()\entiColision, 0,ang, 0, #PB_Absolute)
ScaleEntity(deco()\item()\entiColision, deco()\coliSize\x,deco()\coliSize\y,deco()\coliSize\z,#PB_Absolute)
EntityRenderMode(deco()\item()\entiColision,#PB_Shadow_None)
CreateEntityBody(deco()\item()\entiColision, #PB_Entity_StaticBody,0,0.1,0.7);,1,0,0,0, deco()\coliSize\x,deco()\coliSize\y,deco()\coliSize\z)
SetEntityCollisionFilter(deco()\item()\entiColision,1<<1,1<<1)
HideEntity(deco()\item()\entiColision,1)
Else
CreateEntityBody(deco()\item()\enti, deco()\colitype,0,0.1,0.7);,0,0,0)
EndIf
SetEntityCollisionFilter(deco()\item()\enti,1<<1,1<<1)
EndIf
;}
EndIf
Else ;si no existe el objeto original, crea uno
;{ original
AddElement(deco())
deco()\name= name
deco()\tipo= tipo
deco()\textura= tt3d
; deco()\mesh= LoadMesh(#PB_Any,deco()\name)
Select deco()\tipo
Case "plane"
deco()\enti= CreateEntity(#PB_Any,MeshID(meshPlane),MaterialID(textura()\mate),x,y,z)
Case "cube"
deco()\enti= CreateEntity(#PB_Any,MeshID(meshCube),MaterialID(textura()\mate),x,y,z)
EndSelect
ScaleEntity(deco()\enti, scale,scale,scale,#PB_Absolute)
RotateEntity(deco()\enti, 0,ang, 0, #PB_Absolute)
MoveEntity(deco()\enti,x,y,z,#PB_Absolute)
deco()\scale= scale
deco()\colitype= colitype
deco()\coliSize\x= csx
deco()\coliSize\y= csy
deco()\coliSize\z= csz
deco()\noShadow= NoShadow
If deco()\noShadow
EntityRenderMode(deco()\enti,#PB_Shadow_None)
EndIf
If deco()\colitype
If deco()\coliSize\x+deco()\coliSize\y+deco()\coliSize\z > 0 ;colisión extra con nueva entidad
deco()\entiColision= CreateEntity(#PB_Any,MeshID(meshCube),MaterialID(mateColision),x,y,z)
RotateEntity(deco()\entiColision, 0,ang, 0, #PB_Absolute)
ScaleEntity(deco()\entiColision, deco()\coliSize\x,deco()\coliSize\y,deco()\coliSize\z,#PB_Absolute)
CreateEntityBody(deco()\entiColision, #PB_Entity_StaticBody,0,0.1,0.7);,1,0,0,0, deco()\coliSize\x,deco()\coliSize\y,deco()\coliSize\z)
SetEntityCollisionFilter(deco()\entiColision,1<<1,1<<1)
HideEntity(deco()\entiColision,1)
Else
CreateEntityBody(deco()\enti, deco()\colitype,0,0.1,0.7)
SetEntityCollisionFilter(deco()\enti,1<<1,1<<1)
EndIf
EndIf
;}
EndIf
EndProcedure
Procedure addTextura(fileTt3d.s, repeX.f=1,repeZ.f=1, fileNormalMap.s="")
AddElement(textura())
textura()\repeX= repeX
textura()\repeZ= repeZ
textura()\name= fileTt3d
textura()\normname= fileNormalMap
textura()\tt3d= LoadTexture(#PB_Any,textura()\name)
If textura()\normname
textura()\tt3dnorm= LoadTexture(#PB_Any,textura()\normname)
textura()\mate= CreateShaderMaterial(#PB_Any,#PB_Material_BumpShader)
MaterialShaderTexture(textura()\mate, TextureID(textura()\tt3d),TextureID(textura()\tt3dnorm),#Null,#Null)
MaterialShaderParameter(textura()\mate,#PB_Shader_Fragment,"bumpy",#PB_Shader_Float,0.75,0,0,0)
SetMaterialColor(textura()\mate, #PB_Material_DiffuseColor,$112233)
MaterialShininess(textura()\mate, 10,$446688)
Else
textura()\mate= CreateMaterial(#PB_Any,TextureID(textura()\tt3d))
EndIf
ScaleMaterial(textura()\mate, 1 * textura()\repeX, 1 * textura()\repeZ)
EndProcedure
Procedure graba()
Protected.s t, file= SaveFileRequester("Graba","","Escena (.tscn)|*.tscn",0)
If file
If CreateFile(0, file)
t+ "TEXTURAS"+#LF$
ForEach textura()
t+ textura()\name + "|"
t+ StrF(textura()\repeX) + "|"
t+ StrF(textura()\repeZ) + #LF$
Next
ForEach deco()
t+ "OBJETO "+Str(ListIndex(deco()))+#LF$
t+ deco()\name + "|" ;file
t+ deco()\tipo + "|" ;plano, cubo...
t+ deco()\textura + "|" ; si es del tipo plano, cubo, etc, debe incluir una textura
t+ StrF(deco()\pos\x) + "|"
t+ StrF(deco()\pos\y) + "|"
t+ StrF(deco()\pos\z) + "|"
t+ StrF(deco()\scale) + "|"
t+ StrF(deco()\ang\x) + "|" ;angulo
t+ StrF(deco()\ang\y) + "|" ;angulo
t+ StrF(deco()\ang\z) + "|" ;angulo
t+ Str(deco()\colitype) + "|"
t+ StrF(deco()\coliSize\x) + "|"
t+ StrF(deco()\coliSize\y) + "|"
t+ StrF(deco()\coliSize\z) + "|"
t+ Str(deco()\noShadow) + #LF$
ForEach deco()\item()
t+ StrF(deco()\item()\pos\x) + "|"
t+ StrF(deco()\item()\pos\y) + "|"
t+ StrF(deco()\item()\pos\z) + "|"
t+ StrF(deco()\item()\scale) + "|"
t+ StrF(deco()\item()\ang\x) + "|" ;angulo
t+ StrF(deco()\item()\ang\y) + "|" ;angulo
t+ StrF(deco()\item()\ang\z) + #LF$ ;angulo
Next
Next
WriteString(0,t)
CloseFile(0)
EndIf
EndIf
EndProcedure
Procedure carga(file.s, borra.a=0)
Protected.s t
Protected.s name,tipo,textura
Protected colitype, noshadow
Protected.f px,py,pz,scale ,ax,ay,az, cx,xy,xz
If ReadFile(0,file)
While Eof(0)=0
t= ReadString(0)
If t<>""
Select Left(t,6)
Case "TEXTUR"
modo= 1
Case "OBJETO"
modo= 2
EndSelect
If Left(t,6)<>"TEXTUR" And Left(t,6)<>"OBJETO"
Select modo
Case 3 ;copia
; name= StringField(t,1,"|")
If tipo= "plane" Or tipo="cubo"
addPrimitive(deco()\name,tipo,textura,ValF(StringField(t,1,"|")),ValF(StringField(t,2,"|")),ValF(StringField(t,3,"|")),
colitype,ValF(StringField(t,4,"|")),ValF(StringField(t,6,"|")),cx,cy,cz,noshadow)
deco()\item()\pos\x= ValF(StringField(t,1,"|"))
deco()\item()\pos\y= ValF(StringField(t,2,"|"))
deco()\item()\pos\z= ValF(StringField(t,3,"|"))
deco()\item()\ang\x= ValF(StringField(t,5,"|"))
deco()\item()\ang\y= ValF(StringField(t,6,"|"))
deco()\item()\ang\z= ValF(StringField(t,7,"|"))
Else
add(deco()\name,
ValF(StringField(t,1,"|")),ValF(StringField(t,2,"|")),ValF(StringField(t,3,"|")),
deco()\colitype,ValF(StringField(t,4,"|")),ValF(StringField(t,5,"|")),ValF(StringField(t,6,"|")),ValF(StringField(t,7,"|")),
deco()\coliSize\x,deco()\coliSize\y,deco()\coliSize\z,deco()\noShadow)
EndIf
Case 1 ;textura
addTextura(StringField(t,1,"|"), ValF(StringField(t,2,"|")),ValF(StringField(t,3,"|")),StringField(t,4,"|"))
Case 2 ;original
name= StringField(t,1,"|")
tipo= StringField(t,2,"|")
textura= StringField(t,3,"|")
px= ValF(StringField(t,4,"|"))
py= ValF(StringField(t,5,"|"))
pz= ValF(StringField(t,6,"|"))
scale= ValF(StringField(t,7,"|"))
ax= ValF(StringField(t,8,"|"))
ay= ValF(StringField(t,9,"|"))
az= ValF(StringField(t,10,"|"))
colitype= Val(StringField(t,11,"|"))
cx= ValF(StringField(t,12,"|"))
cy= ValF(StringField(t,13,"|"))
cz= ValF(StringField(t,14,"|"))
noshadow= Val(StringField(t,15,"|"))
noshadow= 1
If tipo= "cube" Or tipo= "plane"
addPrimitive(name,tipo,textura, px,py,pz, colitype, scale, ay,
cx,cy,cz,noShadow)
Else
add(name, px,py,pz, colitype,scale,ax,ay,az,
cx,cy,cz,noShadow)
EndIf
modo= 3 ;pasa a las copias
EndSelect
EndIf
EndIf
Wend
CloseFile(0)
EndIf
EndProcedure
EndModule
CompilerIf #PB_Compiler_IsMainFile
Global renderTime
;{ sys
Global fontLit= LoadFont(#PB_Any,"Arial",9)
Global fontMed= LoadFont(#PB_Any,"Arial",16)
Global fontBig= LoadFont(#PB_Any,"Arial",24)
; If RegisterFontFile("Data/Fonts/Lyric Poetry.ttf")
; fontGotic= LoadFont(#PB_Any,"Lyric Poetry",20, #PB_Font_HighQuality)
; EndIf
Procedure sprMouseCursor()
Protected i= CreateImage(#PB_Any,32,32,32,#PB_Image_Transparent)
Protected s= CreateSprite(#PB_Any,32,32,#PB_Sprite_AlphaBlending)
StartVectorDrawing(ImageVectorOutput(i))
VectorSourceColor($ff00ffff)
MovePathCursor(0,0)
AddPathLine(31,15)
AddPathLine(31,31)
AddPathLine(15,31)
AddPathLine(0,0)
FillPath()
VectorSourceColor($440000ff);$ff0088ff)
MovePathCursor(0,0)
AddPathLine(31,15)
AddPathLine(31,31)
AddPathLine(0,0)
FillPath()
VectorSourceColor($ff000000)
MovePathCursor(0,0)
AddPathLine(31,15)
AddPathLine(15,31)
AddPathLine(0,0)
StrokePath(1)
MovePathCursor(31,15)
AddPathLine(31,31)
AddPathLine(15,31)
StrokePath(1)
VectorSourceColor($22000000)
MovePathCursor(31,15)
AddPathLine(31,31)
AddPathLine(15,31)
FillPath()
StopVectorDrawing()
StartDrawing(SpriteOutput(s))
DrawingMode(#PB_2DDrawing_AllChannels)
DrawAlphaImage(ImageID(i),0,0)
StopDrawing()
FreeImage(i)
ProcedureReturn s
EndProcedure
Procedure testGAME()
If KeyboardReleased(#PB_Key_C)
SetClipboardText("MoveCamera(camara,"+StrD(CameraX(0),2)+","+StrD(CameraY(0),2)+","+StrD(CameraZ(0),2)+",#PB_Absolute)")
EndIf
If KeyboardReleased(#PB_Key_F1)
WorldDebug(#PB_World_DebugNone)
CameraRenderMode(0,#PB_Camera_Textured)
ElseIf KeyboardReleased(#PB_Key_F2)
WorldDebug(#PB_World_DebugEntity)
ElseIf KeyboardReleased(#PB_Key_F3)
WorldDebug(#PB_World_DebugBody)
ElseIf KeyboardReleased(#PB_Key_F4)
CameraRenderMode(0,#PB_Camera_Wireframe)
ElseIf KeyboardReleased(#PB_Key_F5)
ElseIf KeyboardReleased(#PB_Key_F12)
ClearDebugOutput()
EndIf
EndProcedure
Procedure CameraUserControl(camera,speed.f=0.1,smooth.f=0.1,yfixed.f=1e10)
Static.f MouseX,Mousey,depx,depz,sdepx,sdepz, fdf.b
depx=-speed*(KeyboardPushed(#PB_Key_Left)-KeyboardPushed(#PB_Key_Right))
depz=-speed*(KeyboardPushed(#PB_Key_Down)-KeyboardPushed(#PB_Key_Up)-MouseWheel()*40)
If KeyboardPushed(#PB_Key_LeftShift):depx *4:depz * 4: EndIf
MouseX = -MouseDeltaX() * 0.05
MouseY = -MouseDeltaY() * 0.05
RotateCamera(camera, MouseY, MouseX, 0, #PB_Relative)
sdepx+(depx-sdepx)*smooth
sdepz+(depz-sdepz)*smooth
MoveCamera (camera, sdepX, 0, -sdepz)
If yfixed<>1e10:MoveCamera(camera,CameraX(camera),yfixed,CameraZ(camera),#PB_Absolute):EndIf
EndProcedure
Procedure sprTxt(spr, txt1.s,txt2.s="",txt3.s="",txt4.s="", ink.l=$ff00ffff)
StartDrawing(SpriteOutput(spr))
DrawingMode(#PB_2DDrawing_AllChannels)
Box(0,0,OutputWidth(),OutputHeight(),RGBA(0,0,0,64))
DrawingMode(#PB_2DDrawing_AlphaBlend | #PB_2DDrawing_Transparent)
DrawingFont(FontID(fontLit))
DrawText(5,5,txt1,ink);,RGBA(0,0,0,0))
DrawText(5,25,txt2,ink)
DrawText(5,45,txt3,ink)
DrawText(5,65,txt4,ink)
StopDrawing()
EndProcedure
Procedure ini3D()
InitEngine3D(#PB_Engine3D_DebugLog):InitSprite():InitKeyboard():InitMouse()
OpenWindow(0, 0,0, 1200,660, "NPC waypoint anim",#PB_Window_ScreenCentered)
; AntialiasingMode(#PB_AntialiasingMode_x6)
OpenWindowedScreen(WindowID(0), 0, 0, WindowWidth(0),WindowHeight(0), 0, 0, 0)
CompilerIf #PB_Compiler_Version >= 620 ;this is for new PB. Thanks miso :)
;{ 6.20 shader
Add3DArchive(#PB_Compiler_Home + "examples/3d/Data/Main", #PB_3DArchive_FileSystem)
Add3DArchive("datos/modelos/corvette",#PB_3DArchive_FileSystem)
Add3DArchive("datos/modelos/road",#PB_3DArchive_FileSystem)
Add3DArchive("datos/modelos/nature",#PB_3DArchive_FileSystem)
Add3DArchive("datos/modelos/edificios",#PB_3DArchive_FileSystem)
Add3DArchive("datos/texturas",#PB_3DArchive_FileSystem)
Add3DArchive("datos/fonts",#PB_3DArchive_FileSystem)
Parse3DScripts()
;}
CompilerElse
Add3DArchive("datos/modelos/corvette",#PB_3DArchive_FileSystem)
Add3DArchive("datos/modelos/road",#PB_3DArchive_FileSystem)
Add3DArchive("datos/modelos/nature",#PB_3DArchive_FileSystem)
Add3DArchive("datos/modelos/edificios",#PB_3DArchive_FileSystem)
Add3DArchive("datos/texturas",#PB_3DArchive_FileSystem)
Add3DArchive("datos/fonts",#PB_3DArchive_FileSystem)
Parse3DScripts()
CompilerEndIf
EnableWorldPhysics(1)
EnableWorldCollisions(1)
WorldShadows(#PB_Shadow_Modulative,-1,RGB(128,128,128),1024)
CreateLight(0, $ffffff,0,0,0,#PB_Light_Directional):LightDirection(0,-0.15,-1,0.15)
AmbientColor($888888)
CreateCamera(0,0,0,100,100)
MoveCamera(camara,7.14,10.25,-9.36,#PB_Absolute)
CameraLookAt(0, 0,0,0)
CameraBackColor(0,$555555)
glLineWidth_(5)
CreateLine3D(#PB_Any, -20,0,0,$0000ff, 20,0,0,$00ffff)
CreateLine3D(#PB_Any, 0,0,-20,$ff0000, 0,0,20,$ffff00)
CreateLine3D(#PB_Any, 0,0,0,$004400, 0,20,0,$00ff00)
Global sprInfo= CreateSprite(#PB_Any,200,150,#PB_Sprite_AlphaBlending)
EndProcedure
;}
ini3D()
sprMouse= sprMouseCursor()
deco::ini()
; deco::carga("nivel1newB.tscn")
; deco::addTextura("adobemuro.png",0.0625,0.0625)
; deco::addPrimitive("sueloDesert1","plane","adobemuro.png",0,0,0, #PB_Entity_StaticBody, 50, 0,0,0,0,#True)
; deco::add("meta_obj.mesh", 0,0,0, #PB_Entity_StaticBody, 0.0022, 0,180,0, 0,0,0, #True)
listaItem.s= "casaAdobe1.mesh|casaAdobe2_obj.mesh|canion1_obj.mesh|canion2_obj.mesh|"
entiNow= 0
Global itemOriginal=-1
Global itemCopia=-1
itemAcrear= 1
paso.f= 1
tx2.s
tx3.s
tx4.s= StringField(listaItem, itemAcrear,"|")
tx5.s
Procedure updateObjeto(x.f=0,y.f=0,z.f=0, ax.f=0,ay.f=0,az.f=0, sx.f=0,sy.f=0,sz.f=0)
If itemCopia= -1
If itemOriginal > -1
SelectElement(deco::deco(), itemOriginal)
deco::deco()\pos\x+ x
deco::deco()\pos\y+ y
deco::deco()\pos\z+ z
deco::deco()\ang\x+ ax
deco::deco()\ang\y+ ay
deco::deco()\ang\z+ az
RotateEntity(deco::deco()\enti,deco::deco()\ang\x, deco::deco()\ang\y, deco::deco()\ang\z,#PB_Absolute)
DisableEntityBody(deco::deco()\enti,1)
MoveEntity(deco::deco()\enti, deco::deco()\pos\x,deco::deco()\pos\y,deco::deco()\pos\z, #PB_Absolute)
DisableEntityBody(deco::deco()\enti,0)
EndIf
Else
If itemCopia > -1
SelectElement(deco::deco(), itemOriginal)
SelectElement(deco::deco()\item(), itemCopia)
deco::deco()\item()\pos\x+ x
deco::deco()\item()\pos\y+ y
deco::deco()\item()\pos\z+ z
deco::deco()\item()\ang\x+ ax
deco::deco()\item()\ang\y+ ay
deco::deco()\item()\ang\z+ az
RotateEntity(deco::deco()\item()\enti,deco::deco()\item()\ang\x, deco::deco()\item()\ang\y, deco::deco()\item()\ang\z,#PB_Absolute)
DisableEntityBody(deco::deco()\item()\enti,1)
MoveEntity(deco::deco()\item()\enti, deco::deco()\pos\x,deco::deco()\pos\y,deco::deco()\pos\z, #PB_Absolute)
DisableEntityBody(deco::deco()\item()\enti,0)
EndIf
EndIf
deco::deco()\pos\x= (deco::deco()\pos\x * 100) / 100
deco::deco()\pos\y= (deco::deco()\pos\y * 100) / 100
deco::deco()\pos\z= (deco::deco()\pos\z * 100) / 100
deco::deco()\ang\x= (deco::deco()\ang\x * 100) / 100
deco::deco()\ang\y= (deco::deco()\ang\y * 100) / 100
deco::deco()\ang\z= (deco::deco()\ang\z * 100) / 100
EndProcedure
;---- LOOP
Repeat
; While WindowEvent():Wend
Repeat
ev= WindowEvent()
If ev= #WM_RBUTTONUP
ReleaseMouse(#False)
EndIf
Until ev= 0
ExamineMouse(): ExamineKeyboard()
CameraUserControl(0,0.1)
testGAME()
;{ edicion
If MouseButton(#PB_MouseButton_Left)
If mlb= 0
mlb= 1
itemOriginal= -1
itemCopia= -1
entiNow= 0
pik= MouseRayCast(0,MouseX(),MouseY(), 1<<1 )
If pik > 0
; Debug pik
ForEach deco::deco()
If deco::deco()\enti= pik Or deco::deco()\entiColision= pik
original= #True
itemOriginal= ListIndex(deco::deco())
entiNow= pik
Debug "ORIGINAL"
tx2= deco::deco()\name
Break
EndIf
ForEach deco::deco()\item()
If deco::deco()\item()\enti= pik Or deco::deco()\item()\entiColision= pik
original= #False
itemOriginal= ListIndex(deco::deco())
itemCopia= ListIndex(deco::deco()\item())
entiNow= pik
Debug "COPIA "+Str(entiNow)
tx2= "C"+Str(ListIndex(deco::deco()\item()))+"-"+deco::deco()\name
Break 2
EndIf
Next
Next
CreateLine3D(1,EntityX(pik)-4,EntityY(pik)+0.1,EntityZ(pik),$ffffff, EntityX(pik)+4,EntityY(pik)+0.1,EntityZ(pik),$ffffff)
CreateLine3D(2,EntityX(pik),EntityY(pik)+0.1,EntityZ(pik)-4,$ffffff, EntityX(pik),EntityY(pik)+0.1,EntityZ(pik)+4,$ffffff)
CreateLine3D(3,EntityX(pik),EntityY(pik)+4,EntityZ(pik),$ffffff, EntityX(pik),EntityY(pik)-4,EntityZ(pik),$ffffff)
Else
If IsMesh(1):FreeMesh(1):EndIf
If IsMesh(2):FreeMesh(2):EndIf
If IsMesh(3):FreeMesh(3):EndIf
entiNow= 0
EndIf
EndIf
Else
mlb= 0
EndIf
;}
;--- TECLADO
;{ graba, nuevo, sombras
If KeyboardReleased(#PB_Key_0) ; con sombras o sin
sombrasAll !1
deco::deco()\noShadow= sombrasAll
If sombrasAll
ForEach deco::deco()
EntityRenderMode(deco::deco()\enti,#PB_Entity_CastShadow)
ForEach deco::deco()\item()
EntityRenderMode(deco::deco()\item()\enti,#PB_Entity_CastShadow)
Next
Next
Else
ForEach deco::deco()
EntityRenderMode(deco::deco()\enti,#PB_Shadow_None)
ForEach deco::deco()\item()
EntityRenderMode(deco::deco()\item()\enti,#PB_Shadow_None)
Next
Next
EndIf
EndIf
If KeyboardReleased(#PB_Key_O) ; selecciona item anterior de la lista
itemAcrear - 1:If itemAcrear < 1: itemAcrear= CountString(listaItem,"|"):EndIf
tx4= StringField(listaItem, itemAcrear,"|")
ElseIf KeyboardReleased(#PB_Key_P) ; selecciona item siguiente de la lista
itemAcrear + 1:If itemAcrear > CountString(listaItem,"|"): itemAcrear=1:EndIf
tx4= StringField(listaItem, itemAcrear,"|")
EndIf
If KeyboardReleased(#PB_Key_N) ;limpia la escena
resp= MessageRequester("Cuidadin!!!","¿Quieres borrar la escena de los cojones?",#PB_MessageRequester_YesNo)
If resp=#PB_MessageRequester_Yes:deco::freeEscena():EndIf
EndIf
If KeyboardReleased(#PB_Key_G)
ReleaseMouse(#True)
deco::graba()
EndIf
;}
If IsEntity(entiNow)
If KeyboardReleased(#PB_Key_U)
If itemCopia= -1
If itemOriginal > -1
SelectElement(deco::deco(), itemOriginal)
deco::deco()\ang\x= 0
deco::deco()\ang\y= 0
deco::deco()\ang\z= 0
DisableEntityBody(deco::deco()\enti,1)
RotateEntity(deco::deco()\enti,deco::deco()\ang\x, deco::deco()\ang\y, deco::deco()\ang\z,#PB_Absolute)
DisableEntityBody(deco::deco()\enti,0)
Debug itemOriginal
EndIf
Else
If itemCopia > -1
SelectElement(deco::deco(), itemOriginal)
SelectElement(deco::deco()\item(), itemCopia)
deco::deco()\item()\ang\x= 0
deco::deco()\item()\ang\y= 0
deco::deco()\item()\ang\z= 0
DisableEntityBody(deco::deco()\item()\enti,1)
RotateEntity(deco::deco()\item()\enti,deco::deco()\item()\ang\x, deco::deco()\item()\ang\y, deco::deco()\item()\ang\z,#PB_Absolute)
DisableEntityBody(deco::deco()\item()\enti,0)
EndIf
EndIf
EndIf
If KeyboardPushed(#PB_Key_LeftShift) Or KeyboardPushed(#PB_Key_RightShift)
paso= 0.01
Else
paso= 0.1
EndIf
;{ duplic, borra
If KeyboardReleased(#PB_Key_Delete)
If itemOriginal > -1
If itemCopia = -1
Debug "borra original"
resp= MessageRequester("Hey!","Objeto original."+#LF$+"¿Quieres eliminarlo?",#PB_MessageRequester_YesNo)
If resp=#PB_MessageRequester_Yes
SelectElement(deco::deco(),itemOriginal)
If IsEntity(deco::deco()\enti):FreeEntity(deco::deco()\enti):EndIf
If IsEntity(deco::deco()\entiColision):FreeEntity(deco::deco()\entiColision):EndIf
DeleteElement(deco::deco())
EndIf
Else
Debug "borra copia"
SelectElement(deco::deco(),itemOriginal)
SelectElement(deco::deco()\item(),itemCopia)
If IsEntity(deco::deco()\item()\enti):FreeEntity(deco::deco()\item()\enti):EndIf
If IsEntity(deco::deco()\item()\entiColision):FreeEntity(deco::deco()\item()\entiColision):EndIf
DeleteElement(deco::deco()\item())
EndIf
EndIf
entiNow= 0
EndIf
;---- COPIA
If KeyboardReleased(#PB_Key_F)
If original
SelectElement(deco::deco(),itemOriginal)
deco::add(deco::deco()\name, deco::deco()\pos\x,deco::deco()\pos\y,deco::deco()\pos\z,
deco::deco()\colitype,deco::deco()\scale, deco::deco()\ang\x,deco::deco()\ang\y,deco::deco()\ang\z,
deco::deco()\coliSize\x, deco::deco()\coliSize\y, deco::deco()\coliSize\z, deco::deco()\noShadow)
Else
SelectElement(deco::deco(),itemOriginal)
SelectElement(deco::deco()\item(),itemCopia)
deco::add(deco::deco()\name, deco::deco()\item()\pos\x,deco::deco()\item()\pos\y,deco::deco()\item()\pos\z,
deco::deco()\colitype,deco::deco()\scale, deco::deco()\item()\ang\x,deco::deco()\item()\ang\y,deco::deco()\item()\ang\z,
deco::deco()\coliSize\x, deco::deco()\coliSize\y, deco::deco()\coliSize\z, deco::deco()\noShadow)
EndIf
EndIf
;}
If KeyboardPushed(#PB_Key_LeftControl)
solouno= 1
Else
solouno= 0
EndIf
;{ gira y mueve
If solouno
If KeyboardReleased(#PB_Key_R)
If KeyboardReleased(#PB_Key_LeftShift)
updateObjeto(0,0,0, 0,-1,0)
Else
updateObjeto(0,0,0, 0,1,0)
EndIf
EndIf
If KeyboardReleased(#PB_Key_T)
If KeyboardPushed(#PB_Key_LeftShift)
updateObjeto(0,0,0, 0,0,-1)
Else
updateObjeto(0,0,0, 0,0,1)
EndIf
EndIf
If KeyboardReleased(#PB_Key_Y)
If KeyboardPushed(#PB_Key_LeftShift)
updateObjeto(0,0,0, -1,0,0)
Else
updateObjeto(0,0,0, 1,0,0)
EndIf
EndIf
If KeyboardReleased(#PB_Key_Z)
updateObjeto(0,paso,0)
ElseIf KeyboardReleased(#PB_Key_X)
updateObjeto(0,-paso,0)
EndIf
If KeyboardReleased(#PB_Key_W)
updateObjeto(0,0,paso,0)
ElseIf KeyboardReleased(#PB_Key_S)
updateObjeto(0,0,-paso,0)
EndIf
If KeyboardReleased(#PB_Key_A)
updateObjeto(paso,0)
ElseIf KeyboardReleased(#PB_Key_D)
updateObjeto(-paso,0)
EndIf
Else
If KeyboardPushed(#PB_Key_R)
If KeyboardPushed(#PB_Key_LeftShift)
updateObjeto(0,0,0, 0,-1)
Else
updateObjeto(0,0,0, 0,1)
EndIf
EndIf
If KeyboardPushed(#PB_Key_T)
If KeyboardPushed(#PB_Key_LeftShift)
updateObjeto(0,0,0, 0,0,-1)
Else
updateObjeto(0,0,0, 0,0,1)
EndIf
EndIf
If KeyboardPushed(#PB_Key_Y)
If KeyboardPushed(#PB_Key_LeftShift)
updateObjeto(0,0,0, -1,0,0)
Else
updateObjeto(0,0,0, 1,0,0)
EndIf
EndIf
If KeyboardPushed(#PB_Key_Z)
updateObjeto(0,paso,0)
ElseIf KeyboardPushed(#PB_Key_X)
updateObjeto(0,-paso,0)
EndIf
If KeyboardPushed(#PB_Key_W)
updateObjeto(0,0,paso,0)
ElseIf KeyboardPushed(#PB_Key_S)
updateObjeto(0,0,-paso,0)
EndIf
If KeyboardPushed(#PB_Key_A)
updateObjeto(paso,0)
ElseIf KeyboardPushed(#PB_Key_D)
updateObjeto(-paso,0)
EndIf
EndIf
;}
EndIf
If entiNow
CreateLine3D(1,EntityX(entiNow)-4,EntityY(entiNow)+0.1,EntityZ(entiNow),$ffffff, EntityX(entiNow)+4,EntityY(entiNow)+0.1,EntityZ(entiNow),$ffffff)
CreateLine3D(2,EntityX(entiNow),EntityY(entiNow)+0.1,EntityZ(entiNow)-4,$ffffff, EntityX(entiNow),EntityY(entiNow)+0.1,EntityZ(entiNow)+4,$ffffff)
CreateLine3D(3,EntityX(entiNow),EntityY(entiNow)+4,EntityZ(entiNow),$ffffff, EntityX(entiNow),EntityY(entiNow)-4,EntityZ(entiNow),$ffffff)
If original
If itemOriginal > -1
SelectElement(deco::deco(),itemOriginal)
DisableEntityBody(deco::deco()\enti,1)
MoveEntity(deco::deco()\enti, deco::deco()\pos\x,deco::deco()\pos\y,deco::deco()\pos\z, #PB_Absolute)
DisableEntityBody(deco::deco()\enti,0)
If IsEntity(deco::deco()\entiColision):MoveEntity(deco::deco()\entiColision, deco::deco()\pos\x,deco::deco()\pos\y,deco::deco()\pos\z, #PB_Absolute):EndIf
EndIf
Else
If itemCopia > -1
SelectElement(deco::deco(),itemOriginal)
SelectElement(deco::deco()\item(),itemCopia)
If IsEntity(deco::deco()\item()\enti)
MoveEntity(deco::deco()\item()\enti, deco::deco()\item()\pos\x,deco::deco()\item()\pos\y,deco::deco()\item()\pos\z, #PB_Absolute)
EndIf
If IsEntity(deco::deco()\item()\entiColision):MoveEntity(deco::deco()\item()\entiColision, deco::deco()\item()\pos\x,deco::deco()\item()\pos\y,deco::deco()\item()\pos\z, #PB_Absolute):EndIf
EndIf
EndIf
EndIf
;}
renderTime= RenderWorld()
If IsEntity(entiNow)
tx3= StrF(EntityX(entiNow),2)+" , "+StrF(EntityY(entiNow),2)+" , "+StrF(EntityZ(entiNow),2)+" , "+StrF(EntityYaw(entiNow),2)
EndIf
sprTxt(sprInfo,"FPS: "+Str(Engine3DStatus(#PB_Engine3D_CurrentFPS)), tx2, tx3, tx4 )
DisplayTransparentSprite(sprInfo, 10,10)
DisplayTransparentSprite(sprMouse, MouseX(),MouseY())
FlipBuffers()
Until KeyboardPushed(#PB_Key_Escape)
End
CompilerEndIf
Can add pf_shadoko shaders for better results.
This is an scenary maked with this code:

For the next version...
If I can... It will be better. This was a draft to get an idea because I never take notes or plan how to make the programs, everything is in the coconut

I hope it will help someone, that they make a game, that they sell it, that they become rich, that they set up an aerospace company... hmm. Wait!, that's already happened...
