January 30, 2006

24 Hours To Go...

...and I still haven't updated my homepage.

The reason? I just spent the last four hours reducing my unread personal E-mail from 612 to 0.

Time crunches suck.

January 29, 2006

Oompa-Loompa...

I've loved being at Ritual. For one thing, I rate business cards.

My current business card says that I am "QA Manager/Head Oompa-Loompa." When I was in Utah last week, I was asked by one of my old co-workers from Microsoft Game Studios why it was "Head Oompa-Loompa." Here's the explanation that I gave.

QA and Oompa-Loompa's are very similar. We toil tirelessly behind the scenes, and nobody outside of the factory really knows of our existence. (The only time that QA is even mentioned by the outside world is when there wasn't enough QA done...)

We do the grunt work, the repetitive tasks. We're also called upon to deliver the morality lessons to those who affect our work. We're the ones who preach that crash bugs are bad, that spending 10,000 faces on a doorknob isn't an appropriate use of memory, that remixing celebration sounds into something that sounds perverse isn't appropriate in a rated-"E" title, that perhaps dangling an anatomically-correct fetus over a fire isn't the most appropriate item to place in a game.

Finally, we rarely if ever complain because for the most part we love doing what we do.

QA is full of the unsung heroes of the video game industry. For every item like "Hot Coffee" that slips through the cracks, we stop hundreds of thousands of similar issues from ever seeing the light of day. And in the end, our products, like Willa Wonka's, are that much sweeter.

January 12, 2006

Firefox (WARNING: Language)

I've been working on the manual for "SiN: Episodes." The manual part isn't the hard part, though. Getting it to work across browsers, on the other hand...

...well, I've come to the following conclusion. Firefox's CSS support sucks donkey balls.

I'm trying to be a good webizen and do a nice table-free design for the most part. The entire manual right now validates XHTML 1.0 compliant. All formatting is done via a style sheet, and Firefox is the only browser that's bitching about it because it hates nested div tags.

What's even scarier is that for simple behavior like this, Internet Explorer is handling it correctly. IE has no problems with the nested divs, CSS standard borders, etc.

At this point, I'm half tempted to just say, "The manual is IE only and be done with it..."

Picture of Firefox CSS bug

So, Firefox fans, riddle me this. I have a div tag around each major section. The width of the div section is set to 100%, with display:block and float:none set as well. Inside, I have a small amount of text. I have another div inside the div set to float right. That section is slightly larger than the small amount of text in the div.

So, why does IE and Opera properly handle this CSS combination, while Firefox stumbles home like a drunken prom date?

January 8, 2006

Managed DirectX: Rendering Doom3/Quake4 Fonts

As stated in an earlier posting, I'm working on a Doom3 modification. The catch is, I'm only using the Doom3 content, not the engine.

So, if you want to render Doom3 fonts using Managed DirectX, here's the code. (ResourceToStream is a helper function that returns a MemoryStream from the byte array passed to it.

Imports Microsoft.DirectX
Imports Microsoft.DirectX.Direct3D

Public Class idFont

Private _font As Texture
Private _device As Device
Private _sprite As Sprite
Private Structure fontchar
Dim height As Integer ' number of scan lines
Dim top As Integer ' top of glyph in buffer
Dim bottom As Integer ' bottom of glyph in buffer
Dim pitch As Integer ' width for copying
Dim xSkip As Integer ' x adjustment
Dim imageWidth As Integer ' width of actual image
Dim imageHeight As Integer ' height of actual image
Dim s As Single ' x offset in image where glyph starts
Dim t As Single ' y offset in image where glyph starts
Dim s2, t2 As Single
End Structure
Private xRes, yRes As Integer
Private height As Integer = 0

Private _chars(255) As fontchar

Public Sub New(ByVal fontdata() As Byte, _
ByVal fontlayoutdata() As Byte, _
ByVal device As Device)

_font = TextureLoader.FromStream(device, ResourceToStream(fontdata))
xRes = _font.GetSurfaceLevel(0).Description.Width
yRes = _font.GetSurfaceLevel(0).Description.Height
Dim s As New IO.BinaryReader(ResourceToStream(fontlayoutdata))
For x As Integer = 0 To 255
With _chars(x)
.height = s.ReadInt32
If .height > height Then height = .height
.top = s.ReadInt32
.bottom = s.ReadInt32
.pitch = s.ReadInt32
.xSkip = s.ReadInt32
.imageWidth = s.ReadInt32
.imageHeight = s.ReadInt32
.s = s.ReadSingle
.t = s.ReadSingle
.s2 = s.ReadSingle
.t2 = s.ReadSingle
s.ReadInt32() : s.ReadBytes(32) ' Dispose unneeded extra stuff
End With
Next
s.Close()

_sprite = New Sprite(device)
End Sub

Public Function DrawText(ByVal text As String, _
ByVal x As Integer, _
ByVal y As Integer, _
ByVal color As Color) As Integer

If text Is Nothing Then Return y
If text.Length = 0 Then Return y

Dim c As Integer, ch As fontchar
_sprite.Begin(SpriteFlags.AlphaBlend)

For z As Integer = 0 To text.Length - 1
c = Asc(text.Chars(z))

If c >= 0 And c <= 255 Then
ch = _chars(c)
' Valid char, let's go
_sprite.Draw2D(_font, _
New Rectangle(ch.s * xRes, ch.t * yRes, ch.imageWidth, ch.imageHeight), _
New Size(ch.imageWidth, ch.imageHeight), _
New PointF(x, y + height - ch.top), _
color)
x += ch.xSkip
End If
Next

_sprite.End()

