Tutorial Map Building Functions Enhanced
#1
I don't know if anyone remember, Killingdyl is the one who made building functions, and he released a version of BO too:

Init
Code:
init() { level.doorwait = 2; level.elevator_model["enter"] = "mp_flag_allies_1"; level.elevator_model["exit"] = "mp_flag_axis_1"; precacheModel(level.elevator_model["enter"]); precacheModel(level.elevator_model["exit"]); }

Elevator
Code:
CreateElevator(enter, exit, angle) { flag = spawn( "script_model", enter, 0 ); flag setModel( level.elevator_model["enter"] ); flag SetOwner( level ); wait 0.01; flag = spawn( "script_model", exit, 0 ); flag setModel( level.elevator_model["exit"] ); flag SetOwner( level ); wait 0.01; self thread ElevatorThink(enter, exit, angle); } ElevatorThink(enter, exit, angle) { self endon("disconnect"); while(1) { for(i = 0; i < level.players.size; i++) { if(DistanceSquared(enter, level.players[i].origin) <= Power(50, 2)){ level.players[i] SetOrigin(exit); level.players[i] SetPlayerAngles(angle); } } wait .25; } }

Green Block
Code:
CreateBlocksGreen(pos, angle) { block = spawn("script_model", pos, 1 ); block setModel(level.crateModelFriendly); block.angles = angle; block SetOwner( level ); wait 0.01; }

Red Block
Code:
CreateBlocksRed(pos, angle) { block = spawn("script_model", pos , 1); block setModel(level.crateModelEnemy); block.angles = angle; block SetOwner( level ); wait 0.01; }

