diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 67feabb..ac64bbf 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -132,23 +132,27 @@ jobs: chmod 600 ~/.ssh/known_hosts - name: Install rsync run: apt-get update -qq && apt-get install -y rsync openssh-client + # S1 — НЕ блокирующий: при недоступности S1 (downtime) деплой не должен + # валиться, главное доставить на S2. ConnectTimeout 20с чтобы не висеть. - name: Deploy to S1 (85.175.7.40:1998) + continue-on-error: true run: | rsync -az --delete-after --human-readable --exclude=wiki --exclude=kubikon-assets \ - -e "ssh -i ~/.ssh/id_deploy -o UserKnownHostsFile=~/.ssh/known_hosts -p 1998" \ + -e "ssh -i ~/.ssh/id_deploy -o UserKnownHostsFile=~/.ssh/known_hosts -o ConnectTimeout=20 -p 1998" \ build/ min@85.175.7.40:/var/www/rublox-studio/build/ - name: Deploy to S2 (192.168.0.124:22, runner в той же сети) run: | rsync -az --delete-after --human-readable --exclude=wiki --exclude=kubikon-assets \ -e "ssh -i ~/.ssh/id_deploy -o UserKnownHostsFile=~/.ssh/known_hosts -p 22" \ build/ min@192.168.0.124:/var/www/rublox-studio/build/ - - name: Verify deploy + - name: Verify S1 (не блокирующий) + continue-on-error: true run: | - echo "=== S1 ===" - ssh -i ~/.ssh/id_deploy -o UserKnownHostsFile=~/.ssh/known_hosts -p 1998 \ + ssh -i ~/.ssh/id_deploy -o UserKnownHostsFile=~/.ssh/known_hosts -o ConnectTimeout=20 -p 1998 \ min@85.175.7.40 \ "ls /var/www/rublox-studio/build/index.html && du -sh /var/www/rublox-studio/build/" - echo "=== S2 ===" + - name: Verify S2 (обязательный) + run: | ssh -i ~/.ssh/id_deploy -o UserKnownHostsFile=~/.ssh/known_hosts -p 22 \ min@192.168.0.124 \ "ls /var/www/rublox-studio/build/index.html && du -sh /var/www/rublox-studio/build/" diff --git a/src/community/docsGames.js b/src/community/docsGames.js index 3e577db..3c2b525 100644 --- a/src/community/docsGames.js +++ b/src/community/docsGames.js @@ -318,4 +318,9 @@ export const GAMES = [ desc: 'Кастомные скины: герой превращается в пончик, машину, пришельца. Магазин скинов на B.', mechanics: ['game.player.setSkin', 'non-humanoid скины', 'магазин скинов', 'таблички'], previewShot: 'guide-zoo-scene.png', openProjectId: 2046, ready: true }, + { id: 'guide-strelka', num: 55, group: 'g5', stars: 1, icon: 'gamepad', + title: 'Туториал — собери монетки', + desc: '3D-стрелка-указатель «иди сюда»: дорожка из бегущих шевронов + парящий маркер над целью. Дошёл — стрелка прыгает на следующую.', + mechanics: ['game.fx.pointer', 'preset стрелки', 'setTarget/update', 'onTouch цели'], + previewShot: 'guide-strelka-scene.png', openProjectId: 333, ready: true }, ]; diff --git a/src/editor/GameSettingsModal.jsx b/src/editor/GameSettingsModal.jsx index 96f8dbb..fdfa26c 100644 --- a/src/editor/GameSettingsModal.jsx +++ b/src/editor/GameSettingsModal.jsx @@ -44,6 +44,7 @@ const GameSettingsModal = ({ open, initial, onClose, onSave, onCaptureScreenshot const [isPublic, setIsPublic] = useState(false); const [multiplayer, setMultiplayer] = useState(false); const [maxPlayers, setMaxPlayers] = useState(10); + const [isTest, setIsTest] = useState(false); const [error, setError] = useState(''); const fileInputRef = useRef(null); @@ -58,6 +59,7 @@ const GameSettingsModal = ({ open, initial, onClose, onSave, onCaptureScreenshot setThumbnail(initial?.thumbnail || ''); setIsPublic(!!initial?.is_public); setMultiplayer(!!initial?.multiplayer); + setIsTest(!!initial?.is_test); setMaxPlayers( typeof initial?.max_players === 'number' ? Math.max(2, Math.min(50, initial.max_players)) @@ -117,6 +119,7 @@ const GameSettingsModal = ({ open, initial, onClose, onSave, onCaptureScreenshot is_public: isPublic, multiplayer, max_players: Math.max(2, Math.min(50, Number(maxPlayers) || 10)), + is_test: isTest, }); }; @@ -252,6 +255,26 @@ const GameSettingsModal = ({ open, initial, onClose, onSave, onCaptureScreenshot + {/* Тестовая игра — для разработки/проверки в плеере. + Не попадает в каталог (лента/поиск/профиль). */} + + {multiplayer && (