3 Commits

Author SHA1 Message Date
Aaron Kimbrell
18bab8d57a fix: place player-built models using their stored position and rotation
DarkflameServer now normalizes UGC model LXFML around the model's own
origin and stores the model's world position and rotation in
properties_contents, the same as for prebuilt models. The property
viewer only applied that transform to prebuilt models, so every
player-built model rendered piled up at the origin.

Pass each UGC model's stored position and rotation to the LDD converter
and apply it to every brick (including flex parts) when building the
scene.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 19:39:36 -05:00
Aaron Kimbrell
79b84db793 make ND db match DLU 2025-10-04 03:18:56 -05:00
David Markowitz
f8b1c086cf Update SQLAlchemy version to 2.0.40 (#114)
fixes an issue with models on newer versions of python
2025-09-29 22:16:37 -05:00
2 changed files with 88 additions and 13 deletions

View File

@@ -139,6 +139,7 @@ class Account(db.Model, UserMixin):
unique=True unique=True
) )
# ND Exclusive
email = db.Column( email = db.Column(
db.Unicode(255), db.Unicode(255),
nullable=True, nullable=True,
@@ -146,6 +147,7 @@ class Account(db.Model, UserMixin):
unique=False unique=False
) )
# ND Exclusive
email_confirmed_at = db.Column(db.DateTime()) email_confirmed_at = db.Column(db.DateTime())
password = db.Column( password = db.Column(
@@ -166,6 +168,7 @@ class Account(db.Model, UserMixin):
server_default='0' server_default='0'
) )
# ND Exclusive
active = db.Column( active = db.Column(
mysql.BOOLEAN, mysql.BOOLEAN,
nullable=False, nullable=False,
@@ -215,7 +218,7 @@ class Account(db.Model, UserMixin):
db.session.delete(self) db.session.delete(self)
db.session.commit() db.session.commit()
# ND Exclusive
class AccountInvitation(db.Model): class AccountInvitation(db.Model):
__tablename__ = 'account_invites' __tablename__ = 'account_invites'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
@@ -456,17 +459,34 @@ class Leaderboard(db.Model):
passive_deletes=True passive_deletes=True
) )
time = db.Column( primaryScore = db.Column(
mysql.BIGINT(unsigned=True), mysql.BIGINT(unsigned=True),
nullable=False, nullable=False,
server_default='0' server_default='0'
) )
score = db.Column( secondaryScore = db.Column(
mysql.BIGINT(unsigned=True), mysql.BIGINT(unsigned=True),
nullable=False, nullable=False,
server_default='0' server_default='0'
) )
tertiaryScore = db.Column(
mysql.BIGINT(unsigned=True),
nullable=False,
server_default='0'
)
numWins = db.Column(
mysql.INTEGER(unsigned=True),
nullable=False,
server_default='0'
)
timesPlayed = db.Column(
mysql.INTEGER(unsigned=True),
nullable=False,
server_default='0'
)
def save(self): def save(self):
db.session.add(self) db.session.add(self)
@@ -486,7 +506,7 @@ class Mail(db.Model):
) )
sender_id = db.Column( sender_id = db.Column(
mysql.INTEGER, mysql.BIGINT,
nullable=False nullable=False
) )
@@ -584,12 +604,15 @@ class PetNames(db.Model):
mysql.TEXT, mysql.TEXT,
nullable=False nullable=False
) )
# ND Exclusive
approved = db.Column( approved = db.Column(
mysql.INTEGER(unsigned=True), mysql.INTEGER(unsigned=True),
nullable=False, nullable=False,
server_default='0' server_default='0'
) )
# ND Exclusive
owner_id = db.Column( owner_id = db.Column(
mysql.BIGINT, mysql.BIGINT,
nullable=True nullable=True
@@ -721,7 +744,7 @@ class Property(db.Model):
class UGC(db.Model): class UGC(db.Model):
__tablename__ = 'ugc' __tablename__ = 'ugc'
id = db.Column( id = db.Column(
mysql.INTEGER, mysql.BIGINT,
primary_key=True primary_key=True
) )
account_id = db.Column( account_id = db.Column(
@@ -801,7 +824,7 @@ class PropertyContent(db.Model):
) )
ugc_id = db.Column( ugc_id = db.Column(
db.INT, db.BIGINT,
db.ForeignKey(UGC.id, ondelete='CASCADE'), db.ForeignKey(UGC.id, ondelete='CASCADE'),
nullable=True nullable=True
) )
@@ -852,6 +875,39 @@ class PropertyContent(db.Model):
nullable=False, nullable=False,
) )
model_name = db.Column(
mysql.TEXT,
nullable=False,
server_default=''
)
model_description = db.Column(
mysql.TEXT,
nullable=False,
server_default=''
)
behavior_1 = db.Column(
mysql.BIGINT(),
server_default='0'
)
behavior_2 = db.Column(
mysql.BIGINT(),
server_default='0'
)
behavior_3 = db.Column(
mysql.BIGINT(),
server_default='0'
)
behavior_4 = db.Column(
mysql.BIGINT(),
server_default='0'
)
behavior_5 = db.Column(
mysql.BIGINT(),
server_default='0'
)
def save(self): def save(self):
db.session.add(self) db.session.add(self)
db.session.commit() db.session.commit()
@@ -956,7 +1012,7 @@ class BugReport(db.Model):
) )
reporter_id = db.Column( reporter_id = db.Column(
mysql.INTEGER(), mysql.BIGINT,
nullable=False, nullable=False,
server_default='0' server_default='0'
) )