Return x

End Function

End Class
The reason for returning the x variable is so you can mix fonts on a single line. It returns the horizontal position where it stopped rendering.

January 3, 2006

Bruce Carver, 1948-2005


I got an E-mail today from a former co-worker at Access Software/Microsoft Game Studios telling me that Bruce Carver, founder of Access Software, the creative genius behind "Beach-Head," "Links" and the "Tex Murphy" series, passed away December 28 from cancer.

He was 57.

It's unfortunate that my last memories of him were tainted by his erratic behavior at the time. While he could be extremely polarizing, he was a hell of a motivator and knew the capabilities of his technology and his staff almost better than anyone else.

He was a shrewd negotiator, and an inventive game designer. He will be missed by those who knew him.

January 2, 2006

SharpZipLib: Adding Files To A ZIP File

I had several visitors to my site in December looking for information on how to add files to a ZIP file, including information on how to add in path information.

Here is the code I use to write a file to a ZIP file:

    Private ZipToWrite As ZipOutputStream
Private Crc As New Crc32

Private Sub CreateZip(ByVal fname As String)
ZipToWrite = New ZipOutputStream(File.Create(fname))
ZipToWrite.SetLevel(9) ' 9 = maximum compression
End Sub

Private Sub CloseZip()
ZipToWrite.Finish()
ZipToWrite.Close()
End Sub

Private Sub WriteFileToZip(ByVal fname As String, ByVal basepath As String)
Dim strmFile As FileStream = File.OpenRead(fname)
Dim abyBuffer(strmFile.Length - 1) As Byte

strmFile.Read(abyBuffer, 0, abyBuffer.Length)
Dim objZipEntry As ZipEntry
If basepath.Length <> 0 Then
objZipEntry = New ZipEntry(fname.Replace(basepath, String.Empty).ToLower)
Else
objZipEntry = New ZipEntry(fname.ToLower)
End If

objZipEntry.DateTime = DateTime.Now
objZipEntry.Size = strmFile.Length
strmFile.Close()
Crc.Reset()
Crc.Update(abyBuffer)
objZipEntry.Crc = Crc.Value
ZipToWrite.PutNextEntry(objZipEntry)
ZipToWrite.Write(abyBuffer, 0, abyBuffer.Length)
End Sub

The purpose of the basepath parameter is so that you can ZIP up a folder and have the folder name magically go away.

Mind you, some ZIP utilities, when unzipping multiple folders, expect a directory entry for that folder...and if they don't find the directory entry, they'll crash out or say that your ZIP file is corrupt. In that case, before you add the first file for a subdirectory, add in a 0-byte file with this sub...
    Private Sub WriteZeroByteFileToZip(ByVal fname As String, ByVal basepath As String)
Dim abyBuffer(0) As Byte

Dim objZipEntry As ZipEntry
If basepath.Length <> 0 Then
objZipEntry = New ZipEntry(fname.Replace(basepath, String.Empty).ToLower & "\")
Else
objZipEntry = New ZipEntry(fname.ToLower & "\")
End If

objZipEntry.DateTime = DateTime.Now
objZipEntry.Size = 0
Crc.Reset()
Crc.Update(0)
objZipEntry.Crc = Crc.Value
ZipToWrite.PutNextEntry(objZipEntry)
End Sub

I hope that helps.

January 1, 2006

New Year's Resolutions and a Look Back

Well, 2006. It's interesting to think that since I started this blog, I've made 423 entries. There hasn't really been a pattern to what I've done, I've just gone where the winds have blown.

To be honest, though, that's usually how I succeed at something. I decide to do something on a lark, and I generally succeed, because there's no pressure for me to do well. If I screw up, it's no biggie. Because I don't have that pressure, I do really well.

That's how I got hired at Access. That's how I stayed on at Microsoft. That's how I got hired by Layton City. That's how I got hired by Ritual. (Speaking of which, my one-year anniversary is on Tuesday.)

I guess success comes to those who feel like they're already successful. So here's to a successful 2006, because I'm already a winner for having gotten this far.

New Years Resolutions
  1. I resolve to try not to dredge up painful memories from the past. One of my bad habits is bringing up moments of failure from the distant past and refreshing them in my mind. Anything that caused me emotional pain is eligible...crashing into a parked camper, for example.

    Fact of the matter is that I can't distance myself from these moments. The failures of my past helped define me, as did my successes. I just need to learn to accept these moments rather than beat myself with them.
  2. I resolve to update my domain (RomSteady.net) at least once a month.

    I pay over $100 a year for the domain and web space. I better be putting something there.
  3. I resolve to finish at least one of the game mods I have in the queue. I've been working on several game modifications under the radar.

    The one I've been working on that's the furthest along is "Doom3: Moonbase Assault," a turn-based-strategy game in the Doom3 universe. (Already have the models loading and animating...I've also integrated the level editor into Visual Studio 2005.) Basically, I want to be able to tell the story of the marines who went in to get the last survivor at the end of Doom3.

    (Come on, you guys knew that there had to be a reason for all of the Doom3 utilities I've been creating...)
  4. I resolve to go to driving school and try to get my driver's license back.

    Walking everywhere has been a real benefit to me. It has taught me patience, perserverence and time-management skills. It's also helped me lose eighty pounds since I started at Ritual. However, there are major downsides to boot. I've lost many opportunities over the years because of my lack of a driver's license, and I intend to make sure I don't lose these opportunities in the future.

Anyway, I'm going to be in Utah from January 23 to January 27 visiting family and trying to take care of my old home, so have a Happy New Year. May 2006 be memorable for good reasons.