Forum'da paylaşılan kadar görüntü odaklı değil ama gayet işlevsel iş görür nitelik de
local-bsd üzerinde Ngix-PHP8-PHP-FPM Kullanıyorum Farklı yerler de denemedim.
Gerekli oldukça güncellerim.
Kurulum için destek yok.
mob_proto.txt,mob_names.txt ve mobproto.php aynı klasörde olmak zorunda
pkg install -y nginx php82 php82-mysqli php82-mbstring php82-gd php82-curl php82-zlib php82-zip php82-xml php82-fpm php82-filter php82-iconv
sysrc nginx_enable="YES"
sysrc php_fpm_enable="YES"
service php-fpm start
service nginx start
/usr/local/etc/nginx/nginx.conf
Kod:





mobproto.php:
local-bsd üzerinde Ngix-PHP8-PHP-FPM Kullanıyorum Farklı yerler de denemedim.
Gerekli oldukça güncellerim.
Kurulum için destek yok.
mob_proto.txt,mob_names.txt ve mobproto.php aynı klasörde olmak zorunda
pkg install -y nginx php82 php82-mysqli php82-mbstring php82-gd php82-curl php82-zlib php82-zip php82-xml php82-fpm php82-filter php82-iconv
sysrc nginx_enable="YES"
sysrc php_fpm_enable="YES"
service php-fpm start
service nginx start
/usr/local/etc/nginx/nginx.conf
Kod:
worker_processes 1;
events { worker_connections 1024; }
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
server {
listen 80;
server_name localhost;
root /usr/local/www/nginx;
index index.php index.html;
location / {
try_files $uri $uri/ =404;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
}





mobproto.php:
<?php
error_reporting(E_ERROR | E_PARSE);
$proto_file = 'mob_proto.txt';
$names_file = 'mob_names.txt';
$limit = 50;
$sayfa = isset($_GET['p']) ? max(1, (int)$_GET['p']) : 1;
$search = isset($_GET['search']) ? trim($_GET['search']) : '';
$offset = ($sayfa - 1) * $limit;
$cols_list = explode("\t", "Vnum Name Rank Type BattleType Level Size AiFlags MountCapacity RaceFlags ImmuneFlags Empire Folder OnClick St Dx Ht Iq MinDamage MaxDamage MaxHp RegenCycle RegenPercent MinGold MaxGold Exp Def AttackSpeed MoveSpeed AggressiveHpPct AggressiveSight AttackRange DropItemGroup ResurrectionVnum EnchantCurse EnchantSlow EnchantPoison EnchantStun EnchantCritical EnchantPenetrate ResistSword ResistTwoHanded ResistDagger ResistBell ResistFan ResistBow ResistFire ResistElect ResistMagic ResistWind ResistPoison DamMultiply SummonVnum DrainSp MobColor PolymorphItem SkillLevel0 SkillVnum0 SkillLevel1 SkillVnum1 SkillLevel2 SkillVnum2 SkillLevel3 SkillVnum3 SkillLevel4 SkillVnum4 SpBerserk SpStoneSkin SpGodSpeed SpDeathBlow SpRevive");
$mob_names = [];
if (file_exists($names_file)) {
foreach (file($names_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
$parts = explode("\t", $line);
if (count($parts) >= 2) $mob_names[trim($parts[0])] = trim($parts[1]);
}
}
// 1. KAYIT İŞLEMİ (GÜNCELLEME)
if (isset($_POST['confirm_bulk_save'])) {
$updates = json_decode($_POST['bulk_data'], true);
if ($updates) {
$full = file($proto_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$header_line = $full[0]; unset($full[0]);
$proto_dict = [];
foreach($full as $line) {
$p = explode("\t", $line);
$proto_dict[trim($p[0])] = $line;
}
foreach ($updates as $vnum => $new_row_array) {
$proto_dict[$vnum] = implode("\t", array_map('trim', $new_row_array));
if(isset($new_row_array[1])) $mob_names[$vnum] = $new_row_array[1];
}
$out = [$header_line];
foreach($proto_dict as $line) $out[] = $line;
file_put_contents($proto_file, implode("\n", $out) . "\n");
$n_out = [];
foreach($mob_names as $nv => $nn) $n_out[] = $nv . "\t" . $nn;
file_put_contents($names_file, implode("\n", $n_out) . "\n");
header("Location: ".$_SERVER['PHP_SELF']."?p=$sayfa&search=".urlencode($search)); exit;
}
}
// 2. YENİ MOB EKLE
if (isset($_POST['tam_ekle'])) {
$yeni_data = $_POST['yeni'];
$yeni_vnum = trim($yeni_data[0]);
if(!empty($yeni_vnum)) {
$proto_lines = file($proto_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$exists = false;
foreach($proto_lines as $l) { if(trim(explode("\t", $l)[0]) == $yeni_vnum) { $exists = true; break; } }
if ($exists) { $error_msg = "HATA: $yeni_vnum mevcut!"; }
else {
$proto_lines[] = implode("\t", array_map('trim', $yeni_data));
file_put_contents($proto_file, implode("\n", $proto_lines) . "\n");
$n_lines = file($names_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$n_lines[] = $yeni_vnum . "\t" . trim($yeni_data[1]);
file_put_contents($names_file, implode("\n", $n_lines) . "\n");
header("Location: ".$_SERVER['PHP_SELF']."?search=".$yeni_vnum); exit;
}
}
}
// 3. MOB SİL
if (isset($_GET['sil'])) {
$target = trim($_GET['sil']);
foreach([$proto_file, $names_file] as $f) {
$lines = file($f, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$new = []; foreach($lines as $l) if(trim(explode("\t", $l)[0]) != $target) $new[] = $l;
file_put_contents($f, implode("\n", $new) . "\n");
}
header("Location: ".$_SERVER['PHP_SELF']."?p=$sayfa&search=$search"); exit;
}
// Verileri Hazırla
$proto_all = file($proto_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$header_row = $proto_all[0];
unset($proto_all[0]);
// --- SON 5 MOB BÖLÜMÜ ---
$last_5_raw = array_slice($proto_all, -5);
$recent_list = [];
foreach(array_reverse($last_5_raw) as $line) {
$p = explode("\t", $line);
$v = trim($p[0]);
$recent_list[] = ['vnum' => $v, 'name' => $mob_names[$v] ?? (isset($p[1]) ? trim($p[1]) : 'NONAME')];
}
// ------------------------
$filtered = [];
foreach ($proto_all as $line) {
$parts = explode("\t", $line); $v = trim($parts[0]);
$n = $mob_names[$v] ?? (isset($parts[1]) ? trim($parts[1]) : 'NONAME');
if ($search === '' || stripos($v, $search) !== false || stripos($n, $search) !== false) {
$clean = [];
for($i=0; $i<count($cols_list); $i++) {
$val = isset($parts[$i]) ? trim($parts[$i]) : "0";
if($i==1) $val = $n;
$clean[] = $val;
}
$filtered[] = $clean;
}
}
$total_pages = max(1, ceil(count($filtered) / $limit));
$current_data = array_slice($filtered, $offset, $limit);
?>
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>MOB PROTO DÜZENLEYİCİ</title>
<style>
:root { --accent: #e74c3c; --bg: #0d1117; --panel:#e74c3c2; --bor#0d11170363d; --ho#161b221262d; --suc#30363d238636; }
#21262dbody { backgr#238636ar(--bg); color: #c9d1d9; font-family: sans-serif#c9d1d9n: 0; overflow: hidden; height: 100vh; }
header { background: #010409; height: 60px; display: #010409lign-items: center; padding: 0 20px; gap: 15px; border-bottom: 2px solid var(--accent); }
.recent-bar { background: #010409; padding: 5px 20px; bord#010409om: 1px solid var(--border); display: flex; gap: 10px; align-items: center; font-size: 11px; overflow-x: auto; white-space: nowrap; }
.recent-item { color: #7ee787; text-decoration: none; #7ee787: 2px 8px; background: var(--panel); border-radius: 4px; border: 1px solid var(--border); }
.recent-item:hover { border-color: var(--accent); }
.viewport { height: calc(100vh - 95px); width: 100%; overflow: auto; }
table { border-collapse: separate; border-spacing: 0; width: max-content; }
th { background: #1c2128; padding: 10px; border-b#1c21281px solid var(--border); border-right: 1px solid var(--border); position: sticky; top: 0; z-index: 100; font-size: 11px; }
tbody tr:hover td { background: var(--hover) !important; }
.sticky-col { position: sticky; background: #0d1117; z-index: 80; }
#0d1117l { left: 0; width: 40px; text-align: center; }
.col-vnum { left: 40px; width: 80px; color: var(--accent); font-weight: bold; border-right: 2px solid var(--accent); }
.col-name { left: 120px; width: 160px; color: #7ee787; border-right: 2px solid#7ee787accent); }
td { border-bottom: 1px solid var(--border); border-right: 1px solid var(--border); }
input.cell { background: transparent; border: 1px solid transparent; color: #fff; padding: 8px; width: 100%;[HASH=7268]#fff[/HASH]line: none; font-size: 12px; }
input.cell:focus { background: rgba(31, 111, 235, 0.2) !important; border: 1px solid #1f6feb !important; }
in#1f6febnged { background: rgba(35, 134, 54, 0.3) !important; border: 1px solid var(--success) !important; }
.btn { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; color: white; font-size: 12px; text-decoration: none; display: inline-flex; align-items: center; }
.btn-save { background: #238636; } .btn-add { background#238636-accent); } .btn-nav { background: #30363d; }
.modal { disp#30363dne; position: fixed; inset: 0; background: rgba(0,0,0,0.85); z-index: 9999; padding: 40px; }
.modal-content { background: var(--panel); padding: 25px; border-radius: 8px; max-width: 900px; margin: auto; border: 1px solid var(--accent); max-height: 80vh; overflow-y: auto; }
.diff-row { border-bottom: 1px solid var(--border); padding: 8px; font-family: monospace; font-size: 12px; line-height: 1.5; }
</style>
</head>
<body>
<?php if(isset($error_msg)): ?><script>alert("<?=str_replace('"', '\"', $error_msg)?>");</script><?php endif; ?>
<header>
<a href="<?=$_SERVER['PHP_SELF']?>">🏠 MOB PROTO</a>
<form method="GET">
<input type="text" name="search" placeholder="Vnum veya İsim..." value="<?=htmlspecialchars($search)?>">
</form>
<div>
<a href="?p=<?=max(1, $sayfa-1)?>&search=<?=$search?>">«</a>
<span><?=$sayfa?> / <?=$total_pages?></span>
<a href="?p=<?=min($total_pages, $sayfa+1)?>&search=<?=$search?>">»</a>
</div>
<button onclick="document.getElementById('addModal').style.display='block'">+ YENİ MOB</button>
<button onclick="saveCheck()">DEĞİŞİKLİKLERİ KAYDET</button>
<div>ORHAN BULUT</div>
</header>
<div>
<b>SON EKLENENLER:</b>
<?php foreach($recent_list as $rm): ?>
<a href="?search=<?=$rm['vnum']?>">[<?=$rm['vnum']?>] <?=$rm['name']?></a>
<?php endforeach; ?>
</div>
<div>
<table>
<thead>
<tr>
<th>SİL</th>
<?php foreach($cols_list as $k => $b): ?>
<th><?=$b?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php foreach ($current_data as $row): $v = $row[0]; $name = $row[1]; ?>
<tr>
<td><a href="?sil=<?=$v?>&p=<?=$sayfa?>&search=<?=$search?>" onclick="return confirm('<?=$v?> silinecek?')">×</a></td>
<?php foreach ($row as $i => $val): $cl = ($i==0?'sticky-col col-vnum':($i==1?'sticky-col col-name':'')); ?>
<td><input type="text" value="<?=htmlspecialchars($val)?>" oninput="track(this)"></td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div id="saveModal">
<div>
<h2>Değişiklik Özeti</h2>
<div id="diffArea"></div>
<form method="POST">
<input type="hidden" name="bulk_data" id="bulk_data_input">
<button type="submit" name="confirm_bulk_save">ONAYLA VE KAYDET</button>
<button type="button" onclick="document.getElementById('saveModal').style.display='none'">İPTAL</button>
</form>
</div>
</div>
<div id="addModal">
<div>
<h2>Yeni Mob Ekle</h2>
<form method="POST">
<div>
<?php foreach($cols_list as $i => $name): ?>
<div><label><?=$name?></label><br>
<input type="text" name="yeni[<?=$i?>]" value="<?=$i>1?'0':''?>"></div>
<?php endforeach; ?>
</div>
<button type="submit" name="tam_ekle">MOB'U EKLE</button>
<button type="button" onclick="document.getElementById('addModal').style.display='none'">VAZGEÇ</button>
</form>
</div>
</div>
<script>
const COL_NAMES = <?=json_encode($cols_list)?>;
function track(el) {
if (el.value.trim() !== el.getAttribute('data-orig').trim()) el.classList.add('changed');
else el.classList.remove('changed');
}
function saveCheck() {
let diffs = ""; let updates = {}; let found = false;
document.querySelectorAll('input.cell').forEach(inp => {
let cur = inp.value.trim();
let ori = inp.getAttribute('data-orig').trim();
if (cur !== ori) {
found = true;
let v = inp.getAttribute('data-vnum');
if (!updates[v]) {
updates[v] = [];
document.querySelectorAll(`input.cell[data-vnum="${v}"]`).forEach(i => updates[v][i.getAttribute('data-idx')] = i.value.trim());
}
diffs += `<div><span>[${v}]</span> <b>${inp.getAttribute('data-name')}</b> <br> <span>${COL_NAMES[inp.getAttribute('data-idx')]}</span>: <span>${ori}</span> → <span>${cur}</span></div>`;
}
});
if (!found) { alert("Değişiklik yok."); return; }
document.getElementById('diffArea').innerHTML = diffs;
document.getElementById('bulk_data_input').value = JSON.stringify(updates);
document.getElementById('saveModal').style.display = 'block';
}
</script>
</body>
</html>