View File

@@ -277,7 +277,9 @@
this.xmldata = '' this.xmldata = ''
} }
AddScene(file){ // placement: optional THREE.Matrix4 that places this model in the property. DLU stores player-built
// (UGC) models normalized around their own origin, with the position and rotation in properties_contents.
AddScene(file, placement = null){
let lxfmlfile = new DBURLFile(file,file) let lxfmlfile = new DBURLFile(file,file)
this.xmldata = lxfmlfile.read() this.xmldata = lxfmlfile.read()
@@ -305,7 +307,9 @@
for (let j = 0; j < childnodes.length ;j++) { for (let j = 0; j < childnodes.length ;j++) {
let childnode = childnodes[j] let childnode = childnodes[j]
if (childnode.nodeName == 'Brick'){ if (childnode.nodeName == 'Brick'){
this.Bricks.push(new Brick(childnode)) let brick = new Brick(childnode)
brick.placement = placement
this.Bricks.push(brick)
} }
} }
} }
@@ -531,9 +535,9 @@
} }
} }
LoadScene(filename){ LoadScene(filename, placement = null){
if(this.database.initok){ if(this.database.initok){
this.scene.AddScene(filename) this.scene.AddScene(filename, placement)
} }
} }
@@ -608,6 +612,11 @@
n14, n24 ,n34, n44); n14, n24 ,n34, n44);
} }
// Move the brick from model space to its place on the property
if (bri.placement){
m.premultiply(bri.placement)
}
let decoCount = 0 let decoCount = 0
for (const [partindex, part] of geo.Parts.entries()){ for (const [partindex, part] of geo.Parts.entries()){
@@ -1033,10 +1042,14 @@
//Start //Start
// Player-built models with the position and rotation stored for them in properties_contents
let lxfml_file_list = [ let lxfml_file_list = [
{% for model in content %} {% for model in content %}
{% if model.lot == 14 %} {% if model.lot == 14 %}
"{{url_for('properties.get_model', id=model.id, file_format='lxfml', lod=lod)}}"{{ ", " if not loop.last else "" }} {
"url": "{{url_for('properties.get_model', id=model.id, file_format='lxfml', lod=lod)}}",
"pos": {{ model.pos[0]|tojson }}
}{{ ", " if not loop.last else "" }}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
] ]
@@ -1047,7 +1060,13 @@
let converter = new Converter() let converter = new Converter()
converter.LoadDBURL(ldddburl) converter.LoadDBURL(ldddburl)
for (let i = 0; i < lxfml_file_list.length; i++) { for (let i = 0; i < lxfml_file_list.length; i++) {
converter.LoadScene(lxfml_file_list[i]) let pos = lxfml_file_list[i].pos
let placement = new THREE.Matrix4().compose(
new THREE.Vector3(pos.x, pos.y, pos.z),
new THREE.Quaternion(pos.rx, pos.ry, pos.rz, pos.rw),
new THREE.Vector3(1, 1, 1)
)
converter.LoadScene(lxfml_file_list[i].url, placement)
} }
converter.Export('test.webgl') converter.Export('test.webgl')