Door
Code:
CreateDoors(open, close, xangle, yangle, zangle, size, height, hp, range) { offset = (((size / 2) - 0.5) * -1); center = spawn("script_model", open, 1); center SetOwner( level ); for(j = 0; j < size; j++){ door = spawn("script_model", open + ((35, 0, 0) * offset), 1); door setModel(level.crateModelEnemy); door SetOwner( level ); door LinkTo(center); for(h = 1; h < height; h++){ door = spawn("script_model", open + ((35, 0, 0) * offset) - ((0, 95, 0) * h), 1); door setModel(level.crateModelEnemy); door SetOwner( level ); door LinkTo(center); } offset += 1; } if(!isDefined(yangle)){ Temp = VectorToAngles(open - close); yangle = Temp[1]-90; } center.angles = (xangle, yangle, 0-zangle); center.state = "open"; center.maxhp = hp; center.hp = hp; center.range = range; center thread DoorThinkUse(open, close); //center thread DoorThinkRepair(); center thread DoorUse(); center thread ResetDoors(open, hp); wait 0.01; } DoorThinkUse(open, close) { while(1) { if(self.hp > 0){ self waittill ( "triggeruse" , player ); if(player.team == "allies"){ if(self.state == "open"){ self MoveTo(close, level.doorwait); wait level.doorwait; self.state = "close"; continue; } if(self.state == "close"){ self MoveTo(open, level.doorwait); wait level.doorwait; self.state = "open"; continue; } } if(player.team == "axis"){ if(self.state == "close"){ if(player.attackeddoor == 0){ hitChance = 0; switch(player GetStance()) { case "prone": hitChance = 15; break; case "crouch": hitChance = 45; break; case "stand": hitChance = 90; break; default: break; } if(randomInt(100) < hitChance){ self.hp--; player iPrintlnBold("HIT - " + self.hp + "/" + self.maxhp); if(self.hp <= int(self.maxhp/5)){ self playSound("uin_alert_lockon_start"); } } else { player iPrintlnBold("^1MISS - " + self.hp + "/" + self.maxhp); } player.attackeddoor = 1; player thread DoorAttackWait(); continue; } } } } else { if(self.state == "close"){ self MoveTo(open, level.doorwait); } self.state = "broken"; wait .5; } } } DoorThinkRepair() { while(1) { self waittill ( "triggerrepair" , player ); if(self.hp < self.maxhp){ if(self.hp > 0){ player thread RepairDoor(self); } else { if(useHoldThink(player, "Fixing Door...", 20000)){ self.hp = self.maxhp; self.state = "open"; player.bounty += 200; } } } } } RepairDoor(door) { self notify("RepairingDoor"); self endon("RepairingDoor"); self endon("disconnect"); self endon("death"); if(useHoldThink(self, "Repairing Door...", 5000)){ if(door.state != "broken"){ door.hp += 1; self.hprepaired += 1; self iPrintlnBold("Repaired - " + door.hp + "/" + door.maxhp); if(self.hprepaired >= 5){ self.bounty += 50; self.hprepaired = 0; } } } } DoorUse() { while(1) { for(i = 0; i < level.players.size; i++) { if(DistanceSquared(self.origin, level.players[i].origin) <= Power(self.range, 2)){ if(level.players[i].team == "allies"){ if(level.players[i] getCurrentWeapon() == "onemanarmy_mp"){ if(self.state == "broken"){ level.players[i].hint = "^1Door is Broken. ^7Press and Hold ^3[{+activate}] ^7to ^2Fix ^7the door"; } else { if(self.hp < self.maxhp){ level.players[i].hint = "Press and Hold ^3[{+activate}] ^7to ^2Repair ^7the door"; } } } else { if(self.state == "open"){ level.players[i].hint = "Press ^3[{+activate}] ^7to ^2Close ^7the door"; } if(self.state == "close"){ level.players[i].hint = "Press ^3[{+activate}] ^7to ^2Open ^7the door"; } if(self.state == "broken"){ level.players[i].hint = "^1Door is Broken"; } } } if(level.players[i].team == "axis"){ if(self.state == "close"){ level.players[i].hint = "Press ^3[{+activate}] ^7to ^2Attack ^7the door"; } if(self.state == "broken"){ level.players[i].hint = "^1Door is Broken"; } } if(level.players[i] getCurrentWeapon() == "onemanarmy_mp"){ if(level.players[i] UseButtonPressed()){ self notify( "triggerrepair" , level.players[i]); } } else { if(level.players[i] UseButtonPressed()){ self notify( "triggeruse" , level.players[i]); } } } } wait .045; } } DoorAttackWait() { self notify("attackwait"); self endon("attackwait"); self endon("disconnect"); self endon("death"); wait 1; self.attackeddoor = 0; } ResetDoors(open, hp) { while(1) { level waittill("RESETDOORS"); self.hp = hp; self MoveTo(open, level.doorwait); self.state = "open"; } }

Ramp
Code:
CreateRamps(top, bottom) { D = Distance(top, bottom); blocks = roundUp(D/35); CX = top[0] - bottom[0]; CY = top[1] - bottom[1]; CZ = top[2] - bottom[2]; XA = CX/blocks; YA = CY/blocks; ZA = CZ/blocks; CXY = Distance((top[0], top[1], 0), (bottom[0], bottom[1], 0)); Temp = VectorToAngles(top - bottom); BA = (Temp[0], Temp[1], Temp[2]); for(b = 0; b < blocks; b++){ block = spawn("script_model", (bottom + ((XA, YA, ZA) * B)), 1); block setModel(level.crateModelFriendly); block.angles = BA; block SetOwner( level ); wait 0.01; } block = spawn("script_model", (bottom + ((XA, YA, ZA) * blocks) - (0, 0, 5)), 1); block setModel(level.crateModelFriendly); block.angles = (0, BA[1], BA[2]); block SetOwner( level ); wait 0.01; }

Bridge
Code:
CreateBridge(point1, point2, point3) { points = []; points[0] = point1; points[1] = point2; points[2] = point3; for(i = 0; i < points.size; i++){ if(!isDefined(points[i+1])){ return; } D = Distance(points[i], points[i+1]); blocks = roundUp(D/35); CX = points[i][0] - points[i+1][0]; CY = points[i][1] - points[i+1][1]; CZ = points[i][2] - points[i+1][2]; XA = CX/blocks; YA = CY/blocks; ZA = CZ/blocks; CXY = Distance((points[i][0], points[i][1], 0), (points[i+1][0], points[i+1][1], 0)); Temp = VectorToAngles(points[i] - points[i+1]); BA = (Temp[0], Temp[1], Temp[2]); for(b = 0; b <= blocks; b++){ block = spawn("script_model", (points[i+1] + ((XA, YA, ZA) * B)), 1); block setModel(level.crateModelFriendly); block.angles = BA; block SetOwner( level ); wait 0.01; } } }

Grid
Code:
CreateGrids(corner1, corner2, angle) { W = Distance((corner1[0], 0, 0), (corner2[0], 0, 0)); L = Distance((0, corner1[1], 0), (0, corner2[1], 0)); H = Distance((0, 0, corner1[2]), (0, 0, corner2[2])); CX = corner2[0] - corner1[0]; CY = corner2[1] - corner1[1]; CZ = corner2[2] - corner1[2]; ROWS = roundUp(W/35); COLUMNS = roundUp(L/70); HEIGHT = roundUp(H/35); XA = CX/ROWS; YA = CY/COLUMNS; ZA = CZ/HEIGHT; center = spawn("script_model", corner1, 0); for(r = 0; r <= ROWS; r++){ for(c = 0; c <= COLUMNS; c++){ for(h = 0; h <= HEIGHT; h++){ block = spawn("script_model", (corner1 + (XA * r, YA * c, ZA * h)), 1); block setModel(level.crateModelFriendly); block.angles = (0, 0, 0); block SetOwner( level ); block LinkTo(center); wait 0.01; } } } center.angles = angle; }

Straight Wall
Code:
CreateStraightWalls(start, end) { D = Distance((start[0], start[1], 0), (end[0], end[1], 0)); H = Distance((0, 0, start[2]), (0, 0, end[2])); blocks = roundUp(D/70); height = roundUp(H/35); CX = end[0] - start[0]; CY = end[1] - start[1]; CZ = end[2] - start[2]; XA = (CX/blocks); YA = (CY/blocks); ZA = (CZ/height); TXA = (XA/4); TYA = (YA/4); Temp = VectorToAngles(end - start); Angle = (90, Temp[1]+90, 0); for(h = 0; h < height; h++){ block = spawn("script_model", (start + (TXA, TYA, 10) + ((0, 0, ZA) * h)), 1); block setModel(level.crateModelFriendly); block.angles = Angle; block SetOwner( level ); wait 0.001; for(i = 1; i < blocks; i++){ block = spawn("script_model", (start + ((XA, YA, 0) * i) + (0, 0, 10) + ((0, 0, ZA) * h)), 1); block setModel(level.crateModelFriendly); block.angles = Angle; block SetOwner( level ); wait 0.001; } block = spawn("script_model", ((end[0], end[1], start[2]) + (TXA * -1, TYA * -1, 10) + ((0, 0, ZA) * h)), 1); block setModel(level.crateModelFriendly); block.angles = Angle; block SetOwner( level ); wait 0.001; } }

Tilted Wall
Code:
CreateTiltedWalls(start, end, height) { D = Distance((start[0], start[1], 0), (end[0], end[1], 0)); blocks = roundUp(D/70); CX = end[0] - start[0]; CY = end[1] - start[1]; CZ = end[2] - start[2]; XA = (CX/blocks); YA = (CY/blocks); ZA = 35; TXA = (XA/4); TYA = (YA/4); Temp = VectorToAngles(end - start); Angle = (90, Temp[1]+90, 0); center = spawn("script_model", start, 1); center SetOwner( level ); for(h = 0; h < height; h++){ block = spawn("script_model", (start + (TXA, TYA, 10) + ((0, 0, ZA) * h)), 1); block setModel(level.crateModelFriendly); block.angles = Angle; block SetOwner( level ); block LinkTo(center); wait 0.001; for(i = 1; i < blocks; i++){ block = spawn("script_model", (start + ((XA, YA, 0) * i) + (0, 0, 10) + ((0, 0, ZA) * h)), 1); block setModel(level.crateModelFriendly); block.angles = Angle; block SetOwner( level ); block LinkTo(center); wait 0.001; } block = spawn("script_model", ((end[0], end[1], start[2]) + (TXA * -1, TYA * -1, 10) + ((0, 0, ZA) * h)), 1); block setModel(level.crateModelFriendly); block.angles = Angle; block SetOwner( level ); block LinkTo(center); wait 0.001; } center.angles = Temp; }

Adapt Wall
Code:
CreateAdaptWalls(start, end, height, offset) { D = Distance((start[0], start[1], 0), (end[0], end[1], 0)); blocks = roundUp(D/70); CX = end[0] - start[0]; CY = end[1] - start[1]; XA = (CX/blocks); YA = (CY/blocks); ZA = 35; TXA = (XA/4); TYA = (YA/4); Temp = VectorToAngles(end - start); Angle = (90, Temp[1]+90, 0); tempstart = bulletTrace(start, start + (0, 0, 100000), false, undefined)["position"] - (0,0,500); tempend = bulletTrace(end, end + (0, 0, 100000), false, undefined)["position"] - (0,0,500); for(h = 0; h < height; h++){ spawnlocation = bulletTrace(tempstart, tempstart - (0, 0, 100000), false, undefined)["position"]; block = spawn("script_model", (spawnlocation + (TXA, TYA, 15 + offset)), 1); block setModel(level.crateModelFriendly); block.angles = Angle; block SetOwner( level ); wait 0.001; for(i = 1; i < blocks; i++){ spawnlocation = bulletTrace(tempstart + ((XA, YA, 0) * i), tempstart + ((XA, YA, 0) * i) - (0, 0, 100000), false, undefined)["position"]; block = spawn("script_model", (spawnlocation + (0, 0, 15 + offset)), 1); block setModel(level.crateModelFriendly); block.angles = Angle; block SetOwner( level ); wait 0.001; } spawnlocation = bulletTrace(tempend, tempend - (0, 0, 100000), false, undefined)["position"]; block = spawn("script_model", (spawnlocation + (TXA * -1, TYA * -1, 15 + offset)), 1); block setModel(level.crateModelFriendly); block.angles = Angle; block SetOwner( level ); wait 0.001; } }

Cluster
Code:
CreateCluster(amount, pos, radius) { for(i = 0; i < amount; i++) { half = radius / 2; power = ((randomInt(radius) - half), (randomInt(radius) - half), 500); block = spawn("script_model", pos + (0, 0, 1000), 1 ); block setModel(level.crateModelFriendly); block.angles = (90, 0, 0); block SetOwner( level ); block PhysicsLaunch(block.origin, power); block thread ResetCluster(pos, radius); wait 0.05; } } ResetCluster(pos, radius) { wait 5; self RotateTo(((randomInt(36)*10), (randomInt(36)*10), (randomInt(36)*10)), 1); level waittill("RESETCLUSTER"); self thread CreateCluster(1, pos, radius); self delete(); }

Weapon
Code:
CreateWeapon(weapon, name, pos, angle, stock, respawntime) { weapon_model = getWeaponModel(weapon); if(weapon_model == ""){ weapon_model = weapon; } Wep = spawn("script_model", pos+(0,0,3), 1); Wep.angles = angle; Wep SetOwner( level ); Wep setModel(weapon_model); Wep thread WeaponThink(weapon, name, pos, angle, stock, respawntime); wait 0.01; } WeaponThink(weapon, name, pos, angle, stock, respawntime) { area = spawn("trigger_radius", self.origin, 0, 25, 50); while(1) { area waittill("trigger", player); player.hint = "Press ^3[{+activate}] ^7to pick up " + name; if(player UseButtonPressed()){ switch( weapon ) { case "frag_grenade_mp": case "sticky_grenade_mp": case "hatchet_mp": case "flash_grenade_mp": case "concussion_grenade_mp": case "tabun_gas_mp": case "nightingale_mp": case "willy_pete_mp": player takeWeapon(player getCurrentOffhand()); player giveWeapon( weapon ); player setWeaponAmmoClip( weapon, 2 ); break; default: player giveWeapon(weapon); player GiveMaxAmmo(weapon); player.weaponlist = player GetWeaponsListPrimaries(); if(player.weaponList.size > level.maxweaponper){ player takeWeapon(player getCurrentWeapon()); } player SwitchToWeapon(weapon); break; } if(stock > 1){ stock--; } else { if(isDefined(respawntime)){ level thread WeaponRespawnThink(weapon, name, pos, angle, stock, respawntime); } area delete(); self delete(); return; } } wait .04; } } WeaponRespawnThink(weapon, name, pos, angle, stock, respawntime) { wait respawntime; CreateWeapon(weapon, name, pos, angle, stock, respawntime); }

Table
Code:
CreateTables(start, end) { D = Distance((start[0], start[1], 0), (end[0], end[1], 0)); blocks = roundUp(D/75); CX = end[0] - start[0]; CY = end[1] - start[1]; XA = (CX/blocks); YA = (CY/blocks); TXA = (XA/4); TYA = (YA/4); Temp = VectorToAngles(end - start); Angle = (0, Temp[1]+90, 0); block = spawn("script_model", (start + (TXA, TYA, 10)), 1); block setModel(level.crateModelEnemy); block.angles = angle; wait 0.001; for(i = 1; i < blocks; i++){ block = spawn("script_model", (start + ((XA, YA, 0) * i) + (0, 0, 10)), 1); block setModel(level.crateModelEnemy); block.angles = angle; block SetOwner( level ); wait 0.001; } block = spawn("script_model", ((end[0], end[1], start[2]) + (TXA * -1, TYA * -1, 10)), 1); block setModel(level.crateModelEnemy); block.angles = angle; block SetOwner( level ); wait 0.001; }

Ammo Box
Code:
CreateAmmoBox(pos, angle, team) { block = spawn("script_model", pos, 1 ); block setModel(level.crateModelFriendly); block.angles = angle; block SetOwner( level ); /*if(team == "allies" || team == "all"){ curObjID = maps\mp\gametypes\_gameobjects::getNextObjID(); objective_add( curObjID, "invisible", (0,0,0) ); objective_position( curObjID, block.origin ); objective_state( curObjID, "active" ); objective_team( curObjID, "allies" ); block.objIdFriendly = curObjID; block maps\mp\_entityheadIcons::setHeadIcon( "allies", "waypoint_ammo_friendly", (0,0,24), 14, 14 ); } if(team == "axis" || team == "all"){ curObjID = maps\mp\gametypes\_gameobjects::getNextObjID(); objective_add( curObjID, "invisible", (0,0,0) ); objective_position( curObjID, block.origin ); objective_state( curObjID, "active" ); objective_team( curObjID, "axis" ); block.objIdFriendly = curObjID; block maps\mp\_entityheadIcons::setHeadIcon( "axis", "waypoint_ammo_friendly", (0,0,24), 14, 14 ); }*/ block thread AmmoBoxThink(team); wait 0.01; } AmmoBoxThink(team) { while(1) { for(i = 0; i < level.players.size; i++) { if(team != "all" && level.players[i].team != team){ continue; } if(DistanceSquared(self.origin, level.players[i].origin) <= Power(50, 2)){ level.players[i].hint = "Press ^3[{+activate}] ^7to Refill your ammo"; if(level.players[i] UseButtonPressed()){ level.players[i] DisableWeapons(); level.players[i].weaponlist = level.players[i] GetWeaponsList(); for( idx = 0; idx < level.players[i].weaponlist.size; idx++ ) { weapon = level.players[i].weaponlist[idx]; switch( weapon ) { case "frag_grenade_mp": case "sticky_grenade_mp": case "hatchet_mp": case "flash_grenade_mp": case "concussion_grenade_mp": case "tabun_gas_mp": case "nightingale_mp": case "willy_pete_mp": level.players[i] SetWeaponAmmoStock( weapon, 999 ); break; default: level.players[i] GiveMaxAmmo( weapon ); break; } } level.players[i] playLocalSound( "ammo_crate_use" ); wait 1.5; level.players[i] EnableWeapons(); } } } wait .045; } }

Misc
Code:
roundUp( floatVal ) { if ( int( floatVal ) != floatVal ) return int( floatVal+1 ); else return int( floatVal ); } Power( number, power ) { returnnumber = 1; for(i = 0; i < power; i++){ returnnumber *= number; } return returnnumber; } useHoldThink( player, string, useTime ) // self == a script origin (useEnt) or the crate { player notify ( "use_hold" ); player freeze_player_controls( true ); player _disableWeapon(); self.curProgress = 0; self.inUse = true; self.useRate = 0; self.useTime = useTime; player thread personalUseBar( self, string ); result = useHoldThinkLoop( player ); if ( isDefined( player ) ) { player notify( "done_using" ); } if ( isDefined( player ) ) { if ( IsAlive(player) ) { player _enableWeapon(); player freeze_player_controls( false ); } } if ( IsDefined( self ) ) { self.inUse = false; } // result may be undefined if useholdthinkloop hits an endon if ( isdefined( result ) && result ) return true; return false; } useHoldThinkLoop( player ) { level endon ( "game_ended" ); self endon("disabled"); timedOut = 0; while( IsDefined(self) && isAlive( player ) && player useButtonPressed() && !player.throwingGrenade && !player meleeButtonPressed() && !player IsInVehicle() && self.curProgress < self.useTime ) { timedOut += 0.05; self.curProgress += (50 * self.useRate); self.useRate = 1; if ( self.curProgress >= self.useTime ) { self.inUse = false; wait .05; return isAlive( player ); } wait 0.05; } return false; } personalUseBar( object, string ) { self endon("disconnect"); if( isDefined( self.useBar ) ) return; self.useBar = createSecondaryProgressBar(); self.useBarText = createSecondaryProgressBarText(); self.useBarText setText( string ); lastRate = -1; while ( isAlive( self ) && IsDefined(object) && object.inUse && !level.gameEnded ) { if ( lastRate != object.useRate ) { if( object.curProgress > object.useTime) object.curProgress = object.useTime; self.useBar updateBar( object.curProgress / object.useTime, (1000 / object.useTime) * object.useRate ); if ( !object.useRate ) { self.useBar hideElem(); self.useBarText hideElem(); } else { self.useBar showElem(); self.useBarText showElem(); } } lastRate = object.useRate; wait ( 0.05 ); } self.useBar destroyElem(); self.useBarText destroyElem(); }

Full Credit to Killingdyl Big Grin
(This is from his forum)

P.S: Pastebin Version:
http://pastebin.com/2tFG506t
[Image: wyipjqo9qon7rc2v1lo.jpg]
Reply

#2
Thanks !
Reply

#3
btw some of them might not work correctly lol i never tested them all
Reply

#4
(12-29-2010, 07:19)Killingdyl Wrote: btw some of them might not work correctly lol i never tested them all
Ammobox objective isn't working
I get unknown function about this and the problem is here:
Code:
block maps\mp\_entityheadIcons::setHeadIcon( "allies", "waypoint_ammo_friendly", (0,0,24), 14, 14 );
Code:
block maps\mp\_entityheadIcons::setHeadIcon( "axis", "waypoint_ammo_friendly", (0,0,24), 14, 14 );

Also, all hint text isn't working too...And repair door too
[Image: wyipjqo9qon7rc2v1lo.jpg]
Reply

#5
i told u they all dont work lol
Reply

#6
(12-29-2010, 07:46)Killingdyl Wrote: i told u they all dont work lol
They miss something...I bet it is in Quarantine...

[Image: wyipjqo9qon7rc2v1lo.jpg]
Reply

#7
THanks Dyll and Ali for sharing, Shouldn't this be put on mod release page?
--
Reply

#8
nah its modding help Wink
Reply

#9
(12-29-2010, 10:07)Eekhoorn Wrote: THanks Dyll and Ali for sharing, Shouldn't this be put on mod release page?
It is just code, not mod, should be in help section
[Image: wyipjqo9qon7rc2v1lo.jpg]
Reply

#10
CreateDoor = Don't working =/
Reply



Possibly Related Threads…
Thread Author Replies Views Last Post
  [Release] Matrix Functions Yamato 1 4,035 07-29-2013, 20:14
Last Post: Rendflex
  [Release] nCr and Factorial Functions Yamato 2 3,749 06-18-2013, 17:24
Last Post: Nekochan
  [Release] Random Functions 1.1 Yamato 22 18,199 04-20-2013, 10:13
Last Post: Dominator56
  [Release] Enhanced Botz alistair3149 11 32,700 01-02-2013, 12:59
Last Post: Paroxysmics
  Functions surtek 14 14,671 09-19-2012, 07:57
Last Post: Pozzuh
Information [Solved] Info about functions, events and etc. Nerus 2 3,549 09-09-2012, 20:57
Last Post: surtek
  Seldom required functions Phl3x_ 3 4,304 06-03-2012, 10:53
Last Post: RaZ
  Help I find This functions and i need your help. Bloodfocus 5 5,106 04-23-2012, 01:43
Last Post: House
  Help Functions iPrintLnBold() [Z00MBY] Alex 7 7,861 04-16-2012, 08:44
Last Post: [Z00MBY] Alex
  Preview Racing Mod Enhanced iAegle 18 13,616 03-25-2012, 05:35
Last Post: rotceh_dnih

Forum Jump:


Users browsing this thread:
1 Guest(s)

Forum Powered By MyBB, Theme by © 2002-2026 Melroy van den Berg.