File size: 6,484 Bytes
42583aa
c0f5b84
 
 
 
 
 
42583aa
c0f5b84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42583aa
c0f5b84
 
42583aa
 
 
c0f5b84
42583aa
 
c0f5b84
42583aa
 
c0f5b84
42583aa
 
c0f5b84
42583aa
 
 
 
 
 
 
 
 
 
c0f5b84
42583aa
c0f5b84
42583aa
 
 
c0f5b84
42583aa
 
c0f5b84
42583aa
 
c0f5b84
42583aa
 
c0f5b84
 
 
 
 
 
 
 
 
42583aa
c0f5b84
 
 
42583aa
c0f5b84
42583aa
 
 
c0f5b84
 
42583aa
c0f5b84
 
 
 
 
 
42583aa
c0f5b84
42583aa
c0f5b84
42583aa
 
 
c0f5b84
42583aa
 
 
c0f5b84
 
42583aa
 
c0f5b84
 
 
 
 
 
 
42583aa
c0f5b84
42583aa
c0f5b84
42583aa
c0f5b84
 
42583aa
 
 
c0f5b84
42583aa
c0f5b84
42583aa
c0f5b84
 
42583aa
 
 
 
 
 
c0f5b84
 
42583aa
c0f5b84
42583aa
c0f5b84
 
42583aa
c0f5b84
42583aa
c0f5b84
 
42583aa
 
 
 
 
 
 
 
c0f5b84
 
 
42583aa
 
c0f5b84
42583aa
 
 
 
 
 
 
 
c0f5b84
 
42583aa
 
c0f5b84
42583aa
c0f5b84
42583aa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# app.py
import streamlit as st  # ๐ŸŒ Streamlit magic
import streamlit.components.v1 as components  # ๐Ÿ–ผ๏ธ Embed custom HTML/JS
import os  # ๐Ÿ“‚ File operations
import json  # ๐Ÿ”„ JSON encoding/decoding
import pandas as pd  # ๐Ÿ“Š DataFrame handling
import uuid  # ๐Ÿ†” Unique IDs
import math  # โž— Math utilities
import time  # โณ Time utilities

from gamestate import GameState  # ๐Ÿ’ผ Shared game-state singleton

# ๐Ÿš€ Page setup
st.set_page_config(page_title="Infinite World Builder", layout="wide")

# ๐Ÿ“ Constants for world dimensions & CSV schema
SAVE_DIR = "saved_worlds"
PLOT_WIDTH = 50.0  # โ†”๏ธ Plot width in world units
PLOT_DEPTH = 50.0  # โ†•๏ธ Plot depth in world units
CSV_COLUMNS = [
    'obj_id', 'type',
    'pos_x', 'pos_y', 'pos_z',
    'rot_x', 'rot_y', 'rot_z', 'rot_order'
]

# ๐Ÿ—‚๏ธ Ensure directory for saved plots
os.makedirs(SAVE_DIR, exist_ok=True)

# ๐Ÿ”ง Load prefab kits definitions
@st.cache_data(ttl=3600)
def load_kits():
    try:
        with open("kits.json", "r", encoding="utf-8") as f:
            return json.load(f)
    except FileNotFoundError:
        st.error("kits.json missing! ๐Ÿ“ฆ")
        return {}
    except Exception as e:
        st.error(f"Error loading kits.json: {e}")
        return {}

kits = load_kits()  # ๐Ÿ”— name โ†’ module list

# --- Existing load/save/state functions ---
@st.cache_data(ttl=3600)
def load_plot_metadata():
    try:
        files = [f for f in os.listdir(SAVE_DIR) if f.endswith('.csv') and f.startswith('plot_X')]
    except Exception as e:
        st.error(f"Error scanning {SAVE_DIR}: {e}")
        return []
    parsed = []
    for fn in files:
        try:
            parts = fn[:-4].split('_')  # remove .csv
            gx = int(parts[1][1:]); gz = int(parts[2][1:])
            name = ' '.join(parts[3:]) if len(parts)>3 else f"Plot({gx},{gz})"
            parsed.append({
                'id': fn[:-4], 'filename': fn,
                'grid_x': gx, 'grid_z': gz,
                'name': name,
                'x_offset': gx*PLOT_WIDTH,
                'z_offset': gz*PLOT_DEPTH
            })
        except:
            st.warning(f"Skipping invalid file: {fn}")
    parsed.sort(key=lambda p: (p['grid_x'], p['grid_z']))
    return parsed


def load_plot_objects(filename, x_offset, z_offset):
    path = os.path.join(SAVE_DIR, filename)
    try:
        df = pd.read_csv(path)
        if not all(c in df.columns for c in ['type','pos_x','pos_y','pos_z']):
            st.warning(f"Missing cols in {filename}")
            return []
        df['obj_id'] = df.get('obj_id', pd.Series([str(uuid.uuid4()) for _ in df.index]))
        for col, default in [('rot_x',0.0),('rot_y',0.0),('rot_z',0.0),('rot_order','XYZ')]:
            if col not in df.columns: df[col]=default
        objs = []
        for _, r in df.iterrows():
            o = r.to_dict()
            o['pos_x'] += x_offset; o['pos_z'] += z_offset
            objs.append(o)
        return objs
    except Exception:
        return []


def save_plot_data(filename, objects_list, px, pz):
    path = os.path.join(SAVE_DIR, filename)
    if not isinstance(objects_list, list):
        st.error("Invalid save data")
        return False
    rel=[]
    for o in objects_list:
        pos=o.get('position',{}); rot=o.get('rotation',{})
        typ=o.get('type','Unknown'); oid=o.get('obj_id',str(uuid.uuid4()))
        if not all(k in pos for k in ['x','y','z']) or typ=='Unknown': continue
        rel.append({
            'obj_id':oid,'type':typ,
            'pos_x':pos['x']-px,'pos_y':pos['y'],'pos_z':pos['z']-pz,
            'rot_x':rot.get('_x',0.0),'rot_y':rot.get('_y',0.0),'rot_z':rot.get('_z',0.0),'rot_order':rot.get('_order','XYZ')
        })
    try:
        pd.DataFrame(rel,columns=CSV_COLUMNS).to_csv(path,index=False)
        st.success(f"Saved {len(rel)} to {filename}")
        return True
    except Exception as e:
        st.error(f"Save failed: {e}")
        return False

@st.cache_resource
def get_game_state():
    return GameState(save_dir=SAVE_DIR,csv_filename='world_state.csv')

game_state=get_game_state()

# ๐Ÿง  Session defaults
st.session_state.setdefault('selected_object','None')

# ๐Ÿ”„ Load metadata & objects
plots_metadata=load_plot_metadata()
all_initial_objects=[]
for p in plots_metadata:
    all_initial_objects+=load_plot_objects(p['filename'],p['x_offset'],p['z_offset'])

# ๐Ÿ–ฅ๏ธ Sidebar UI\ with kits
with st.sidebar:
    st.title("๐Ÿ—๏ธ World Controls")
    st.header("๐ŸŒŸ Place Kits")
    options=["None"]+list(kits.keys())
    idx=options.index(st.session_state.selected_object) if st.session_state.selected_object in options else 0
    sel=st.selectbox("Select Kit:",options,index=idx,key="selected_object_widget")
    if sel!=st.session_state.selected_object:
        st.session_state.selected_object=sel
    st.markdown("---")
    st.header("๐Ÿ’พ Save Work")
    if st.button("๐Ÿ’พ Save Current Work"):
        from streamlit_js_eval import streamlit_js_eval
        streamlit_js_eval(js_code="getSaveDataAndPosition();",key="js_save_processor")
        st.rerun()

# ๐Ÿ  Main area
st.header("๐ŸŒ Infinite Shared 3D World")
st.caption("Click to explore & place kits, then ๐Ÿ’พ to save!")

# ๐Ÿ”Œ Inject state into JS
injected_state={
    "ALL_INITIAL_OBJECTS":all_initial_objects,
    "PLOTS_METADATA":plots_metadata,
    "SELECTED_OBJECT_TYPE":st.session_state.selected_object,
    "PLOT_WIDTH":PLOT_WIDTH,
    "PLOT_DEPTH":PLOT_DEPTH,
    "GAME_STATE":game_state.get_state(),
    "KITS":kits
}

try:
    html=open('index.html','r',encoding='utf-8').read()
    script=f"""
<script>
window.ALL_INITIAL_OBJECTS={json.dumps(injected_state['ALL_INITIAL_OBJECTS'])};
window.PLOTS_METADATA={json.dumps(injected_state['PLOTS_METADATA'])};
window.SELECTED_OBJECT_TYPE={json.dumps(injected_state['SELECTED_OBJECT_TYPE'])};
window.PLOT_WIDTH={json.dumps(injected_state['PLOT_WIDTH'])};
window.PLOT_DEPTH={json.dumps(injected_state['PLOT_DEPTH'])};
window.GAME_STATE={json.dumps(injected_state['GAME_STATE'])};
window.KITS={json.dumps(injected_state['KITS'])};
console.log('State injected',{{objs:window.ALL_INITIAL_OBJECTS.length,plots:window.PLOTS_METADATA.length, kits:Object.keys(window.KITS).length}});
</script>
"""
    html_mod=html.replace('</head>',script+'\n</head>',1)
    components.html(html_mod,height=750,scrolling=False)
except FileNotFoundError:
    st.error("index.html missing!")
except Exception as e:
    st.error(f"HTML inject error: {e}")